summaryrefslogtreecommitdiffstats
path: root/vcl/osx
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/osx')
-rw-r--r--vcl/osx/DataFlavorMapping.cxx788
-rw-r--r--vcl/osx/DataFlavorMapping.hxx126
-rw-r--r--vcl/osx/DragActionConversion.cxx84
-rw-r--r--vcl/osx/DragActionConversion.hxx40
-rw-r--r--vcl/osx/DragSource.cxx338
-rw-r--r--vcl/osx/DragSource.hxx125
-rw-r--r--vcl/osx/DragSourceContext.cxx55
-rw-r--r--vcl/osx/DragSourceContext.hxx50
-rw-r--r--vcl/osx/DropTarget.cxx543
-rw-r--r--vcl/osx/DropTarget.hxx154
-rw-r--r--vcl/osx/HtmlFmtFlt.cxx165
-rw-r--r--vcl/osx/HtmlFmtFlt.hxx38
-rw-r--r--vcl/osx/OSXTransferable.cxx198
-rw-r--r--vcl/osx/OSXTransferable.hxx71
-rw-r--r--vcl/osx/PictToBmpFlt.cxx72
-rw-r--r--vcl/osx/PictToBmpFlt.hxx34
-rw-r--r--vcl/osx/README.a11y7
-rw-r--r--vcl/osx/a11yactionwrapper.h33
-rw-r--r--vcl/osx/a11yactionwrapper.mm96
-rw-r--r--vcl/osx/a11ycomponentwrapper.h36
-rw-r--r--vcl/osx/a11ycomponentwrapper.mm102
-rw-r--r--vcl/osx/a11yfactory.mm193
-rw-r--r--vcl/osx/a11yfocuslistener.cxx82
-rw-r--r--vcl/osx/a11yfocuslistener.hxx44
-rw-r--r--vcl/osx/a11yfocustracker.cxx269
-rw-r--r--vcl/osx/a11ylistener.cxx145
-rw-r--r--vcl/osx/a11yrolehelper.h32
-rw-r--r--vcl/osx/a11yrolehelper.mm296
-rw-r--r--vcl/osx/a11yselectionwrapper.h34
-rw-r--r--vcl/osx/a11yselectionwrapper.mm100
-rw-r--r--vcl/osx/a11ytablewrapper.h36
-rw-r--r--vcl/osx/a11ytablewrapper.mm212
-rw-r--r--vcl/osx/a11ytextattributeswrapper.h30
-rw-r--r--vcl/osx/a11ytextattributeswrapper.mm354
-rw-r--r--vcl/osx/a11ytextwrapper.h55
-rw-r--r--vcl/osx/a11ytextwrapper.mm296
-rw-r--r--vcl/osx/a11yutil.h31
-rw-r--r--vcl/osx/a11yutil.mm47
-rw-r--r--vcl/osx/a11yvaluewrapper.h37
-rw-r--r--vcl/osx/a11yvaluewrapper.mm87
-rw-r--r--vcl/osx/a11ywrapper.mm1616
-rw-r--r--vcl/osx/a11ywrapperbutton.h32
-rw-r--r--vcl/osx/a11ywrapperbutton.mm58
-rw-r--r--vcl/osx/a11ywrappercheckbox.h32
-rw-r--r--vcl/osx/a11ywrappercheckbox.mm65
-rw-r--r--vcl/osx/a11ywrappercombobox.h42
-rw-r--r--vcl/osx/a11ywrappercombobox.mm165
-rw-r--r--vcl/osx/a11ywrappergroup.h31
-rw-r--r--vcl/osx/a11ywrappergroup.mm53
-rw-r--r--vcl/osx/a11ywrapperlist.h30
-rw-r--r--vcl/osx/a11ywrapperlist.mm44
-rw-r--r--vcl/osx/a11ywrapperradiobutton.h32
-rw-r--r--vcl/osx/a11ywrapperradiobutton.mm64
-rw-r--r--vcl/osx/a11ywrapperradiogroup.h30
-rw-r--r--vcl/osx/a11ywrapperradiogroup.mm44
-rw-r--r--vcl/osx/a11ywrapperrow.h31
-rw-r--r--vcl/osx/a11ywrapperrow.mm54
-rw-r--r--vcl/osx/a11ywrapperscrollarea.h32
-rw-r--r--vcl/osx/a11ywrapperscrollarea.mm81
-rw-r--r--vcl/osx/a11ywrapperscrollbar.h30
-rw-r--r--vcl/osx/a11ywrapperscrollbar.mm47
-rw-r--r--vcl/osx/a11ywrappersplitter.h30
-rw-r--r--vcl/osx/a11ywrappersplitter.mm44
-rw-r--r--vcl/osx/a11ywrapperstatictext.h31
-rw-r--r--vcl/osx/a11ywrapperstatictext.mm52
-rw-r--r--vcl/osx/a11ywrappertabgroup.h30
-rw-r--r--vcl/osx/a11ywrappertabgroup.mm46
-rw-r--r--vcl/osx/a11ywrappertextarea.h30
-rw-r--r--vcl/osx/a11ywrappertextarea.mm44
-rw-r--r--vcl/osx/a11ywrappertoolbar.h30
-rw-r--r--vcl/osx/a11ywrappertoolbar.mm46
-rw-r--r--vcl/osx/clipboard.cxx353
-rw-r--r--vcl/osx/clipboard.hxx149
-rw-r--r--vcl/osx/cuidraw.hxx45
-rw-r--r--vcl/osx/documentfocuslistener.cxx230
-rw-r--r--vcl/osx/documentfocuslistener.hxx97
-rw-r--r--vcl/osx/printaccessoryview.mm1264
-rw-r--r--vcl/osx/printview.mm80
-rw-r--r--vcl/osx/res/MainMenu.nib/classes.nib4
-rw-r--r--vcl/osx/res/MainMenu.nib/info.nib21
-rw-r--r--vcl/osx/res/MainMenu.nib/keyedobjects.nibbin0 -> 3615 bytes
-rw-r--r--vcl/osx/saldata.cxx281
-rw-r--r--vcl/osx/salframe.cxx2027
-rw-r--r--vcl/osx/salframeview.mm2595
-rw-r--r--vcl/osx/salgdiutils.cxx362
-rw-r--r--vcl/osx/salinst.cxx1093
-rw-r--r--vcl/osx/salmacos.cxx529
-rw-r--r--vcl/osx/salmenu.cxx888
-rw-r--r--vcl/osx/salnativewidgets.cxx1366
-rw-r--r--vcl/osx/salnsmenu.mm261
-rw-r--r--vcl/osx/salnstimer.mm40
-rw-r--r--vcl/osx/salobj.cxx444
-rw-r--r--vcl/osx/salprn.cxx677
-rw-r--r--vcl/osx/salsys.cxx118
-rw-r--r--vcl/osx/saltimer.cxx206
-rw-r--r--vcl/osx/service_entry.cxx62
-rw-r--r--vcl/osx/vclnsapp.mm481
97 files changed, 22204 insertions, 0 deletions
diff --git a/vcl/osx/DataFlavorMapping.cxx b/vcl/osx/DataFlavorMapping.cxx
new file mode 100644
index 0000000000..361e268bcd
--- /dev/null
+++ b/vcl/osx/DataFlavorMapping.cxx
@@ -0,0 +1,788 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include "DataFlavorMapping.hxx"
+#include "HtmlFmtFlt.hxx"
+#include "PictToBmpFlt.hxx"
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/processfactory.hxx>
+
+#include <rtl/ustring.hxx>
+#include <osl/endian.h>
+
+#include <cassert>
+#include <string.h>
+#include <string_view>
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+using namespace ::com::sun::star::datatransfer;
+using namespace ::com::sun::star::uno;
+using namespace com::sun::star::lang;
+using namespace cppu;
+
+namespace
+{
+ /* Determine whether or not a DataFlavor is valid.
+ */
+ bool isValidFlavor(const DataFlavor& aFlavor)
+ {
+ size_t len = aFlavor.MimeType.getLength();
+ Type dtype = aFlavor.DataType;
+ return ((len > 0) && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get()) || (dtype == cppu::UnoType<OUString>::get())));
+ }
+
+ OUString NSStringToOUString( const NSString* cfString)
+ {
+ assert(cfString && "Invalid parameter");
+
+ const char* utf8Str = [cfString UTF8String];
+ unsigned int len = rtl_str_getLength(utf8Str);
+
+ return OUString(utf8Str, len, RTL_TEXTENCODING_UTF8);
+ }
+
+ NSString* OUStringToNSString(std::u16string_view ustring)
+ {
+ OString utf8Str = OUStringToOString(ustring, RTL_TEXTENCODING_UTF8);
+ return [NSString stringWithCString: utf8Str.getStr() encoding: NSUTF8StringEncoding];
+ }
+
+ const char* FLAVOR_SESX = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ const char* FLAVOR_SLSDX = "application/x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link Source Descriptor (XML)\"";
+ const char* FLAVOR_ESX = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"";
+ const char* FLAVOR_LSX = "application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\"";
+ const char* FLAVOR_EOX = "application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\"";
+ const char* FLAVOR_SVXB = "application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\"";
+ const char* FLAVOR_GDIMF = "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\"";
+ const char* FLAVOR_WMF = "application/x-openoffice-wmf;windows_formatname=\"Image WMF\"";
+ const char* FLAVOR_EMF = "application/x-openoffice-emf;windows_formatname=\"Image EMF\"";
+ const char* FLAVOR_SODX = "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\"";
+ const char* FLAVOR_LINK = "application/x-openoffice-link;windows_formatname=\"Link\"";
+ const char* FLAVOR_DUMMY_INTERNAL = "application/x-openoffice-internal";
+
+ struct FlavorMap
+ {
+ const NSString* SystemFlavor;
+ const char* OOoFlavor;
+ const char* HumanPresentableName;
+ bool DataTypeOUString; // sequence<byte> otherwise
+ };
+
+ // This is a list of the bidirectional mapping between (internal) MIME types and (system)
+ // pasteboard types.
+
+ // Only pasteboard types mentioned here will be recognized, mapped, and available for pasting in a
+ // fresh LibreOffice process. When copy-pasting in-process, the situation is different.
+
+ // Also MIME types not mentioned here will be stored on the pasteboard (using the same type name),
+ // though. But that is IMHO a bit pointless as they in general won't then be pasteable anyway in a
+ // new LibreOffice process. See the use of the maOfficeOnlyTypes array.
+
+ // The SystemFlavor member is nil for the cases where there is no predefined pasteboard type UTI
+ // and we use the internal MIME type (media type) also on the pasteboard. That is OK in macOS,
+ // there is no requirement that the types are well-formed UTIs. It is different on iOS, I think,
+ // though. For an introduction to UTIs, see for instance
+ // https://alastairs-place.net/blog/2012/06/06/utis-are-better-than-you-think-and-heres-why/
+ //
+ // In those cases the MIME type might actually have parameters appended, separated by semicolons.
+ // At least the FLAVOR_SODX one must have at least a typename="%PRODUCTNAME %PRODUCTVERSION
+ // Spreadsheet" parameter (with macros expanded and translated) for LO to recognise it. See
+ // lcl_TestFormat() in sc/source/ui/view/cellsh.cxx.
+
+ const FlavorMap flavorMap[] =
+ {
+ { NSPasteboardTypeString, "text/plain;charset=utf-16", "Unicode Text (UTF-16)", true },
+ { NSPasteboardTypeRTF, "text/rtf", "Rich Text Format", false },
+ { NSPasteboardTypePDF, "application/pdf", "PDF File", false },
+ { NSPasteboardTypeTIFF, "image/png", "Portable Network Graphics", false },
+ { NSPasteboardTypeHTML, "text/html", "Plain Html", false },
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create
+ // multiple pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ { NSFilenamesPboardType, "application/x-openoffice-filelist;windows_formatname=\"FileList\"", "FileList", false },
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ { nil, FLAVOR_SESX, "Star Embed Source (XML)", false },
+ { nil, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false },
+ { nil, FLAVOR_ESX, "Star Embed Source (XML)", false },
+ { nil, FLAVOR_LSX, "Star Link Source (XML)", false },
+ { nil, FLAVOR_EOX, "Star Embedded Object (XML)", false },
+ { nil, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false },
+ { nil, FLAVOR_GDIMF, "GDIMetaFile", false },
+ { nil, FLAVOR_WMF, "Windows MetaFile", false },
+ { nil, FLAVOR_EMF, "Windows Enhanced MetaFile", false },
+ { nil, FLAVOR_SODX, "Star Object Descriptor (XML)", false },
+ { nil, FLAVOR_LINK, "Dynamic Data Exchange (DDE link)", false },
+ { nil, FLAVOR_DUMMY_INTERNAL, "internal data",false }
+ };
+
+ #define SIZE_FLAVOR_MAP (sizeof(flavorMap)/sizeof(FlavorMap))
+
+ bool isByteSequenceType(const Type& theType)
+ {
+ return (theType == cppu::UnoType<Sequence<sal_Int8>>::get());
+ }
+
+ bool isOUStringType(const Type& theType)
+ {
+ return (theType == cppu::UnoType<OUString>::get() );
+ }
+
+/* A base class for other data provider.
+ */
+class DataProviderBaseImpl : public DataProvider
+{
+public:
+ DataProviderBaseImpl(const Any& data);
+ DataProviderBaseImpl(id data);
+ virtual ~DataProviderBaseImpl() override;
+
+protected:
+ Any mData;
+ //NSData* mSystemData;
+ id mSystemData;
+};
+
+} // unnamed namespace
+
+DataProviderBaseImpl::DataProviderBaseImpl(const Any& data) :
+ mData(data),
+ mSystemData(nil)
+{
+}
+
+DataProviderBaseImpl::DataProviderBaseImpl(id data) :
+ mSystemData(data)
+{
+ [mSystemData retain];
+}
+
+DataProviderBaseImpl::~DataProviderBaseImpl()
+{
+ if (mSystemData)
+ {
+ [mSystemData release];
+ }
+}
+
+namespace {
+
+class UniDataProvider : public DataProviderBaseImpl
+{
+public:
+ UniDataProvider(const Any& data);
+
+ UniDataProvider(NSData* data);
+
+ virtual NSData* getSystemData() override;
+
+ virtual Any getOOoData() override;
+};
+
+}
+
+UniDataProvider::UniDataProvider(const Any& data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+UniDataProvider::UniDataProvider(NSData* data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+NSData* UniDataProvider::getSystemData()
+{
+ OUString ustr;
+ mData >>= ustr;
+
+ OString strUtf8;
+ ustr.convertToString(&strUtf8, RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS);
+
+ return [NSData dataWithBytes: strUtf8.getStr() length: strUtf8.getLength()];
+}
+
+Any UniDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ oOOData <<= OUString(static_cast<const char*>([mSystemData bytes]),
+ [mSystemData length],
+ RTL_TEXTENCODING_UTF8);
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+namespace {
+
+class ByteSequenceDataProvider : public DataProviderBaseImpl
+{
+public:
+ ByteSequenceDataProvider(const Any& data);
+
+ ByteSequenceDataProvider(NSData* data);
+
+ virtual NSData* getSystemData() override;
+
+ virtual Any getOOoData() override;
+};
+
+}
+
+ByteSequenceDataProvider::ByteSequenceDataProvider(const Any& data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+ByteSequenceDataProvider::ByteSequenceDataProvider(NSData* data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+NSData* ByteSequenceDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> rawData;
+ mData >>= rawData;
+
+ return [NSData dataWithBytes: rawData.getArray() length: rawData.getLength()];
+}
+
+Any ByteSequenceDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> byteSequence;
+ byteSequence.realloc(flavorDataLength);
+ memcpy(byteSequence.getArray(), [mSystemData bytes], flavorDataLength);
+ oOOData <<= byteSequence;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+namespace {
+
+class HTMLFormatDataProvider : public DataProviderBaseImpl
+{
+public:
+ HTMLFormatDataProvider(NSData* data);
+
+ virtual NSData* getSystemData() override;
+
+ virtual Any getOOoData() override;
+};
+
+}
+
+HTMLFormatDataProvider::HTMLFormatDataProvider(NSData* data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+NSData* HTMLFormatDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> textHtmlData;
+ mData >>= textHtmlData;
+
+ Sequence<sal_Int8> htmlFormatData = TextHtmlToHTMLFormat(textHtmlData);
+
+ return [NSData dataWithBytes: htmlFormatData.getArray() length: htmlFormatData.getLength()];
+}
+
+Any HTMLFormatDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> unkHtmlData;
+
+ unkHtmlData.realloc(flavorDataLength);
+ memcpy(unkHtmlData.getArray(), [mSystemData bytes], flavorDataLength);
+
+ Sequence<sal_Int8>* pPlainHtml = &unkHtmlData;
+ Sequence<sal_Int8> plainHtml;
+
+ if (isHTMLFormat(unkHtmlData))
+ {
+ plainHtml = HTMLFormatToTextHtml(unkHtmlData);
+ pPlainHtml = &plainHtml;
+ }
+
+ oOOData <<= *pPlainHtml;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+namespace {
+
+
+class PNGDataProvider : public DataProviderBaseImpl
+{
+ NSBitmapImageFileType meImageType;
+public:
+ PNGDataProvider( const Any&, NSBitmapImageFileType);
+
+ PNGDataProvider( NSData*, NSBitmapImageFileType);
+
+ virtual NSData* getSystemData() override;
+
+ virtual Any getOOoData() override;
+};
+
+}
+
+PNGDataProvider::PNGDataProvider( const Any& data, NSBitmapImageFileType eImageType) :
+ DataProviderBaseImpl(data),
+ meImageType( eImageType )
+{
+}
+
+PNGDataProvider::PNGDataProvider( NSData* data, NSBitmapImageFileType eImageType) :
+ DataProviderBaseImpl(data),
+ meImageType( eImageType )
+{
+}
+
+NSData* PNGDataProvider::getSystemData()
+{
+ Sequence<sal_Int8> pngData;
+ mData >>= pngData;
+
+ Sequence<sal_Int8> imgData;
+ NSData* sysData = nullptr;
+ if( PNGToImage( pngData, imgData, meImageType))
+ sysData = [NSData dataWithBytes: imgData.getArray() length: imgData.getLength()];
+
+ return sysData;
+}
+
+/* The AOO 'PCT' filter is not yet good enough to be used
+ and there is no flavor defined for exchanging 'PCT' with AOO
+ so we convert 'PCT' to a PNG and provide this to AOO
+*/
+Any PNGDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if( mSystemData)
+ {
+ const unsigned int flavorDataLength = [mSystemData length];
+ Sequence<sal_Int8> imgData( flavorDataLength);
+ memcpy( imgData.getArray(), [mSystemData bytes], flavorDataLength);
+
+ Sequence<sal_Int8> pngData;
+ if( ImageToPNG( imgData, pngData))
+ oOOData <<= pngData;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+namespace {
+
+class FileListDataProvider : public DataProviderBaseImpl
+{
+public:
+ FileListDataProvider(const Any& data);
+ FileListDataProvider(NSArray* data);
+
+ virtual NSData* getSystemData() override;
+ virtual Any getOOoData() override;
+};
+
+}
+
+FileListDataProvider::FileListDataProvider(const Any& data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+FileListDataProvider::FileListDataProvider(NSArray* data) :
+ DataProviderBaseImpl(data)
+{
+}
+
+NSData* FileListDataProvider::getSystemData()
+{
+ return [NSData data];
+}
+
+Any FileListDataProvider::getOOoData()
+{
+ Any oOOData;
+
+ if (mSystemData)
+ {
+ size_t length = [mSystemData count];
+ size_t lenSeqRequired = 0;
+
+ for (size_t i = 0; i < length; i++)
+ {
+ NSString* fname = [mSystemData objectAtIndex: i];
+ lenSeqRequired += [fname maximumLengthOfBytesUsingEncoding: NSUnicodeStringEncoding] + sizeof(unichar);
+ }
+
+ Sequence<sal_Int8> oOOFileList(lenSeqRequired);
+ unichar* pBuffer = reinterpret_cast<unichar*>(oOOFileList.getArray());
+ memset(pBuffer, 0, lenSeqRequired);
+
+ for (size_t i = 0; i < length; i++)
+ {
+ NSString* fname = [mSystemData objectAtIndex: i];
+ [fname getCharacters: pBuffer];
+ size_t l = [fname length];
+ pBuffer += l + 1;
+ }
+
+ oOOData <<= oOOFileList;
+ }
+ else
+ {
+ oOOData = mData;
+ }
+
+ return oOOData;
+}
+
+DataFlavorMapper::DataFlavorMapper()
+{
+ Reference<XComponentContext> xContext = comphelper::getProcessComponentContext();
+ mrXMimeCntFactory = MimeContentTypeFactory::create( xContext );
+}
+
+DataFlavorMapper::~DataFlavorMapper()
+{
+ // release potential NSStrings
+ for( OfficeOnlyTypes::iterator it = maOfficeOnlyTypes.begin(); it != maOfficeOnlyTypes.end(); ++it )
+ {
+ [it->second release];
+ it->second = nil;
+ }
+}
+
+DataFlavor DataFlavorMapper::systemToOpenOfficeFlavor( const NSString* systemDataFlavor) const
+{
+ DataFlavor oOOFlavor;
+
+ for (size_t i = 0; i < SIZE_FLAVOR_MAP; i++)
+ {
+ if ((flavorMap[i].SystemFlavor == nil && ([systemDataFlavor isEqualToString:[NSString stringWithUTF8String:flavorMap[i].OOoFlavor]]
+ ||
+ [systemDataFlavor hasPrefix:[[NSString stringWithUTF8String:flavorMap[i].OOoFlavor] stringByAppendingString:@";"]]))
+ ||
+ (flavorMap[i].SystemFlavor != nil && [systemDataFlavor isEqualToString:const_cast<NSString*>(flavorMap[i].SystemFlavor)]))
+ {
+ if (flavorMap[i].SystemFlavor == nil)
+ oOOFlavor.MimeType = NSStringToOUString(systemDataFlavor);
+ else
+ oOOFlavor.MimeType = OUString::createFromAscii(flavorMap[i].OOoFlavor);
+ oOOFlavor.HumanPresentableName = OUString::createFromAscii(flavorMap[i].HumanPresentableName);
+ oOOFlavor.DataType = flavorMap[i].DataTypeOUString ? cppu::UnoType<OUString>::get() : cppu::UnoType<Sequence<sal_Int8>>::get();
+ return oOOFlavor;
+ }
+ } // for
+
+ // look if this might be an internal type; if it comes in here it must have
+ // been through openOfficeToSystemFlavor before, so it should then be in the map
+ OUString aTryFlavor( NSStringToOUString( systemDataFlavor ) );
+ if( maOfficeOnlyTypes.find( aTryFlavor ) != maOfficeOnlyTypes.end() )
+ {
+ oOOFlavor.MimeType = aTryFlavor;
+ oOOFlavor.HumanPresentableName.clear();
+ oOOFlavor.DataType = cppu::UnoType<Sequence<sal_Int8>>::get();
+ }
+
+ return oOOFlavor;
+}
+
+const NSString* DataFlavorMapper::openOfficeToSystemFlavor( const DataFlavor& oOOFlavor, bool& rbInternal, bool bIsSystemClipboard ) const
+{
+ const NSString* sysFlavor = nullptr;
+ rbInternal = false;
+
+ for( size_t i = 0; i < SIZE_FLAVOR_MAP; ++i )
+ {
+ if (oOOFlavor.MimeType.startsWith(OUString::createFromAscii(flavorMap[i].OOoFlavor)))
+ {
+ // tdf#151679 Do not push FLAVOR_LINK to macOS general pasteboard
+ // When copying text from a Writer document and the FLAVOR_LINK
+ // flavor is pasted, Writer will edit the copied text in order
+ // to create a bookmark for DDE.
+ // The problem is that many macOS clipboard managers fetch *all*
+ // available flavors that are available in the macOS general
+ // pasteboard instead of just one flavor and this triggers the
+ // FLAVOR_LINK flavor's unusual editing behavor in Writer every
+ // time the user copies Writer text.
+ // Users have reported in tdf#1515679 that on macOS, Microsoft
+ // Writer, Excel, and PowerPoint do not recognize this flavor
+ // like is done on Windows so, in theory, we can just filter out
+ // this flavor when adding flavors to the macOS general pasteboard.
+ // With this change, the FLAVOR_LINK flavor will still be visible
+ // when copying and pasting within a single LibreOffice instance
+ // as well as when dragging from LibreOffice to other applications.
+ if (bIsSystemClipboard && !strcmp(FLAVOR_LINK, flavorMap[i].OOoFlavor))
+ return nullptr;
+
+ if (flavorMap[i].SystemFlavor != nil)
+ sysFlavor = flavorMap[i].SystemFlavor;
+ else
+ sysFlavor = OUStringToNSString(oOOFlavor.MimeType);
+ }
+ }
+
+ if(!sysFlavor)
+ {
+ rbInternal = true;
+ OfficeOnlyTypes::const_iterator it = maOfficeOnlyTypes.find( oOOFlavor.MimeType );
+
+ if( it == maOfficeOnlyTypes.end() )
+ sysFlavor = maOfficeOnlyTypes[ oOOFlavor.MimeType ] = OUStringToNSString( oOOFlavor.MimeType );
+ else
+ sysFlavor = it->second;
+ }
+
+ return sysFlavor;
+}
+
+NSString* DataFlavorMapper::openOfficeImageToSystemFlavor(NSPasteboard* pPasteboard)
+{
+ NSArray *supportedTypes = [NSArray arrayWithObjects: NSPasteboardTypeTIFF, nil];
+ NSString *sysFlavor = [pPasteboard availableTypeFromArray:supportedTypes];
+ return sysFlavor;
+}
+
+DataProviderPtr_t DataFlavorMapper::getDataProvider( const NSString* systemFlavor, Reference<XTransferable> const & rTransferable) const
+{
+ DataProviderPtr_t dp;
+
+ try
+ {
+ DataFlavor oOOFlavor = systemToOpenOfficeFlavor(systemFlavor);
+
+ Any data = rTransferable->getTransferData(oOOFlavor);
+
+ if (isByteSequenceType(data.getValueType()))
+ {
+ /*
+ the HTMLFormatDataProvider prepends segment information to HTML
+ this is useful for exchange with MS Word (which brings this stuff from Windows)
+ but annoying for other applications. Since this extension is not a standard datatype
+ on the Mac, let us not provide but provide normal HTML
+
+ if ([systemFlavor caseInsensitiveCompare: NSHTMLPboardType] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new HTMLFormatDataProvider(data));
+ }
+ else
+ */
+ if ([systemFlavor caseInsensitiveCompare: NSPasteboardTypeTIFF] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t( new PNGDataProvider( data, NSBitmapImageFileTypeTIFF));
+ }
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create
+ // multiple pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ else if ([systemFlavor caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame)
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+ dp = DataProviderPtr_t(new FileListDataProvider(data));
+ }
+ else
+ {
+ dp = DataProviderPtr_t(new ByteSequenceDataProvider(data));
+ }
+ }
+ else // Must be OUString type
+ {
+ SAL_WARN_IF(
+ !isOUStringType(data.getValueType()), "vcl",
+ "must be OUString type");
+ dp = DataProviderPtr_t(new UniDataProvider(data));
+ }
+ }
+ catch( const UnsupportedFlavorException& e )
+ {
+ SAL_WARN( "vcl.osx.clipboard", "DataFlavorMapper::getDataProvider(): Exception: " << e.Message );
+ // Somebody violates the contract of the clipboard
+ // interface @see XTransferable
+ }
+
+ return dp;
+}
+
+DataProviderPtr_t DataFlavorMapper::getDataProvider( const NSString* /*systemFlavor*/, NSArray* systemData)
+{
+ return DataProviderPtr_t(new FileListDataProvider(systemData));
+}
+
+DataProviderPtr_t DataFlavorMapper::getDataProvider( const NSString* systemFlavor, NSData* systemData)
+{
+ DataProviderPtr_t dp;
+
+ if ([systemFlavor caseInsensitiveCompare: NSPasteboardTypeString] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new UniDataProvider(systemData));
+ }
+ else if ([systemFlavor caseInsensitiveCompare: NSPasteboardTypeHTML] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t(new HTMLFormatDataProvider(systemData));
+ }
+ else if ([systemFlavor caseInsensitiveCompare: NSPasteboardTypeTIFF] == NSOrderedSame)
+ {
+ dp = DataProviderPtr_t( new PNGDataProvider(systemData, NSBitmapImageFileTypeTIFF));
+ }
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create multiple
+ // pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ else if ([systemFlavor caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame)
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+ //dp = DataProviderPtr_t(new FileListDataProvider(systemData));
+ }
+ else
+ {
+ dp = DataProviderPtr_t(new ByteSequenceDataProvider(systemData));
+ }
+
+ return dp;
+}
+
+bool DataFlavorMapper::isValidMimeContentType(const OUString& contentType) const
+{
+ bool result = true;
+
+ try
+ {
+ Reference<XMimeContentType> xCntType(mrXMimeCntFactory->createMimeContentType(contentType));
+ }
+ catch( const IllegalArgumentException& e )
+ {
+ SAL_WARN("vcl.osx.clipboard", "DataFlavorMapper::isValidMimeContentType(): Exception: " << e.Message);
+ result = false;
+ }
+
+ return result;
+}
+
+NSArray* DataFlavorMapper::flavorSequenceToTypesArray(const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors, bool bIsSystemClipboard) const
+{
+ sal_uInt32 nFlavors = flavors.getLength();
+ NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity: 1];
+
+ bool bNeedDummyInternalFlavor(false);
+
+ for (sal_uInt32 i = 0; i < nFlavors; i++)
+ {
+ if( flavors[i].MimeType.startsWith("image/bmp") )
+ {
+ [array addObject: NSPasteboardTypeTIFF];
+ }
+ else
+ {
+ const NSString* str = openOfficeToSystemFlavor(flavors[i], bNeedDummyInternalFlavor, bIsSystemClipboard);
+
+ if (str != nullptr)
+ {
+ [str retain];
+ [array addObject: str];
+ }
+ }
+ }
+
+ // #i89462# #i90747#
+ // in case no system flavor was found to report
+ // report at least one so D&D between OOo targets works
+ if( [array count] == 0 || bNeedDummyInternalFlavor)
+ {
+ [array addObject: [NSString stringWithUTF8String: FLAVOR_DUMMY_INTERNAL]];
+ }
+
+ return [array autorelease];
+}
+
+css::uno::Sequence<css::datatransfer::DataFlavor> DataFlavorMapper::typesArrayToFlavorSequence(NSArray* types) const
+{
+ int nFormats = [types count];
+ Sequence<DataFlavor> flavors;
+
+ for (int i = 0; i < nFormats; i++)
+ {
+ NSString* sysFormat = [types objectAtIndex: i];
+ DataFlavor oOOFlavor = systemToOpenOfficeFlavor(sysFormat);
+
+ if (isValidFlavor(oOOFlavor))
+ {
+ flavors.realloc(flavors.getLength() + 1);
+ flavors.getArray()[flavors.getLength() - 1] = oOOFlavor;
+ }
+ }
+
+ return flavors;
+}
+
+NSArray* DataFlavorMapper::getAllSupportedPboardTypes()
+{
+ NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity: SIZE_FLAVOR_MAP];
+
+ for (sal_uInt32 i = 0; i < SIZE_FLAVOR_MAP; i++)
+ {
+ if (flavorMap[i].SystemFlavor != nil)
+ [array addObject: flavorMap[i].SystemFlavor];
+ else
+ [array addObject: [NSString stringWithUTF8String: flavorMap[i].OOoFlavor]];
+ }
+
+ return [array autorelease];
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DataFlavorMapping.hxx b/vcl/osx/DataFlavorMapping.hxx
new file mode 100644
index 0000000000..1d4d4219d3
--- /dev/null
+++ b/vcl/osx/DataFlavorMapping.hxx
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/DataFlavor.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#include <memory>
+#include <unordered_map>
+
+/* An interface to get the clipboard data in either
+ system or OOo format.
+ */
+class DataProvider
+{
+public:
+ virtual ~DataProvider() {};
+
+ /* Get the clipboard data in the system format.
+ The caller has to retain/release the returned
+ CFDataRef on demand.
+ */
+ virtual NSData* getSystemData() = 0;
+
+ /* Get the clipboard data in OOo format.
+ */
+ virtual css::uno::Any getOOoData() = 0;
+};
+
+typedef std::unique_ptr<DataProvider> DataProviderPtr_t;
+
+class DataFlavorMapper
+{
+public:
+ /* Initialize a DataFavorMapper instance. Throws a RuntimeException in case the XMimeContentTypeFactory service
+ cannot be created.
+ */
+ DataFlavorMapper();
+ ~DataFlavorMapper();
+
+ /* Map a system data flavor to an OpenOffice data flavor.
+ Return an empty string if there is not suitable
+ mapping from a system data flavor to an LibreOffice data
+ flavor.
+ */
+ css::datatransfer::DataFlavor systemToOpenOfficeFlavor( const NSString* systemDataFlavor) const;
+
+ /* Map an OpenOffice data flavor to a system data flavor.
+ If there is no suitable mapping available NULL will
+ be returned.
+ */
+ const NSString* openOfficeToSystemFlavor(const css::datatransfer::DataFlavor& oooDataFlavor, bool& rbInternal, bool bIsSystemClipboard = false) const;
+
+ /* Select the best available image data type
+ If there is no suitable mapping available NULL will
+ be returned.
+ */
+ static NSString* openOfficeImageToSystemFlavor(NSPasteboard* pPasteboard);
+
+ /* Get a data provider which is able to provide the data 'rTransferable' offers in a format that can
+ be put on to the system clipboard.
+ */
+ DataProviderPtr_t getDataProvider( const NSString* systemFlavor,
+ const css::uno::Reference< css::datatransfer::XTransferable > & rTransferable) const;
+
+ /* Get a data provider which is able to provide 'systemData' in the OOo expected format.
+ */
+ static DataProviderPtr_t getDataProvider( const NSString* systemFlavor, NSArray* systemData);
+
+ /* Get a data provider which is able to provide 'systemData' in the OOo expected format.
+ */
+ static DataProviderPtr_t getDataProvider( const NSString* systemFlavor, NSData* systemData);
+
+ /* Translate a sequence of DataFlavors into an NSArray of system types.
+ Only those DataFlavors for which a suitable mapping to a system
+ type exist will be contained in the returned types array.
+ */
+ NSArray* flavorSequenceToTypesArray(const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors, bool bIsSystemClipboard = false) const;
+
+ /* Translate an NSArray of system types into a sequence of DataFlavors.
+ Only those types for which a suitable mapping to a DataFlavor
+ exist will be contained in the new DataFlavor Sequence.
+ */
+ css::uno::Sequence<css::datatransfer::DataFlavor> typesArrayToFlavorSequence(NSArray* types) const;
+
+ /* Returns an NSArray containing all pasteboard types supported by OOo
+ */
+ static NSArray* getAllSupportedPboardTypes();
+
+private:
+ /* Determines if the provided Mime content type is valid.
+ */
+ bool isValidMimeContentType(const OUString& contentType) const;
+
+private:
+ css::uno::Reference< css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ typedef std::unordered_map< OUString, NSString* > OfficeOnlyTypes;
+ mutable OfficeOnlyTypes maOfficeOnlyTypes;
+};
+
+typedef std::shared_ptr<DataFlavorMapper> DataFlavorMapperPtr_t;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragActionConversion.cxx b/vcl/osx/DragActionConversion.cxx
new file mode 100644
index 0000000000..d44c3384a6
--- /dev/null
+++ b/vcl/osx/DragActionConversion.cxx
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "DragActionConversion.hxx"
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+using namespace com::sun::star::datatransfer::dnd;
+
+/* Convert office drag actions as defined in
+ <type>css::datatransfer::dnd::DNDConstants</type>
+ into system conform drag actions.
+ */
+unsigned int OfficeToSystemDragActions(sal_Int8 dragActions)
+{
+ unsigned int actions = NSDragOperationNone;
+
+ if (dragActions & DNDConstants::ACTION_COPY)
+ {
+ actions |= NSDragOperationCopy;
+ }
+
+ if (dragActions & DNDConstants::ACTION_MOVE)
+ {
+ actions |= NSDragOperationMove;
+ }
+
+ if (dragActions & DNDConstants::ACTION_LINK)
+ {
+ actions |= NSDragOperationLink;
+ }
+
+ return actions;
+}
+
+/* Convert system conform drag actions into office conform
+ drag actions as defined in
+ <type>css::datatransfer::dnd::DNDConstants</type>.
+ */
+sal_Int8 SystemToOfficeDragActions(unsigned int dragActions)
+{
+ sal_Int8 actions = DNDConstants::ACTION_NONE;
+
+ if (dragActions & NSDragOperationCopy)
+ {
+ actions |= DNDConstants::ACTION_COPY;
+ }
+
+ if (dragActions & NSDragOperationMove)
+ {
+ actions |= DNDConstants::ACTION_MOVE;
+ }
+
+ if (dragActions & NSDragOperationLink)
+ {
+ actions |= DNDConstants::ACTION_LINK;
+ }
+
+ // We map NSDragOperationGeneric to ACTION_DEFAULT to
+ // signal that we have to decide for a drag action
+ if (dragActions & NSDragOperationGeneric)
+ {
+ actions |= DNDConstants::ACTION_DEFAULT;
+ }
+
+ return actions;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragActionConversion.hxx b/vcl/osx/DragActionConversion.hxx
new file mode 100644
index 0000000000..4435f18ee4
--- /dev/null
+++ b/vcl/osx/DragActionConversion.hxx
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+/* Convert office drag actions as defined in
+ <type>css::datatransfer::dnd::DNDConstants</type>
+ into system conform drag actions.
+ */
+unsigned int OfficeToSystemDragActions(sal_Int8 dragActions);
+
+/* Convert system conform drag actions into office conform
+ drag actions as defined in
+ <type>css::datatransfer::dnd::DNDConstants</type>.
+ */
+sal_Int8 SystemToOfficeDragActions(unsigned int dragActions);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSource.cxx b/vcl/osx/DragSource.cxx
new file mode 100644
index 0000000000..fbe3b216a6
--- /dev/null
+++ b/vcl/osx/DragSource.cxx
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/awt/MouseButton.hpp>
+
+#include <rtl/ustring.hxx>
+
+#include <cppuhelper/supportsservice.hxx>
+
+#include "DragSource.hxx"
+#include "DragSourceContext.hxx"
+#include "clipboard.hxx"
+#include "DragActionConversion.hxx"
+
+#include <osx/salframe.h>
+
+#include <cassert>
+
+using namespace cppu;
+using namespace osl;
+using namespace com::sun::star;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::awt::MouseButton;
+using namespace com::sun::star::awt;
+using namespace com::sun::star::lang;
+using namespace comphelper;
+
+// For LibreOffice internal D&D we provide the Transferable without NSDragPboard
+// interference as a shortcut, see tdf#100097 for how dbaccess depends on this
+uno::Reference<XTransferable> DragSource::g_XTransferable;
+NSView* DragSource::g_DragSourceView = nil;
+bool DragSource::g_DropSuccessSet = false;
+bool DragSource::g_DropSuccess = false;
+
+static OUString dragSource_getImplementationName()
+{
+ return "com.sun.star.comp.datatransfer.dnd.OleDragSource_V1";
+}
+
+static Sequence<OUString> dragSource_getSupportedServiceNames()
+{
+ return { OUString("com.sun.star.datatransfer.dnd.OleDragSource") };
+}
+
+@implementation DragSourceHelper;
+
+-(DragSourceHelper*)initWithDragSource: (DragSource*) pds
+{
+ self = [super init];
+
+ if (self)
+ {
+ mDragSource = pds;
+ }
+
+ return self;
+}
+
+-(void)mouseDown: (NSEvent*)theEvent
+{
+ mDragSource->saveMouseEvent(theEvent);
+}
+
+-(void)mouseDragged: (NSEvent*)theEvent
+{
+ mDragSource->saveMouseEvent(theEvent);
+}
+
+-(unsigned int)draggingSourceOperationMaskForLocal: (BOOL)isLocal
+{
+ return mDragSource->getSupportedDragOperations(isLocal);
+}
+
+-(void)draggedImage:(NSImage*)anImage beganAt:(NSPoint)aPoint
+{
+ (void)anImage;
+ (void)aPoint;
+ DragSourceDragEvent dsde(mDragSource->getXWeak(),
+ new DragSourceContext,
+ mDragSource,
+ DNDConstants::ACTION_COPY,
+ DNDConstants::ACTION_COPY);
+
+ mDragSource->mXDragSrcListener->dragEnter(dsde);
+}
+
+-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
+{
+ (void)anImage;
+ (void)aPoint;
+ // an internal drop can accept the drop but fail with dropComplete( false )
+ // this is different than the Cocoa API
+ bool bDropSuccess = operation != NSDragOperationNone;
+ if( DragSource::g_DropSuccessSet )
+ bDropSuccess = DragSource::g_DropSuccess;
+
+ DragSourceDropEvent dsde(mDragSource->getXWeak(),
+ new DragSourceContext,
+ static_cast< XDragSource* >(mDragSource),
+ SystemToOfficeDragActions(operation),
+ bDropSuccess );
+
+ mDragSource->mXDragSrcListener->dragDropEnd(dsde);
+ mDragSource->mXDragSrcListener.clear();
+}
+
+-(void)draggedImage:(NSImage *)draggedImage movedTo:(NSPoint)screenPoint
+{
+ (void)draggedImage;
+ (void)screenPoint;
+ DragSourceDragEvent dsde(mDragSource->getXWeak(),
+ new DragSourceContext,
+ mDragSource,
+ DNDConstants::ACTION_COPY,
+ DNDConstants::ACTION_COPY);
+
+ mDragSource->mXDragSrcListener->dragOver(dsde);
+}
+
+@end
+
+DragSource::DragSource():
+ WeakComponentImplHelper<XDragSource, XInitialization, XServiceInfo>(m_aMutex),
+ mView(nullptr),
+ mpFrame(nullptr),
+ mLastMouseEventBeforeStartDrag(nil),
+ mDragSourceHelper(nil),
+ m_MouseButton(0)
+{
+}
+
+DragSource::~DragSource()
+{
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ [static_cast<id <MouseEventListener>>(mView) unregisterMouseEventListener: mDragSourceHelper];
+ [mDragSourceHelper release];
+}
+
+void SAL_CALL DragSource::initialize(const Sequence< Any >& aArguments)
+{
+ if (aArguments.getLength() < 2)
+ {
+ throw Exception("DragSource::initialize: Not enough parameter.",
+ getXWeak());
+ }
+
+ Any pNSView = aArguments[1];
+ sal_uInt64 tmp = 0;
+ pNSView >>= tmp;
+ mView = reinterpret_cast<NSView*>(tmp);
+
+ /* All SalFrameView the base class for all VCL system views inherits from
+ NSView in order to get mouse and other events. This is the only way to
+ get these events. In order to start a drag operation we need to provide
+ the mouse event which was the trigger. SalFrameView therefore implements
+ a hook mechanism so that we can get mouse events for our purpose.
+ */
+ if (![mView respondsToSelector: @selector(registerMouseEventListener:)] ||
+ ![mView respondsToSelector: @selector(unregisterMouseEventListener:)])
+ {
+ throw Exception("DragSource::initialize: Provided view doesn't support mouse listener",
+ getXWeak());
+ }
+ NSWindow* pWin = [mView window];
+ if( ! pWin || ![pWin respondsToSelector: @selector(getSalFrame)] )
+ {
+ throw Exception("DragSource::initialize: Provided view is not attached to a vcl frame",
+ getXWeak());
+ }
+ mpFrame = reinterpret_cast<AquaSalFrame*>([pWin performSelector: @selector(getSalFrame)]);
+
+ mDragSourceHelper = [[DragSourceHelper alloc] initWithDragSource: this];
+
+ if (mDragSourceHelper == nil)
+ {
+ throw Exception("DragSource::initialize: Cannot initialize DragSource",
+ getXWeak());
+ }
+
+ [static_cast<id <MouseEventListener>>(mView) registerMouseEventListener: mDragSourceHelper];
+}
+
+sal_Bool SAL_CALL DragSource::isDragImageSupported( )
+{
+ return true;
+}
+
+sal_Int32 SAL_CALL DragSource::getDefaultCursor( sal_Int8 /*dragAction*/ )
+{
+ return 0;
+}
+
+void SAL_CALL DragSource::startDrag(const DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32 /*cursor*/,
+ sal_Int32 /*image*/,
+ const uno::Reference<XTransferable >& transferable,
+ const uno::Reference<XDragSourceListener >& listener )
+{
+ MutexGuard guard(m_aMutex);
+
+ assert(listener.is() && "DragSource::startDrag: No XDragSourceListener provided");
+ assert(transferable.is() && "DragSource::startDrag: No transferable provided");
+
+ trigger.Event >>= mMouseEvent;
+ m_MouseButton= mMouseEvent.Buttons;
+ mXDragSrcListener = listener;
+ mXCurrentContext = static_cast<XDragSourceContext*>(new DragSourceContext);
+ rtl::Reference<AquaClipboard> clipb(new AquaClipboard(nullptr, false));
+ g_XTransferable = transferable;
+ clipb->setContents(g_XTransferable, uno::Reference<XClipboardOwner>());
+ mDragSourceActions = sourceActions;
+ g_DragSourceView = mView;
+
+ NSSize sz;
+ sz.width = 5;
+ sz.height = 5;
+
+ NSImage* dragImage;
+ dragImage = [[NSImage alloc] initWithSize: sz];
+
+ NSRect bounds;
+ bounds.origin = NSMakePoint(0,0);
+ bounds.size = sz;
+
+ [dragImage lockFocus];
+ [[NSColor blackColor] set];
+ [NSBezierPath fillRect: bounds];
+ [dragImage unlockFocus];
+
+ NSPoint pInWnd = [mLastMouseEventBeforeStartDrag locationInWindow];
+ NSPoint p;
+ p = [mView convertPoint: pInWnd fromView: nil];
+ p.x = p.x - sz.width/2;
+ p.y = p.y - sz.height/2;
+
+ // reset drop success flags
+ g_DropSuccessSet = false;
+ g_DropSuccess = false;
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.7 dragImage:at:offset:event:pasteboard:source:slideBack:
+ [mView dragImage: dragImage
+ at: p
+ offset: NSMakeSize(0,0)
+ event: mLastMouseEventBeforeStartDrag
+ pasteboard: clipb->getPasteboard()
+ source: mDragSourceHelper
+ slideBack: true];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ [dragImage release];
+
+ g_XTransferable.clear();
+ g_DragSourceView = nil;
+
+ // reset drop success flags
+ g_DropSuccessSet = false;
+ g_DropSuccess = false;
+}
+
+// In order to initiate a D&D operation we need to
+// provide the triggering mouse event which we get
+// from the SalFrameView that is associated with
+// this DragSource
+void DragSource::saveMouseEvent(NSEvent* theEvent)
+{
+ if (mLastMouseEventBeforeStartDrag != nil)
+ {
+ [mLastMouseEventBeforeStartDrag release];
+ }
+
+ mLastMouseEventBeforeStartDrag = theEvent;
+}
+
+/* isLocal indicates whether or not the DnD operation is OOo
+ internal.
+ */
+unsigned int DragSource::getSupportedDragOperations(bool isLocal) const
+{
+ unsigned int srcActions = OfficeToSystemDragActions(mDragSourceActions);
+
+ if (isLocal)
+ {
+ // Support NSDragOperation generic which means we can
+ // decide which D&D operation to choose. We map
+ // NSDragOperationGeneric to DNDConstants::ACTION_DEFAULT
+ // in SystemToOfficeDragActions to signal this and
+ // use it in DropTarget::determineDropAction
+ srcActions |= NSDragOperationGeneric;
+ }
+ else
+ {
+ // Mask out link and move operations on external DnD
+ srcActions &= ~(NSDragOperationMove | NSDragOperationLink);
+ }
+
+ return srcActions;
+}
+
+OUString SAL_CALL DragSource::getImplementationName( )
+{
+ return dragSource_getImplementationName();
+}
+
+sal_Bool SAL_CALL DragSource::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL DragSource::getSupportedServiceNames()
+{
+ return dragSource_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSource.hxx b/vcl/osx/DragSource.hxx
new file mode 100644
index 0000000000..96a8fb48a8
--- /dev/null
+++ b/vcl/osx/DragSource.hxx
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSource.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/uno/XComponentContext.hpp>
+#include <osl/thread.h>
+#include <com/sun/star/awt/MouseEvent.hpp>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+class DragSource;
+class AquaSalFrame;
+
+/* The functions declared in this protocol are actually
+ declared in vcl/inc/osx/salframe.h. Because we want
+ to avoid importing VCL headers in UNO services and
+ on the other hand want to avoid warnings caused by
+ gcc complaining about unknowness of these functions
+ we declare them in a protocol here and cast at the
+ appropriate places.
+*/
+@protocol MouseEventListener
+-(void)registerMouseEventListener:(id)theHandler;
+-(void)unregisterMouseEventListener:(id)theHandler;
+@end
+
+@interface DragSourceHelper : NSObject
+{
+ DragSource* mDragSource;
+}
+
+-(DragSourceHelper*)initWithDragSource: (DragSource*) pds;
+
+-(void)mouseDown: (NSEvent*)theEvent;
+-(void)mouseDragged: (NSEvent*)theEvent;
+
+-(unsigned int)draggingSourceOperationMaskForLocal:(BOOL)isLocal;
+-(void)draggedImage:(NSImage*)anImage beganAt:(NSPoint)aPoint;
+-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation;
+-(void)draggedImage:(NSImage *)draggedImage movedTo:(NSPoint)screenPoint;
+
+@end
+
+class DragSource : public ::cppu::BaseMutex,
+ public ::cppu::WeakComponentImplHelper< css::datatransfer::dnd::XDragSource,
+ css::lang::XInitialization,
+ css::lang::XServiceInfo >
+{
+public:
+ DragSource();
+ virtual ~DragSource() override;
+ DragSource(const DragSource&) = delete;
+ DragSource& operator=(const DragSource&) = delete;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XDragSource
+ virtual sal_Bool SAL_CALL isDragImageSupported( ) override;
+
+ virtual sal_Int32 SAL_CALL getDefaultCursor(sal_Int8 dragAction) override;
+
+ virtual void SAL_CALL startDrag( const css::datatransfer::dnd::DragGestureEvent& trigger,
+ sal_Int8 sourceActions,
+ sal_Int32 cursor,
+ sal_Int32 image,
+ const css::uno::Reference< css::datatransfer::XTransferable >& transferable,
+ const css::uno::Reference< css::datatransfer::dnd::XDragSourceListener >& listener ) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ void saveMouseEvent(NSEvent* theEvent);
+ unsigned int getSupportedDragOperations(bool isLocal) const;
+
+public:
+ // The context notifies the XDragSourceListeners
+ css::uno::Reference< css::datatransfer::dnd::XDragSourceContext > mXCurrentContext;
+
+ id mView;
+ AquaSalFrame* mpFrame;
+ NSEvent* mLastMouseEventBeforeStartDrag;
+ DragSourceHelper* mDragSourceHelper;
+ css::awt::MouseEvent mMouseEvent;
+ css::uno::Reference< css::datatransfer::XTransferable > mXTransferable;
+ css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mXDragSrcListener;
+ // The mouse button that set off the drag and drop operation
+ short m_MouseButton;
+ sal_Int8 mDragSourceActions;
+
+ static css::uno::Reference< css::datatransfer::XTransferable > g_XTransferable;
+ static NSView* g_DragSourceView;
+ static bool g_DropSuccessSet;
+ static bool g_DropSuccess;
+
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSourceContext.cxx b/vcl/osx/DragSourceContext.cxx
new file mode 100644
index 0000000000..253dc867d3
--- /dev/null
+++ b/vcl/osx/DragSourceContext.cxx
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+
+#include "DragSourceContext.hxx"
+
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace com::sun::star::uno;
+using namespace cppu;
+
+DragSourceContext::DragSourceContext() :
+ WeakComponentImplHelper<XDragSourceContext>(m_aMutex)
+{
+}
+
+DragSourceContext::~DragSourceContext()
+{
+}
+
+sal_Int32 SAL_CALL DragSourceContext::getCurrentCursor( )
+{
+ return 0;
+}
+
+void SAL_CALL DragSourceContext::setCursor( sal_Int32 /*cursorId*/ )
+{
+}
+
+void SAL_CALL DragSourceContext::setImage( sal_Int32 /*imageId*/ )
+{
+}
+
+void SAL_CALL DragSourceContext::transferablesFlavorsChanged( )
+{
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DragSourceContext.hxx b/vcl/osx/DragSourceContext.hxx
new file mode 100644
index 0000000000..e1f986d3d4
--- /dev/null
+++ b/vcl/osx/DragSourceContext.hxx
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp>
+#include <cppuhelper/compbase.hxx>
+#include <cppuhelper/basemutex.hxx>
+
+// This class fires events to XDragSourceListener implementations.
+// Of that interface only dragDropEnd and dropActionChanged are called.
+// The functions dragEnter, dragExit and dragOver are not supported
+// currently.
+// An instance of SourceContext only lives as long as the drag and drop
+// operation lasts.
+class DragSourceContext: public cppu::BaseMutex,
+ public cppu::WeakComponentImplHelper<css::datatransfer::dnd::XDragSourceContext>
+{
+public:
+ DragSourceContext();
+ virtual ~DragSourceContext() override;
+ DragSourceContext(const DragSourceContext&) = delete;
+ DragSourceContext& operator=(const DragSourceContext&) = delete;
+
+ virtual sal_Int32 SAL_CALL getCurrentCursor( ) override;
+
+ virtual void SAL_CALL setCursor( sal_Int32 cursorId ) override;
+
+ virtual void SAL_CALL setImage( sal_Int32 imageId ) override;
+
+ virtual void SAL_CALL transferablesFlavorsChanged( ) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DropTarget.cxx b/vcl/osx/DropTarget.cxx
new file mode 100644
index 0000000000..d9e34030d1
--- /dev/null
+++ b/vcl/osx/DropTarget.cxx
@@ -0,0 +1,543 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDragEnterEvent.hpp>
+#include <cppuhelper/interfacecontainer.hxx>
+#include "clipboard.hxx"
+#include "DropTarget.hxx"
+#include "DragActionConversion.hxx"
+#include "DragSource.hxx"
+#include <rtl/ustring.h>
+#include <premac.h>
+#include <Carbon/Carbon.h>
+#include <postmac.h>
+#include <osx/salframe.h>
+#include <osx/salframeview.h>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace cppu;
+using namespace osl;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::datatransfer::dnd;
+using namespace com::sun::star::datatransfer::dnd::DNDConstants;
+using namespace com::sun::star::datatransfer::clipboard;
+using namespace com::sun::star::lang;
+using namespace com::sun::star::uno;
+using namespace com::sun::star;
+using namespace comphelper;
+
+static OUString dropTarget_getImplementationName()
+{
+ return "com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1";
+}
+
+static Sequence<OUString> dropTarget_getSupportedServiceNames()
+{
+ return { OUString("com.sun.star.datatransfer.dnd.OleDropTarget") };
+}
+
+namespace /* private */
+{
+ // Cocoa's coordinate system has its origin lower-left, VCL's
+ // coordinate system upper-left hence we need to transform
+ // coordinates
+
+ void CocoaToVCL(NSPoint& rPoint, const NSRect& bounds)
+ {
+ rPoint.y = bounds.size.height - rPoint.y;
+ }
+}
+
+@implementation DropTargetHelper
+
+-(DropTargetHelper*)initWithDropTarget:(DropTarget*)pdt
+{
+ self = [super init];
+
+ if (self)
+ {
+ mDropTarget = pdt;
+ }
+
+ return self;
+}
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ return mDropTarget->draggingEntered(sender);
+}
+
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ return mDropTarget->draggingUpdated(sender);
+}
+
+-(void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ mDropTarget->draggingExited(sender);
+}
+
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+ (void) sender;
+ return DropTarget::prepareForDragOperation();
+}
+
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ (void) sender;
+ return mDropTarget->performDragOperation();
+}
+
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
+{
+ mDropTarget->concludeDragOperation(sender);
+}
+
+@end
+
+DropTarget::DropTarget() :
+ WeakComponentImplHelper<XInitialization, XDropTarget, XDropTargetDragContext, XDropTargetDropContext, XServiceInfo>(m_aMutex),
+ mView(nil),
+ mpFrame(nullptr),
+ mDropTargetHelper(nil),
+ mbActive(false),
+ mDragSourceSupportedActions(DNDConstants::ACTION_NONE),
+ mSelectedDropAction(DNDConstants::ACTION_NONE),
+ mDefaultActions(DNDConstants::ACTION_COPY_OR_MOVE | DNDConstants::ACTION_LINK | DNDConstants::ACTION_DEFAULT)
+{
+ mDataFlavorMapper = std::make_shared<DataFlavorMapper>();
+}
+
+DropTarget::~DropTarget()
+{
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ [static_cast<id <DraggingDestinationHandler>>(mView) unregisterDraggingDestinationHandler:mDropTargetHelper];
+ [mDropTargetHelper release];
+}
+
+sal_Int8 DropTarget::determineDropAction(sal_Int8 dropActions, id sender) const
+{
+ sal_Int8 dropAct = dropActions;
+ bool srcAndDestEqual = false;
+
+ if ([sender draggingSource] != nil)
+ {
+ // Internal DnD
+ NSView* destView = [[sender draggingDestinationWindow] contentView];
+ srcAndDestEqual = (DragSource::g_DragSourceView == destView);
+ }
+
+ // If ACTION_DEFAULT is set this means NSDragOperationGeneric
+ // has been set and we map this to ACTION_MOVE or ACTION_COPY
+ // depending on whether or not source and dest are equal,
+ // this hopefully satisfies all parties
+ if( (dropActions == DNDConstants::ACTION_DEFAULT)
+ || ((dropActions == mDragSourceSupportedActions)
+ && !(~mDragSourceSupportedActions & DNDConstants::ACTION_COPY_OR_MOVE ) ) )
+ {
+ dropAct = srcAndDestEqual ? DNDConstants::ACTION_MOVE :
+ DNDConstants::ACTION_COPY;
+ }
+ // if more than one drop actions have been specified
+ // set ACTION_DEFAULT in order to let the drop target
+ // decide which one to use
+ else if (dropActions != DNDConstants::ACTION_NONE &&
+ dropActions != DNDConstants::ACTION_MOVE &&
+ dropActions != DNDConstants::ACTION_COPY &&
+ dropActions != DNDConstants::ACTION_LINK)
+ {
+ if (srcAndDestEqual)
+ {
+ dropAct = dropActions;
+ }
+ else // source and destination are different
+ {
+ if (dropActions & DNDConstants::ACTION_COPY)
+ dropAct = DNDConstants::ACTION_COPY;
+ else if (dropActions & DNDConstants::ACTION_MOVE)
+ dropAct = DNDConstants::ACTION_MOVE;
+ else if (dropActions & DNDConstants::ACTION_LINK)
+ dropAct = DNDConstants::ACTION_LINK;
+ }
+
+ dropAct |= DNDConstants::ACTION_DEFAULT;
+ }
+
+ return dropAct;
+}
+
+NSDragOperation DropTarget::draggingEntered(id sender)
+{
+ // Initially when DnD will be started no modifier key can be pressed yet
+ // thus we are getting all actions that the drag source supports, we save
+ // this value because later the system masks the drag source actions if
+ // a modifier key will be pressed
+ mDragSourceSupportedActions = SystemToOfficeDragActions([sender draggingSourceOperationMask]);
+
+ // Only if the drop target is really interested in the drag actions
+ // supported by the source
+ if (mDragSourceSupportedActions & mDefaultActions)
+ {
+ sal_Int8 currentAction = determineDropAction(mDragSourceSupportedActions, sender);
+
+ NSRect bounds = [mView bounds];
+ NSPoint mouseLoc = [NSEvent mouseLocation];
+
+ id wnd = [mView window];
+ NSPoint dragLocation = [mView convertPoint:[wnd convertRectFromScreen:NSMakeRect(mouseLoc.x, mouseLoc.y, 1, 1)].origin fromView:nil];
+
+ CocoaToVCL(dragLocation, bounds);
+
+ sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x);
+ sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y);
+
+ NSPasteboard* dragPboard = [sender draggingPasteboard];
+ mXCurrentDragClipboard = new AquaClipboard(dragPboard, false);
+
+ uno::Reference<XTransferable> xTransferable = DragSource::g_XTransferable.is() ?
+ DragSource::g_XTransferable : mXCurrentDragClipboard->getContents();
+
+ DropTargetDragEnterEvent dtdee(getXWeak(),
+ 0,
+ this,
+ currentAction,
+ posX,
+ posY,
+ mDragSourceSupportedActions,
+ xTransferable->getTransferDataFlavors());
+
+ fire_dragEnter(dtdee);
+ }
+
+ return OfficeToSystemDragActions(mSelectedDropAction);
+}
+
+NSDragOperation DropTarget::draggingUpdated(id sender)
+{
+ sal_Int8 currentDragSourceActions =
+ SystemToOfficeDragActions([sender draggingSourceOperationMask]);
+ NSDragOperation dragOp = NSDragOperationNone;
+
+ if (currentDragSourceActions & mDefaultActions)
+ {
+ sal_Int8 currentAction = determineDropAction(currentDragSourceActions, sender);
+ NSRect bounds = [mView bounds];
+ NSPoint mouseLoc = [NSEvent mouseLocation];
+
+ id wnd = [mView window];
+ NSPoint dragLocation = [mView convertPoint:[wnd convertRectFromScreen:NSMakeRect(mouseLoc.x, mouseLoc.y, 1, 1)].origin fromView:nil];
+
+ CocoaToVCL(dragLocation, bounds);
+
+ sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x);
+ sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y);
+
+ DropTargetDragEvent dtde(getXWeak(),
+ 0,
+ this,
+ currentAction,
+ posX,
+ posY,
+ mDragSourceSupportedActions);
+
+ fire_dragOver(dtde);
+
+ // drag over callbacks likely have rendered something
+ [mView setNeedsDisplay: true];
+
+ dragOp = OfficeToSystemDragActions(mSelectedDropAction);
+
+ //NSLog(@"Drag update: Source actions: %x proposed action %x selected action %x", mDragSourceSupportedActions, currentAction, mSelectedDropAction);
+ }
+
+ if (dragOp == NSDragOperationNone)
+ [[NSCursor operationNotAllowedCursor] set];
+ else if (dragOp == NSDragOperationCopy)
+ [[NSCursor dragCopyCursor] set];
+ else
+ [[NSCursor arrowCursor] set];
+
+ return dragOp;
+}
+
+void DropTarget::draggingExited(id /*sender*/)
+{
+ DropTargetEvent dte(getXWeak(), 0);
+ fire_dragExit(dte);
+ mDragSourceSupportedActions = DNDConstants::ACTION_NONE;
+ mSelectedDropAction = DNDConstants::ACTION_NONE;
+ [[NSCursor arrowCursor] set];
+}
+
+BOOL DropTarget::prepareForDragOperation()
+{
+ return true;
+}
+
+BOOL DropTarget::performDragOperation()
+{
+ bool bSuccess = false;
+
+ if (mSelectedDropAction != DNDConstants::ACTION_NONE)
+ {
+ uno::Reference<XTransferable> xTransferable = DragSource::g_XTransferable;
+
+ if (!DragSource::g_XTransferable.is())
+ {
+ xTransferable = mXCurrentDragClipboard->getContents();
+ }
+
+ NSRect bounds = [mView bounds];
+ NSPoint mouseLoc = [NSEvent mouseLocation];
+
+ id wnd = [mView window];
+ NSPoint dragLocation = [mView convertPoint:[wnd convertRectFromScreen:NSMakeRect(mouseLoc.x, mouseLoc.y, 1, 1)].origin fromView:nil];
+
+ CocoaToVCL(dragLocation, bounds);
+
+ sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x);
+ sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y);
+
+ DropTargetDropEvent dtde(getXWeak(),
+ 0,
+ this,
+ mSelectedDropAction,
+ posX,
+ posY,
+ mDragSourceSupportedActions,
+ xTransferable);
+
+ fire_drop(dtde);
+
+ bSuccess = true;
+ }
+
+ return bSuccess;
+}
+
+void DropTarget::concludeDragOperation(id /*sender*/)
+{
+ mDragSourceSupportedActions = DNDConstants::ACTION_NONE;
+ mSelectedDropAction = DNDConstants::ACTION_NONE;
+ mXCurrentDragClipboard.clear();
+ [[NSCursor arrowCursor] set];
+}
+
+// called from WeakComponentImplHelperX::dispose
+// WeakComponentImplHelper calls disposing before it destroys
+// itself.
+void SAL_CALL DropTarget::disposing()
+{
+}
+
+void SAL_CALL DropTarget::initialize(const Sequence< Any >& aArguments)
+{
+ if (aArguments.getLength() < 2)
+ {
+ throw RuntimeException("DropTarget::initialize: Cannot install window event handler",
+ getXWeak());
+ }
+
+ Any pNSView = aArguments[0];
+ sal_uInt64 tmp = 0;
+ pNSView >>= tmp;
+ mView = reinterpret_cast<id>(tmp);
+ mpFrame = [static_cast<SalFrameView*>(mView) getSalFrame];
+
+ mDropTargetHelper = [[DropTargetHelper alloc] initWithDropTarget: this];
+
+ [static_cast<id <DraggingDestinationHandler>>(mView) registerDraggingDestinationHandler:mDropTargetHelper];
+ [mView registerForDraggedTypes: DataFlavorMapper::getAllSupportedPboardTypes()];
+
+ id wnd = [mView window];
+ NSWindow* parentWnd = [wnd parentWindow];
+ unsigned int topWndStyle = (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable);
+ unsigned int wndStyles = [wnd styleMask] & topWndStyle;
+
+ if (parentWnd == nil && (wndStyles == topWndStyle))
+ {
+ [wnd registerDraggingDestinationHandler:mDropTargetHelper];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create
+ // multiple pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ [wnd registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, nil]];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+}
+
+void SAL_CALL DropTarget::addDropTargetListener(const uno::Reference<XDropTargetListener>& dtl)
+{
+ rBHelper.addListener(cppu::UnoType<decltype(dtl)>::get(), dtl);
+}
+
+void SAL_CALL DropTarget::removeDropTargetListener(const uno::Reference<XDropTargetListener>& dtl)
+{
+ rBHelper.removeListener(cppu::UnoType<decltype(dtl)>::get(), dtl);
+}
+
+sal_Bool SAL_CALL DropTarget::isActive( )
+{
+ return mbActive;
+}
+
+void SAL_CALL DropTarget::setActive(sal_Bool active)
+{
+ mbActive = active;
+}
+
+sal_Int8 SAL_CALL DropTarget::getDefaultActions()
+{
+ return mDefaultActions;
+}
+
+void SAL_CALL DropTarget::setDefaultActions(sal_Int8 actions)
+{
+ OSL_ENSURE( actions < 8, "No valid default actions");
+ mDefaultActions= actions;
+}
+
+void SAL_CALL DropTarget::acceptDrag(sal_Int8 dragOperation)
+{
+ mSelectedDropAction = dragOperation;
+}
+
+void SAL_CALL DropTarget::rejectDrag()
+{
+ mSelectedDropAction = DNDConstants::ACTION_NONE;
+}
+
+void SAL_CALL DropTarget::acceptDrop(sal_Int8 dropOperation)
+{
+ mSelectedDropAction = dropOperation;
+}
+
+void SAL_CALL DropTarget::rejectDrop()
+{
+ mSelectedDropAction = DNDConstants::ACTION_NONE;
+}
+
+void SAL_CALL DropTarget::dropComplete(sal_Bool success)
+{
+ // Reset the internal transferable used as shortcut in case this is
+ // an internal D&D operation
+ DragSource::g_XTransferable.clear();
+ DragSource::g_DropSuccessSet = true;
+ DragSource::g_DropSuccess = success;
+}
+
+void DropTarget::fire_drop( const DropTargetDropEvent& dte)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->drop( dte); }
+ catch(RuntimeException&) {}
+ }
+ }
+}
+
+void DropTarget::fire_dragEnter(const DropTargetDragEnterEvent& e)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->dragEnter( e); }
+ catch (RuntimeException&) {}
+ }
+ }
+}
+
+void DropTarget::fire_dragExit(const DropTargetEvent& dte)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->dragExit( dte); }
+ catch (RuntimeException&) {}
+ }
+ }
+}
+
+void DropTarget::fire_dragOver(const DropTargetDragEvent& dtde)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer );
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->dragOver( dtde); }
+ catch (RuntimeException&) {}
+ }
+ }
+}
+
+void DropTarget::fire_dropActionChanged(const DropTargetDragEvent& dtde)
+{
+ OInterfaceContainerHelper* pContainer= rBHelper.getContainer( cppu::UnoType<XDropTargetListener>::get());
+ if( pContainer)
+ {
+ OInterfaceIteratorHelper iter( *pContainer);
+ while( iter.hasMoreElements())
+ {
+ uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next()));
+
+ try { listener->dropActionChanged( dtde); }
+ catch (RuntimeException&) {}
+ }
+ }
+}
+
+OUString SAL_CALL DropTarget::getImplementationName()
+{
+ return dropTarget_getImplementationName();
+}
+
+sal_Bool SAL_CALL DropTarget::supportsService( const OUString& ServiceName )
+{
+ return cppu::supportsService(this, ServiceName);
+}
+
+Sequence< OUString > SAL_CALL DropTarget::getSupportedServiceNames( )
+{
+ return dropTarget_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/DropTarget.hxx b/vcl/osx/DropTarget.hxx
new file mode 100644
index 0000000000..ffc53a4e17
--- /dev/null
+++ b/vcl/osx/DropTarget.hxx
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "DataFlavorMapping.hxx"
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+
+#include <com/sun/star/datatransfer/dnd/XDropTargetListener.hpp>
+#include <com/sun/star/datatransfer/dnd/DropTargetDragEnterEvent.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDragContext.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTargetDropContext.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+class DropTarget;
+class AquaSalFrame;
+
+/* The functions declared in this protocol are actually
+ declared in vcl/inc/osx/salframe.h. Because we want
+ to avoid importing VCL headers in UNO services and
+ on the other hand want to avoid warnings caused by
+ gcc complaining about unknowness of these functions
+ we declare them in a protocol here and cast at the
+ appropriate places.
+*/
+@protocol DraggingDestinationHandler
+-(void)registerDraggingDestinationHandler:(id)theHandler;
+-(void)unregisterDraggingDestinationHandler:(id)theHandler;
+@end
+
+@interface DropTargetHelper : NSObject
+{
+ DropTarget* mDropTarget;
+}
+
+-(DropTargetHelper*)initWithDropTarget:(DropTarget*)pdt;
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender;
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender;
+-(void)draggingExited:(id <NSDraggingInfo>)sender;
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender;
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender;
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender;
+
+@end
+
+class DropTarget: public cppu::BaseMutex,
+ public cppu::WeakComponentImplHelper< css::lang::XInitialization,
+ css::datatransfer::dnd::XDropTarget,
+ css::datatransfer::dnd::XDropTargetDragContext,
+ css::datatransfer::dnd::XDropTargetDropContext,
+ css::lang::XServiceInfo >
+{
+public:
+ DropTarget();
+ virtual ~DropTarget() override;
+ DropTarget(const DropTarget&) = delete;
+ DropTarget& operator=(const DropTarget&) = delete;
+
+ // Overrides WeakComponentImplHelper::disposing which is called by
+ // WeakComponentImplHelper::dispose
+ // Must be called.
+ virtual void SAL_CALL disposing() override;
+
+ // XInitialization
+ virtual void SAL_CALL initialize( const css::uno::Sequence< css::uno::Any >& aArguments ) override;
+
+ // XDropTarget
+ virtual void SAL_CALL addDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& dtl ) override;
+
+ virtual void SAL_CALL removeDropTargetListener( const css::uno::Reference< css::datatransfer::dnd::XDropTargetListener >& dtl ) override;
+
+ // Default is not active
+ virtual sal_Bool SAL_CALL isActive() override;
+ virtual void SAL_CALL setActive(sal_Bool isActive) override;
+ virtual sal_Int8 SAL_CALL getDefaultActions() override;
+ virtual void SAL_CALL setDefaultActions(sal_Int8 actions) override;
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) override;
+ virtual void SAL_CALL rejectDrag() override;
+
+ // XDropTargetDragContext
+ virtual void SAL_CALL acceptDrop(sal_Int8 dropOperation) override;
+ virtual void SAL_CALL rejectDrop() override;
+ virtual void SAL_CALL dropComplete(sal_Bool success) override;
+
+ // XServiceInfo
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ // NSDraggingDestination protocol functions
+ NSDragOperation draggingEntered(id sender);
+ NSDragOperation draggingUpdated(id sender);
+ void draggingExited(id sender);
+ static BOOL prepareForDragOperation();
+ BOOL performDragOperation();
+ void concludeDragOperation(id sender);
+
+ /* If multiple actions are supported by the drag source and
+ the user did not choose a specific action by pressing a
+ modifier key choose a default action to be proposed to
+ the application.
+ */
+ sal_Int8 determineDropAction(sal_Int8 dropActions, id sender) const;
+
+private:
+ void fire_drop(const css::datatransfer::dnd::DropTargetDropEvent& dte);
+ void fire_dragEnter(const css::datatransfer::dnd::DropTargetDragEnterEvent& dtdee);
+ void fire_dragExit(const css::datatransfer::dnd::DropTargetEvent& dte);
+ void fire_dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde);
+ void fire_dropActionChanged(const css::datatransfer::dnd::DropTargetDragEvent& dtde);
+
+private:
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetDragContext > mXCurrentDragContext;
+ css::uno::Reference< css::datatransfer::dnd::XDropTargetDropContext > mXCurrentDropContext;
+ css::uno::Reference< css::datatransfer::clipboard::XClipboard > mXCurrentDragClipboard;
+ DataFlavorMapperPtr_t mDataFlavorMapper;
+ id mView;
+ AquaSalFrame* mpFrame;
+ DropTargetHelper* mDropTargetHelper;
+ bool mbActive;
+ sal_Int8 mDragSourceSupportedActions;
+ sal_Int8 mSelectedDropAction;
+ sal_Int8 mDefaultActions;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/HtmlFmtFlt.cxx b/vcl/osx/HtmlFmtFlt.cxx
new file mode 100644
index 0000000000..3549ecd210
--- /dev/null
+++ b/vcl/osx/HtmlFmtFlt.cxx
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "HtmlFmtFlt.hxx"
+
+#include <rtl/string.h>
+#include <osl/diagnose.h>
+
+#include <string>
+#include <sstream>
+#include <vector>
+#include <iomanip>
+#include <cassert>
+
+using namespace com::sun::star::uno;
+
+// converts the openoffice text/html clipboard format to the HTML Format
+// well known under MS Windows
+// the MS HTML Format has a header before the real html data
+
+// Version:1.0 Version number of the clipboard. Starting is 0.9
+// StartHTML: Byte count from the beginning of the clipboard to the start
+// of the context, or -1 if no context
+// EndHTML: Byte count from the beginning of the clipboard to the end
+// of the context, or -1 if no context
+// StartFragment: Byte count from the beginning of the clipboard to the
+// start of the fragment
+// EndFragment: Byte count from the beginning of the clipboard to the
+// end of the fragment
+// StartSelection: Byte count from the beginning of the clipboard to the
+// start of the selection
+// EndSelection: Byte count from the beginning of the clipboard to the
+// end of the selection
+
+// StartSelection and EndSelection are optional
+// The fragment should be preceded and followed by the HTML comments
+// <!--StartFragment--> and <!--EndFragment--> (no space between !-- and the
+// text
+
+namespace
+{
+std::string GetHtmlFormatHeader(size_t startHtml, size_t endHtml, size_t startFragment, size_t endFragment)
+{
+ std::ostringstream htmlHeader;
+ htmlHeader << "Version:1.0" << '\r' << '\n';
+ htmlHeader << "StartHTML:" << std::setw(10) << std::setfill('0') << std::dec << startHtml << '\r' << '\n';
+ htmlHeader << "EndHTML:" << std::setw(10) << std::setfill('0') << std::dec << endHtml << '\r' << '\n';
+ htmlHeader << "StartFragment:" << std::setw(10) << std::setfill('0') << std::dec << startFragment << '\r' << '\n';
+ htmlHeader << "EndFragment:" << std::setw(10) << std::setfill('0') << std::dec << endFragment << '\r' << '\n';
+ return htmlHeader.str();
+}
+
+}
+
+// the office always writes the start and end html tag in upper cases and
+// without spaces both tags don't allow parameters
+const std::string TAG_HTML("<html>");
+const std::string TAG_END_HTML("</html>");
+
+// The body tag may have parameters so we need to search for the
+// closing '>' manually e.g. <BODY param> #92840#
+const std::string TAG_BODY("<body");
+const std::string TAG_END_BODY("</body");
+
+Sequence<sal_Int8> TextHtmlToHTMLFormat(Sequence<sal_Int8> const & aTextHtml)
+{
+ OSL_ASSERT(aTextHtml.getLength() > 0);
+
+ if (aTextHtml.getLength() <= 0)
+ return Sequence<sal_Int8>();
+
+ // fill the buffer with dummy values to calc the exact length
+ std::string dummyHtmlHeader = GetHtmlFormatHeader(0, 0, 0, 0);
+ size_t lHtmlFormatHeader = dummyHtmlHeader.length();
+
+ std::string textHtml(
+ reinterpret_cast<const char*>(aTextHtml.getConstArray()),
+ reinterpret_cast<const char*>(aTextHtml.getConstArray()) + aTextHtml.getLength());
+
+ std::string::size_type nStartHtml = textHtml.find(TAG_HTML) + lHtmlFormatHeader - 1; // we start one before '<HTML>' Word 2000 does also so
+ std::string::size_type nEndHtml = textHtml.find(TAG_END_HTML) + lHtmlFormatHeader + TAG_END_HTML.length() + 1; // our SOffice 5.2 wants 2 behind </HTML>?
+
+ // The body tag may have parameters so we need to search for the
+ // closing '>' manually e.g. <BODY param> #92840#
+ std::string::size_type nStartFragment = textHtml.find(">", textHtml.find(TAG_BODY)) + lHtmlFormatHeader + 1;
+ std::string::size_type nEndFragment = textHtml.find(TAG_END_BODY) + lHtmlFormatHeader;
+
+ std::string htmlFormat = GetHtmlFormatHeader(nStartHtml, nEndHtml, nStartFragment, nEndFragment);
+ htmlFormat += textHtml;
+
+ Sequence<sal_Int8> byteSequence(htmlFormat.length() + 1); // space the trailing '\0'
+ memset(byteSequence.getArray(), 0, byteSequence.getLength());
+
+ memcpy(
+ static_cast<void*>(byteSequence.getArray()),
+ static_cast<const void*>(htmlFormat.c_str()),
+ htmlFormat.length());
+
+ return byteSequence;
+}
+
+const char* const HtmlStartTag = "<html";
+
+Sequence<sal_Int8> HTMLFormatToTextHtml(const Sequence<sal_Int8>& aHTMLFormat)
+{
+ assert(isHTMLFormat(aHTMLFormat) && "No HTML Format provided");
+
+ Sequence<sal_Int8>& nonconstHTMLFormatRef = const_cast< Sequence<sal_Int8>& >(aHTMLFormat);
+ char* dataStart = reinterpret_cast<char*>(nonconstHTMLFormatRef.getArray());
+ char* dataEnd = dataStart + nonconstHTMLFormatRef.getLength() - 1;
+ const char* htmlStartTag = strcasestr(dataStart, HtmlStartTag);
+
+ assert(htmlStartTag && "Seems to be no HTML at all");
+
+ // It doesn't seem to be HTML? Well then simply return what has been
+ // provided in non-debug builds
+ if (htmlStartTag == nullptr)
+ {
+ return aHTMLFormat;
+ }
+
+ sal_Int32 len = dataEnd - htmlStartTag;
+ Sequence<sal_Int8> plainHtmlData(len);
+
+ memcpy(static_cast<void*>(plainHtmlData.getArray()), htmlStartTag, len);
+
+ return plainHtmlData;
+}
+
+/* A simple format detection. We are just comparing the first few bytes
+ of the provided byte sequence to see whether or not it is the MS
+ Office Html format. If it shows that this is not reliable enough we
+ can improve this
+*/
+const char HtmlFormatStart[] = "Version:";
+int const HtmlFormatStartLen = sizeof(HtmlFormatStart) - 1;
+
+bool isHTMLFormat(const Sequence<sal_Int8>& aHtmlSequence)
+{
+ if (aHtmlSequence.getLength() < HtmlFormatStartLen)
+ return false;
+
+ return rtl_str_compareIgnoreAsciiCase_WithLength(HtmlFormatStart,
+ HtmlFormatStartLen,
+ reinterpret_cast<const char*>(aHtmlSequence.getConstArray()),
+ HtmlFormatStartLen) == 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/HtmlFmtFlt.hxx b/vcl/osx/HtmlFmtFlt.hxx
new file mode 100644
index 0000000000..5286efb1de
--- /dev/null
+++ b/vcl/osx/HtmlFmtFlt.hxx
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Sequence.hxx>
+
+/* Transform plain HTML into the format expected by MS Office.
+ */
+css::uno::Sequence<sal_Int8> TextHtmlToHTMLFormat(css::uno::Sequence<sal_Int8> const& aTextHtml);
+
+/* Transform the MS Office HTML format into plain HTML.
+ */
+css::uno::Sequence<sal_Int8> HTMLFormatToTextHtml(const css::uno::Sequence<sal_Int8>& aHTMLFormat);
+
+/* Detects whether the given byte sequence contains the MS Office Html format.
+
+ @returns True if the MS Office Html format will be detected False otherwise.
+ */
+bool isHTMLFormat(const css::uno::Sequence<sal_Int8>& aHtmlSequence);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/OSXTransferable.cxx b/vcl/osx/OSXTransferable.cxx
new file mode 100644
index 0000000000..e55141a606
--- /dev/null
+++ b/vcl/osx/OSXTransferable.cxx
@@ -0,0 +1,198 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <sal/log.hxx>
+#include <utility>
+
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <sal/types.h>
+#include <osl/diagnose.h>
+
+#include "OSXTransferable.hxx"
+
+#include "DataFlavorMapping.hxx"
+
+#include <quartz/utils.h>
+
+using namespace cppu;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::datatransfer;
+using namespace com::sun::star::lang;
+
+namespace
+{
+ bool isValidFlavor( const DataFlavor& aFlavor )
+ {
+ size_t len = aFlavor.MimeType.getLength();
+ Type dtype = aFlavor.DataType;
+ return ((len > 0) && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get()) || (dtype == cppu::UnoType<OUString>::get())));
+ }
+
+bool cmpAllContentTypeParameter(const Reference<XMimeContentType> & xLhs,
+ const Reference<XMimeContentType> & xRhs)
+{
+ Sequence<OUString> xLhsFlavors = xLhs->getParameters();
+ Sequence<OUString> xRhsFlavors = xRhs->getParameters();
+
+ // Stop here if the number of parameters is different already
+ if (xLhsFlavors.getLength() != xRhsFlavors.getLength())
+ return false;
+
+ try
+ {
+ OUString pLhs;
+ OUString pRhs;
+
+ for (sal_Int32 i = 0; i < xLhsFlavors.getLength(); i++)
+ {
+ pLhs = xLhs->getParameterValue(xLhsFlavors[i]);
+ pRhs = xRhs->getParameterValue(xLhsFlavors[i]);
+
+ if (!pLhs.equalsIgnoreAsciiCase(pRhs))
+ {
+ return false;
+ }
+ }
+ }
+ catch(IllegalArgumentException&)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+} // unnamed namespace
+
+OSXTransferable::OSXTransferable(const Reference<XMimeContentTypeFactory> & rXMimeCntFactory,
+ DataFlavorMapperPtr_t pDataFlavorMapper,
+ NSPasteboard* pasteboard) :
+ mrXMimeCntFactory(rXMimeCntFactory),
+ mDataFlavorMapper(pDataFlavorMapper),
+ mPasteboard(pasteboard)
+{
+ [mPasteboard retain];
+
+ initClipboardItemList();
+}
+
+OSXTransferable::~OSXTransferable()
+{
+ [mPasteboard release];
+}
+
+Any SAL_CALL OSXTransferable::getTransferData( const DataFlavor& aFlavor )
+{
+ if (!isValidFlavor(aFlavor) || !isDataFlavorSupported(aFlavor))
+ {
+ throw UnsupportedFlavorException("AquaClipboard: Unsupported data flavor",
+ static_cast<XTransferable*>(this));
+ }
+
+ bool bInternal(false);
+ NSString const * sysFormat =
+ (aFlavor.MimeType.startsWith("image/png"))
+ ? DataFlavorMapper::openOfficeImageToSystemFlavor( mPasteboard )
+ : mDataFlavorMapper->openOfficeToSystemFlavor(aFlavor, bInternal);
+ DataProviderPtr_t dp;
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSFilenamesPboardType' is deprecated: first deprecated in macOS 10.14 - Create multiple
+ // pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead"
+ if ([sysFormat caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame)
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+ NSArray* sysData = [mPasteboard propertyListForType: const_cast<NSString *>(sysFormat)];
+ dp = DataFlavorMapper::getDataProvider(sysFormat, sysData);
+ }
+ else
+ {
+ NSData* sysData = [mPasteboard dataForType: const_cast<NSString *>(sysFormat)];
+ dp = DataFlavorMapper::getDataProvider(sysFormat, sysData);
+ }
+
+ if (!dp)
+ {
+ throw UnsupportedFlavorException("AquaClipboard: Unsupported data flavor",
+ static_cast<XTransferable*>(this));
+ }
+
+ return dp->getOOoData();
+}
+
+Sequence< DataFlavor > SAL_CALL OSXTransferable::getTransferDataFlavors( )
+{
+ return mFlavorList;
+}
+
+sal_Bool SAL_CALL OSXTransferable::isDataFlavorSupported(const DataFlavor& aFlavor)
+{
+ for (const DataFlavor& rFlavor : std::as_const(mFlavorList))
+ if (compareDataFlavors(aFlavor, rFlavor))
+ return true;
+
+ return false;
+}
+
+void OSXTransferable::initClipboardItemList()
+{
+ NSArray* pboardFormats = [mPasteboard types];
+
+ if (pboardFormats == nullptr)
+ {
+ throw RuntimeException("AquaClipboard: Cannot get clipboard data",
+ static_cast<XTransferable*>(this));
+ }
+
+ SAL_INFO("vcl.osx.clipboard", "Types on pasteboard: " << NSStringArrayToOUString(pboardFormats));
+
+
+ mFlavorList = mDataFlavorMapper->typesArrayToFlavorSequence(pboardFormats);
+}
+
+/* Compares two DataFlavors. Returns true if both DataFlavor have the same media type
+ and the number of parameter and all parameter values do match otherwise false
+ is returned.
+ */
+bool OSXTransferable::compareDataFlavors(const DataFlavor& lhs, const DataFlavor& rhs )
+{
+ try
+ {
+ Reference<XMimeContentType> xLhs(mrXMimeCntFactory->createMimeContentType(lhs.MimeType));
+ Reference<XMimeContentType> xRhs(mrXMimeCntFactory->createMimeContentType(rhs.MimeType));
+
+ if (!xLhs->getFullMediaType().equalsIgnoreAsciiCase(xRhs->getFullMediaType()) ||
+ !cmpAllContentTypeParameter(xLhs, xRhs))
+ {
+ return false;
+ }
+ }
+ catch( IllegalArgumentException& )
+ {
+ OSL_FAIL( "Invalid content type detected" );
+ return false;
+ }
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/OSXTransferable.hxx b/vcl/osx/OSXTransferable.hxx
new file mode 100644
index 0000000000..e4a00b880e
--- /dev/null
+++ b/vcl/osx/OSXTransferable.hxx
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/XMimeContentType.hpp>
+
+#include "DataFlavorMapping.hxx"
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+#include <memory>
+#include <vector>
+
+class OSXTransferable : public ::cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+public:
+ explicit OSXTransferable(css::uno::Reference< css::datatransfer::XMimeContentTypeFactory> const & rXMimeCntFactory,
+ DataFlavorMapperPtr_t pDataFlavorMapper,
+ NSPasteboard* pasteboard);
+
+ virtual ~OSXTransferable() override;
+ OSXTransferable(const OSXTransferable&) = delete;
+ OSXTransferable& operator=(const OSXTransferable&) = delete;
+
+ // XTransferable
+
+ virtual css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override;
+
+ virtual css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override;
+
+ virtual sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override;
+
+ // Helper functions not part of the XTransferable interface
+
+ void initClipboardItemList();
+
+ //css::uno::Any getClipboardItemData(ClipboardItemPtr_t clipboardItem);
+
+ bool compareDataFlavors( const css::datatransfer::DataFlavor& lhs,
+ const css::datatransfer::DataFlavor& rhs );
+
+private:
+ css::uno::Sequence< css::datatransfer::DataFlavor > mFlavorList;
+ css::uno::Reference< css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ DataFlavorMapperPtr_t mDataFlavorMapper;
+ NSPasteboard* mPasteboard;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/PictToBmpFlt.cxx b/vcl/osx/PictToBmpFlt.cxx
new file mode 100644
index 0000000000..a818cb5e75
--- /dev/null
+++ b/vcl/osx/PictToBmpFlt.cxx
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <premac.h>
+#include <Carbon/Carbon.h>
+#include <postmac.h>
+
+#include <string.h>
+
+#include "PictToBmpFlt.hxx"
+
+bool ImageToPNG( css::uno::Sequence<sal_Int8> const & rImgData,
+ css::uno::Sequence<sal_Int8>& rPngData)
+{
+ NSData* pData = [NSData dataWithBytesNoCopy: const_cast<sal_Int8 *>(rImgData.getConstArray()) length: rImgData.getLength() freeWhenDone: false];
+ if( !pData)
+ return false;
+
+ NSBitmapImageRep* pRep =[NSBitmapImageRep imageRepWithData: pData];
+ if( !pRep)
+ return false;
+
+ NSData* pOut = [pRep representationUsingType: NSBitmapImageFileTypePNG properties: @{ }];
+ if( !pOut)
+ return false;
+
+ const size_t nPngSize = [pOut length];
+ rPngData.realloc( nPngSize);
+ [pOut getBytes: rPngData.getArray() length: nPngSize];
+ return (nPngSize > 0);
+}
+
+bool PNGToImage( css::uno::Sequence<sal_Int8> const & rPngData,
+ css::uno::Sequence<sal_Int8>& rImgData,
+ NSBitmapImageFileType eOutFormat
+ )
+{
+ NSData* pData = [NSData dataWithBytesNoCopy: const_cast<sal_Int8*>(rPngData.getConstArray()) length: rPngData.getLength() freeWhenDone: false];
+ if( !pData)
+ return false;
+
+ NSBitmapImageRep* pRep = [NSBitmapImageRep imageRepWithData: pData];
+ if( !pRep)
+ return false;
+
+ NSData* pOut = [pRep representationUsingType: eOutFormat properties: @{ }];
+ if( !pOut)
+ return false;
+
+ const size_t nImgSize = [pOut length];
+ rImgData.realloc( nImgSize);
+ [pOut getBytes: rImgData.getArray() length: nImgSize];
+ return (nImgSize > 0);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/PictToBmpFlt.hxx b/vcl/osx/PictToBmpFlt.hxx
new file mode 100644
index 0000000000..d43146e03f
--- /dev/null
+++ b/vcl/osx/PictToBmpFlt.hxx
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <premac.h>
+#include <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+bool ImageToPNG(css::uno::Sequence<sal_Int8> const& rImgData,
+ css::uno::Sequence<sal_Int8>& rPngData);
+
+bool PNGToImage(css::uno::Sequence<sal_Int8> const& rPngData,
+ css::uno::Sequence<sal_Int8>& rImgData, NSBitmapImageFileType eOutFormat);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/README.a11y b/vcl/osx/README.a11y
new file mode 100644
index 0000000000..4422713bc4
--- /dev/null
+++ b/vcl/osx/README.a11y
@@ -0,0 +1,7 @@
+Naming scheme:
+
+a11yXYZhelper: Helper class providing static methods
+
+a11yXYZwrapper: Wrapper around one (or two) UNO-interfaces
+
+a11ywrapperXYZ: Subclass of a11ywrapper for a specific AXRole
diff --git a/vcl/osx/a11yactionwrapper.h b/vcl/osx/a11yactionwrapper.h
new file mode 100644
index 0000000000..eb0141c805
--- /dev/null
+++ b/vcl/osx/a11yactionwrapper.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yActionWrapper : NSObject
+{
+}
++ (NSArray*)actionNamesForElement:(AquaA11yWrapper*)wrapper;
++ (void)doAction:(NSString*)action ofElement:(AquaA11yWrapper*)wrapper;
++ (NSAccessibilityActionName)actionNameForSelector:(SEL)aSelector;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yactionwrapper.mm b/vcl/osx/a11yactionwrapper.mm
new file mode 100644
index 0000000000..9bea25c119
--- /dev/null
+++ b/vcl/osx/a11yactionwrapper.mm
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+
+#include "a11yactionwrapper.h"
+
+// Wrapper for XAccessibleAction
+
+@implementation AquaA11yActionWrapper : NSObject
+
++(NSString *)nativeActionNameFor:(NSString *)actionName {
+ // TODO: Optimize ?
+ // Use NSAccessibilityActionDescription
+ if ( [ actionName isEqualToString: @"press" ] ) {
+ return NSAccessibilityPressAction;
+ } else if ( [ actionName isEqualToString: @"togglePopup" ] ) {
+ return NSAccessibilityShowMenuAction;
+ } else if ( [ actionName isEqualToString: @"select" ] ) {
+ return NSAccessibilityPickAction;
+ } else if ( [ actionName isEqualToString: @"incrementLine" ] ) {
+ return NSAccessibilityIncrementAction;
+ } else if ( [ actionName isEqualToString: @"decrementLine" ] ) {
+ return NSAccessibilityDecrementAction;
+ } else if ( [ actionName isEqualToString: @"incrementBlock" ] ) {
+ return NSAccessibilityIncrementAction; // TODO ?
+ } else if ( [ actionName isEqualToString: @"decrementBlock" ] ) {
+ return NSAccessibilityDecrementAction; // TODO ?
+ } else if ( [ actionName isEqualToString: @"Browse" ] ) {
+ return NSAccessibilityPressAction; // TODO ?
+ } else {
+ return [ NSString string ];
+ }
+}
+
++(NSArray *)actionNamesForElement:(AquaA11yWrapper *)wrapper {
+ NSMutableArray * actionNames = [ [ NSMutableArray alloc ] init ];
+ if ( [ wrapper accessibleAction ] ) {
+ for ( int cnt = 0; cnt < [ wrapper accessibleAction ] -> getAccessibleActionCount(); cnt++ ) {
+ [ actionNames addObject: [ AquaA11yActionWrapper nativeActionNameFor: CreateNSString ( [ wrapper accessibleAction ] -> getAccessibleActionDescription ( cnt ) ) ] ];
+ }
+ }
+ return actionNames;
+}
+
++(void)doAction:(NSString *)action ofElement:(AquaA11yWrapper *)wrapper {
+ if ( [ wrapper accessibleAction ] ) {
+ for ( int cnt = 0; cnt < [ wrapper accessibleAction ] -> getAccessibleActionCount(); cnt++ ) {
+ if ( [ action isEqualToString: [ AquaA11yActionWrapper nativeActionNameFor: CreateNSString ( [ wrapper accessibleAction ] -> getAccessibleActionDescription ( cnt ) ) ] ] ) {
+ [ wrapper accessibleAction ] -> doAccessibleAction ( cnt );
+ break;
+ }
+ }
+ }
+}
+
++(NSAccessibilityActionName)actionNameForSelector:(SEL)aSelector
+{
+ NSAccessibilityActionName pRet = nil;
+
+ if ( aSelector == @selector(accessibilityPerformDecrement) ) {
+ pRet = NSAccessibilityDecrementAction;
+ } else if ( aSelector == @selector(accessibilityPerformIncrement) ) {
+ pRet = NSAccessibilityIncrementAction;
+ } else if ( aSelector == @selector(accessibilityPerformPick) ) {
+ pRet = NSAccessibilityPickAction;
+ } else if ( aSelector == @selector(accessibilityPerformPress) ) {
+ pRet = NSAccessibilityPressAction;
+ } else if ( aSelector == @selector(accessibilityPerformShowMenu) ) {
+ pRet = NSAccessibilityShowMenuAction;
+ }
+
+ return pRet;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ycomponentwrapper.h b/vcl/osx/a11ycomponentwrapper.h
new file mode 100644
index 0000000000..a63f327e43
--- /dev/null
+++ b/vcl/osx/a11ycomponentwrapper.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yComponentWrapper : NSObject
+{
+}
++ (id)sizeAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)positionAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)descriptionAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (BOOL)isAttributeSettable:(NSString*)attribute forElement:(AquaA11yWrapper*)wrapper;
++ (void)setFocusedAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ycomponentwrapper.mm b/vcl/osx/a11ycomponentwrapper.mm
new file mode 100644
index 0000000000..d9d6db1754
--- /dev/null
+++ b/vcl/osx/a11ycomponentwrapper.mm
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <quartz/utils.h>
+#include "a11ycomponentwrapper.h"
+#include "a11yrolehelper.h"
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::uno;
+
+// Wrapper for XAccessibleComponent and XAccessibleExtendedComponent
+
+@implementation AquaA11yComponentWrapper : NSObject
+
++(id)sizeAttributeForElement:(AquaA11yWrapper *)wrapper {
+ Size size = [ wrapper accessibleComponent ] -> getSize();
+ NSSize nsSize = NSMakeSize ( static_cast<float>(size.Width), static_cast<float>(size.Height) );
+ return [ NSValue valueWithSize: nsSize ];
+}
+
+// TODO: should be merged with AquaSalFrame::VCLToCocoa... to a general helper method
++(id)positionAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // VCL coordinates are in upper-left-notation, Cocoa likes it the Cartesian way (lower-left)
+ NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
+ Size size = [ wrapper accessibleComponent ] -> getSize();
+ Point location = [ wrapper accessibleComponent ] -> getLocationOnScreen();
+ NSPoint nsPoint = NSMakePoint ( static_cast<float>(location.X), static_cast<float>( screenRect.size.height - size.Height - location.Y ) );
+ return [ NSValue valueWithPoint: nsPoint ];
+}
+
++(id)descriptionAttributeForElement:(AquaA11yWrapper *)wrapper {
+ if ( [ wrapper accessibleExtendedComponent ] ) {
+ return CreateNSString ( [ wrapper accessibleExtendedComponent ] -> getToolTipText() );
+ } else {
+ return nil;
+ }
+}
+
++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects:
+ NSAccessibilitySizeAttribute,
+ NSAccessibilityPositionAttribute,
+ NSAccessibilityFocusedAttribute,
+ NSAccessibilityEnabledAttribute,
+ nil ] ];
+ [ pool release ];
+}
+
++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper {
+ bool isSettable = false;
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ if ( [ attribute isEqualToString: NSAccessibilityFocusedAttribute ]
+ && ! [ [ AquaA11yRoleHelper getNativeRoleFrom: [ wrapper accessibleContext ] ] isEqualToString: NSAccessibilityScrollBarRole ]
+ && ! [ [ AquaA11yRoleHelper getNativeRoleFrom: [ wrapper accessibleContext ] ] isEqualToString: NSAccessibilityStaticTextRole ] ) {
+ isSettable = true;
+ }
+ [ pool release ];
+ return isSettable;
+}
+
++(void)setFocusedAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
+ if ( [ value boolValue ] == YES ) {
+ if ( [ wrapper accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
+ // special treatment for comboboxes: find the corresponding PANEL and set focus to it
+ Reference < XAccessible > rxParent = [ wrapper accessibleContext ] -> getAccessibleParent();
+ if ( rxParent.is() ) {
+ Reference < XAccessibleContext > rxContext = rxParent->getAccessibleContext();
+ if ( rxContext.is() && rxContext -> getAccessibleRole() == AccessibleRole::PANEL ) {
+ Reference < XAccessibleComponent > rxComponent( rxParent -> getAccessibleContext(), UNO_QUERY );
+ if ( rxComponent.is() ) {
+ rxComponent -> grabFocus();
+ }
+ }
+ }
+ } else {
+ [ wrapper accessibleComponent ] -> grabFocus();
+ }
+ }
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfactory.mm b/vcl/osx/a11yfactory.mm
new file mode 100644
index 0000000000..0783252c7e
--- /dev/null
+++ b/vcl/osx/a11yfactory.mm
@@ -0,0 +1,193 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <osx/a11yfactory.h>
+#include <osx/a11yfocustracker.hxx>
+
+#include "a11yfocuslistener.hxx"
+#include "a11yrolehelper.h"
+#include "a11ywrapperbutton.h"
+#include "a11ywrapperstatictext.h"
+#include "a11ywrappertextarea.h"
+#include "a11ywrappercheckbox.h"
+#include "a11ywrappercombobox.h"
+#include "a11ywrappergroup.h"
+#include "a11ywrapperlist.h"
+#include "a11ywrapperradiobutton.h"
+#include "a11ywrapperradiogroup.h"
+#include "a11ywrapperrow.h"
+#include "a11ywrapperscrollarea.h"
+#include "a11ywrapperscrollbar.h"
+#include "a11ywrappersplitter.h"
+#include "a11ywrappertabgroup.h"
+#include "a11ywrappertoolbar.h"
+#include "a11ytablewrapper.h"
+
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+static bool enabled = false;
+
+@implementation AquaA11yFactory : NSObject
+
+#pragma mark -
+#pragma mark Wrapper Repository
+
++(NSMutableDictionary *)allWrapper {
+ static NSMutableDictionary * mdAllWrapper = nil;
+ if ( mdAllWrapper == nil ) {
+ mdAllWrapper = [ [ [ NSMutableDictionary alloc ] init ] retain ];
+ // initialize keyboard focus tracker
+ rtl::Reference< AquaA11yFocusListener > listener( AquaA11yFocusListener::get() );
+ TheAquaA11yFocusTracker().setFocusListener(listener);
+ enabled = true;
+ }
+ return mdAllWrapper;
+}
+
++(NSValue *)keyForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ return [ NSValue valueWithPointer: rxAccessibleContext.get() ];
+}
+
++(NSValue *)keyForAccessibleContextAsRadioGroup: (Reference < XAccessibleContext >) rxAccessibleContext {
+ return [ NSValue valueWithPointer: ( rxAccessibleContext.get() + 2 ) ];
+}
+
++(AquaA11yWrapper *)wrapperForAccessible: (Reference < XAccessible >) rxAccessible {
+ if ( rxAccessible.is() ) {
+ Reference< XAccessibleContext > xAccessibleContext = rxAccessible->getAccessibleContext();
+ if( xAccessibleContext.is() ) {
+ return [ AquaA11yFactory wrapperForAccessibleContext: xAccessibleContext ];
+ }
+ }
+ return nil;
+}
+
++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ return [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: YES asRadioGroup: NO ];
+}
+
++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate {
+ return [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: bCreate asRadioGroup: NO ];
+}
+
++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate asRadioGroup:(BOOL) asRadioGroup{
+ NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ];
+ NSValue * nKey = nil;
+ if ( asRadioGroup ) {
+ nKey = [ AquaA11yFactory keyForAccessibleContextAsRadioGroup: rxAccessibleContext ];
+ } else {
+ nKey = [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ];
+ }
+ AquaA11yWrapper * aWrapper = static_cast<AquaA11yWrapper *>([ dAllWrapper objectForKey: nKey ]);
+ if ( aWrapper != nil ) {
+ [ aWrapper retain ];
+ } else if ( bCreate ) {
+ NSString * nativeRole = [ AquaA11yRoleHelper getNativeRoleFrom: rxAccessibleContext.get() ];
+ // TODO: reflection
+ if ( [ nativeRole isEqualToString: NSAccessibilityButtonRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperButton alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityTextAreaRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperTextArea alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityStaticTextRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperStaticText alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityComboBoxRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperComboBox alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityGroupRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperGroup alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityToolbarRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperToolbar alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityScrollAreaRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperScrollArea alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityTabGroupRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperTabGroup alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityScrollBarRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperScrollBar alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityCheckBoxRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperCheckBox alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityRadioGroupRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperRadioGroup alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityRadioButtonRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperRadioButton alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityRowRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperRow alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityListRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperList alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilitySplitterRole ] ) {
+ aWrapper = [ [ AquaA11yWrapperSplitter alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else if ( [ nativeRole isEqualToString: NSAccessibilityTableRole ] ) {
+ aWrapper = [ [ AquaA11yTableWrapper alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ } else {
+ aWrapper = [ [ AquaA11yWrapper alloc ] initWithAccessibleContext: rxAccessibleContext ];
+ }
+ [ nativeRole release ];
+ [ aWrapper setActsAsRadioGroup: asRadioGroup ];
+ #if 0
+ /* #i102033# NSAccessibility does not seemt to know an equivalent for transient children.
+ That means we need to cache this, else e.g. tree list boxes are not accessible (moreover
+ it crashes by notifying dead objects - which would seemt o be another bug)
+
+ FIXME:
+ Unfortunately this can increase memory consumption drastically until the non transient parent
+ is destroyed and finally all the transients are released.
+ */
+ if ( ! (rxAccessibleContext -> getAccessibleStateSet() & AccessibleStateType::TRANSIENT ) )
+ #endif
+ {
+ [ dAllWrapper setObject: aWrapper forKey: nKey ];
+ }
+ }
+ return aWrapper;
+}
+
++(void)insertIntoWrapperRepository: (AquaA11yWrapper *) element forAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ];
+ [ dAllWrapper setObject: element forKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ];
+}
+
++(void)removeFromWrapperRepositoryFor: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext {
+ // TODO: when RADIO_BUTTON search for associated RadioGroup-wrapper and delete that as well
+ AquaA11yWrapper * theWrapper = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: NO ];
+ if ( theWrapper != nil ) {
+ NSAccessibilityPostNotification( theWrapper, NSAccessibilityUIElementDestroyedNotification );
+ [ [ AquaA11yFactory allWrapper ] removeObjectForKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ];
+ [ theWrapper release ];
+ }
+}
+
++(void)registerWrapper: (AquaA11yWrapper *) theWrapper {
+ if ( enabled && theWrapper ) {
+ // insertIntoWrapperRepository gets called from SalFrameView itself to bootstrap the bridge initially
+ [ theWrapper accessibleContext ];
+ }
+}
+
++(void)revokeWrapper: (AquaA11yWrapper *) theWrapper {
+ if ( enabled && theWrapper ) {
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ theWrapper accessibleContext ] ];
+ }
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfocuslistener.cxx b/vcl/osx/a11yfocuslistener.cxx
new file mode 100644
index 0000000000..bae851647e
--- /dev/null
+++ b/vcl/osx/a11yfocuslistener.cxx
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osx/a11yfocustracker.hxx>
+#include <osx/a11yfactory.h>
+
+#include "a11yfocuslistener.hxx"
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+rtl::Reference< AquaA11yFocusListener > AquaA11yFocusListener::theListener;
+
+rtl::Reference< AquaA11yFocusListener > const & AquaA11yFocusListener::get()
+{
+ if ( ! theListener.is() )
+ theListener = new AquaA11yFocusListener();
+
+ return theListener;
+}
+
+AquaA11yFocusListener::AquaA11yFocusListener() : m_focusedObject(nil)
+{
+}
+
+id AquaA11yFocusListener::getFocusedUIElement()
+{
+ if ( nil == m_focusedObject ) {
+ Reference< XAccessible > xAccessible( TheAquaA11yFocusTracker().getFocusedObject() );
+ try {
+ if( xAccessible.is() ) {
+ Reference< XAccessibleContext > xContext(xAccessible->getAccessibleContext());
+ if( xContext.is() )
+ m_focusedObject = [ AquaA11yFactory wrapperForAccessibleContext: xContext ];
+ }
+ } catch(const RuntimeException &) {
+ // intentionally do nothing ..
+ }
+ }
+
+ return m_focusedObject;
+}
+
+void
+AquaA11yFocusListener::focusedObjectChanged(const Reference< XAccessible >& xAccessible)
+{
+ if ( nil != m_focusedObject ) {
+ [ m_focusedObject release ];
+ m_focusedObject = nil;
+ }
+
+ try {
+ if( xAccessible.is() ) {
+ Reference< XAccessibleContext > xContext(xAccessible->getAccessibleContext());
+ if( xContext.is() )
+ {
+ m_focusedObject = [ AquaA11yFactory wrapperForAccessibleContext: xContext ];
+ NSAccessibilityPostNotification(m_focusedObject, NSAccessibilityFocusedUIElementChangedNotification);
+ }
+ }
+ } catch(const RuntimeException &) {
+ // intentionally do nothing ..
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfocuslistener.hxx b/vcl/osx/a11yfocuslistener.hxx
new file mode 100644
index 0000000000..9cd6b656db
--- /dev/null
+++ b/vcl/osx/a11yfocuslistener.hxx
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/keyboardfocuslistener.hxx>
+#include <osx/osxvcltypes.h>
+
+class AquaA11yFocusListener : public KeyboardFocusListener
+{
+ id m_focusedObject;
+
+ static rtl::Reference<AquaA11yFocusListener> theListener;
+
+ AquaA11yFocusListener();
+ virtual ~AquaA11yFocusListener() override{};
+
+public:
+ static rtl::Reference<AquaA11yFocusListener> const& get();
+
+ id getFocusedUIElement();
+
+ // KeyboardFocusListener
+ virtual void focusedObjectChanged(
+ const css::uno::Reference<css::accessibility::XAccessible>& xAccessible) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yfocustracker.cxx b/vcl/osx/a11yfocustracker.cxx
new file mode 100644
index 0000000000..2aaa8b0a8e
--- /dev/null
+++ b/vcl/osx/a11yfocustracker.cxx
@@ -0,0 +1,269 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/toolbox.hxx>
+#include <vcl/menu.hxx>
+
+#include <osx/a11yfocustracker.hxx>
+
+#include "documentfocuslistener.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+#include <com/sun/star/accessibility/XAccessibleSelection.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+AquaA11yFocusTracker& TheAquaA11yFocusTracker()
+{
+ static AquaA11yFocusTracker SINGLETON;
+ return SINGLETON;
+}
+
+static vcl::Window *
+getWindow(const ::VclSimpleEvent *pEvent)
+{
+ return static_cast< const ::VclWindowEvent *> (pEvent)->GetWindow();
+}
+
+// callback function for Application::addEventListener
+
+void AquaA11yFocusTracker::WindowEventHandler(void * pThis, VclSimpleEvent& rEvent)
+{
+ AquaA11yFocusTracker *pFocusTracker = static_cast<AquaA11yFocusTracker *>(
+ pThis);
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowPaint:
+ pFocusTracker-> toolbox_open_floater( getWindow(&rEvent) );
+ break;
+ case VclEventId::WindowGetFocus:
+ pFocusTracker->window_got_focus( getWindow(&rEvent) );
+ break;
+ case VclEventId::ObjectDying:
+ pFocusTracker->m_aDocumentWindowList.erase( getWindow(&rEvent) );
+ [[fallthrough]];
+ case VclEventId::ToolboxHighlightOff:
+ pFocusTracker->toolbox_highlight_off( getWindow(&rEvent) );
+ break;
+ case VclEventId::ToolboxHighlight:
+ pFocusTracker->toolbox_highlight_on( getWindow(&rEvent) );
+ break;
+ case VclEventId::TabpageActivate:
+ pFocusTracker->tabpage_activated( getWindow(&rEvent) );
+ break;
+ case VclEventId::MenuHighlight:
+ // Inspired by code in WindowEventHandler in
+ // vcl/unx/gtk/a11y/atkutil.cxx, find out what kind of event
+ // it is to avoid blindly using a static_cast and crash,
+ // fdo#47275.
+ if( const VclMenuEvent* pMenuEvent = dynamic_cast < const VclMenuEvent* > (&rEvent) )
+ {
+ pFocusTracker->menu_highlighted( pMenuEvent );
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+AquaA11yFocusTracker::AquaA11yFocusTracker() :
+ m_aWindowEventLink(this, WindowEventHandler),
+ m_xDocumentFocusListener(new DocumentFocusListener(*this))
+{
+ Application::AddEventListener(m_aWindowEventLink);
+ window_got_focus(Application::GetFocusWindow());
+}
+
+AquaA11yFocusTracker::~AquaA11yFocusTracker() {}
+
+void AquaA11yFocusTracker::setFocusedObject(const Reference< XAccessible >& xAccessible)
+{
+ if( xAccessible != m_xFocusedObject )
+ {
+ m_xFocusedObject = xAccessible;
+
+ if( m_aFocusListener.is() )
+ m_aFocusListener->focusedObjectChanged(xAccessible);
+ }
+}
+
+void AquaA11yFocusTracker::notify_toolbox_item_focus(ToolBox *pToolBox)
+{
+ Reference< XAccessible > xAccessible( pToolBox->GetAccessible() );
+
+ if( xAccessible.is() )
+ {
+ Reference< XAccessibleContext > xContext(xAccessible->getAccessibleContext());
+
+ if( xContext.is() )
+ {
+ try {
+ ToolBox::ImplToolItems::size_type nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() );
+ if( nPos != ToolBox::ITEM_NOTFOUND )
+ setFocusedObject( xContext->getAccessibleChild( nPos ) );
+ //TODO: ToolBox::ImplToolItems::size_type -> sal_Int32!
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object has invalid index in parent");
+ }
+ }
+ }
+}
+
+void AquaA11yFocusTracker::toolbox_open_floater(vcl::Window *pWindow)
+{
+ bool bToolboxFound = false;
+ bool bFloatingWindowFound = false;
+ vcl::Window * pFloatingWindow = nullptr;
+ while ( pWindow != nullptr ) {
+ if ( pWindow->GetType() == WindowType::TOOLBOX ) {
+ bToolboxFound = true;
+ } else if ( pWindow->GetType() == WindowType::FLOATINGWINDOW ) {
+ bFloatingWindowFound = true;
+ pFloatingWindow = pWindow;
+ }
+ pWindow = pWindow->GetParent();
+ }
+ if ( bToolboxFound && bFloatingWindowFound ) {
+ Reference < XAccessible > rxAccessible = pFloatingWindow -> GetAccessible();
+ if ( ! rxAccessible.is() ) {
+ return;
+ }
+ Reference < XAccessibleContext > rxContext = rxAccessible -> getAccessibleContext();
+ if ( ! rxContext.is() ) {
+ return;
+ }
+ if ( rxContext -> getAccessibleChildCount() > 0 ) {
+ try {
+ Reference < XAccessible > rxAccessibleChild = rxContext -> getAccessibleChild( 0 );
+ if ( ! rxAccessibleChild.is() ) {
+ return;
+ }
+ setFocusedObject ( rxAccessibleChild );
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "No valid accessible objects in parent");
+ }
+ }
+ }
+}
+
+void AquaA11yFocusTracker::toolbox_highlight_on(vcl::Window *pWindow)
+{
+ // Make sure either the toolbox or its parent toolbox has the focus
+ if ( ! pWindow->HasFocus() )
+ {
+ ToolBox* pToolBoxParent = dynamic_cast< ToolBox * >( pWindow->GetParent() );
+ if ( ! pToolBoxParent || ! pToolBoxParent->HasFocus() )
+ return;
+ }
+
+ notify_toolbox_item_focus(static_cast <ToolBox *> (pWindow));
+}
+
+void AquaA11yFocusTracker::toolbox_highlight_off(vcl::Window const *pWindow)
+{
+ ToolBox* pToolBoxParent = dynamic_cast< ToolBox * >( pWindow->GetParent() );
+
+ // Notify when leaving sub toolboxes
+ if( pToolBoxParent && pToolBoxParent->HasFocus() )
+ notify_toolbox_item_focus( pToolBoxParent );
+}
+
+void AquaA11yFocusTracker::tabpage_activated(vcl::Window *pWindow)
+{
+ Reference< XAccessible > xAccessible( pWindow->GetAccessible() );
+
+ if( xAccessible.is() )
+ {
+ Reference< XAccessibleSelection > xSelection(xAccessible->getAccessibleContext(), UNO_QUERY);
+
+ if( xSelection.is() )
+ setFocusedObject( xSelection->getSelectedAccessibleChild(0) );
+ }
+}
+
+void AquaA11yFocusTracker::menu_highlighted(const VclMenuEvent *pEvent)
+{
+ Menu * pMenu = pEvent->GetMenu();
+
+ if( pMenu )
+ {
+ Reference< XAccessible > xAccessible( pMenu->GetAccessible() );
+
+ if( xAccessible.is() )
+ setFocusedObject( xAccessible );
+ }
+}
+
+void AquaA11yFocusTracker::window_got_focus(vcl::Window *pWindow)
+{
+ // The menu bar is handled through VclEventId::MenuHighlightED
+ if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WindowType::MENUBARWINDOW )
+ return;
+
+ // ToolBoxes are handled through VclEventId::ToolboxHighlight
+ if( pWindow->GetType() == WindowType::TOOLBOX )
+ return;
+
+ if( pWindow->GetType() == WindowType::TABCONTROL )
+ {
+ tabpage_activated( pWindow );
+ return;
+ }
+
+ Reference< XAccessible > xAccessible(pWindow->GetAccessible());
+
+ if( ! xAccessible.is() )
+ return;
+
+ Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext();
+
+ if( ! xContext.is() )
+ return;
+
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ if( ! nStateSet )
+ return;
+
+/* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we
+ * need to add listeners to the children instead of re-using the tabpage stuff
+ */
+ if( (nStateSet & AccessibleStateType::FOCUSED) && (pWindow->GetType() != WindowType::TREELISTBOX) )
+ {
+ setFocusedObject( xAccessible );
+ }
+ else
+ {
+ if( m_aDocumentWindowList.insert(pWindow).second )
+ m_xDocumentFocusListener->attachRecursive(xAccessible, xContext, nStateSet);
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ylistener.cxx b/vcl/osx/a11ylistener.cxx
new file mode 100644
index 0000000000..b49b450927
--- /dev/null
+++ b/vcl/osx/a11ylistener.cxx
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <osx/salinst.h>
+#include <osx/a11ylistener.hxx>
+#include <osx/a11yfactory.h>
+#include <osx/a11yfocustracker.hxx>
+#include <osx/a11ywrapper.h>
+
+#include "a11ytextwrapper.h"
+
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChange.hpp>
+#include <com/sun/star/accessibility/AccessibleTableModelChangeType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+static NSString * getTableNotification( const AccessibleEventObject& aEvent )
+{
+ AccessibleTableModelChange aChange;
+ NSString * notification = nil;
+
+ if( (aEvent.NewValue >>= aChange) &&
+ (aChange.Type == AccessibleTableModelChangeType::ROWS_INSERTED ||
+ aChange.Type == AccessibleTableModelChangeType::ROWS_REMOVED))
+ {
+ notification = NSAccessibilityRowCountChangedNotification;
+ }
+
+ return notification;
+}
+
+AquaA11yEventListener::AquaA11yEventListener(id wrapperObject, sal_Int16 role) : m_wrapperObject(wrapperObject), m_role(role)
+{
+ [ m_wrapperObject retain ];
+}
+
+AquaA11yEventListener::~AquaA11yEventListener()
+{
+ [ m_wrapperObject release ];
+}
+
+void SAL_CALL
+AquaA11yEventListener::disposing( const EventObject& )
+{
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ static_cast<AquaA11yWrapper *>(m_wrapperObject) accessibleContext ] ];
+}
+
+void SAL_CALL
+AquaA11yEventListener::notifyEvent( const AccessibleEventObject& aEvent )
+{
+ NSString * notification = nil;
+ id element = m_wrapperObject;
+ ::css::awt::Rectangle bounds;
+
+ // TODO: NSAccessibilityValueChanged, NSAccessibilitySelectedRowsChangedNotification
+ switch( aEvent.EventId )
+ {
+ case AccessibleEventId::ACTIVE_DESCENDANT_CHANGED:
+ if( m_role != AccessibleRole::LIST ) {
+ Reference< XAccessible > xAccessible;
+ if( aEvent.NewValue >>= xAccessible )
+ TheAquaA11yFocusTracker().setFocusedObject( xAccessible );
+ }
+ break;
+
+ case AccessibleEventId::NAME_CHANGED:
+ notification = NSAccessibilityTitleChangedNotification;
+ break;
+
+ case AccessibleEventId::CHILD:
+ // only needed for tooltips (says Apple)
+ if ( m_role == AccessibleRole::TOOL_TIP ) {
+ if(aEvent.NewValue.hasValue()) {
+ notification = NSAccessibilityCreatedNotification;
+ } else if(aEvent.OldValue.hasValue()) {
+ notification = NSAccessibilityUIElementDestroyedNotification;
+ }
+ }
+ break;
+
+ case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ // TODO: deprecate or remember all children
+ break;
+
+ case AccessibleEventId::BOUNDRECT_CHANGED:
+ bounds = [ element accessibleComponent ] -> getBounds();
+ if ( m_oldBounds.X != 0 && ( bounds.X != m_oldBounds.X || bounds.Y != m_oldBounds.Y ) ) {
+ NSAccessibilityPostNotification(element, NSAccessibilityMovedNotification); // post directly since both cases can happen simultaneously
+ }
+ if ( m_oldBounds.X != 0 && ( bounds.Width != m_oldBounds.Width || bounds.Height != m_oldBounds.Height ) ) {
+ NSAccessibilityPostNotification(element, NSAccessibilityResizedNotification); // post directly since both cases can happen simultaneously
+ }
+ m_oldBounds = bounds;
+ break;
+
+ case AccessibleEventId::SELECTION_CHANGED:
+ notification = NSAccessibilitySelectedChildrenChangedNotification;
+ break;
+
+ case AccessibleEventId::TEXT_SELECTION_CHANGED:
+ notification = NSAccessibilitySelectedTextChangedNotification;
+ break;
+
+ case AccessibleEventId::TABLE_MODEL_CHANGED:
+ notification = getTableNotification(aEvent);
+ break;
+
+ case AccessibleEventId::CARET_CHANGED:
+ notification = NSAccessibilitySelectedTextChangedNotification;
+ break;
+
+ case AccessibleEventId::TEXT_CHANGED:
+ notification = NSAccessibilityValueChangedNotification;
+ break;
+
+ default:
+ break;
+ }
+
+ if( nil != notification )
+ NSAccessibilityPostNotification(element, notification);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yrolehelper.h b/vcl/osx/a11yrolehelper.h
new file mode 100644
index 0000000000..db349ad38d
--- /dev/null
+++ b/vcl/osx/a11yrolehelper.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+
+@interface AquaA11yRoleHelper : NSObject
+{
+}
++(id)getNativeRoleFrom: (css::accessibility::XAccessibleContext *) accessibleContext;
++(id)getNativeSubroleFrom: (sal_Int16) nRole;
++(id)getRoleDescriptionFrom: (NSString *) role with: (NSString *) subRole;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yrolehelper.mm b/vcl/osx/a11yrolehelper.mm
new file mode 100644
index 0000000000..a1cf62f327
--- /dev/null
+++ b/vcl/osx/a11yrolehelper.mm
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/a11yfactory.h>
+
+#include "a11yrolehelper.h"
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+@implementation AquaA11yRoleHelper
+
++(id)simpleMapNativeRoleFrom: (XAccessibleContext *) accessibleContext {
+ id nativeRole = nil;
+
+ if (accessibleContext == nullptr)
+ return nativeRole;
+
+ switch( accessibleContext -> getAccessibleRole() ) {
+#define MAP(a,b) \
+ case a: nativeRole = b; break
+
+ MAP( AccessibleRole::UNKNOWN, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::ALERT, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::BLOCK_QUOTE, NSAccessibilityTextAreaRole );
+ MAP( AccessibleRole::COLUMN_HEADER, NSAccessibilityColumnRole );
+ MAP( AccessibleRole::CANVAS, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::CHECK_BOX, NSAccessibilityCheckBoxRole );
+ MAP( AccessibleRole::CHECK_MENU_ITEM, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::COLOR_CHOOSER, NSAccessibilityColorWellRole ); // FIXME
+ MAP( AccessibleRole::COMBO_BOX, NSAccessibilityComboBoxRole );
+ MAP( AccessibleRole::DATE_EDITOR, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::DESKTOP_ICON, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::DESKTOP_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::DIRECTORY_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::DIALOG, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::DOCUMENT, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::EMBEDDED_OBJECT, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::END_NOTE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FILE_CHOOSER, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FILLER, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FONT_CHOOSER, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FOOTER, NSAccessibilityGroupRole ); // FIXME
+ MAP( AccessibleRole::FOOTNOTE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::FRAME, NSAccessibilityWindowRole );
+ MAP( AccessibleRole::GLASS_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::GRAPHIC, NSAccessibilityImageRole );
+ MAP( AccessibleRole::GROUP_BOX, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::HEADER, NSAccessibilityGroupRole ); // FIXME
+ MAP( AccessibleRole::HEADING, NSAccessibilityTextAreaRole ); // FIXME
+ MAP( AccessibleRole::HYPER_LINK, NSAccessibilityLinkRole );
+ MAP( AccessibleRole::ICON, NSAccessibilityImageRole );
+ MAP( AccessibleRole::INTERNAL_FRAME, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::LABEL, NSAccessibilityStaticTextRole );
+ MAP( AccessibleRole::LAYERED_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::LIST, NSAccessibilityMenuRole );
+ MAP( AccessibleRole::LIST_ITEM, NSAccessibilityMenuItemRole );
+ MAP( AccessibleRole::MENU, NSAccessibilityMenuRole );
+ MAP( AccessibleRole::MENU_BAR, NSAccessibilityMenuBarRole );
+ MAP( AccessibleRole::MENU_ITEM, NSAccessibilityMenuItemRole );
+ MAP( AccessibleRole::OPTION_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::PAGE_TAB, NSAccessibilityButtonRole );
+ MAP( AccessibleRole::PAGE_TAB_LIST, NSAccessibilityTabGroupRole );
+ MAP( AccessibleRole::PANEL, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::PARAGRAPH, NSAccessibilityTextAreaRole );
+ MAP( AccessibleRole::PASSWORD_TEXT, NSAccessibilityTextFieldRole );
+ MAP( AccessibleRole::POPUP_MENU, NSAccessibilityMenuRole );
+ MAP( AccessibleRole::PUSH_BUTTON, NSAccessibilityButtonRole );
+ MAP( AccessibleRole::PROGRESS_BAR, NSAccessibilityProgressIndicatorRole );
+ MAP( AccessibleRole::RADIO_BUTTON, NSAccessibilityRadioButtonRole );
+ MAP( AccessibleRole::RADIO_MENU_ITEM, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::ROW_HEADER, NSAccessibilityRowRole );
+ MAP( AccessibleRole::ROOT_PANE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::SCROLL_BAR, NSAccessibilityScrollBarRole );
+ MAP( AccessibleRole::SCROLL_PANE, NSAccessibilityScrollAreaRole );
+ MAP( AccessibleRole::SHAPE, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::SEPARATOR, NSAccessibilitySplitterRole ); // FIXME
+ MAP( AccessibleRole::SLIDER, NSAccessibilitySliderRole );
+ MAP( AccessibleRole::SPIN_BOX, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::SPLIT_PANE, NSAccessibilitySplitGroupRole );
+ MAP( AccessibleRole::STATUS_BAR, NSAccessibilityGroupRole ); // FIXME
+ MAP( AccessibleRole::TABLE, NSAccessibilityTableRole );
+ MAP( AccessibleRole::TABLE_CELL, NSAccessibilityTextFieldRole );
+ MAP( AccessibleRole::TEXT, NSAccessibilityTextAreaRole );
+ MAP( AccessibleRole::TEXT_FRAME, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::TOGGLE_BUTTON, NSAccessibilityCheckBoxRole );
+ MAP( AccessibleRole::TOOL_BAR, NSAccessibilityToolbarRole );
+ MAP( AccessibleRole::TOOL_TIP, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::TREE, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::VIEW_PORT, NSAccessibilityUnknownRole ); // FIXME
+ MAP( AccessibleRole::WINDOW, NSAccessibilityWindowRole );
+
+ MAP( AccessibleRole::BUTTON_DROPDOWN, NSAccessibilityMenuButtonRole );
+ MAP( AccessibleRole::BUTTON_MENU, NSAccessibilityMenuButtonRole );
+ MAP( AccessibleRole::CAPTION, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::CHART, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::FORM, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::IMAGE_MAP, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::NOTE, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::PAGE, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::RULER, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::SECTION, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::TREE_ITEM, NSAccessibilityUnknownRole );
+ MAP( AccessibleRole::TREE_TABLE, NSAccessibilityUnknownRole );
+
+ MAP( AccessibleRole::DOCUMENT_PRESENTATION, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::DOCUMENT_SPREADSHEET, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::DOCUMENT_TEXT, NSAccessibilityGroupRole );
+ MAP( AccessibleRole::STATIC, NSAccessibilityStaticTextRole );
+ MAP( AccessibleRole::NOTIFICATION, NSAccessibilityStaticTextRole );
+
+#undef MAP
+ default:
+ break;
+ }
+ return nativeRole;
+}
+
++(id)getNativeRoleFrom: (XAccessibleContext *) accessibleContext {
+ id nativeRole = [ AquaA11yRoleHelper simpleMapNativeRoleFrom: accessibleContext ];
+ if ( accessibleContext -> getAccessibleRole() == AccessibleRole::LABEL ) {
+ if ( accessibleContext -> getAccessibleChildCount() > 0 ) {
+ [ nativeRole release ];
+ nativeRole = NSAccessibilityOutlineRole;
+ } else if ( accessibleContext -> getAccessibleParent().is() ) {
+ Reference < XAccessibleContext > rxParentContext = accessibleContext -> getAccessibleParent() -> getAccessibleContext();
+ if ( rxParentContext.is() ) {
+ NSString * roleParent = static_cast<NSString *>([ AquaA11yRoleHelper simpleMapNativeRoleFrom: rxParentContext.get() ]);
+ if ( [ roleParent isEqualToString: NSAccessibilityOutlineRole ] ) {
+ [ nativeRole release ];
+ nativeRole = NSAccessibilityRowRole;
+ }
+ [ roleParent release ];
+ }
+ }
+ } else if ( accessibleContext -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
+ try {
+ Reference < XAccessible > rxAccessible = accessibleContext -> getAccessibleChild(0);
+ if ( rxAccessible.is() ) {
+ Reference < XAccessibleContext > rxAccessibleContext = rxAccessible -> getAccessibleContext();
+ if ( rxAccessibleContext.is() && rxAccessibleContext -> getAccessibleRole() == AccessibleRole::TEXT ) {
+ sal_Int64 nStateSet = rxAccessibleContext -> getAccessibleStateSet();
+ if ( !(nStateSet & AccessibleStateType::EDITABLE ) ) {
+ [ nativeRole release ];
+ nativeRole = NSAccessibilityPopUpButtonRole;
+ }
+ }
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "No valid accessible objects in parent");
+ }
+ }
+ return nativeRole;
+}
+
++(id)getNativeSubroleFrom: (sal_Int16) nRole {
+ id nativeSubrole = nil;
+ switch( nRole ) {
+#define MAP(a,b) \
+ case a: nativeSubrole = b; break
+
+ MAP( AccessibleRole::UNKNOWN, NSAccessibilityUnknownSubrole );
+ MAP( AccessibleRole::ALERT, NSAccessibilitySystemDialogSubrole );
+ MAP( AccessibleRole::BLOCK_QUOTE, @"" );
+ MAP( AccessibleRole::COLUMN_HEADER, @"" );
+ MAP( AccessibleRole::CANVAS, @"" );
+ MAP( AccessibleRole::CHECK_BOX, @"" );
+ MAP( AccessibleRole::CHECK_MENU_ITEM, @"" );
+ MAP( AccessibleRole::COLOR_CHOOSER, @"" );
+ MAP( AccessibleRole::COMBO_BOX, @"" );
+ MAP( AccessibleRole::DATE_EDITOR, @"" );
+ MAP( AccessibleRole::DESKTOP_ICON, @"" );
+ MAP( AccessibleRole::DESKTOP_PANE, @"" );
+ MAP( AccessibleRole::DIRECTORY_PANE, @"" );
+ MAP( AccessibleRole::DIALOG, NSAccessibilityDialogSubrole );
+ MAP( AccessibleRole::DOCUMENT, @"" );
+ MAP( AccessibleRole::EMBEDDED_OBJECT, @"" );
+ MAP( AccessibleRole::END_NOTE, @"" );
+ MAP( AccessibleRole::FILE_CHOOSER, @"" );
+ MAP( AccessibleRole::FILLER, @"" );
+ MAP( AccessibleRole::FONT_CHOOSER, @"" );
+ MAP( AccessibleRole::FOOTER, @"" );
+ MAP( AccessibleRole::FOOTNOTE, @"" );
+ MAP( AccessibleRole::FRAME, @"" );
+ MAP( AccessibleRole::GLASS_PANE, @"" );
+ MAP( AccessibleRole::GRAPHIC, @"" );
+ MAP( AccessibleRole::GROUP_BOX, @"" );
+ MAP( AccessibleRole::HEADER, @"" );
+ MAP( AccessibleRole::HEADING, @"" );
+ MAP( AccessibleRole::HYPER_LINK, NSAccessibilityTextLinkSubrole );
+ MAP( AccessibleRole::ICON, @"" );
+ MAP( AccessibleRole::INTERNAL_FRAME, @"" );
+ MAP( AccessibleRole::LABEL, @"" );
+ MAP( AccessibleRole::LAYERED_PANE, @"" );
+ MAP( AccessibleRole::LIST, @"" );
+ MAP( AccessibleRole::LIST_ITEM, NSAccessibilityOutlineRowSubrole );
+ MAP( AccessibleRole::MENU, @"" );
+ MAP( AccessibleRole::MENU_BAR, @"" );
+ MAP( AccessibleRole::MENU_ITEM, @"" );
+ MAP( AccessibleRole::OPTION_PANE, @"" );
+ MAP( AccessibleRole::PAGE_TAB, @"" );
+ MAP( AccessibleRole::PAGE_TAB_LIST, @"" );
+ MAP( AccessibleRole::PANEL, @"" );
+ MAP( AccessibleRole::PARAGRAPH, @"" );
+ MAP( AccessibleRole::PASSWORD_TEXT, NSAccessibilitySecureTextFieldSubrole );
+ MAP( AccessibleRole::POPUP_MENU, @"" );
+ MAP( AccessibleRole::PUSH_BUTTON, @"" );
+ MAP( AccessibleRole::PROGRESS_BAR, @"" );
+ MAP( AccessibleRole::RADIO_BUTTON, @"" );
+ MAP( AccessibleRole::RADIO_MENU_ITEM, @"" );
+ MAP( AccessibleRole::ROW_HEADER, @"" );
+ MAP( AccessibleRole::ROOT_PANE, @"" );
+ MAP( AccessibleRole::SCROLL_BAR, @"" );
+ MAP( AccessibleRole::SCROLL_PANE, @"" );
+ MAP( AccessibleRole::SHAPE, @"" );
+ MAP( AccessibleRole::SEPARATOR, @"" );
+ MAP( AccessibleRole::SLIDER, @"" );
+ MAP( AccessibleRole::SPIN_BOX, @"" );
+ MAP( AccessibleRole::SPLIT_PANE, @"" );
+ MAP( AccessibleRole::STATUS_BAR, @"" );
+ MAP( AccessibleRole::TABLE, @"" );
+ MAP( AccessibleRole::TABLE_CELL, @"" );
+ MAP( AccessibleRole::TEXT, @"" );
+ MAP( AccessibleRole::TEXT_FRAME, @"" );
+ MAP( AccessibleRole::TOGGLE_BUTTON, @"" );
+ MAP( AccessibleRole::TOOL_BAR, @"" );
+ MAP( AccessibleRole::TOOL_TIP, @"" );
+ MAP( AccessibleRole::TREE, @"" );
+ MAP( AccessibleRole::VIEW_PORT, @"" );
+ MAP( AccessibleRole::WINDOW, NSAccessibilityStandardWindowSubrole );
+
+ MAP( AccessibleRole::BUTTON_DROPDOWN, @"" );
+ MAP( AccessibleRole::BUTTON_MENU, @"" );
+ MAP( AccessibleRole::CAPTION, @"" );
+ MAP( AccessibleRole::CHART, @"" );
+ MAP( AccessibleRole::FORM, @"" );
+ MAP( AccessibleRole::IMAGE_MAP, @"" );
+ MAP( AccessibleRole::NOTE, @"" );
+ MAP( AccessibleRole::PAGE, @"" );
+ MAP( AccessibleRole::RULER, @"" );
+ MAP( AccessibleRole::SECTION, @"" );
+ MAP( AccessibleRole::TREE_ITEM, @"" );
+ MAP( AccessibleRole::TREE_TABLE, @"" );
+
+ MAP( AccessibleRole::DOCUMENT_PRESENTATION, @"" );
+ MAP( AccessibleRole::DOCUMENT_SPREADSHEET, @"" );
+ MAP( AccessibleRole::DOCUMENT_TEXT, @"" );
+
+ MAP( AccessibleRole::STATIC, @"" );
+ MAP( AccessibleRole::NOTIFICATION, @"" );
+
+#undef MAP
+ default:
+ break;
+ }
+ return nativeSubrole;
+}
+
++(id)getRoleDescriptionFrom: (NSString *) role with: (NSString *) subRole {
+ id roleDescription;
+ if ( [ subRole length ] == 0 )
+ roleDescription = NSAccessibilityRoleDescription( role, nil );
+ else
+ roleDescription = NSAccessibilityRoleDescription( role, subRole );
+ return roleDescription;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yselectionwrapper.h b/vcl/osx/a11yselectionwrapper.h
new file mode 100644
index 0000000000..3b62fbd903
--- /dev/null
+++ b/vcl/osx/a11yselectionwrapper.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11ySelectionWrapper : NSObject
+{
+}
++ (id)selectedChildrenAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (BOOL)isAttributeSettable:(NSString*)attribute forElement:(AquaA11yWrapper*)wrapper;
++ (void)setSelectedChildrenAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yselectionwrapper.mm b/vcl/osx/a11yselectionwrapper.mm
new file mode 100644
index 0000000000..9d3beee2d3
--- /dev/null
+++ b/vcl/osx/a11yselectionwrapper.mm
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <osx/a11yfactory.h>
+
+#include "a11yselectionwrapper.h"
+#include "a11ytablewrapper.h"
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+@implementation AquaA11ySelectionWrapper : NSObject
+
++(id)selectedChildrenAttributeForElement:(AquaA11yWrapper *)wrapper
+{
+ Reference< XAccessibleSelection > xAccessibleSelection = [ wrapper accessibleSelection ];
+ if( xAccessibleSelection.is() )
+ {
+ NSMutableArray * children = [ [ NSMutableArray alloc ] init ];
+ try {
+ sal_Int64 n = xAccessibleSelection -> getSelectedAccessibleChildCount();
+
+ // Fix hanging when selecting a column or row in Calc
+ // When a Calc column is selected, the child count will be
+ // at least a million. Constructing that many C++ Calc objects
+ // takes several minutes even on a fast Silicon Mac so apply
+ // the maximum table cell limit here.
+ if ( n < 0 )
+ n = 0;
+ else if ( n > MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ n = MAXIMUM_ACCESSIBLE_TABLE_CELLS;
+
+ for ( sal_Int64 i=0 ; i < n ; ++i ) {
+ [ children addObject: [ AquaA11yFactory wrapperForAccessible: xAccessibleSelection -> getSelectedAccessibleChild( i ) ] ];
+ }
+
+ return children;
+
+ } catch ( Exception&)
+ {
+ }
+ }
+
+ return nil;
+}
+
+
++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames
+{
+ [ attributeNames addObject: NSAccessibilitySelectedChildrenAttribute ];
+}
+
++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper
+{
+ (void)wrapper;
+ if ( [ attribute isEqualToString: NSAccessibilitySelectedChildrenAttribute ] )
+ {
+ return YES;
+ }
+ else
+ {
+ return NO;
+ }
+}
+
++(void)setSelectedChildrenAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value
+{
+ Reference< XAccessibleSelection > xAccessibleSelection = [ wrapper accessibleSelection ];
+ try {
+ xAccessibleSelection -> clearAccessibleSelection();
+
+ unsigned c = [ value count ];
+ for ( unsigned i = 0 ; i < c ; ++i ) {
+ xAccessibleSelection -> selectAccessibleChild( [ [ value objectAtIndex: i ] accessibleContext ] -> getAccessibleIndexInParent() );
+ }
+ } catch ( Exception&) {
+ }
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytablewrapper.h b/vcl/osx/a11ytablewrapper.h
new file mode 100644
index 0000000000..bc8ce4f39f
--- /dev/null
+++ b/vcl/osx/a11ytablewrapper.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+#define MAXIMUM_ACCESSIBLE_TABLE_CELLS 1000
+
+@interface AquaA11yTableWrapper : AquaA11yWrapper
+{
+}
++ (id)childrenAttributeForElement:(AquaA11yTableWrapper*)wrapper;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames object:(AquaA11yWrapper*)pObject;
+
+- (id)rowsAttribute;
+- (id)columnsAttribute;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytablewrapper.mm b/vcl/osx/a11ytablewrapper.mm
new file mode 100644
index 0000000000..1c155313c9
--- /dev/null
+++ b/vcl/osx/a11ytablewrapper.mm
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/a11yfactory.h>
+#include <sal/log.hxx>
+#include "a11ytablewrapper.h"
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::uno;
+
+@implementation AquaA11yTableWrapper : AquaA11yWrapper
+
++(id)childrenAttributeForElement:(AquaA11yTableWrapper *)wrapper
+{
+ XAccessibleTable * accessibleTable = [ wrapper accessibleTable ];
+ NSArray* pResult = nil;
+ if( accessibleTable )
+ {
+ NSMutableArray * cells = [ [ NSMutableArray alloc ] init ];
+ try
+ {
+ sal_Int32 nRows = accessibleTable->getAccessibleRowCount();
+ sal_Int32 nCols = accessibleTable->getAccessibleColumnCount();
+
+ // tdf#152648 Handle overflow when multiplying rows and columns
+ sal_Int64 nCells = static_cast<sal_Int64>(nRows) * static_cast<sal_Int64>(nCols);
+ if( nCells >= 0 && nCells < MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ {
+ // make all children visible to the hierarchy
+ for ( sal_Int32 rowCount = 0; rowCount < nRows; rowCount++ )
+ {
+ for ( sal_Int32 columnCount = 0; columnCount < nCols; columnCount++ )
+ {
+ Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( rowCount, columnCount );
+ if ( rAccessibleCell.is() )
+ {
+ id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ];
+ [ cells addObject: cell_wrapper ];
+ [ cell_wrapper release ];
+ }
+ }
+ }
+ }
+ else
+ {
+ XAccessibleComponent * accessibleComponent = [ wrapper accessibleComponent ];
+ // find out which cells are actually visible by determining the top-left-cell and the bottom-right-cell
+ Size tableSize = accessibleComponent -> getSize();
+ Point point;
+ point.X = 0;
+ point.Y = 0;
+ Reference < XAccessible > rAccessibleTopLeft = accessibleComponent -> getAccessibleAtPoint ( point );
+ point.X = tableSize.Width - 1;
+ point.Y = tableSize.Height - 1;
+ Reference < XAccessible > rAccessibleBottomRight = accessibleComponent -> getAccessibleAtPoint ( point );
+ if ( rAccessibleTopLeft.is() && rAccessibleBottomRight.is() )
+ {
+ sal_Int64 idxTopLeft = rAccessibleTopLeft -> getAccessibleContext() -> getAccessibleIndexInParent();
+ sal_Int64 idxBottomRight = rAccessibleBottomRight -> getAccessibleContext() -> getAccessibleIndexInParent();
+ sal_Int32 rowTopLeft = accessibleTable -> getAccessibleRow ( idxTopLeft );
+ sal_Int32 columnTopLeft = accessibleTable -> getAccessibleColumn ( idxTopLeft );
+ sal_Int32 rowBottomRight = accessibleTable -> getAccessibleRow ( idxBottomRight );
+ sal_Int32 columnBottomRight = accessibleTable -> getAccessibleColumn ( idxBottomRight );
+ SAL_WARN("vcl", "creating " << ((rowBottomRight - rowTopLeft) * (columnBottomRight - columnTopLeft)) << " cells");
+ // create an array containing the visible cells
+ for ( sal_Int32 rowCount = rowTopLeft; rowCount <= rowBottomRight; rowCount++ )
+ {
+ for ( sal_Int32 columnCount = columnTopLeft; columnCount <= columnBottomRight; columnCount++ )
+ {
+ Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( rowCount, columnCount );
+ if ( rAccessibleCell.is() )
+ {
+ id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ];
+ [ cells addObject: cell_wrapper ];
+ [ cell_wrapper release ];
+ }
+ }
+ }
+ }
+ }
+ pResult = NSAccessibilityUnignoredChildren( cells );
+ }
+ catch (const Exception &)
+ {
+ }
+ [cells autorelease];
+ }
+
+ return pResult;
+}
+
++(void)addAttributeNamesTo: (NSMutableArray *)attributeNames object: (AquaA11yWrapper*)pObject
+{
+ XAccessibleTable * accessibleTable = [ pObject accessibleTable ];
+ if( accessibleTable )
+ {
+ sal_Int32 nRows = accessibleTable->getAccessibleRowCount();
+ sal_Int32 nCols = accessibleTable->getAccessibleColumnCount();
+
+ // tdf#152648 Handle overflow when multiplying rows and columns
+ sal_Int64 nCells = static_cast<sal_Int64>(nRows) * static_cast<sal_Int64>(nCols);
+ if( nCells >= 0 && nCells < MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ {
+ [ attributeNames addObject: NSAccessibilityRowsAttribute ];
+ [ attributeNames addObject: NSAccessibilityColumnsAttribute ];
+ }
+ }
+}
+
+-(id)rowsAttribute
+{
+ NSArray* pResult = nil;
+
+ XAccessibleTable * accessibleTable = [ self accessibleTable ];
+ if( accessibleTable )
+ {
+ sal_Int32 nRows = accessibleTable->getAccessibleRowCount();
+ sal_Int32 nCols = accessibleTable->getAccessibleColumnCount();
+
+ // tdf#152648 Handle overflow when multiplying rows and columns
+ sal_Int64 nCells = static_cast<sal_Int64>(nRows) * static_cast<sal_Int64>(nCols);
+ if( nCells >= 0 && nCells < MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ {
+ NSMutableArray * cells = [ [ NSMutableArray alloc ] init ];
+ try
+ {
+ for( sal_Int32 n = 0; n < nRows; n++ )
+ {
+ Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( n, 0 );
+ if ( rAccessibleCell.is() )
+ {
+ id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ];
+ [ cells addObject: cell_wrapper ];
+ [ cell_wrapper release ];
+ }
+ }
+ pResult = NSAccessibilityUnignoredChildren( cells );
+ }
+ catch (const Exception &)
+ {
+ pResult = nil;
+ }
+ [ cells autorelease ];
+ }
+ }
+
+ return pResult;
+}
+
+-(id)columnsAttribute
+{
+ NSArray* pResult = nil;
+
+ XAccessibleTable * accessibleTable = [ self accessibleTable ];
+
+ if( accessibleTable )
+ {
+ sal_Int32 nRows = accessibleTable->getAccessibleRowCount();
+ sal_Int32 nCols = accessibleTable->getAccessibleColumnCount();
+
+ // tdf#152648 Handle overflow when multiplying rows and columns
+ sal_Int64 nCells = static_cast<sal_Int64>(nRows) * static_cast<sal_Int64>(nCols);
+ if( nCells >= 0 && nCells < MAXIMUM_ACCESSIBLE_TABLE_CELLS )
+ {
+ NSMutableArray * cells = [ [ NSMutableArray alloc ] init ];
+ try
+ {
+ // find out number of columns
+ for( sal_Int32 n = 0; n < nCols; n++ )
+ {
+ Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( 0, n );
+ if ( rAccessibleCell.is() )
+ {
+ id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ];
+ [ cells addObject: cell_wrapper ];
+ [ cell_wrapper release ];
+ }
+ }
+ pResult = NSAccessibilityUnignoredChildren( cells );
+ }
+ catch (const Exception &)
+ {
+ pResult = nil;
+ }
+ [ cells autorelease ];
+ }
+ }
+
+ return pResult;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextattributeswrapper.h b/vcl/osx/a11ytextattributeswrapper.h
new file mode 100644
index 0000000000..7df60a9383
--- /dev/null
+++ b/vcl/osx/a11ytextattributeswrapper.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yTextAttributesWrapper : NSObject
+{
+}
++(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextattributeswrapper.mm b/vcl/osx/a11ytextattributeswrapper.mm
new file mode 100644
index 0000000000..4404dc6463
--- /dev/null
+++ b/vcl/osx/a11ytextattributeswrapper.mm
@@ -0,0 +1,354 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+#include <quartz/salgdi.h>
+
+#include "a11ytextattributeswrapper.h"
+
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/awt/FontUnderline.hpp>
+#include <com/sun/star/awt/FontWeight.hpp>
+#include <com/sun/star/awt/FontStrikeout.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <com/sun/star/text/TextMarkupType.hpp>
+#include <com/sun/star/style/ParagraphAdjust.hpp>
+
+namespace css_awt = ::com::sun::star::awt;
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// cannot use NSFontDescriptor as it has no notion of explicit NSUn{bold,italic}FontMask
+@interface AquaA11yFontDescriptor : NSObject
+{
+ NSString *_name;
+ NSFontTraitMask _traits;
+ CGFloat _size;
+}
+-(void)setName:(NSString*)name;
+-(void)setBold:(NSFontTraitMask)bold;
+-(void)setItalic:(NSFontTraitMask)italic;
+-(void)setSize:(CGFloat)size;
+-(NSFont*)font;
+@end
+
+@implementation AquaA11yFontDescriptor
+- (id)init
+{
+ if((self = [super init]))
+ {
+ _name = nil;
+ _traits = 0;
+ _size = 0.0;
+ }
+ return self;
+}
+
+- (id)initWithDescriptor:(AquaA11yFontDescriptor*)descriptor {
+ if((self = [super init]))
+ {
+ _name = [descriptor->_name retain];
+ _traits = descriptor->_traits;
+ _size = descriptor->_size;
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [_name release];
+ [super dealloc];
+}
+
+-(void)setName:(NSString*)name {
+ if (_name != name) {
+ [name retain];
+ [_name release];
+ _name = name;
+ }
+}
+
+-(void)setBold:(NSFontTraitMask)bold {
+ _traits &= ~(NSBoldFontMask | NSUnboldFontMask);
+ _traits |= bold & (NSBoldFontMask | NSUnboldFontMask);
+};
+
+-(void)setItalic:(NSFontTraitMask)italic {
+ _traits &= ~(NSItalicFontMask | NSUnitalicFontMask);
+ _traits |= italic & (NSItalicFontMask | NSUnitalicFontMask);
+};
+
+-(void)setSize:(CGFloat)size { _size = size; }
+
+-(NSFont*)font {
+ return [[NSFontManager sharedFontManager] fontWithFamily:_name traits:_traits weight:0 size:_size];
+}
+@end
+
+@implementation AquaA11yTextAttributesWrapper : NSObject
+
++(int)convertUnderlineStyle:(PropertyValue)property {
+ int underlineStyle = NSUnderlineStyleNone;
+ sal_Int16 value = 0;
+ property.Value >>= value;
+ if ( value != ::css_awt::FontUnderline::NONE
+ && value != ::css_awt::FontUnderline::DONTKNOW) {
+ underlineStyle = NSUnderlineStyleSingle;
+ }
+ return underlineStyle;
+}
+
++(int)convertBoldStyle:(PropertyValue)property {
+ int boldStyle = NSUnboldFontMask;
+ float value = 0;
+ property.Value >>= value;
+ if ( value == ::css_awt::FontWeight::SEMIBOLD
+ || value == ::css_awt::FontWeight::BOLD
+ || value == ::css_awt::FontWeight::ULTRABOLD
+ || value == ::css_awt::FontWeight::BLACK ) {
+ boldStyle = NSBoldFontMask;
+ }
+ return boldStyle;
+}
+
++(int)convertItalicStyle:(PropertyValue)property {
+ int italicStyle = NSUnitalicFontMask;
+ ::css_awt::FontSlant value = property.Value.get< ::css_awt::FontSlant>();
+ if ( value == ::css_awt::FontSlant_ITALIC ) {
+ italicStyle = NSItalicFontMask;
+ }
+ return italicStyle;
+}
+
++(BOOL)isStrikethrough:(PropertyValue)property {
+ bool strikethrough = false;
+ sal_Int16 value = 0;
+ property.Value >>= value;
+ if ( value != ::css_awt::FontStrikeout::NONE
+ && value != ::css_awt::FontStrikeout::DONTKNOW ) {
+ strikethrough = true;
+ }
+ return strikethrough;
+}
+
++(BOOL)convertBoolean:(PropertyValue)property {
+ bool myBoolean = false;
+ bool value = false;
+ property.Value >>= value;
+ if ( value ) {
+ myBoolean = true;
+ }
+ return myBoolean;
+}
+
++(NSNumber *)convertShort:(PropertyValue)property {
+ sal_Int16 value = 0;
+ property.Value >>= value;
+ return [ NSNumber numberWithShort: value ];
+}
+
++(void)addColor:(Color)nColor forAttribute:(NSString *)attribute andRange:(NSRange)range toString:(NSMutableAttributedString *)string {
+ if( nColor == COL_TRANSPARENT )
+ return;
+ const RGBAColor aRGBAColor( nColor);
+ CGColorRef aColorRef = CGColorCreate ( CGColorSpaceCreateWithName ( kCGColorSpaceGenericRGB ), aRGBAColor.AsArray() );
+ [ string addAttribute: attribute value: reinterpret_cast<id>(aColorRef) range: range ];
+ CGColorRelease( aColorRef );
+}
+
++(void)addFont:(NSFont *)font toString:(NSMutableAttributedString *)string forRange:(NSRange)range {
+ if ( font != nil ) {
+ NSDictionary * fontDictionary = [ NSDictionary dictionaryWithObjectsAndKeys:
+ [ font fontName ], NSAccessibilityFontNameKey,
+ [ font familyName ], NSAccessibilityFontFamilyKey,
+ [ font displayName ], NSAccessibilityVisibleNameKey,
+ [ NSNumber numberWithFloat: [ font pointSize ] ], NSAccessibilityFontSizeKey,
+ nil
+ ];
+ [ string addAttribute: NSAccessibilityFontTextAttribute
+ value: fontDictionary
+ range: range
+ ];
+ }
+}
+
++(void)applyAttributesFrom:(Sequence < PropertyValue > const &)attributes toString:(NSMutableAttributedString *)string forRange:(NSRange)range fontDescriptor:(AquaA11yFontDescriptor*)fontDescriptor {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ // vars
+ sal_Int32 underlineColor = 0;
+ bool underlineHasColor = false;
+ // add attributes to string
+ for ( const PropertyValue& property : attributes ) {
+ // TODO: NSAccessibilityAttachmentTextAttribute, NSAccessibilityLinkTextAttribute
+ // NSAccessibilityStrikethroughColorTextAttribute is unsupported by UNP-API
+ if ( property.Value.hasValue() ) {
+ if ( property.Name == "CharUnderline" ) {
+ int style = [ AquaA11yTextAttributesWrapper convertUnderlineStyle: property ];
+ if ( style != NSUnderlineStyleNone ) {
+ [ string addAttribute: NSAccessibilityUnderlineTextAttribute value: [ NSNumber numberWithInt: style ] range: range ];
+ }
+ } else if ( property.Name == "CharFontName" ) {
+ OUString fontname;
+ property.Value >>= fontname;
+ [fontDescriptor setName:CreateNSString(fontname)];
+ } else if ( property.Name == "CharWeight" ) {
+ [fontDescriptor setBold:[AquaA11yTextAttributesWrapper convertBoldStyle:property]];
+ } else if ( property.Name == "CharPosture" ) {
+ [fontDescriptor setItalic:[AquaA11yTextAttributesWrapper convertItalicStyle:property]];
+ } else if ( property.Name == "CharHeight" ) {
+ float size;
+ property.Value >>= size;
+ [fontDescriptor setSize:size];
+ } else if ( property.Name == "CharStrikeout" ) {
+ if ( [ AquaA11yTextAttributesWrapper isStrikethrough: property ] ) {
+ [ string addAttribute: NSAccessibilityStrikethroughTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ];
+ }
+ } else if ( property.Name == "CharShadowed" ) {
+ if ( [ AquaA11yTextAttributesWrapper convertBoolean: property ] ) {
+ [ string addAttribute: NSAccessibilityShadowTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ];
+ }
+ } else if ( property.Name == "CharUnderlineColor" ) {
+ property.Value >>= underlineColor;
+ } else if ( property.Name == "CharUnderlineHasColor" ) {
+ underlineHasColor = [ AquaA11yTextAttributesWrapper convertBoolean: property ];
+ } else if ( property.Name == "CharColor" ) {
+ [ AquaA11yTextAttributesWrapper addColor: Color(ColorTransparency, property.Value.get<sal_Int32>()) forAttribute: NSAccessibilityForegroundColorTextAttribute andRange: range toString: string ];
+ } else if ( property.Name == "CharBackColor" ) {
+ [ AquaA11yTextAttributesWrapper addColor: Color(ColorTransparency, property.Value.get<sal_Int32>()) forAttribute: NSAccessibilityBackgroundColorTextAttribute andRange: range toString: string ];
+ } else if ( property.Name == "CharEscapement" ) {
+ // values < zero mean subscript
+ // values > zero mean superscript
+ // this is true for both NSAccessibility-API and UNO-API
+ NSNumber * number = [ AquaA11yTextAttributesWrapper convertShort: property ];
+ if ( [ number shortValue ] != 0 ) {
+ [ string addAttribute: NSAccessibilitySuperscriptTextAttribute value: number range: range ];
+ }
+ } else if ( property.Name == "ParaAdjust" ) {
+ sal_Int32 alignment;
+ property.Value >>= alignment;
+ NSNumber *textAlignment = nil;
+ switch(static_cast<css::style::ParagraphAdjust>(alignment)) {
+ case css::style::ParagraphAdjust_RIGHT:
+ textAlignment = [NSNumber numberWithInteger:NSTextAlignmentRight];
+ break;
+ case css::style::ParagraphAdjust_CENTER:
+ textAlignment = [NSNumber numberWithInteger:NSTextAlignmentCenter];
+ break;
+ case css::style::ParagraphAdjust_BLOCK:
+ textAlignment = [NSNumber numberWithInteger:NSTextAlignmentJustified];
+ break;
+ case css::style::ParagraphAdjust_LEFT:
+ default:
+ textAlignment = [NSNumber numberWithInteger:NSTextAlignmentLeft];
+ break;
+ }
+ NSDictionary *paragraphStyle = [NSDictionary dictionaryWithObjectsAndKeys:textAlignment, @"AXTextAlignment", textAlignment, @"AXVisualTextAlignment", nil];
+ [string addAttribute:@"AXParagraphStyle" value:paragraphStyle range:range];
+ }
+ }
+ }
+ // add underline information
+ if ( underlineHasColor ) {
+ [ AquaA11yTextAttributesWrapper addColor: Color(ColorTransparency, underlineColor) forAttribute: NSAccessibilityUnderlineColorTextAttribute andRange: range toString: string ];
+ }
+ // add font information
+ NSFont * font = [fontDescriptor font];
+ [AquaA11yTextAttributesWrapper addFont:font toString:string forRange:range];
+ [ pool release ];
+}
+
++(void)addMarkup:(XAccessibleTextMarkup*)markup withType:(sal_Int32)type toString:(NSMutableAttributedString*)string inRange:(NSRange)range {
+ const sal_Int32 markupCount = markup->getTextMarkupCount(type);
+ for (sal_Int32 markupIndex = 0; markupIndex < markupCount; ++markupIndex) {
+ TextSegment markupSegment = markup->getTextMarkup(markupIndex, type);
+ NSRange markupRange = NSMakeRange(markupSegment.SegmentStart, markupSegment.SegmentEnd - markupSegment.SegmentStart);
+ markupRange = NSIntersectionRange(range, markupRange);
+ if (markupRange.length > 0) {
+ markupRange.location -= range.location;
+ switch(type) {
+ case css::text::TextMarkupType::SPELLCHECK: {
+ [string addAttribute:NSAccessibilityMisspelledTextAttribute value:[NSNumber numberWithBool:YES] range:markupRange];
+ [string addAttribute:@"AXMarkedMisspelled" value:[NSNumber numberWithBool:YES] range:markupRange];
+ break;
+ }
+ }
+ }
+ }
+}
+
++(void)addMarkup:(XAccessibleTextMarkup*)markup toString:(NSMutableAttributedString*)string inRange:(NSRange)range {
+ [AquaA11yTextAttributesWrapper addMarkup:markup withType:css::text::TextMarkupType::SPELLCHECK toString:string inRange:range];
+}
+
+// tdf#148453 Fix crash by turning off optimization for Objective-C selector
+// The default attributes sequence sometimes crashes when it is released but
+// only when compiler optimization is enabled, so disable optimization for the
+// +[AquaA11yTextAttributesWrapper createAttributedStringForElement] selector.
++(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange __attribute__((optnone)) {
+ static const Sequence < OUString > emptySequence;
+ // vars
+ NSMutableAttributedString * string = nil;
+ int loc = [ origRange rangeValue ].location;
+ int len = [ origRange rangeValue ].length;
+ int endIndex = loc + len;
+ int currentIndex = loc;
+ try {
+ NSString * myString = CreateNSString ( [ wrapper accessibleText ] -> getText() ); // TODO: dirty fix for i87817
+ string = [ [ NSMutableAttributedString alloc ] initWithString: CreateNSString ( [ wrapper accessibleText ] -> getTextRange ( loc, loc + len ) ) ];
+ [ string autorelease ];
+ if ( [ wrapper accessibleTextAttributes ] && [myString characterAtIndex:0] != 57361) { // TODO: dirty fix for i87817
+ [ string beginEditing ];
+ // add default attributes for whole string
+ Sequence < PropertyValue > defaultAttributes = [ wrapper accessibleTextAttributes ] -> getDefaultAttributes ( emptySequence );
+ AquaA11yFontDescriptor *defaultFontDescriptor = [[AquaA11yFontDescriptor alloc] init];
+ [ AquaA11yTextAttributesWrapper applyAttributesFrom: defaultAttributes toString: string forRange: NSMakeRange ( 0, len ) fontDescriptor: defaultFontDescriptor ];
+ // add attributes for attribute run(s)
+ while ( currentIndex < endIndex ) {
+ TextSegment textSegment = [ wrapper accessibleText ] -> getTextAtIndex ( currentIndex, AccessibleTextType::ATTRIBUTE_RUN );
+ int endOfRange = endIndex > textSegment.SegmentEnd ? textSegment.SegmentEnd : endIndex;
+ NSRange rangeForAttributeRun = NSMakeRange ( currentIndex - loc , endOfRange - currentIndex );
+ // add run attributes
+ Sequence < PropertyValue > attributes = [ wrapper accessibleTextAttributes ] -> getRunAttributes ( currentIndex, emptySequence );
+ AquaA11yFontDescriptor *fontDescriptor = [[AquaA11yFontDescriptor alloc] initWithDescriptor:defaultFontDescriptor];
+ [ AquaA11yTextAttributesWrapper applyAttributesFrom: attributes toString: string forRange: rangeForAttributeRun fontDescriptor: fontDescriptor ];
+ [fontDescriptor release];
+ currentIndex = textSegment.SegmentEnd;
+ }
+ [defaultFontDescriptor release];
+ if ([wrapper accessibleTextMarkup])
+ [AquaA11yTextAttributesWrapper addMarkup:[wrapper accessibleTextMarkup] toString:string inRange:[origRange rangeValue]];
+ [ string endEditing ];
+ }
+ } catch ( IllegalArgumentException & ) {
+ // empty
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ } catch ( RuntimeException& ) {
+ // at least don't crash
+ }
+ return string;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextwrapper.h b/vcl/osx/a11ytextwrapper.h
new file mode 100644
index 0000000000..3449813231
--- /dev/null
+++ b/vcl/osx/a11ytextwrapper.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yTextWrapper : NSObject
+{
+}
++ (id)valueAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)numberOfCharactersAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)selectedTextAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)selectedTextRangeAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)visibleCharacterRangeAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)sharedTextUIElementsAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)sharedCharacterRangeAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)stringForRangeAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)range;
++ (id)attributedStringForRangeAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)range;
++ (id)rangeForIndexAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)index;
++ (id)rangeForPositionAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)point;
++ (id)boundsForRangeAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)range;
++ (id)styleRangeForIndexAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)index;
++ (id)rTFForRangeAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)range;
++ (id)lineForIndexAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)index;
++ (id)rangeForLineAttributeForElement:(AquaA11yWrapper*)wrapper forParameter:(id)line;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (void)addParameterizedAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (NSArray*)specialAttributeNames;
++ (NSArray*)specialParameterizedAttributeNames;
++ (BOOL)isAttributeSettable:(NSString*)attribute forElement:(AquaA11yWrapper*)wrapper;
++ (void)setVisibleCharacterRangeAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
++ (void)setSelectedTextRangeAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
++ (void)setSelectedTextAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
++ (void)setValueAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ytextwrapper.mm b/vcl/osx/a11ytextwrapper.mm
new file mode 100644
index 0000000000..cfd4ae7c1c
--- /dev/null
+++ b/vcl/osx/a11ytextwrapper.mm
@@ -0,0 +1,296 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+#include "a11ytextwrapper.h"
+#include "a11ytextattributeswrapper.h"
+#include "a11yutil.h"
+
+#include <com/sun/star/accessibility/AccessibleTextType.hpp>
+#include <com/sun/star/awt/Rectangle.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+// Wrapper for XAccessibleText, XAccessibleEditableText and XAccessibleMultiLineText
+
+@implementation AquaA11yTextWrapper : NSObject
+
++(id)valueAttributeForElement:(AquaA11yWrapper *)wrapper {
+ return CreateNSString ( [ wrapper accessibleText ] -> getText() );
+}
+
++(void)setValueAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value
+{
+ // TODO
+ (void)wrapper;
+ (void)value;
+}
+
++(id)numberOfCharactersAttributeForElement:(AquaA11yWrapper *)wrapper {
+ return [ NSNumber numberWithLong: [ wrapper accessibleText ] -> getCharacterCount() ];
+}
+
++(id)selectedTextAttributeForElement:(AquaA11yWrapper *)wrapper {
+ return CreateNSString ( [ wrapper accessibleText ] -> getSelectedText() );
+}
+
++(void)setSelectedTextAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
+ if ( [ wrapper accessibleEditableText ] ) {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ OUString newText = GetOUString ( static_cast<NSString *>(value) );
+ NSRange selectedTextRange = [ [ AquaA11yTextWrapper selectedTextRangeAttributeForElement: wrapper ] rangeValue ];
+ try {
+ [ wrapper accessibleEditableText ] -> replaceText ( selectedTextRange.location, selectedTextRange.location + selectedTextRange.length, newText );
+ } catch ( const Exception & ) {
+ // empty
+ }
+ [ pool release ];
+ }
+}
+
++(id)selectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper {
+ sal_Int32 start = [ wrapper accessibleText ] -> getSelectionStart();
+ sal_Int32 end = [ wrapper accessibleText ] -> getSelectionEnd();
+ if ( start != end ) {
+ return [ NSValue valueWithRange: NSMakeRange ( start, end - start ) ]; // true selection
+ } else {
+ sal_Int32 caretPos = [ wrapper accessibleText ] -> getCaretPosition();
+ if ( caretPos < 0 || caretPos > [ wrapper accessibleText ] -> getCharacterCount() ) {
+ return nil;
+ }
+ return [ NSValue valueWithRange: NSMakeRange ( caretPos, 0 ) ]; // insertion point
+ }
+}
+
++(void)setSelectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
+ NSRange range = [ value rangeValue ];
+ try {
+ [ wrapper accessibleText ] -> setSelection ( range.location, range.location + range.length );
+ } catch ( const Exception & ) {
+ // empty
+ }
+}
+
++(id)visibleCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // the OOo a11y API returns only the visible portion...
+ return [ NSValue valueWithRange: NSMakeRange ( 0, [ wrapper accessibleText ] -> getCharacterCount() ) ];
+}
+
++(void)setVisibleCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value
+{
+ // do nothing
+ (void)wrapper;
+ (void)value;
+}
+
++(id)sharedTextUIElementsAttributeForElement:(AquaA11yWrapper *)wrapper
+{
+ return [NSArray arrayWithObject:wrapper];
+}
+
++(id)sharedCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper
+{
+ return [ NSValue valueWithRange: NSMakeRange ( 0, [wrapper accessibleText]->getCharacterCount() ) ];
+}
+
++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames {
+ [ attributeNames addObjectsFromArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+}
+
++(NSArray *)specialAttributeNames {
+ return [ NSArray arrayWithObjects:
+ NSAccessibilityValueAttribute,
+ NSAccessibilityNumberOfCharactersAttribute,
+ NSAccessibilitySelectedTextAttribute,
+ NSAccessibilitySelectedTextRangeAttribute,
+ NSAccessibilityVisibleCharacterRangeAttribute,
+ NSAccessibilitySharedTextUIElementsAttribute,
+ NSAccessibilitySharedCharacterRangeAttribute,
+ nil ];
+}
+
++(void)addParameterizedAttributeNamesTo:(NSMutableArray *)attributeNames {
+ [ attributeNames addObjectsFromArray: [ AquaA11yTextWrapper specialParameterizedAttributeNames ] ];
+}
+
++(NSArray *)specialParameterizedAttributeNames {
+ return [ NSArray arrayWithObjects:
+ NSAccessibilityStringForRangeParameterizedAttribute,
+ NSAccessibilityAttributedStringForRangeParameterizedAttribute,
+ NSAccessibilityRangeForIndexParameterizedAttribute,
+ NSAccessibilityRangeForPositionParameterizedAttribute,
+ NSAccessibilityBoundsForRangeParameterizedAttribute,
+ NSAccessibilityStyleRangeForIndexParameterizedAttribute,
+ NSAccessibilityRTFForRangeParameterizedAttribute,
+ NSAccessibilityLineForIndexParameterizedAttribute,
+ NSAccessibilityRangeForLineParameterizedAttribute,
+ nil ];
+}
+
++(id)lineForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
+ NSNumber * lineNumber = nil;
+ try {
+ sal_Int32 line = [ wrapper accessibleMultiLineText ] -> getLineNumberAtIndex ( static_cast<sal_Int32>([ index intValue ]) );
+ lineNumber = [ NSNumber numberWithInt: line ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ }
+ return lineNumber;
+}
+
++(id)rangeForLineAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)line {
+ NSValue * range = nil;
+ try {
+ TextSegment textSegment = [ wrapper accessibleMultiLineText ] -> getTextAtLineNumber ( [ line intValue ] );
+ range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ }
+ return range;
+}
+
++(id)stringForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
+ int loc = [ range rangeValue ].location;
+ int len = [ range rangeValue ].length;
+ NSMutableString * textRange = [ [ NSMutableString alloc ] init ];
+ try {
+ [ textRange appendString: CreateNSString ( [ wrapper accessibleText ] -> getTextRange ( loc, loc + len ) ) ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ }
+ return textRange;
+}
+
++(id)attributedStringForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
+ return [ AquaA11yTextAttributesWrapper createAttributedStringForElement: wrapper inOrigRange: range ];
+}
+
++(id)rangeForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
+ NSValue * range = nil;
+ try {
+ TextSegment textSegment = [ wrapper accessibleText ] -> getTextBeforeIndex ( [ index intValue ], AccessibleTextType::GLYPH );
+ range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ } catch ( IllegalArgumentException & ) {
+ // empty
+ }
+ return range;
+}
+
++(id)rangeForPositionAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)point {
+ NSValue * value = nil;
+ css::awt::Point aPoint( [ AquaA11yUtil nsPointToVclPoint: point ]);
+ const css::awt::Point screenPos = [ wrapper accessibleComponent ] -> getLocationOnScreen();
+ aPoint.X -= screenPos.X;
+ aPoint.Y -= screenPos.Y;
+ sal_Int32 index = [ wrapper accessibleText ] -> getIndexAtPoint( aPoint );
+ if ( index > -1 ) {
+ value = [ AquaA11yTextWrapper rangeForIndexAttributeForElement: wrapper forParameter: [ NSNumber numberWithLong: index ] ];
+ }
+ return value;
+}
+
++(id)boundsForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
+ NSValue * rect = nil;
+ try {
+ // TODO: this is ugly!!!
+ // the UNP-API can only return the bounds for a single character, not for a range
+ int loc = [ range rangeValue ].location;
+ int len = [ range rangeValue ].length;
+ int minx = 0x7fffffff, miny = 0x7fffffff, maxx = 0, maxy = 0;
+ for ( int i = 0; i < len; i++ ) {
+ Rectangle vclRect = [ wrapper accessibleText ] -> getCharacterBounds ( loc + i );
+ if ( vclRect.X < minx ) {
+ minx = vclRect.X;
+ }
+ if ( vclRect.Y < miny ) {
+ miny = vclRect.Y;
+ }
+ if ( vclRect.Width + vclRect.X > maxx ) {
+ maxx = vclRect.Width + vclRect.X;
+ }
+ if ( vclRect.Height + vclRect.Y > maxy ) {
+ maxy = vclRect.Height + vclRect.Y;
+ }
+ }
+ if ( [ wrapper accessibleComponent ] ) {
+ // get location on screen (must be added since get CharacterBounds returns values relative to parent)
+ css::awt::Point screenPos = [ wrapper accessibleComponent ] -> getLocationOnScreen();
+ css::awt::Point pos ( minx + screenPos.X, miny + screenPos.Y );
+ css::awt::Point size ( maxx - minx, maxy - miny );
+ NSValue * nsPos = [ AquaA11yUtil vclPointToNSPoint: pos ];
+ rect = [ NSValue valueWithRect: NSMakeRect ( [ nsPos pointValue ].x, [ nsPos pointValue ].y - size.Y, size.X, size.Y ) ];
+ //printf("Range: %s --- Rect: %s\n", [ NSStringFromRange ( [ range rangeValue ] ) UTF8String ], [ NSStringFromRect ( [ rect rectValue ] ) UTF8String ]);
+ }
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ }
+ return rect;
+}
+
++(id)styleRangeForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index {
+ NSValue * range = nil;
+ try {
+ TextSegment textSegment = [ wrapper accessibleText ] -> getTextAtIndex ( [ index intValue ], AccessibleTextType::ATTRIBUTE_RUN );
+ range = [ NSValue valueWithRange: NSMakeRange ( textSegment.SegmentStart, textSegment.SegmentEnd - textSegment.SegmentStart ) ];
+ } catch ( IndexOutOfBoundsException & ) {
+ // empty
+ } catch ( IllegalArgumentException & ) {
+ // empty
+ }
+ return range;
+}
+
++(id)rTFForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range {
+ NSData * rtfData = nil;
+ NSAttributedString * attrString = static_cast<NSAttributedString *>([ AquaA11yTextWrapper attributedStringForRangeAttributeForElement: wrapper forParameter: range ]);
+ if ( attrString != nil ) {
+ @try {
+ rtfData = [ attrString RTFFromRange: [ range rangeValue ] documentAttributes: @{NSDocumentTypeDocumentAttribute : NSRTFTextDocumentType} ];
+ } @catch ( NSException *) {
+ // empty
+ }
+ }
+ return rtfData;
+}
+
++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper {
+ bool isSettable = false;
+ if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ]
+ || [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ]
+ || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ]
+ || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) {
+ if ( ! [ [ wrapper accessibilityAttributeValue: NSAccessibilityRoleAttribute ] isEqualToString: NSAccessibilityStaticTextRole ] ) {
+ isSettable = true;
+ }
+ }
+ return isSettable;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yutil.h b/vcl/osx/a11yutil.h
new file mode 100644
index 0000000000..26b1e9faed
--- /dev/null
+++ b/vcl/osx/a11yutil.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/awt/Point.hpp>
+
+@interface AquaA11yUtil : NSObject
+{
+}
++ (NSValue*)vclPointToNSPoint:(css::awt::Point)vclPoint;
++ (css::awt::Point)nsPointToVclPoint:(NSValue*)nsPoint;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yutil.mm b/vcl/osx/a11yutil.mm
new file mode 100644
index 0000000000..87edfc73cf
--- /dev/null
+++ b/vcl/osx/a11yutil.mm
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/osxvcltypes.h>
+#include <tools/long.hxx>
+
+#include "a11yutil.h"
+
+using namespace ::com::sun::star::awt;
+
+@implementation AquaA11yUtil : NSObject
+
+// TODO: should be merged with AquaSalFrame::VCLToCocoa... to a general helper method
++(NSValue *)vclPointToNSPoint:(Point)vclPoint {
+ // VCL coordinates are in upper-left-notation, Cocoa likes it the Cartesian way (lower-left)
+ NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
+ NSPoint nsPoint = NSMakePoint ( static_cast<float>(vclPoint.X), static_cast<float>( screenRect.size.height - vclPoint.Y ) );
+ return [ NSValue valueWithPoint: nsPoint ];
+}
+
+// TODO: should be merged with AquaSalFrame::VCLToCocoa... to a general helper method
++(Point)nsPointToVclPoint:(NSValue *)nsPoint {
+ // VCL coordinates are in upper-left-notation, Cocoa likes it the Cartesian way (lower-left)
+ NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
+ return Point ( static_cast<tools::Long>([ nsPoint pointValue ].x), static_cast<tools::Long>(screenRect.size.height - [ nsPoint pointValue ].y) );
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yvaluewrapper.h b/vcl/osx/a11yvaluewrapper.h
new file mode 100644
index 0000000000..232bd8ad6d
--- /dev/null
+++ b/vcl/osx/a11yvaluewrapper.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/salinst.h>
+#include <osx/osxvcltypes.h>
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yValueWrapper : NSObject
+{
+}
++ (id)valueAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)minValueAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (id)maxValueAttributeForElement:(AquaA11yWrapper*)wrapper;
++ (void)addAttributeNamesTo:(NSMutableArray*)attributeNames;
++ (BOOL)isAttributeSettable:(NSString*)attribute forElement:(AquaA11yWrapper*)wrapper;
++ (void)setValueAttributeForElement:(AquaA11yWrapper*)wrapper to:(id)value;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11yvaluewrapper.mm b/vcl/osx/a11yvaluewrapper.mm
new file mode 100644
index 0000000000..0cf3786fbe
--- /dev/null
+++ b/vcl/osx/a11yvaluewrapper.mm
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include "a11yvaluewrapper.h"
+#include "a11ywrapperstatictext.h"
+
+using namespace ::com::sun::star::uno;
+
+// Wrapper for XAccessibleValue
+// Remember: A UNO-Value is a single numeric value. Regarding the Mac A11y-API, a value can be anything!
+
+@implementation AquaA11yValueWrapper : NSObject
+
++(id)valueAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // TODO: Detect Type from Any
+ if ( [ wrapper accessibleValue ] ) {
+ sal_Int32 value = 0;
+ [ wrapper accessibleValue ] -> getCurrentValue() >>= value;
+ return [ NSNumber numberWithLong: value ];
+ }
+ return [ NSNumber numberWithLong: 0 ];
+}
+
++(id)minValueAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // TODO: Detect Type from Any
+ if ( [ wrapper accessibleValue ] ) {
+ sal_Int32 value = 0;
+ [ wrapper accessibleValue ] -> getMinimumValue() >>= value;
+ return [ NSNumber numberWithLong: value ];
+ }
+ return [ NSNumber numberWithLong: 0 ];
+}
+
++(id)maxValueAttributeForElement:(AquaA11yWrapper *)wrapper {
+ // TODO: Detect Type from Any
+ if ( [ wrapper accessibleValue ] ) {
+ sal_Int32 value = 0;
+ [ wrapper accessibleValue ] -> getMaximumValue() >>= value;
+ return [ NSNumber numberWithLong: value ];
+ }
+ return [ NSNumber numberWithLong: 0 ];
+}
+
++(void)setValueAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value {
+ // TODO: Detect Type from NSNumber
+ if ( [ value isKindOfClass: [ NSNumber class ] ]
+ && [ wrapper accessibleValue ] ) {
+ NSNumber * number = static_cast<NSNumber *>(value);
+ Any numberAny ( [ number longValue ] );
+ [ wrapper accessibleValue ] -> setCurrentValue ( numberAny );
+ }
+}
+
++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames {
+ [ attributeNames addObject: NSAccessibilityValueAttribute ];
+}
+
++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper {
+ bool isSettable = false;
+ if ( [ wrapper accessibleValue ]
+ && [ attribute isEqualToString: NSAccessibilityValueAttribute ]
+ && ! [ wrapper isKindOfClass: [ AquaA11yWrapperStaticText class ] ] ) {
+ isSettable = true;
+ }
+ return isSettable;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapper.mm b/vcl/osx/a11ywrapper.mm
new file mode 100644
index 0000000000..df1d3690df
--- /dev/null
+++ b/vcl/osx/a11ywrapper.mm
@@ -0,0 +1,1616 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <osx/salinst.h>
+#include <osx/saldata.hxx>
+
+#include <osx/a11ywrapper.h>
+#include <osx/a11ylistener.hxx>
+#include <osx/a11yfactory.h>
+#include <osx/a11yfocustracker.hxx>
+
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+
+#include "a11yfocuslistener.hxx"
+#include "a11yactionwrapper.h"
+#include "a11ycomponentwrapper.h"
+#include "a11yselectionwrapper.h"
+#include "a11ytablewrapper.h"
+#include "a11ytextwrapper.h"
+#include "a11yvaluewrapper.h"
+#include "a11yrolehelper.h"
+
+#include <com/sun/star/accessibility/AccessibleRole.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
+#include <com/sun/star/accessibility/AccessibleRelationType.hpp>
+#include <com/sun/star/lang/DisposedException.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::awt;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+@interface SalFrameWindow : NSWindow
+{
+}
+-(Reference<XAccessibleContext>)accessibleContext;
+@end
+
+static bool isPopupMenuOpen = false;
+
+static std::ostream &operator<<(std::ostream &s, NSObject *obj) {
+ return s << [[obj description] UTF8String];
+}
+
+@implementation AquaA11yWrapper
+
+#pragma mark -
+#pragma mark Init and dealloc
+
+-(id)init {
+ return [ super init ];
+}
+
+-(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ self = [ super init ];
+ if ( self ) {
+ [ self setDefaults: rxAccessibleContext ];
+ }
+ return self;
+}
+
+-(void) setDefaults: (Reference < XAccessibleContext >) rxAccessibleContext {
+ mActsAsRadioGroup = NO;
+ maReferenceWrapper.rAccessibleContext = rxAccessibleContext;
+ mIsTableCell = NO;
+ // Querying all supported interfaces
+ try {
+ // XAccessibleComponent
+ maReferenceWrapper.rAccessibleComponent.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleExtendedComponent
+ maReferenceWrapper.rAccessibleExtendedComponent.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleSelection
+ maReferenceWrapper.rAccessibleSelection.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTable
+ maReferenceWrapper.rAccessibleTable.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleText
+ maReferenceWrapper.rAccessibleText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleEditableText
+ maReferenceWrapper.rAccessibleEditableText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleValue
+ maReferenceWrapper.rAccessibleValue.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleAction
+ maReferenceWrapper.rAccessibleAction.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTextAttributes
+ maReferenceWrapper.rAccessibleTextAttributes.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleMultiLineText
+ maReferenceWrapper.rAccessibleMultiLineText.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleTextMarkup
+ maReferenceWrapper.rAccessibleTextMarkup.set( rxAccessibleContext, UNO_QUERY );
+ // XAccessibleEventBroadcaster
+ #if 0
+ /* #i102033# NSAccessibility does not seemt to know an equivalent for transient children.
+ That means we need to cache this, else e.g. tree list boxes are not accessible (moreover
+ it crashes by notifying dead objects - which would seemt o be another bug)
+
+ FIXME:
+ Unfortunately this can increase memory consumption drastically until the non transient parent
+ is destroyed and finally all the transients are released.
+ */
+ if ( ! ( rxAccessibleContext -> getAccessibleStateSet() & AccessibleStateType::TRANSIENT ) )
+ #endif
+ {
+ Reference< XAccessibleEventBroadcaster > xBroadcaster(rxAccessibleContext, UNO_QUERY);
+ if( xBroadcaster.is() ) {
+ /*
+ * We intentionally do not hold a reference to the event listener in the wrapper object,
+ * but let the listener control the life cycle of the wrapper instead ..
+ */
+ xBroadcaster->addAccessibleEventListener( new AquaA11yEventListener( self, rxAccessibleContext -> getAccessibleRole() ) );
+ }
+ }
+ // TABLE_CELL
+ if ( rxAccessibleContext -> getAccessibleRole() == AccessibleRole::TABLE_CELL ) {
+ mIsTableCell = YES;
+ }
+ } catch ( const Exception ) {
+ }
+}
+
+#pragma mark -
+#pragma mark Utility Section
+
+// generates selectors for attribute name AXAttributeNameHere
+// (getter without parameter) attributeNameHereAttribute
+// (getter with parameter) attributeNameHereAttributeForParameter:
+// (setter) setAttributeNameHereAttributeForElement:to:
+-(SEL)selectorForAttribute:(NSString *)attribute asGetter:(BOOL)asGetter withGetterParameter:(BOOL)withGetterParameter {
+ SEL selector = static_cast<SEL>(nil);
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ @try {
+ // step 1: create method name from attribute name
+ NSMutableString * methodName = [ NSMutableString string ];
+ if ( ! asGetter ) {
+ [ methodName appendString: @"set" ];
+ }
+ NSRange const aRange = { 2, 1 };
+ NSString * firstChar = [ attribute substringWithRange: aRange ]; // drop leading "AX" and get first char
+ if ( asGetter ) {
+ [ methodName appendString: [ firstChar lowercaseString ] ];
+ } else {
+ [ methodName appendString: firstChar ];
+ }
+ [ methodName appendString: [ attribute substringFromIndex: 3 ] ]; // append rest of attribute name
+ // append rest of method name
+ [ methodName appendString: @"Attribute" ];
+ if ( ! asGetter ) {
+ [ methodName appendString: @"ForElement:to:" ];
+ } else if ( asGetter && withGetterParameter ) {
+ [ methodName appendString: @"ForParameter:" ];
+ }
+ // step 2: create selector
+ selector = NSSelectorFromString ( methodName );
+ } @catch ( id ) {
+ selector = static_cast<SEL>(nil);
+ }
+ [ pool release ];
+ return selector;
+}
+
+-(Reference < XAccessible >)getFirstRadioButtonInGroup {
+ Reference < XAccessibleRelationSet > rxAccessibleRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
+ if( rxAccessibleRelationSet.is() )
+ {
+ AccessibleRelation relationMemberOf = rxAccessibleRelationSet -> getRelationByType ( AccessibleRelationType::MEMBER_OF );
+ if ( relationMemberOf.RelationType == AccessibleRelationType::MEMBER_OF && relationMemberOf.TargetSet.hasElements() )
+ return Reference < XAccessible > ( relationMemberOf.TargetSet[0], UNO_QUERY );
+ }
+ return Reference < XAccessible > ();
+}
+
+-(BOOL)isFirstRadioButtonInGroup {
+ Reference < XAccessible > rFirstMateAccessible = [ self getFirstRadioButtonInGroup ];
+ if ( rFirstMateAccessible.is() && rFirstMateAccessible -> getAccessibleContext().get() == [ self accessibleContext ] ) {
+ return YES;
+ }
+ return NO;
+}
+
+#pragma mark -
+#pragma mark Attribute Value Getters
+// ( called via Reflection by accessibilityAttributeValue )
+
+/*
+ Radiobutton grouping is done differently in NSAccessibility and the UNO-API. In UNO related radio buttons share an entry in their
+ RelationSet. In NSAccessibility the relationship is expressed through the hierarchy. An AXRadioGroup contains two or more AXRadioButton
+ objects. Since this group is not available in the UNO hierarchy, an extra wrapper is used for it. This wrapper shares almost all
+ attributes with the first radio button of the group, except for the role, subrole, role description, parent and children attributes.
+ So in this five methods there is a special treatment for radio buttons and groups.
+*/
+
+-(id)roleAttribute {
+ if ( mActsAsRadioGroup ) {
+ return NSAccessibilityRadioGroupRole;
+ }
+ else {
+ return [ AquaA11yRoleHelper getNativeRoleFrom: [ self accessibleContext ] ];
+ }
+}
+
+-(id)subroleAttribute {
+ if ( mActsAsRadioGroup ) {
+ return @"";
+ } else {
+ NSString * subRole = [ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ];
+ if ( ! [ subRole isEqualToString: @"" ] ) {
+ return subRole;
+ } else {
+ [ subRole release ];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.10 accessibilityAttributeValue:
+ return [ super accessibilityAttributeValue: NSAccessibilitySubroleAttribute ];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ }
+}
+
+-(id)titleAttribute {
+ return CreateNSString ( [ self accessibleContext ] -> getAccessibleName() );
+}
+
+-(id)descriptionAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
+ return [ self titleAttribute ];
+ } else if ( [ self accessibleExtendedComponent ] ) {
+ return [ AquaA11yComponentWrapper descriptionAttributeForElement: self ];
+ } else {
+ return CreateNSString ( [ self accessibleContext ] -> getAccessibleDescription() );
+ }
+}
+
+-(id)enabledAttribute {
+ sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ if ( nStateSet ) {
+ return [ NSNumber numberWithBool: ( (nStateSet & AccessibleStateType::ENABLED) != 0 ) ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)focusedAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
+ id isFocused = nil;
+ Reference < XAccessible > rxParent = [ self accessibleContext ] -> getAccessibleParent();
+ if ( rxParent.is() ) {
+ Reference < XAccessibleContext > rxContext = rxParent -> getAccessibleContext();
+ if ( rxContext.is() ) {
+ sal_Int64 nStateSet = rxContext -> getAccessibleStateSet();
+ if ( nStateSet ) {
+ isFocused = [ NSNumber numberWithBool: ( ( nStateSet & AccessibleStateType::FOCUSED ) != 0 ) ];
+ }
+ }
+ }
+ return isFocused;
+ } else if ( [ self accessibleContext ] -> getAccessibleStateSet() ) {
+ sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ return [ NSNumber numberWithBool: ( ( nStateSet & AccessibleStateType::FOCUSED ) != 0 ) ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)parentAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON && ! mActsAsRadioGroup ) {
+ Reference < XAccessible > rxAccessible = [ self getFirstRadioButtonInGroup ];
+ if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() ) {
+ Reference < XAccessibleContext > rxAccessibleContext = rxAccessible -> getAccessibleContext();
+ id parent_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: YES asRadioGroup: YES ];
+ [ parent_wrapper autorelease ];
+ return NSAccessibilityUnignoredAncestor( parent_wrapper );
+ }
+ return nil;
+ }
+ try {
+ Reference< XAccessible > xParent( [ self accessibleContext ] -> getAccessibleParent() );
+ if ( xParent.is() ) {
+ Reference< XAccessibleContext > xContext( xParent -> getAccessibleContext() );
+ if ( xContext.is() ) {
+ id parent_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: xContext ];
+ [ parent_wrapper autorelease ];
+ return NSAccessibilityUnignoredAncestor( parent_wrapper );
+ }
+ }
+ } catch (const Exception&) {
+ }
+
+ OSL_ASSERT( false );
+ return nil;
+}
+
+-(id)childrenAttribute {
+ if ( mActsAsRadioGroup ) {
+ NSMutableArray * children = [ [ NSMutableArray alloc ] init ];
+ Reference < XAccessibleRelationSet > rxAccessibleRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
+ AccessibleRelation const relationMemberOf = rxAccessibleRelationSet -> getRelationByType ( AccessibleRelationType::MEMBER_OF );
+ if ( relationMemberOf.RelationType == AccessibleRelationType::MEMBER_OF && relationMemberOf.TargetSet.hasElements() ) {
+ for ( const auto& i : relationMemberOf.TargetSet ) {
+ Reference < XAccessible > rMateAccessible( i, UNO_QUERY );
+ if ( rMateAccessible.is() ) {
+ Reference< XAccessibleContext > rMateAccessibleContext( rMateAccessible -> getAccessibleContext() );
+ if ( rMateAccessibleContext.is() ) {
+ id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rMateAccessibleContext ];
+ [ children addObject: wrapper ];
+ [ wrapper release ];
+ }
+ }
+ }
+ }
+ return children;
+ } else if ( [ self accessibleTable ] )
+ {
+ AquaA11yTableWrapper* pTable = [self isKindOfClass: [AquaA11yTableWrapper class]] ? static_cast<AquaA11yTableWrapper*>(self) : nil;
+ return [ AquaA11yTableWrapper childrenAttributeForElement: pTable ];
+ } else {
+ try {
+ NSMutableArray * children = [ [ NSMutableArray alloc ] init ];
+ Reference< XAccessibleContext > xContext( [ self accessibleContext ] );
+
+ try {
+ sal_Int64 cnt = xContext -> getAccessibleChildCount();
+ for ( sal_Int64 i = 0; i < cnt; i++ ) {
+ Reference< XAccessible > xChild( xContext -> getAccessibleChild( i ) );
+ if( xChild.is() ) {
+ Reference< XAccessibleContext > xChildContext( xChild -> getAccessibleContext() );
+ // the menubar is already accessible (including Apple- and Application-Menu) through NSApplication => omit it here
+ if ( xChildContext.is() && AccessibleRole::MENU_BAR != xChildContext -> getAccessibleRole() ) {
+ id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: xChildContext ];
+ [ children addObject: wrapper ];
+ [ wrapper release ];
+ }
+ }
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object has invalid index in parent");
+ }
+
+ // if not already acting as RadioGroup now is the time to replace RadioButtons with RadioGroups and remove RadioButtons
+ if ( ! mActsAsRadioGroup ) {
+ NSEnumerator * enumerator = [ children objectEnumerator ];
+ AquaA11yWrapper * element;
+ while ( ( element = static_cast<AquaA11yWrapper *>([ enumerator nextObject ]) ) ) {
+ if ( [ element accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON ) {
+ if ( [ element isFirstRadioButtonInGroup ] ) {
+ id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ element accessibleContext ] createIfNotExists: YES asRadioGroup: YES ];
+ [ children replaceObjectAtIndex: [ children indexOfObjectIdenticalTo: element ] withObject: wrapper ];
+ }
+ [ children removeObject: element ];
+ }
+ }
+ }
+
+ [ children autorelease ];
+ return NSAccessibilityUnignoredChildren( children );
+ } catch (const Exception &) {
+ // TODO: Log
+ return nil;
+ }
+ }
+}
+
+-(id)windowAttribute {
+ // go upstairs until reaching the broken connection
+ AquaA11yWrapper * aWrapper = self;
+ int loops = 0;
+ while ( [ aWrapper accessibleContext ] -> getAccessibleParent().is() ) {
+ AquaA11yWrapper *aTentativeParentWrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ aWrapper accessibleContext ] -> getAccessibleParent() -> getAccessibleContext() ];
+ // Quick-and-dirty fix for infinite loop after fixing crash in
+ // fdo#47275
+ if ( aTentativeParentWrapper == aWrapper )
+ break;
+ // Even dirtier fix for infinite loop in fdo#55156
+ if ( loops++ == 100 )
+ break;
+ aWrapper = aTentativeParentWrapper;
+ [ aWrapper autorelease ];
+ }
+ // get associated NSWindow
+ NSWindow* theWindow = [ aWrapper windowForParent ];
+ return theWindow;
+}
+
+-(id)topLevelUIElementAttribute {
+ return [ self windowAttribute ];
+}
+
+-(id)sizeAttribute {
+ if ( [ self accessibleComponent ] ) {
+ return [ AquaA11yComponentWrapper sizeAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)positionAttribute {
+ if ( [ self accessibleComponent ] ) {
+ return [ AquaA11yComponentWrapper positionAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)helpAttribute {
+ return CreateNSString ( [ self accessibleContext ] -> getAccessibleDescription() );
+}
+
+-(id)roleDescriptionAttribute {
+ if ( mActsAsRadioGroup ) {
+ return [ AquaA11yRoleHelper getRoleDescriptionFrom: NSAccessibilityRadioGroupRole with: @"" ];
+ } else if( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON ) {
+ // FIXME: VO should read this because of hierarchy, this is just a workaround
+ // get parent and its children
+ AquaA11yWrapper * parent = [ self parentAttribute ];
+ NSArray * children = [ parent childrenAttribute ];
+ // find index of self
+ int index = 1;
+ NSEnumerator * enumerator = [ children objectEnumerator ];
+ AquaA11yWrapper * child = nil;
+ while ( ( child = [ enumerator nextObject ] ) ) {
+ if ( self == child ) {
+ break;
+ }
+ index++;
+ }
+ // build string
+ NSNumber * nIndex = [ NSNumber numberWithInt: index ];
+ NSNumber * nGroupsize = [ NSNumber numberWithInt: [ children count ] ];
+ NSMutableString * value = [ [ NSMutableString alloc ] init ];
+ [ value appendString: @"radio button " ];
+ [ value appendString: [ nIndex stringValue ] ];
+ [ value appendString: @" of " ];
+ [ value appendString: [ nGroupsize stringValue ] ];
+ // clean up and return string
+ [ nIndex release ];
+ [ nGroupsize release ];
+ [ children release ];
+ return value;
+ } else {
+ return [ AquaA11yRoleHelper getRoleDescriptionFrom:
+ [ AquaA11yRoleHelper getNativeRoleFrom: [ self accessibleContext ] ]
+ with: [ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ] ];
+ }
+}
+
+-(id)valueAttribute {
+ if ( [ [ self roleAttribute ] isEqualToString: NSAccessibilityMenuItemRole ] ) {
+ return nil;
+ } else if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper valueAttributeForElement: self ];
+ } else if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper valueAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)minValueAttribute {
+ if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper minValueAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)maxValueAttribute {
+ if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper maxValueAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)contentsAttribute {
+ return [ self childrenAttribute ];
+}
+
+-(id)selectedChildrenAttribute {
+ return [ AquaA11ySelectionWrapper selectedChildrenAttributeForElement: self ];
+}
+
+-(id)numberOfCharactersAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper numberOfCharactersAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)selectedTextAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper selectedTextAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)selectedTextRangeAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper selectedTextRangeAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)visibleCharacterRangeAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper visibleCharacterRangeAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)tabsAttribute {
+ return self; // TODO ???
+}
+
+-(id)sharedTextUIElementsAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper sharedTextUIElementsAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)sharedCharacterRangeAttribute {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper sharedCharacterRangeAttributeForElement: self ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)expandedAttribute {
+ sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ return [ NSNumber numberWithBool: ( ( nStateSet & AccessibleStateType::EXPANDED ) != 0 ) ];
+}
+
+-(id)selectedAttribute {
+ sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ return [ NSNumber numberWithBool: ( ( nStateSet & AccessibleStateType::SELECTED ) != 0 ) ];
+}
+
+-(id)stringForRangeAttributeForParameter:(id)range {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper stringForRangeAttributeForElement: self forParameter: range ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)attributedStringForRangeAttributeForParameter:(id)range {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper attributedStringForRangeAttributeForElement: self forParameter: range ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)rangeForIndexAttributeForParameter:(id)index {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper rangeForIndexAttributeForElement: self forParameter: index ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)rangeForPositionAttributeForParameter:(id)point {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper rangeForPositionAttributeForElement: self forParameter: point ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)boundsForRangeAttributeForParameter:(id)range {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper boundsForRangeAttributeForElement: self forParameter: range ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)styleRangeForIndexAttributeForParameter:(id)index {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper styleRangeForIndexAttributeForElement: self forParameter: index ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)rTFForRangeAttributeForParameter:(id)range {
+ if ( [ self accessibleText ] ) {
+ return [ AquaA11yTextWrapper rTFForRangeAttributeForElement: self forParameter: range ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)orientationAttribute {
+ NSString * orientation = nil;
+ sal_Int64 stateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ if ( stateSet & AccessibleStateType::HORIZONTAL ) {
+ orientation = NSAccessibilityHorizontalOrientationValue;
+ } else if ( stateSet & AccessibleStateType::VERTICAL ) {
+ orientation = NSAccessibilityVerticalOrientationValue;
+ }
+ return orientation;
+}
+
+-(id)titleUIElementAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRelationSet().is() ) {
+ NSString * title = [ self titleAttribute ];
+ id titleElement = nil;
+ if ( [ title length ] == 0 ) {
+ AccessibleRelation relationLabeledBy = [ self accessibleContext ] -> getAccessibleRelationSet() -> getRelationByType ( AccessibleRelationType::LABELED_BY );
+ if ( relationLabeledBy.RelationType == AccessibleRelationType::LABELED_BY && relationLabeledBy.TargetSet.hasElements() ) {
+ Reference < XAccessible > rxAccessible ( relationLabeledBy.TargetSet[0], UNO_QUERY );
+ titleElement = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessible -> getAccessibleContext() ];
+ }
+ }
+ if ( title ) {
+ [ title release ];
+ }
+ return titleElement;
+ } else {
+ return nil;
+ }
+}
+
+-(id)servesAsTitleForUIElementsAttribute {
+ if ( [ self accessibleContext ] -> getAccessibleRelationSet().is() ) {
+ id titleForElement = nil;
+ AccessibleRelation relationLabelFor = [ self accessibleContext ] -> getAccessibleRelationSet() -> getRelationByType ( AccessibleRelationType::LABEL_FOR );
+ if ( relationLabelFor.RelationType == AccessibleRelationType::LABEL_FOR && relationLabelFor.TargetSet.hasElements() ) {
+ Reference < XAccessible > rxAccessible ( relationLabelFor.TargetSet[0], UNO_QUERY );
+ titleForElement = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessible -> getAccessibleContext() ];
+ }
+ return titleForElement;
+ } else {
+ return nil;
+ }
+}
+
+-(id)lineForIndexAttributeForParameter:(id)index {
+ if ( [ self accessibleMultiLineText ] ) {
+ return [ AquaA11yTextWrapper lineForIndexAttributeForElement: self forParameter: index ];
+ } else {
+ return nil;
+ }
+}
+
+-(id)rangeForLineAttributeForParameter:(id)line {
+ if ( [ self accessibleMultiLineText ] ) {
+ return [ AquaA11yTextWrapper rangeForLineAttributeForElement: self forParameter: line ];
+ } else {
+ return nil;
+ }
+}
+
+#pragma mark -
+#pragma mark Accessibility Protocol
+
+-(id)accessibilityAttributeValue:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeValue:" << attribute << "]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return nil;
+ }
+
+ id value = nil;
+ // if we are no longer in the wrapper repository, we have been disposed
+ AquaA11yWrapper * theWrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ self accessibleContext ] createIfNotExists: NO ];
+ if ( theWrapper || mIsTableCell ) {
+ try {
+ SEL methodSelector = [ self selectorForAttribute: attribute asGetter: YES withGetterParameter: NO ];
+ if ( [ self respondsToSelector: methodSelector ] ) {
+ value = [ self performSelector: methodSelector ];
+ }
+ } catch ( const DisposedException & ) {
+ mIsTableCell = NO; // just to be sure
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
+ return nil;
+ } catch ( const Exception & ) {
+ // empty
+ }
+ }
+ if ( theWrapper ) {
+ [ theWrapper release ]; // the above called method calls retain on the returned Wrapper
+ }
+ return value;
+}
+
+-(BOOL)accessibilityIsIgnored {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityIsIgnored]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return NO;
+ }
+ bool ignored = false;
+ try {
+ sal_Int16 nRole = [ self accessibleContext ] -> getAccessibleRole();
+ switch ( nRole ) {
+ //case AccessibleRole::PANEL:
+ case AccessibleRole::FRAME:
+ case AccessibleRole::ROOT_PANE:
+ case AccessibleRole::SEPARATOR:
+ case AccessibleRole::FILLER:
+ case AccessibleRole::DIALOG:
+ ignored = true;
+ break;
+ default:
+ ignored = ! ( [ self accessibleContext ] -> getAccessibleStateSet() & AccessibleStateType::VISIBLE );
+ break;
+ }
+ } catch ( DisposedException& ) {
+ ignored = true;
+ } catch ( RuntimeException& ) {
+ ignored = true;
+ }
+
+ return ignored; // TODO: to be completed
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeNames]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return nil;
+ }
+ NSString * nativeSubrole = nil;
+ NSString * title = nil;
+ NSMutableArray * attributeNames = nil;
+ sal_Int64 nAccessibleChildren = 0;
+ try {
+ // Default Attributes
+ attributeNames = [ NSMutableArray arrayWithObjects:
+ NSAccessibilityRoleAttribute,
+ NSAccessibilityDescriptionAttribute,
+ NSAccessibilityParentAttribute,
+ NSAccessibilityWindowAttribute,
+ NSAccessibilityHelpAttribute,
+ NSAccessibilityTopLevelUIElementAttribute,
+ NSAccessibilityRoleDescriptionAttribute,
+ nil ];
+ nativeSubrole = static_cast<NSString *>([ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ]);
+ title = static_cast<NSString *>([ self titleAttribute ]);
+ Reference < XAccessibleRelationSet > rxRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
+ // Special Attributes depending on attribute values
+ if ( nativeSubrole && ! [ nativeSubrole isEqualToString: @"" ] ) {
+ [ attributeNames addObject: NSAccessibilitySubroleAttribute ];
+ }
+ try
+ {
+ nAccessibleChildren = [ self accessibleContext ] -> getAccessibleChildCount();
+ if ( nAccessibleChildren > 0 ) {
+ [ attributeNames addObject: NSAccessibilityChildrenAttribute ];
+ }
+ }
+ catch( DisposedException& ) {}
+ catch( RuntimeException& ) {}
+
+ if ( title && ! [ title isEqualToString: @"" ] ) {
+ [ attributeNames addObject: NSAccessibilityTitleAttribute ];
+ }
+ if ( [ title length ] == 0 && rxRelationSet.is() && rxRelationSet -> containsRelation ( AccessibleRelationType::LABELED_BY ) ) {
+ [ attributeNames addObject: NSAccessibilityTitleUIElementAttribute ];
+ }
+ if ( rxRelationSet.is() && rxRelationSet -> containsRelation ( AccessibleRelationType::LABEL_FOR ) ) {
+ [ attributeNames addObject: NSAccessibilityServesAsTitleForUIElementsAttribute ];
+ }
+ // Special Attributes depending on interface
+ if( [self accessibleContext ] -> getAccessibleRole() == AccessibleRole::TABLE )
+ [AquaA11yTableWrapper addAttributeNamesTo: attributeNames object: self];
+
+ if ( [ self accessibleText ] ) {
+ [ AquaA11yTextWrapper addAttributeNamesTo: attributeNames ];
+ }
+ if ( [ self accessibleComponent ] ) {
+ [ AquaA11yComponentWrapper addAttributeNamesTo: attributeNames ];
+ }
+ if ( [ self accessibleSelection ] ) {
+ [ AquaA11ySelectionWrapper addAttributeNamesTo: attributeNames ];
+ }
+ if ( [ self accessibleValue ] ) {
+ [ AquaA11yValueWrapper addAttributeNamesTo: attributeNames ];
+ }
+ if ( nativeSubrole ) {
+ [ nativeSubrole release ];
+ }
+ if ( title ) {
+ [ title release ];
+ }
+ // Related: tdf#153374 Don't release autoreleased attributeNames
+ return attributeNames;
+ } catch ( DisposedException & ) { // Object is no longer available
+ if ( nativeSubrole ) {
+ [ nativeSubrole release ];
+ }
+ if ( title ) {
+ [ title release ];
+ }
+ // Related: tdf#153374 Don't release autoreleased attributeNames
+ // Also, return an autoreleased empty array instead of a retained array.
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
+ return [ NSArray array ];
+ }
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeIsSettable:" << attribute << "]");
+ bool isSettable = false;
+ if ( [ self accessibleText ] ) {
+ isSettable = [ AquaA11yTextWrapper isAttributeSettable: attribute forElement: self ];
+ }
+ if ( ! isSettable && [ self accessibleComponent ] ) {
+ isSettable = [ AquaA11yComponentWrapper isAttributeSettable: attribute forElement: self ];
+ }
+ if ( ! isSettable && [ self accessibleSelection ] ) {
+ isSettable = [ AquaA11ySelectionWrapper isAttributeSettable: attribute forElement: self ];
+ }
+ if ( ! isSettable && [ self accessibleValue ] ) {
+ isSettable = [ AquaA11yValueWrapper isAttributeSettable: attribute forElement: self ];
+ }
+ return isSettable; // TODO: to be completed
+}
+
+-(NSArray *)accessibilityParameterizedAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityParameterizedAttributeNames]");
+ NSMutableArray * attributeNames = [ NSMutableArray array ];
+ // Special Attributes depending on interface
+ if ( [ self accessibleText ] ) {
+ [ AquaA11yTextWrapper addParameterizedAttributeNamesTo: attributeNames ];
+ }
+ return attributeNames; // TODO: to be completed
+}
+
+-(id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeValue:" << attribute << " forParameter:" << (static_cast<NSObject*>(parameter)) << "]");
+ SEL methodSelector = [ self selectorForAttribute: attribute asGetter: YES withGetterParameter: YES ];
+ if ( [ self respondsToSelector: methodSelector ] ) {
+ try {
+ return [ self performSelector: methodSelector withObject: parameter ];
+ } catch ( const DisposedException & ) {
+ mIsTableCell = NO; // just to be sure
+ [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
+ return nil;
+ } catch ( const Exception & ) {
+ // empty
+ }
+ }
+ return nil; // TODO: to be completed
+}
+
+-(BOOL)accessibilitySetOverrideValue:(id)value forAttribute:(NSString *)attribute
+{
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilitySetOverrideValue:" << (static_cast<NSObject*>(value)) << " forAttribute:" << attribute << "]");
+ return NO; // TODO
+}
+
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilitySetValue:" << (static_cast<NSObject*>(value)) << " forAttribute:" << attribute << "]");
+ SEL methodSelector = [ self selectorForAttribute: attribute asGetter: NO withGetterParameter: NO ];
+ if ( [ AquaA11yComponentWrapper respondsToSelector: methodSelector ] ) {
+ [ AquaA11yComponentWrapper performSelector: methodSelector withObject: self withObject: value ];
+ }
+ if ( [ AquaA11yTextWrapper respondsToSelector: methodSelector ] ) {
+ [ AquaA11yTextWrapper performSelector: methodSelector withObject: self withObject: value ];
+ }
+ if ( [ AquaA11ySelectionWrapper respondsToSelector: methodSelector ] ) {
+ [ AquaA11ySelectionWrapper performSelector: methodSelector withObject: self withObject: value ];
+ }
+ if ( [ AquaA11yValueWrapper respondsToSelector: methodSelector ] ) {
+ [ AquaA11yValueWrapper performSelector: methodSelector withObject: self withObject: value ];
+ }
+}
+
+-(id)accessibilityFocusedUIElement {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityFocusedUIElement]");
+ // #i90575# guard NSAccessibility protocol against unwanted access
+ if ( isPopupMenuOpen ) {
+ return nil;
+ }
+
+ // as this seems to be the first API call on a newly created SalFrameView object,
+ // make sure self gets registered in the repository ..
+ [ self accessibleContext ];
+
+ AquaA11yWrapper * focusedUIElement = AquaA11yFocusListener::get()->getFocusedUIElement();
+// AquaA11yWrapper * ancestor = focusedUIElement;
+
+ // Make sure the focused object is a descendant of self
+// do {
+// if( self == ancestor )
+ return focusedUIElement;
+
+// ancestor = [ ancestor accessibilityAttributeValue: NSAccessibilityParentAttribute ];
+// } while( nil != ancestor );
+
+ return self;
+}
+
+-(NSString *)accessibilityActionDescription:(NSString *)action {
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityActionDescription:" << action << "]");
+ return NSAccessibilityActionDescription(action);
+}
+
+-(AquaA11yWrapper *)actionResponder {
+ AquaA11yWrapper * wrapper = nil;
+ // get some information
+ NSString * role = static_cast<NSString *>([ self accessibilityAttributeValue: NSAccessibilityRoleAttribute ]);
+ id enabledAttr = [ self enabledAttribute ];
+ bool enabled = [ enabledAttr boolValue ];
+ NSView * parent = static_cast<NSView *>([ self accessibilityAttributeValue: NSAccessibilityParentAttribute ]);
+ AquaA11yWrapper * parentAsWrapper = nil;
+ if ( [ parent isKindOfClass: [ AquaA11yWrapper class ] ] ) {
+ parentAsWrapper = static_cast<AquaA11yWrapper *>(parent);
+ }
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.10 accessibilityAttributeValue:
+ NSString * parentRole = static_cast<NSString *>([ parent accessibilityAttributeValue: NSAccessibilityRoleAttribute ]);
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ // if we are a textarea inside a combobox, then the combobox is the action responder
+ if ( enabled
+ && [ role isEqualToString: NSAccessibilityTextAreaRole ]
+ && [ parentRole isEqualToString: NSAccessibilityComboBoxRole ]
+ && parentAsWrapper ) {
+ wrapper = parentAsWrapper;
+ } else if ( enabled && [ self accessibleAction ] ) {
+ wrapper = self ;
+ }
+ [ parentRole release ];
+ [ enabledAttr release ];
+ [ role release ];
+ return wrapper;
+}
+
+-(void)accessibilityPerformAction:(NSString *)action {
+ [ self performAction: action ];
+}
+
+-(BOOL)performAction:(NSString *)action {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityPerformAction:" << action << "]");
+ AquaA11yWrapper * actionResponder = [ self actionResponder ];
+ if ( actionResponder ) {
+ [ AquaA11yActionWrapper doAction: action ofElement: actionResponder ];
+ return YES;
+ }
+ return NO;
+}
+
+-(NSArray *)accessibilityActionNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityActionNames]");
+ NSArray * actionNames = nil;
+ AquaA11yWrapper * actionResponder = [ self actionResponder ];
+ if ( actionResponder ) {
+ actionNames = [ AquaA11yActionWrapper actionNamesForElement: actionResponder ];
+ } else {
+ actionNames = [ [ NSArray alloc ] init ];
+ }
+ return actionNames;
+}
+
+#pragma mark -
+#pragma mark Hit Test
+
+-(BOOL)isViewElement:(NSObject *)viewElement hitByPoint:(NSPoint)point {
+ bool hit = false;
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.10 accessibilityAttributeValue:
+ NSValue * position = [ viewElement accessibilityAttributeValue: NSAccessibilityPositionAttribute ];
+ NSValue * size = [ viewElement accessibilityAttributeValue: NSAccessibilitySizeAttribute ];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ if ( position && size ) {
+ float minX = [ position pointValue ].x;
+ float minY = [ position pointValue ].y;
+ float maxX = minX + [ size sizeValue ].width;
+ float maxY = minY + [ size sizeValue ].height;
+ if ( minX < point.x && maxX > point.x && minY < point.y && maxY > point.y ) {
+ hit = true;
+ }
+ }
+ [ pool release ];
+ return hit;
+}
+
+static Reference < XAccessibleContext > hitTestRunner ( css::awt::Point point,
+ Reference < XAccessibleContext > const & rxAccessibleContext ) {
+ Reference < XAccessibleContext > hitChild;
+ Reference < XAccessibleContext > emptyReference;
+ try {
+ Reference < XAccessibleComponent > rxAccessibleComponent ( rxAccessibleContext, UNO_QUERY );
+ if ( rxAccessibleComponent.is() ) {
+ css::awt::Point location = rxAccessibleComponent -> getLocationOnScreen();
+ css::awt::Point hitPoint ( point.X - location.X , point.Y - location.Y);
+ Reference < XAccessible > rxAccessible = rxAccessibleComponent -> getAccessibleAtPoint ( hitPoint );
+ if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() &&
+ rxAccessible -> getAccessibleContext() -> getAccessibleChildCount() == 0 ) {
+ hitChild = rxAccessible -> getAccessibleContext();
+ }
+ }
+
+ // iterate the hierarchy looking doing recursive hit testing.
+ // apparently necessary as a special treatment for e.g. comboboxes
+ if ( !hitChild.is() ) {
+ bool bSafeToIterate = true;
+ sal_Int64 nCount = rxAccessibleContext -> getAccessibleChildCount();
+
+ if (nCount < 0 || nCount > SAL_MAX_UINT16 /* slow enough for anyone */)
+ bSafeToIterate = false;
+ else { // manages descendants is an horror from the a11y standards guys.
+ sal_Int64 nStateSet = rxAccessibleContext -> getAccessibleStateSet();
+ if ( nStateSet & AccessibleStateType::MANAGES_DESCENDANTS )
+ bSafeToIterate = false;
+ }
+
+ if( bSafeToIterate ) {
+ try {
+ for ( sal_Int64 i = 0; i < rxAccessibleContext -> getAccessibleChildCount(); i++ ) {
+ Reference < XAccessible > rxAccessibleChild = rxAccessibleContext -> getAccessibleChild ( i );
+ if ( rxAccessibleChild.is() && rxAccessibleChild -> getAccessibleContext().is() && rxAccessibleChild -> getAccessibleContext() -> getAccessibleRole() != AccessibleRole::LIST ) {
+ Reference < XAccessibleContext > myHitChild = hitTestRunner ( point, rxAccessibleChild -> getAccessibleContext() );
+ if ( myHitChild.is() ) {
+ hitChild = myHitChild;
+ break;
+ }
+ }
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object has invalid index in parent");
+ }
+ }
+ }
+ } catch ( RuntimeException ) {
+ return emptyReference;
+ }
+ return hitChild;
+}
+
+-(id)accessibilityHitTest:(NSPoint)point {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ SAL_INFO("vcl.a11y", "[" << self << " accessibilityHitTest:" << point << "]");
+ static id wrapper = nil;
+ if ( nil != wrapper ) {
+ [ wrapper release ];
+ wrapper = nil;
+ }
+ Reference < XAccessibleContext > hitChild;
+ NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
+ css::awt::Point hitPoint ( static_cast<sal_Int32>(point.x) , static_cast<sal_Int32>(screenRect.size.height - point.y) );
+ // check child windows first
+ NSWindow * window = static_cast<NSWindow *>([ self accessibilityAttributeValue: NSAccessibilityWindowAttribute ]);
+ NSArray * childWindows = [ window childWindows ];
+ if ( [ childWindows count ] > 0 ) {
+ NSWindow * element = nil;
+ NSEnumerator * enumerator = [ childWindows objectEnumerator ];
+ while ( ( element = [ enumerator nextObject ] ) && !hitChild.is() ) {
+ if ( [ element isKindOfClass: [ SalFrameWindow class ] ] && [ self isViewElement: element hitByPoint: point ] ) {
+ // we have a child window that is hit
+ Reference < XAccessibleRelationSet > relationSet = [ static_cast<SalFrameWindow *>(element) accessibleContext ] -> getAccessibleRelationSet();
+ if ( relationSet.is() && relationSet -> containsRelation ( AccessibleRelationType::SUB_WINDOW_OF )) {
+ // we have a valid relation to the parent element
+ AccessibleRelation const relation = relationSet -> getRelationByType ( AccessibleRelationType::SUB_WINDOW_OF );
+ for ( const auto & i : relation.TargetSet ) {
+ Reference < XAccessible > rxAccessible ( i, UNO_QUERY );
+ if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() ) {
+ // hit test for children of parent
+ hitChild = hitTestRunner ( hitPoint, rxAccessible -> getAccessibleContext() );
+ if (hitChild.is())
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ // nothing hit yet, so check ourself
+ if ( ! hitChild.is() ) {
+ if ( !maReferenceWrapper.rAccessibleContext ) {
+ [ self setDefaults: [ self accessibleContext ] ];
+ }
+ hitChild = hitTestRunner ( hitPoint, maReferenceWrapper.rAccessibleContext );
+ }
+ if ( hitChild.is() ) {
+ wrapper = [ AquaA11yFactory wrapperForAccessibleContext: hitChild ];
+ }
+ if ( wrapper ) {
+ [ wrapper retain ]; // TODO: retain only when transient ?
+ }
+ return wrapper;
+}
+
+#pragma mark -
+#pragma mark Access Methods
+
+-(XAccessibleAction *)accessibleAction {
+ return maReferenceWrapper.rAccessibleAction.get();
+}
+
+-(XAccessibleContext *)accessibleContext {
+ return maReferenceWrapper.rAccessibleContext.get();
+}
+
+-(XAccessibleComponent *)accessibleComponent {
+ return maReferenceWrapper.rAccessibleComponent.get();
+}
+
+-(XAccessibleExtendedComponent *)accessibleExtendedComponent {
+ return maReferenceWrapper.rAccessibleExtendedComponent.get();
+}
+
+-(XAccessibleSelection *)accessibleSelection {
+ return maReferenceWrapper.rAccessibleSelection.get();
+}
+
+-(XAccessibleTable *)accessibleTable {
+ return maReferenceWrapper.rAccessibleTable.get();
+}
+
+-(XAccessibleText *)accessibleText {
+ return maReferenceWrapper.rAccessibleText.get();
+}
+
+-(XAccessibleEditableText *)accessibleEditableText {
+ return maReferenceWrapper.rAccessibleEditableText.get();
+}
+
+-(XAccessibleValue *)accessibleValue {
+ return maReferenceWrapper.rAccessibleValue.get();
+}
+
+-(XAccessibleTextAttributes *)accessibleTextAttributes {
+ return maReferenceWrapper.rAccessibleTextAttributes.get();
+}
+
+-(XAccessibleMultiLineText *)accessibleMultiLineText {
+ return maReferenceWrapper.rAccessibleMultiLineText.get();
+}
+
+-(XAccessibleTextMarkup *)accessibleTextMarkup {
+ return maReferenceWrapper.rAccessibleTextMarkup.get();
+}
+
+-(NSWindow*)windowForParent {
+ return nil;
+}
+
+-(void)setActsAsRadioGroup:(BOOL)actsAsRadioGroup {
+ mActsAsRadioGroup = actsAsRadioGroup;
+}
+
+-(BOOL)actsAsRadioGroup {
+ return mActsAsRadioGroup;
+}
+
++(void)setPopupMenuOpen:(BOOL)popupMenuOpen {
+ isPopupMenuOpen = popupMenuOpen;
+}
+
+// NSAccessibility selectors
+
+- (BOOL)isAccessibilityElement
+{
+ return ! [ self accessibilityIsIgnored ];
+}
+
+- (BOOL)isAccessibilityFocused
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityFocusedAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (id)accessibilityTopLevelUIElement
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityTopLevelUIElementAttribute ];
+}
+
+- (id)accessibilityValue
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityValueAttribute ];
+}
+
+- (NSArray *)accessibilityVisibleChildren
+{
+ return [ self accessibilityChildren ];
+}
+
+- (NSAccessibilitySubrole)accessibilitySubrole
+{
+ return [ self accessibilityAttributeValue: NSAccessibilitySubroleAttribute ];
+}
+
+- (NSString *)accessibilityTitle
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityTitleAttribute ];
+}
+
+- (id)accessibilityTitleUIElement
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityTitleUIElementAttribute ];
+}
+
+- (NSAccessibilityOrientation)accessibilityOrientation
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityOrientationAttribute ];
+ if ( pNumber )
+ return NSAccessibilityOrientation([ pNumber integerValue ]);
+ else
+ return NSAccessibilityOrientationUnknown;
+}
+
+- (id)accessibilityParent
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityParentAttribute ];
+}
+
+- (NSAccessibilityRole)accessibilityRole
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityRoleAttribute ];
+}
+
+- (NSString *)accessibilityRoleDescription
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityRoleDescriptionAttribute ];
+}
+
+- (BOOL)isAccessibilitySelected
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilitySelectedAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (NSArray *)accessibilitySelectedChildren
+{
+ return [ self accessibilityAttributeValue: NSAccessibilitySelectedChildrenAttribute ];
+}
+
+- (NSArray *)accessibilityServesAsTitleForUIElements
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityServesAsTitleForUIElementsAttribute ];
+}
+
+- (id)accessibilityMinValue
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityMinValueAttribute ];
+}
+
+- (id)accessibilityMaxValue
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityMaxValueAttribute ];
+}
+
+- (id)accessibilityWindow
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityWindowAttribute ];
+}
+
+- (NSString *)accessibilityHelp
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityHelpAttribute ];
+}
+
+- (BOOL)isAccessibilityExpanded
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityExpandedAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (BOOL)isAccessibilityEnabled
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityEnabledAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (NSArray *)accessibilityChildren
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityChildrenAttribute ];
+}
+
+- (NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder
+{
+ return [ self accessibilityChildren ];
+}
+
+- (NSArray *)accessibilityContents
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityContentsAttribute ];
+}
+
+- (NSString *)accessibilityLabel
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityDescriptionAttribute ];
+}
+
+- (id)accessibilityApplicationFocusedUIElement
+{
+ return [ self accessibilityFocusedUIElement ];
+}
+
+- (BOOL)isAccessibilityDisclosed
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityDisclosingAttribute ];
+ if ( pNumber )
+ return [ pNumber boolValue ];
+ else
+ return NO;
+}
+
+- (id)accessibilityHorizontalScrollBar
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityHorizontalScrollBarAttribute ];
+}
+
+- (id)accessibilityVerticalScrollBar
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityVerticalScrollBarAttribute ];
+}
+
+- (NSArray *)accessibilityTabs
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityTabsAttribute ];
+}
+
+- (NSArray *)accessibilityColumns
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityColumnsAttribute ];
+}
+
+- (NSArray *)accessibilityRows
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityRowsAttribute ];
+}
+
+- (NSRange)accessibilitySharedCharacterRange
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilitySharedCharacterRangeAttribute ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSArray *)accessibilitySharedTextUIElements
+{
+ return [ self accessibilityAttributeValue: NSAccessibilitySharedTextUIElementsAttribute ];
+}
+
+- (NSRange)accessibilityVisibleCharacterRange
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityVisibleCharacterRangeAttribute ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSInteger)accessibilityNumberOfCharacters
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityNumberOfCharactersAttribute ];
+ if ( pNumber )
+ return [ pNumber integerValue ];
+ else
+ return 0;
+}
+
+- (NSString *)accessibilitySelectedText
+{
+ return [ self accessibilityAttributeValue: NSAccessibilitySelectedTextAttribute ];
+}
+
+- (NSRange)accessibilitySelectedTextRange
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilitySelectedTextRangeAttribute ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)aRange
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityAttributedStringForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ];
+}
+
+- (NSRange)accessibilityRangeForLine:(NSInteger)nLine
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForLineParameterizedAttribute forParameter: [NSNumber numberWithInteger: nLine ] ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSString *)accessibilityStringForRange:(NSRange)aRange
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityStringForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ];
+}
+
+- (NSRange)accessibilityRangeForPosition:(NSPoint)aPoint
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForPositionParameterizedAttribute forParameter: [ NSValue valueWithPoint: aPoint ] ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSRange)accessibilityRangeForIndex:(NSInteger)nIndex
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSRect)accessibilityFrameForRange:(NSRange)aRange
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityBoundsForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ];
+ if ( pValue )
+ return [ pValue rectValue ];
+ else
+ return NSZeroRect;
+}
+
+- (NSData *)accessibilityRTFForRange:(NSRange)aRange
+{
+ return [ self accessibilityAttributeValue: NSAccessibilityRTFForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ];
+}
+
+- (NSRange)accessibilityStyleRangeForIndex:(NSInteger)nIndex
+{
+ NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityStyleRangeForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ];
+ if ( pValue )
+ return [ pValue rangeValue ];
+ else
+ return NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSInteger)accessibilityLineForIndex:(NSInteger)nIndex
+{
+ NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityLineForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ];
+ if ( pNumber )
+ return [ pNumber integerValue ];
+ else
+ return 0;
+}
+
+- (BOOL)accessibilityPerformDecrement
+{
+ return [ self performAction: NSAccessibilityDecrementAction ];
+}
+
+- (BOOL)accessibilityPerformPick
+{
+ return [ self performAction: NSAccessibilityPickAction ];
+}
+
+- (BOOL)accessibilityPerformShowMenu
+{
+ return [ self performAction: NSAccessibilityShowMenuAction ];
+}
+
+- (BOOL)accessibilityPerformPress
+{
+ return [ self performAction: NSAccessibilityPressAction ];
+}
+
+- (BOOL)accessibilityPerformIncrement
+{
+ return [ self performAction: NSAccessibilityIncrementAction ];
+}
+
+// NSAccessibilityElement selectors
+
+- (NSRect)accessibilityFrame
+{
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ try {
+ XAccessibleComponent *pAccessibleComponent = [ self accessibleComponent ];
+ if ( pAccessibleComponent ) {
+ com::sun::star::awt::Point location = pAccessibleComponent->getLocationOnScreen();
+ com::sun::star::awt::Size size = pAccessibleComponent->getSize();
+ NSRect screenRect = sal::aqua::getTotalScreenBounds();
+ NSRect frame = NSMakeRect( float(location.X), float( screenRect.size.height - size.Height - location.Y ), float(size.Width), float(size.Height) );
+ return frame;
+ }
+ } catch ( DisposedException& ) {
+ } catch ( RuntimeException& ) {
+ }
+
+ return NSZeroRect;
+}
+
+- (BOOL)accessibilityNotifiesWhenDestroyed
+{
+ return YES;
+}
+
+- (BOOL)isAccessibilitySelectorAllowed:(SEL)aSelector
+{
+ if ( ! aSelector )
+ return NO;
+
+ // don't explicitly report (non-)expanded state when not expandable
+ if (aSelector == @selector(isAccessibilityExpanded))
+ {
+ const sal_Int64 nStateSet = [ self accessibleContext ] -> getAccessibleStateSet();
+ if (!( nStateSet & AccessibleStateType::EXPANDABLE))
+ return false;
+ }
+
+ if ( [ self respondsToSelector: aSelector ] ) {
+ // Ignore actions if action is not supported
+ NSAccessibilityActionName pActionName = [ AquaA11yActionWrapper actionNameForSelector: aSelector ];
+ if ( pActionName ) {
+ NSArray *pActionNames = [ self accessibilityActionNames ];
+ if ( ! pActionNames || ! [ pActionNames containsObject: pActionName ] )
+ return NO;
+ } else {
+ // Ignore "setAccessibility" selectors if attribute is not settable
+ static NSString *pSetPrefix = @"setAccessibility";
+ NSString *pSelName = NSStringFromSelector( aSelector );
+ if ( pSelName && [ pSelName hasPrefix: pSetPrefix ] && [ pSelName hasSuffix: @":" ] ) {
+ NSAccessibilityAttributeName pAttrName = [ pSelName substringToIndex: [ pSelName length ] - 1 ];
+ if ( pAttrName && [ pAttrName length ] > [ pSetPrefix length ] ) {
+ pAttrName = [ pAttrName substringFromIndex: [ pSetPrefix length ] ];
+ if ( pAttrName && [ pAttrName length ] ) {
+ pAttrName = [ @"AX" stringByAppendingString: pAttrName ];
+ if ( pAttrName && [ pAttrName length ] && ! [ self accessibilityIsAttributeSettable: pAttrName ] )
+ return NO;
+ }
+ }
+ }
+ }
+ }
+
+ return [ super isAccessibilitySelectorAllowed: aSelector ];
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperbutton.h b/vcl/osx/a11ywrapperbutton.h
new file mode 100644
index 0000000000..e4819e0c07
--- /dev/null
+++ b/vcl/osx/a11ywrapperbutton.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperButton : AquaA11yWrapper
+{
+}
+- (id)valueAttribute;
+- (id)descriptionAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperbutton.mm b/vcl/osx/a11ywrapperbutton.mm
new file mode 100644
index 0000000000..a2c0d0398f
--- /dev/null
+++ b/vcl/osx/a11ywrapperbutton.mm
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrapperbutton.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXButton role
+
+@implementation AquaA11yWrapperButton : AquaA11yWrapper
+
+-(id)valueAttribute {
+ return [ NSString string ]; // we propagate AXTitle, that's enough
+}
+
+-(id)descriptionAttribute {
+ return [ NSString string ]; // we propagate AXTitle, that's enough
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ if ( [ attributeNames containsObject: NSAccessibilityTitleAttribute ] ) {
+ [ attributeNames removeObject: NSAccessibilityDescriptionAttribute ];
+ } else {
+ [ attributeNames addObject: NSAccessibilityTitleAttribute ];
+ }
+ // Remove text-specific attributes
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercheckbox.h b/vcl/osx/a11ywrappercheckbox.h
new file mode 100644
index 0000000000..ec677d8833
--- /dev/null
+++ b/vcl/osx/a11ywrappercheckbox.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperCheckBox : AquaA11yWrapper
+{
+}
+- (id)valueAttribute;
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercheckbox.mm b/vcl/osx/a11ywrappercheckbox.mm
new file mode 100644
index 0000000000..9e0f221985
--- /dev/null
+++ b/vcl/osx/a11ywrappercheckbox.mm
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrappercheckbox.h"
+#include "a11yvaluewrapper.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXCheckbox role
+
+@implementation AquaA11yWrapperCheckBox : AquaA11yWrapper
+
+-(id)valueAttribute {
+ if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper valueAttributeForElement: self ];
+ }
+ return [ NSNumber numberWithInt: 0 ];
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] ) {
+ return NO;
+ }
+ return [ super accessibilityIsAttributeSettable: attribute ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Remove text-specific attributes
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ [ attributeNames addObject: NSAccessibilityValueAttribute ];
+ [ attributeNames addObject: NSAccessibilityMinValueAttribute ];
+ [ attributeNames addObject: NSAccessibilityMaxValueAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercombobox.h b/vcl/osx/a11ywrappercombobox.h
new file mode 100644
index 0000000000..0f9e21ec09
--- /dev/null
+++ b/vcl/osx/a11ywrappercombobox.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+#include <com/sun/star/accessibility/XAccessibleContext.hpp>
+
+@interface AquaA11yWrapperComboBox : AquaA11yWrapper
+{
+ AquaA11yWrapper* textArea;
+}
+- (id)initWithAccessibleContext:
+ (css::uno::Reference<css::accessibility::XAccessibleContext>)anAccessibleContext;
+- (id)valueAttribute;
+- (id)numberOfCharactersAttribute;
+- (id)selectedTextAttribute;
+- (id)selectedTextRangeAttribute;
+- (id)visibleCharacterRangeAttribute;
+// Accessibility Protocol
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappercombobox.mm b/vcl/osx/a11ywrappercombobox.mm
new file mode 100644
index 0000000000..bfcef7275e
--- /dev/null
+++ b/vcl/osx/a11ywrappercombobox.mm
@@ -0,0 +1,165 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrappercombobox.h"
+#include "a11yrolehelper.h"
+
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::uno;
+
+// Wrapper for AXCombobox role
+
+@implementation AquaA11yWrapperComboBox : AquaA11yWrapper
+
+#pragma mark -
+#pragma mark Specialized Init Method
+
+-(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
+ self = [ super initWithAccessibleContext: rxAccessibleContext ];
+ if ( self != nil )
+ {
+ textArea = nil;
+ }
+ return self;
+}
+
+#pragma mark -
+#pragma mark Private Helper Method
+
+-(AquaA11yWrapper *)textArea {
+ // FIXME: May cause problems when stored. Then get dynamically each time (bad performance!)
+ if ( textArea == nil ) {
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ NSArray * elementChildren = [ super childrenAttribute ];
+ if ( [ elementChildren count ] > 0 ) {
+ NSEnumerator * enumerator = [ elementChildren objectEnumerator ];
+ id child;
+ while ( ( child = [ enumerator nextObject ] ) ) {
+ AquaA11yWrapper * element = static_cast<AquaA11yWrapper *>(child);
+ if ( [ [ AquaA11yRoleHelper getNativeRoleFrom: [ element accessibleContext ] ] isEqualToString: NSAccessibilityTextAreaRole ] ) {
+ textArea = element;
+ break;
+ }
+ }
+ }
+ [ pool release ];
+ }
+ return textArea;
+}
+
+#pragma mark -
+#pragma mark Wrapped Attributes From Contained Text Area
+
+-(id)valueAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] valueAttribute ];
+ }
+ return @"";
+}
+
+-(id)numberOfCharactersAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] numberOfCharactersAttribute ];
+ }
+ return [ NSNumber numberWithInt: 0 ];
+}
+
+-(id)selectedTextAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] selectedTextAttribute ];
+ }
+ return @"";
+}
+
+-(id)selectedTextRangeAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] selectedTextRangeAttribute ];
+ }
+ return [ NSValue valueWithRange: NSMakeRange ( 0, 0 ) ];
+}
+
+-(id)visibleCharacterRangeAttribute {
+ if ( [ self textArea ] != nil ) {
+ return [ [ self textArea ] visibleCharacterRangeAttribute ];
+ }
+ return [ NSValue valueWithRange: NSMakeRange ( 0, 0 ) ];
+}
+
+#pragma mark -
+#pragma mark Accessibility Protocol
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ if ( [ self textArea ] != nil && (
+ [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ]
+ || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ]
+ || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) ) {
+ return [ [ self textArea ] accessibilityIsAttributeSettable: attribute ];
+ }
+ return [ super accessibilityIsAttributeSettable: attribute ];
+}
+
+-(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ if ( [ self textArea ] != nil && (
+ [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ]
+ || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ]
+ || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) ) {
+ return [ [ self textArea ] accessibilitySetValue: value forAttribute: attribute ];
+ }
+ return [ super accessibilitySetValue: value forAttribute: attribute ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects:
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityChildrenAttribute,
+ nil ]
+ ];
+ [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects:
+ NSAccessibilityExpandedAttribute,
+ NSAccessibilityValueAttribute,
+ NSAccessibilityNumberOfCharactersAttribute,
+ NSAccessibilitySelectedTextAttribute,
+ NSAccessibilitySelectedTextRangeAttribute,
+ NSAccessibilityVisibleCharacterRangeAttribute,
+ nil ]
+ ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappergroup.h b/vcl/osx/a11ywrappergroup.h
new file mode 100644
index 0000000000..68d9742e79
--- /dev/null
+++ b/vcl/osx/a11ywrappergroup.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperGroup : AquaA11yWrapper
+{
+}
+- (id)titleUIElementAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappergroup.mm b/vcl/osx/a11ywrappergroup.mm
new file mode 100644
index 0000000000..7ed70d47ba
--- /dev/null
+++ b/vcl/osx/a11ywrappergroup.mm
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappergroup.h"
+
+// Wrapper for AXGroup role
+
+@implementation AquaA11yWrapperGroup : AquaA11yWrapper
+
+-(id)titleUIElementAttribute {
+ return self; // TODO
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects:
+ // NSAccessibilityTitleAttribute,
+ NSAccessibilityEnabledAttribute,
+ NSAccessibilitySelectedChildrenAttribute,
+ nil ]
+ ];
+ [ attributeNames addObject: NSAccessibilityContentsAttribute ];
+ [ attributeNames addObject: NSAccessibilityTitleUIElementAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperlist.h b/vcl/osx/a11ywrapperlist.h
new file mode 100644
index 0000000000..deb6de98de
--- /dev/null
+++ b/vcl/osx/a11ywrapperlist.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperList : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperlist.mm b/vcl/osx/a11ywrapperlist.mm
new file mode 100644
index 0000000000..25b3fa37ac
--- /dev/null
+++ b/vcl/osx/a11ywrapperlist.mm
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrapperlist.h"
+
+using namespace ::com::sun::star::accessibility;
+
+// Wrapper for AXList role
+
+@implementation AquaA11yWrapperList : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames addObject: NSAccessibilityOrientationAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiobutton.h b/vcl/osx/a11ywrapperradiobutton.h
new file mode 100644
index 0000000000..357b9566b0
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiobutton.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRadioButton : AquaA11yWrapper
+{
+}
+- (id)valueAttribute;
+- (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiobutton.mm b/vcl/osx/a11ywrapperradiobutton.mm
new file mode 100644
index 0000000000..5022cc18c2
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiobutton.mm
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrapperradiobutton.h"
+#include "a11ytextwrapper.h"
+#include "a11yvaluewrapper.h"
+
+// Wrapper for AXRadioButton role
+
+@implementation AquaA11yWrapperRadioButton : AquaA11yWrapper
+
+-(id)valueAttribute {
+ if ( [ self accessibleValue ] ) {
+ return [ AquaA11yValueWrapper valueAttributeForElement: self ];
+ }
+ return [ NSNumber numberWithInt: 0 ];
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] ) {
+ return NO;
+ }
+ return [ super accessibilityIsAttributeSettable: attribute ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ [ attributeNames addObject: NSAccessibilityMinValueAttribute ];
+ [ attributeNames addObject: NSAccessibilityMaxValueAttribute ];
+ [ attributeNames addObject: NSAccessibilityValueAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiogroup.h b/vcl/osx/a11ywrapperradiogroup.h
new file mode 100644
index 0000000000..0cbc1cf420
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiogroup.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRadioGroup : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperradiogroup.mm b/vcl/osx/a11ywrapperradiogroup.mm
new file mode 100644
index 0000000000..9768dbbb69
--- /dev/null
+++ b/vcl/osx/a11ywrapperradiogroup.mm
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrapperradiogroup.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXRadioGroup role
+
+@implementation AquaA11yWrapperRadioGroup : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ [ attributeNames removeObject: NSAccessibilityTitleAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperrow.h b/vcl/osx/a11ywrapperrow.h
new file mode 100644
index 0000000000..7933bbef44
--- /dev/null
+++ b/vcl/osx/a11ywrapperrow.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperRow : AquaA11yWrapper
+{
+}
+- (id)disclosingAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperrow.mm b/vcl/osx/a11ywrapperrow.mm
new file mode 100644
index 0000000000..0c140c82c6
--- /dev/null
+++ b/vcl/osx/a11ywrapperrow.mm
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrapperrow.h"
+#include "a11ytextwrapper.h"
+
+// Wrapper for AXRow role
+
+@implementation AquaA11yWrapperRow : AquaA11yWrapper
+
+-(id)disclosingAttribute {
+ // TODO: implement
+ return nil;
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ];
+ [ attributeNames removeObject: NSAccessibilityTitleAttribute ];
+ [ attributeNames removeObject: NSAccessibilityEnabledAttribute ];
+ [ attributeNames removeObject: NSAccessibilityFocusedAttribute ];
+ [ attributeNames addObject: NSAccessibilitySelectedAttribute ];
+ [ attributeNames addObject: NSAccessibilityDisclosingAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollarea.h b/vcl/osx/a11ywrapperscrollarea.h
new file mode 100644
index 0000000000..45af07c1e5
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollarea.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperScrollArea : AquaA11yWrapper
+{
+}
+- (id)verticalScrollBarAttribute;
+- (id)horizontalScrollBarAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollarea.mm b/vcl/osx/a11ywrapperscrollarea.mm
new file mode 100644
index 0000000000..22037220d4
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollarea.mm
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrapperscrollarea.h"
+#include "a11ywrapperscrollbar.h"
+#include "a11yrolehelper.h"
+
+// Wrapper for AXScrollArea role
+
+@implementation AquaA11yWrapperScrollArea : AquaA11yWrapper
+
+-(id)scrollBarWithOrientation:(NSString *)orientation {
+ AquaA11yWrapper * theScrollBar = nil;
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ NSArray * elementChildren = [ self accessibilityAttributeValue: NSAccessibilityChildrenAttribute ];
+ if ( [ elementChildren count ] > 0 ) {
+ NSEnumerator * enumerator = [ elementChildren objectEnumerator ];
+ id child;
+ while ( ( child = [ enumerator nextObject ] ) ) {
+ AquaA11yWrapper * element = static_cast<AquaA11yWrapper *>(child);
+ if ( [ element isKindOfClass: [ AquaA11yWrapperScrollBar class ] ] ) {
+ AquaA11yWrapperScrollBar * scrollBar = static_cast<AquaA11yWrapperScrollBar *>(element);
+ if ( [ [ scrollBar orientationAttribute ] isEqualToString: orientation ] ) {
+ theScrollBar = scrollBar;
+ break;
+ }
+ }
+ }
+ }
+ [ pool release ];
+ return theScrollBar;
+}
+
+-(id)verticalScrollBarAttribute {
+ return [ self scrollBarWithOrientation: NSAccessibilityVerticalOrientationValue ];
+}
+
+-(id)horizontalScrollBarAttribute {
+ return [ self scrollBarWithOrientation: NSAccessibilityHorizontalOrientationValue ];
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObject: NSAccessibilityEnabledAttribute ];
+ [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects:
+ NSAccessibilityContentsAttribute,
+ NSAccessibilityVerticalScrollBarAttribute,
+ NSAccessibilityHorizontalScrollBarAttribute,
+ nil ]
+ ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollbar.h b/vcl/osx/a11ywrapperscrollbar.h
new file mode 100644
index 0000000000..82db782c48
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollbar.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperScrollBar : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperscrollbar.mm b/vcl/osx/a11ywrapperscrollbar.mm
new file mode 100644
index 0000000000..4a4612d3bb
--- /dev/null
+++ b/vcl/osx/a11ywrapperscrollbar.mm
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+
+#include "a11ywrapperscrollbar.h"
+
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+
+using namespace ::com::sun::star::accessibility;
+
+// Wrapper for AXScrollBar role
+
+@implementation AquaA11yWrapperScrollBar : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames addObject: NSAccessibilityOrientationAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappersplitter.h b/vcl/osx/a11ywrappersplitter.h
new file mode 100644
index 0000000000..78ecbb97ba
--- /dev/null
+++ b/vcl/osx/a11ywrappersplitter.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperSplitter : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappersplitter.mm b/vcl/osx/a11ywrappersplitter.mm
new file mode 100644
index 0000000000..39ec496af2
--- /dev/null
+++ b/vcl/osx/a11ywrappersplitter.mm
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappersplitter.h"
+
+using namespace ::com::sun::star::accessibility;
+
+// Wrapper for AXSplitter role
+
+@implementation AquaA11yWrapperSplitter : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames addObject: NSAccessibilityOrientationAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperstatictext.h b/vcl/osx/a11ywrapperstatictext.h
new file mode 100644
index 0000000000..549fd9f9bd
--- /dev/null
+++ b/vcl/osx/a11ywrapperstatictext.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperStaticText : AquaA11yWrapper
+{
+}
+- (id)titleAttribute;
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrapperstatictext.mm b/vcl/osx/a11ywrapperstatictext.mm
new file mode 100644
index 0000000000..114c4179e8
--- /dev/null
+++ b/vcl/osx/a11ywrapperstatictext.mm
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrapperstatictext.h"
+
+// Wrapper for AXStaticText role
+
+@implementation AquaA11yWrapperStaticText : AquaA11yWrapper
+
+-(id)titleAttribute {
+ NSString * title = [ super titleAttribute ];
+ if ( [ title isEqualToString: [ super valueAttribute ] ] ) {
+ return [ NSString string ];
+ }
+ return title;
+}
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObject: NSAccessibilityTitleAttribute ];
+ [ attributeNames removeObject: NSAccessibilitySharedTextUIElementsAttribute ];
+ [ attributeNames removeObject: NSAccessibilitySharedCharacterRangeAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertabgroup.h b/vcl/osx/a11ywrappertabgroup.h
new file mode 100644
index 0000000000..50ac2d1171
--- /dev/null
+++ b/vcl/osx/a11ywrappertabgroup.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperTabGroup : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertabgroup.mm b/vcl/osx/a11ywrappertabgroup.mm
new file mode 100644
index 0000000000..3d32ccc041
--- /dev/null
+++ b/vcl/osx/a11ywrappertabgroup.mm
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappertabgroup.h"
+
+// Wrapper for AXTabGroup role
+
+@implementation AquaA11yWrapperTabGroup : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects:
+ NSAccessibilityContentsAttribute,
+ NSAccessibilityTabsAttribute,
+ nil ]
+ ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertextarea.h b/vcl/osx/a11ywrappertextarea.h
new file mode 100644
index 0000000000..dda30f1b38
--- /dev/null
+++ b/vcl/osx/a11ywrappertextarea.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperTextArea : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertextarea.mm b/vcl/osx/a11ywrappertextarea.mm
new file mode 100644
index 0000000000..354030fb9a
--- /dev/null
+++ b/vcl/osx/a11ywrappertextarea.mm
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappertextarea.h"
+
+// Wrapper for AXTextArea role
+
+@implementation AquaA11yWrapperTextArea : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObject: NSAccessibilityTitleAttribute ];
+ [ attributeNames removeObject: NSAccessibilityEnabledAttribute ];
+ [ attributeNames addObject: NSAccessibilityChildrenAttribute ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertoolbar.h b/vcl/osx/a11ywrappertoolbar.h
new file mode 100644
index 0000000000..9f521e5890
--- /dev/null
+++ b/vcl/osx/a11ywrappertoolbar.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <osx/a11ywrapper.h>
+
+@interface AquaA11yWrapperToolbar : AquaA11yWrapper
+{
+}
+- (NSArray*)accessibilityAttributeNames;
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/a11ywrappertoolbar.mm b/vcl/osx/a11ywrappertoolbar.mm
new file mode 100644
index 0000000000..28b5d01328
--- /dev/null
+++ b/vcl/osx/a11ywrappertoolbar.mm
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <osx/salinst.h>
+#include "a11ywrappertoolbar.h"
+
+// Wrapper for AXToolbar role
+
+@implementation AquaA11yWrapperToolbar : AquaA11yWrapper
+
+-(NSArray *)accessibilityAttributeNames {
+ // Related: tdf#148453 Acquire solar mutex during native accessibility calls
+ SolarMutexGuard aGuard;
+
+ // Default Attributes
+ NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ];
+ // Special Attributes and removing unwanted attributes depending on role
+ [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects:
+ NSAccessibilityTitleAttribute,
+ NSAccessibilityEnabledAttribute,
+ nil ]
+ ];
+ return attributeNames;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/clipboard.cxx b/vcl/osx/clipboard.cxx
new file mode 100644
index 0000000000..f5be6f86c3
--- /dev/null
+++ b/vcl/osx/clipboard.cxx
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "clipboard.hxx"
+
+#include "DataFlavorMapping.hxx"
+#include "OSXTransferable.hxx"
+#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp>
+#include <com/sun/star/lang/IllegalArgumentException.hpp>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/supportsservice.hxx>
+
+using namespace css;
+
+@implementation EventListener;
+
+-(EventListener*)initWithAquaClipboard: (AquaClipboard*) pcb
+{
+ self = [super init];
+
+ if (self)
+ pAquaClipboard = pcb;
+
+ return self;
+}
+
+-(void)pasteboard:(NSPasteboard*)sender provideDataForType:(const NSString*)type
+{
+ if( pAquaClipboard )
+ pAquaClipboard->provideDataForType(sender, type);
+}
+
+-(void)applicationDidBecomeActive:(NSNotification*)aNotification
+{
+ if( pAquaClipboard )
+ pAquaClipboard->applicationDidBecomeActive(aNotification);
+}
+
+-(void)disposing
+{
+ pAquaClipboard = nullptr;
+}
+
+@end
+
+static OUString clipboard_getImplementationName()
+{
+ return "com.sun.star.datatransfer.clipboard.AquaClipboard";
+}
+
+static uno::Sequence<OUString> clipboard_getSupportedServiceNames()
+{
+ return { OUString("com.sun.star.datatransfer.clipboard.SystemClipboard") };
+}
+
+AquaClipboard::AquaClipboard(NSPasteboard* pasteboard, bool bUseSystemPasteboard)
+ : WeakComponentImplHelper<XSystemClipboard, XFlushableClipboard, XServiceInfo>(m_aMutex)
+ , mIsSystemPasteboard(bUseSystemPasteboard)
+{
+ uno::Reference<uno::XComponentContext> xContext = comphelper::getProcessComponentContext();
+
+ mrXMimeCntFactory = datatransfer::MimeContentTypeFactory::create(xContext);
+
+ mpDataFlavorMapper = std::make_shared<DataFlavorMapper>();
+
+ if (pasteboard != nullptr)
+ {
+ mPasteboard = pasteboard;
+ mIsSystemPasteboard = false;
+ }
+ else
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.13 NSDragPboard
+ mPasteboard = bUseSystemPasteboard ? [NSPasteboard generalPasteboard] :
+ [NSPasteboard pasteboardWithName: NSDragPboard];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ if (mPasteboard == nil)
+ {
+ throw uno::RuntimeException("AquaClipboard: Cannot create Cocoa pasteboard",
+ static_cast<XClipboardEx*>(this));
+ }
+ }
+
+ [mPasteboard retain];
+
+ mEventListener = [[EventListener alloc] initWithAquaClipboard: this];
+
+ if (mEventListener == nil)
+ {
+ [mPasteboard release];
+
+ throw uno::RuntimeException(
+ "AquaClipboard: Cannot create pasteboard change listener",
+ static_cast<XClipboardEx*>(this));
+ }
+
+ if (mIsSystemPasteboard)
+ {
+ NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
+
+ [notificationCenter addObserver: mEventListener
+ selector: @selector(applicationDidBecomeActive:)
+ name: @"NSApplicationDidBecomeActiveNotification"
+ object: [NSApplication sharedApplication]];
+ }
+
+ mPasteboardChangeCount = [mPasteboard changeCount];
+}
+
+AquaClipboard::~AquaClipboard()
+{
+ if (mIsSystemPasteboard)
+ {
+ [[NSNotificationCenter defaultCenter] removeObserver: mEventListener];
+ }
+
+ [mEventListener disposing];
+ [mEventListener release];
+ [mPasteboard release];
+}
+
+uno::Reference<datatransfer::XTransferable> SAL_CALL AquaClipboard::getContents()
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ // tdf#144124 Detect if ownership has been lost
+ // The shortcut assumes that lost ownership notifications from the
+ // system clipboard will happen elsewhere. They do under normal
+ // conditions, but do not when some clipboard managers are running.
+ // So, explicitly check ownership to catch such cases.
+ if (mIsSystemPasteboard)
+ applicationDidBecomeActive(nullptr);
+
+ // Shortcut: If we are clipboard owner already we don't need
+ // to drag the data through the system clipboard
+ if (mXClipboardContent.is())
+ {
+ return mXClipboardContent;
+ }
+
+ return uno::Reference<datatransfer::XTransferable>(
+ new OSXTransferable(mrXMimeCntFactory,
+ mpDataFlavorMapper,
+ mPasteboard));
+}
+
+void SAL_CALL AquaClipboard::setContents(
+ uno::Reference<datatransfer::XTransferable> const & xTransferable,
+ uno::Reference<datatransfer::clipboard::XClipboardOwner> const & xClipboardOwner)
+{
+ NSArray* types = xTransferable.is() ?
+ mpDataFlavorMapper->flavorSequenceToTypesArray(xTransferable->getTransferDataFlavors(), true) :
+ [NSArray array];
+
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ uno::Reference<datatransfer::clipboard::XClipboardOwner> oldOwner(mXClipboardOwner);
+ mXClipboardOwner = xClipboardOwner;
+
+ uno::Reference<datatransfer::XTransferable> oldContent(mXClipboardContent);
+ mXClipboardContent = xTransferable;
+
+ mPasteboardChangeCount = [mPasteboard declareTypes: types owner: mEventListener];
+
+ aGuard.clear();
+
+ // if we are already the owner of the clipboard
+ // then fire lost ownership event
+ if (oldOwner.is())
+ {
+ fireLostClipboardOwnershipEvent(oldOwner, oldContent);
+ }
+
+ fireClipboardChangedEvent();
+}
+
+OUString SAL_CALL AquaClipboard::getName()
+{
+ return OUString();
+}
+
+sal_Int8 SAL_CALL AquaClipboard::getRenderingCapabilities()
+{
+ return 0;
+}
+
+void SAL_CALL AquaClipboard::addClipboardListener(uno::Reference<datatransfer::clipboard::XClipboardListener> const & listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!listener.is())
+ throw lang::IllegalArgumentException("empty reference",
+ static_cast<XClipboardEx*>(this), 1);
+
+ mClipboardListeners.push_back(listener);
+}
+
+void SAL_CALL AquaClipboard::removeClipboardListener(uno::Reference<datatransfer::clipboard::XClipboardListener> const & listener)
+{
+ osl::MutexGuard aGuard(m_aMutex);
+
+ if (!listener.is())
+ throw lang::IllegalArgumentException("empty reference",
+ static_cast<XClipboardEx*>(this), 1);
+
+ mClipboardListeners.remove(listener);
+}
+
+void AquaClipboard::applicationDidBecomeActive(NSNotification*)
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ int currentPboardChgCount = [mPasteboard changeCount];
+
+ if (currentPboardChgCount != mPasteboardChangeCount)
+ {
+ mPasteboardChangeCount = currentPboardChgCount;
+
+ // Clear clipboard content and owner and send lostOwnership
+ // notification to the old clipboard owner as well as
+ // ClipboardChanged notification to any clipboard listener
+ uno::Reference<datatransfer::clipboard::XClipboardOwner> oldOwner(mXClipboardOwner);
+ mXClipboardOwner.clear();
+
+ uno::Reference<datatransfer::XTransferable> oldContent(mXClipboardContent);
+ mXClipboardContent.clear();
+
+ aGuard.clear();
+
+ if (oldOwner.is())
+ {
+ fireLostClipboardOwnershipEvent(oldOwner, oldContent);
+ }
+
+ fireClipboardChangedEvent();
+ }
+}
+
+void AquaClipboard::fireClipboardChangedEvent()
+{
+ osl::ClearableMutexGuard aGuard(m_aMutex);
+
+ datatransfer::clipboard::ClipboardEvent aEvent;
+
+ if (!mClipboardListeners.empty())
+ {
+ aEvent = datatransfer::clipboard::ClipboardEvent(getXWeak(), getContents());
+ }
+
+ aGuard.clear();
+
+ for (auto const& rListener : mClipboardListeners)
+ {
+ if (rListener.is())
+ {
+ try
+ {
+ rListener->changedContents(aEvent);
+ }
+ catch (uno::RuntimeException& )
+ {}
+ }
+ }
+}
+
+void AquaClipboard::fireLostClipboardOwnershipEvent(
+ uno::Reference<datatransfer::clipboard::XClipboardOwner> const & rOldOwner,
+ uno::Reference<datatransfer::XTransferable> const & rOldContent)
+{
+ assert(rOldOwner.is());
+
+ try
+ {
+ rOldOwner->lostOwnership(static_cast<XClipboardEx*>(this), rOldContent);
+ }
+ catch(uno::RuntimeException&)
+ {}
+}
+
+void AquaClipboard::provideDataForType(NSPasteboard* sender, const NSString* type)
+{
+ if( mXClipboardContent.is() )
+ {
+ DataProviderPtr_t dp = mpDataFlavorMapper->getDataProvider(type, mXClipboardContent);
+ NSData* pBoardData = nullptr;
+
+ if (dp)
+ {
+ pBoardData = dp->getSystemData();
+ [sender setData: pBoardData forType:const_cast<NSString*>(type)];
+ }
+ }
+}
+
+void SAL_CALL AquaClipboard::flushClipboard()
+{
+ if (mXClipboardContent.is())
+ {
+ uno::Sequence<datatransfer::DataFlavor> flavorList = mXClipboardContent->getTransferDataFlavors();
+ sal_uInt32 nFlavors = flavorList.getLength();
+ bool bInternal(false);
+
+ for (sal_uInt32 i = 0; i < nFlavors; i++)
+ {
+ const NSString* sysType = mpDataFlavorMapper->openOfficeToSystemFlavor(flavorList[i], bInternal);
+
+ if (sysType != nullptr)
+ {
+ provideDataForType(mPasteboard, sysType);
+ }
+ }
+ mXClipboardContent.clear();
+ }
+}
+
+NSPasteboard* AquaClipboard::getPasteboard() const
+{
+ return mPasteboard;
+}
+
+OUString SAL_CALL AquaClipboard::getImplementationName()
+{
+ return clipboard_getImplementationName();
+}
+
+sal_Bool SAL_CALL AquaClipboard::supportsService(OUString const & rServiceName)
+{
+ return cppu::supportsService(this, rServiceName);
+}
+
+uno::Sequence<OUString> SAL_CALL AquaClipboard::getSupportedServiceNames()
+{
+ return clipboard_getSupportedServiceNames();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/clipboard.hxx b/vcl/osx/clipboard.hxx
new file mode 100644
index 0000000000..6213ce6921
--- /dev/null
+++ b/vcl/osx/clipboard.hxx
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "DataFlavorMapping.hxx"
+#include <rtl/ustring.hxx>
+#include <sal/types.h>
+#include <cppuhelper/compbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
+#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
+#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp>
+#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <com/sun/star/lang/XServiceInfo.hpp>
+#include <cppuhelper/basemutex.hxx>
+#include <com/sun/star/lang/XMultiComponentFactory.hpp>
+
+#include <list>
+
+#include <premac.h>
+#import <Cocoa/Cocoa.h>
+#include <postmac.h>
+
+class AquaClipboard;
+
+@interface EventListener : NSObject
+{
+ AquaClipboard* pAquaClipboard;
+}
+
+// Init the pasteboard change listener with a reference to the OfficeClipboard
+// instance
+- (EventListener*)initWithAquaClipboard: (AquaClipboard*) pcb;
+
+// Promise resolver function
+- (void)pasteboard:(NSPasteboard*)sender provideDataForType:(const NSString *)type;
+
+-(void)applicationDidBecomeActive:(NSNotification*)aNotification;
+
+-(void)disposing;
+@end
+
+class AquaClipboard : public ::cppu::BaseMutex,
+ public ::cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard,
+ css::datatransfer::clipboard::XFlushableClipboard,
+ css::lang::XServiceInfo>
+{
+public:
+ /* Create a clipboard instance.
+
+ @param pasteboard
+ If not equal NULL the instance will be instantiated with the provided
+ pasteboard reference and 'bUseSystemClipboard' will be ignored
+
+ @param bUseSystemClipboard
+ If 'pasteboard' is NULL 'bUseSystemClipboard' determines whether the
+ system clipboard will be created (bUseSystemClipboard == true) or if
+ the DragPasteboard if bUseSystemClipboard == false
+ */
+ AquaClipboard(NSPasteboard* pasteboard,
+ bool bUseSystemClipboard);
+
+ virtual ~AquaClipboard() override;
+ AquaClipboard(const AquaClipboard&) = delete;
+ AquaClipboard& operator=(const AquaClipboard&) = delete;
+
+ // XClipboard
+
+ virtual css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override;
+
+ virtual void SAL_CALL setContents(css::uno::Reference<css::datatransfer::XTransferable> const & xTransferable,
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const & xClipboardOwner) override;
+
+ virtual OUString SAL_CALL getName() override;
+
+ // XClipboardEx
+
+ virtual sal_Int8 SAL_CALL getRenderingCapabilities() override;
+
+ // XClipboardNotifier
+
+ virtual void SAL_CALL addClipboardListener(css::uno::Reference<css::datatransfer::clipboard::XClipboardListener> const & listener) override;
+ virtual void SAL_CALL removeClipboardListener(css::uno::Reference<css::datatransfer::clipboard::XClipboardListener> const & listener) override;
+
+ // XFlushableClipboard
+
+ virtual void SAL_CALL flushClipboard() override;
+
+ // XServiceInfo
+
+ virtual OUString SAL_CALL getImplementationName() override;
+ virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override;
+ virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override;
+
+ /* Get a reference to the used pastboard.
+ */
+ NSPasteboard* getPasteboard() const;
+
+ /* Notify the current clipboard owner that he is no longer the clipboard owner.
+ */
+ void fireLostClipboardOwnershipEvent(css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const & oldOwner,
+ css::uno::Reference<css::datatransfer::XTransferable> const & oldContent);
+
+ void pasteboardChangedOwner();
+
+ void provideDataForType(NSPasteboard* sender, const NSString* type);
+
+ void applicationDidBecomeActive(NSNotification* aNotification);
+
+private:
+
+ /* Notify all registered XClipboardListener that the clipboard content
+ has changed.
+ */
+ void fireClipboardChangedEvent();
+
+private:
+ css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory;
+ std::list<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> mClipboardListeners;
+ css::uno::Reference<css::datatransfer::XTransferable> mXClipboardContent;
+ css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> mXClipboardOwner;
+ DataFlavorMapperPtr_t mpDataFlavorMapper;
+ bool mIsSystemPasteboard;
+ NSPasteboard* mPasteboard;
+ int mPasteboardChangeCount;
+ EventListener* mEventListener;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/cuidraw.hxx b/vcl/osx/cuidraw.hxx
new file mode 100644
index 0000000000..de625ce0a8
--- /dev/null
+++ b/vcl/osx/cuidraw.hxx
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/config.h>
+
+#include <premac.h>
+#include <Carbon/Carbon.h>
+#include <postmac.h>
+
+#include <config_features.h>
+
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+
+extern "C" {
+
+typedef CFTypeRef CUIRendererRef;
+
+void CUIDraw(
+ CUIRendererRef r, CGRect rect, CGContextRef ctx, CFDictionaryRef options,
+ CFDictionaryRef * result);
+
+}
+
+#endif
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/documentfocuslistener.cxx b/vcl/osx/documentfocuslistener.cxx
new file mode 100644
index 0000000000..44a3506f86
--- /dev/null
+++ b/vcl/osx/documentfocuslistener.cxx
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "documentfocuslistener.hxx"
+
+#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+#include <com/sun/star/accessibility/AccessibleStateType.hpp>
+#include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
+#include <sal/log.hxx>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::uno;
+
+DocumentFocusListener::DocumentFocusListener(AquaA11yFocusTracker& rTracker) :
+ m_aFocusTracker(rTracker)
+{
+}
+
+void SAL_CALL
+DocumentFocusListener::disposing( const EventObject& aEvent )
+{
+ // Unref the object here, but do not remove as listener since the object
+ // might no longer be in a state that safely allows this.
+ if( aEvent.Source.is() )
+ m_aRefList.erase(aEvent.Source);
+}
+
+void SAL_CALL
+DocumentFocusListener::notifyEvent( const AccessibleEventObject& aEvent )
+{
+ try {
+ switch( aEvent.EventId )
+ {
+ case AccessibleEventId::STATE_CHANGED:
+ {
+ sal_Int64 nState = AccessibleStateType::INVALID;
+ aEvent.NewValue >>= nState;
+
+ if( AccessibleStateType::FOCUSED == nState )
+ m_aFocusTracker.setFocusedObject( getAccessible(aEvent) );
+ }
+ break;
+
+ case AccessibleEventId::CHILD:
+ {
+ Reference< XAccessible > xChild;
+ if( (aEvent.OldValue >>= xChild) && xChild.is() )
+ detachRecursive(xChild);
+
+ if( (aEvent.NewValue >>= xChild) && xChild.is() )
+ attachRecursive(xChild);
+ }
+ break;
+
+ case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
+ {
+ Reference< XAccessible > xAccessible( getAccessible(aEvent) );
+ detachRecursive(xAccessible);
+ attachRecursive(xAccessible);
+ SAL_INFO("vcl", "Invalidate all children called" );
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Focused object has invalid index in parent");
+ }
+}
+
+Reference< XAccessible > DocumentFocusListener::getAccessible(const EventObject& aEvent )
+{
+ Reference< XAccessible > xAccessible(aEvent.Source, UNO_QUERY);
+
+ if( xAccessible.is() )
+ return xAccessible;
+
+ Reference< XAccessibleContext > xContext(aEvent.Source, UNO_QUERY);
+
+ if( xContext.is() )
+ {
+ Reference< XAccessible > xParent( xContext->getAccessibleParent() );
+ if( xParent.is() )
+ {
+ Reference< XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
+ if( xParentContext.is() )
+ {
+ try {
+ return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object has invalid index in parent");
+ }
+ }
+ }
+ }
+
+ return Reference< XAccessible >();
+}
+
+void DocumentFocusListener::attachRecursive(const Reference< XAccessible >& xAccessible)
+{
+ Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ attachRecursive(xAccessible, xContext);
+}
+
+void DocumentFocusListener::attachRecursive(
+ const Reference< XAccessible >& xAccessible,
+ const Reference< XAccessibleContext >& xContext
+)
+{
+ if( xContext.is() )
+ {
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ attachRecursive(xAccessible, xContext, nStateSet);
+ }
+}
+
+void DocumentFocusListener::attachRecursive(
+ const Reference< XAccessible >& xAccessible,
+ const Reference< XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+)
+{
+ if( nStateSet & AccessibleStateType::FOCUSED )
+ m_aFocusTracker.setFocusedObject( xAccessible );
+
+ Reference< XAccessibleEventBroadcaster > xBroadcaster(xContext, UNO_QUERY);
+
+ // If not already done, add the broadcaster to the list and attach as listener.
+ if( xBroadcaster.is() && m_aRefList.insert(xBroadcaster).second )
+ {
+ xBroadcaster->addAccessibleEventListener(static_cast< XAccessibleEventListener *>(this));
+
+ if( ! (nStateSet & AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ try {
+ sal_Int64 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ Reference< XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ attachRecursive(xChild);
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object index does not exist in parent");
+ }
+ }
+ }
+}
+
+void DocumentFocusListener::detachRecursive(const Reference< XAccessible >& xAccessible)
+{
+ Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext();
+
+ if( xContext.is() )
+ detachRecursive(xAccessible, xContext);
+}
+
+void DocumentFocusListener::detachRecursive(
+ const Reference< XAccessible >& xAccessible,
+ const Reference< XAccessibleContext >& xContext
+)
+{
+ sal_Int64 nStateSet = xContext->getAccessibleStateSet();
+
+ detachRecursive(xAccessible, xContext, nStateSet);
+}
+
+void DocumentFocusListener::detachRecursive(
+ const Reference< XAccessible >&,
+ const Reference< XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+)
+{
+ Reference< XAccessibleEventBroadcaster > xBroadcaster(xContext, UNO_QUERY);
+
+ if( xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster) )
+ {
+ xBroadcaster->removeAccessibleEventListener(static_cast< XAccessibleEventListener *>(this));
+
+ if( ! (nStateSet & AccessibleStateType::MANAGES_DESCENDANTS) )
+ {
+ try {
+ sal_Int64 n, nmax = xContext->getAccessibleChildCount();
+ for( n = 0; n < nmax; n++ )
+ {
+ Reference< XAccessible > xChild( xContext->getAccessibleChild( n ) );
+
+ if( xChild.is() )
+ detachRecursive(xChild);
+ }
+ }
+ catch (const IndexOutOfBoundsException&)
+ {
+ SAL_WARN("vcl", "Accessible object index does not exist in parent");
+ }
+ }
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/documentfocuslistener.hxx b/vcl/osx/documentfocuslistener.hxx
new file mode 100644
index 0000000000..00097a7ef9
--- /dev/null
+++ b/vcl/osx/documentfocuslistener.hxx
@@ -0,0 +1,97 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+
+#include <cppuhelper/implbase.hxx>
+
+#include <osx/a11yfocustracker.hxx>
+
+#include <o3tl/sorted_vector.hxx>
+
+
+class DocumentFocusListener :
+ public ::cppu::WeakImplHelper< css::accessibility::XAccessibleEventListener >
+{
+
+public:
+
+ explicit DocumentFocusListener(AquaA11yFocusTracker& rTracker);
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void attachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ void detachRecursive(
+ const css::uno::Reference< css::accessibility::XAccessible >& xAccessible,
+ const css::uno::Reference< css::accessibility::XAccessibleContext >& xContext,
+ sal_Int64 nStateSet
+ );
+
+ /// @throws css::lang::IndexOutOfBoundsException
+ /// @throws css::uno::RuntimeException
+ static css::uno::Reference< css::accessibility::XAccessible > getAccessible(const css::lang::EventObject& aEvent );
+
+ // XEventListener
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ // XAccessibleEventListener
+ virtual void SAL_CALL notifyEvent( const css::accessibility::AccessibleEventObject& aEvent ) override;
+
+private:
+ o3tl::sorted_vector< css::uno::Reference< css::uno::XInterface > > m_aRefList;
+
+ AquaA11yFocusTracker& m_aFocusTracker;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/printaccessoryview.mm b/vcl/osx/printaccessoryview.mm
new file mode 100644
index 0000000000..95ace78d28
--- /dev/null
+++ b/vcl/osx/printaccessoryview.mm
@@ -0,0 +1,1264 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/string_view.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <vcl/print.hxx>
+#include <vcl/image.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/settings.hxx>
+
+#include <osx/printview.h>
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+
+#include <svdata.hxx>
+#include <strings.hrc>
+#include <printaccessoryview.hrc>
+
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+
+#include <map>
+#include <string_view>
+#include <utility>
+
+using namespace vcl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::uno;
+
+namespace {
+
+class ControllerProperties;
+
+}
+
+@interface ControlTarget : NSObject
+{
+ ControllerProperties* mpController;
+}
+-(id)initWithControllerMap: (ControllerProperties*)pController;
+-(void)triggered:(id)pSender;
+-(void)triggeredNumeric:(id)pSender;
+-(void)dealloc;
+@end
+
+@interface AquaPrintPanelAccessoryController : NSViewController< NSPrintPanelAccessorizing >
+{
+ NSPrintOperation *mpPrintOperation;
+ vcl::PrinterController *mpPrinterController;
+ PrintAccessoryViewState *mpViewState;
+}
+
+-(void)forPrintOperation:(NSPrintOperation*)pPrintOp;
+-(void)withPrinterController:(vcl::PrinterController*)pController;
+-(void)withViewState:(PrintAccessoryViewState*)pState;
+
+-(NSPrintOperation*)printOperation;
+-(vcl::PrinterController*)printerController;
+-(PrintAccessoryViewState*)viewState;
+
+-(NSSet*)keyPathsForValuesAffectingPreview;
+-(NSArray*)localizedSummaryItems;
+
+-(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount;
+
+@end
+
+@implementation AquaPrintPanelAccessoryController
+
+-(void)forPrintOperation:(NSPrintOperation*)pPrintOp
+ { mpPrintOperation = pPrintOp; }
+
+-(void)withPrinterController:(vcl::PrinterController*)pController
+ { mpPrinterController = pController; }
+
+-(void)withViewState:(PrintAccessoryViewState*)pState
+ { mpViewState = pState; }
+
+-(NSPrintOperation*)printOperation
+ { return mpPrintOperation; }
+
+-(vcl::PrinterController*)printerController
+ { return mpPrinterController; }
+
+-(PrintAccessoryViewState*)viewState
+ { return mpViewState; }
+
+-(NSSet*)keyPathsForValuesAffectingPreview
+{
+ return [ NSSet setWithObject:@"updatePrintOperation" ];
+}
+
+-(NSArray*)localizedSummaryItems
+{
+ return [ NSArray arrayWithObject:
+ [ NSDictionary dictionary ] ];
+}
+
+-(sal_Int32)updatePrintOperation:(sal_Int32)pLastPageCount
+{
+ // page range may be changed by option choice
+ sal_Int32 nPages = mpPrinterController->getFilteredPageCount();
+
+ mpViewState->bNeedRestart = false;
+ if( nPages != pLastPageCount )
+ {
+ #if OSL_DEBUG_LEVEL > 1
+ SAL_INFO( "vcl.osx.print", "number of pages changed" <<
+ " from " << pLastPageCount << " to " << nPages );
+ #endif
+ mpViewState->bNeedRestart = true;
+ }
+
+ NSTabView* pTabView = [[[self view] subviews] objectAtIndex:0];
+ NSTabViewItem* pItem = [pTabView selectedTabViewItem];
+ if( pItem )
+ mpViewState->nLastPage = [pTabView indexOfTabViewItem: pItem];
+ else
+ mpViewState->nLastPage = 0;
+
+ if( mpViewState->bNeedRestart )
+ {
+ // AppKit does not give a chance of changing the page count
+ // and don't let cancel the dialog either
+ // hack: send a cancel message to the modal window displaying views
+ NSWindow* pNSWindow = [NSApp modalWindow];
+ if( pNSWindow )
+ [pNSWindow cancelOperation: nil];
+ [[mpPrintOperation printInfo] setJobDisposition: NSPrintCancelJob];
+ }
+
+ return nPages;
+}
+
+@end
+
+namespace {
+
+class ControllerProperties
+{
+ std::map< int, OUString > maTagToPropertyName;
+ std::map< int, sal_Int32 > maTagToValueInt;
+ std::map< NSView*, NSView* > maViewPairMap;
+ std::vector< NSObject* > maViews;
+ int mnNextTag;
+ sal_Int32 mnLastPageCount;
+ AquaPrintPanelAccessoryController* mpAccessoryController;
+
+public:
+ ControllerProperties( AquaPrintPanelAccessoryController* i_pAccessoryController )
+ : mnNextTag( 0 )
+ , mnLastPageCount( [i_pAccessoryController printerController]->getFilteredPageCount() )
+ , mpAccessoryController( i_pAccessoryController )
+ {
+ static_assert( SAL_N_ELEMENTS(SV_PRINT_NATIVE_STRINGS) == 5, "resources not found" );
+ }
+
+ static OUString getMoreString()
+ {
+ return VclResId(SV_PRINT_NATIVE_STRINGS[3]);
+ }
+
+ static OUString getPrintSelectionString()
+ {
+ return VclResId(SV_PRINT_NATIVE_STRINGS[4]);
+ }
+
+ int addNameTag( const OUString& i_rPropertyName )
+ {
+ int nNewTag = mnNextTag++;
+ maTagToPropertyName[ nNewTag ] = i_rPropertyName;
+ return nNewTag;
+ }
+
+ int addNameAndValueTag( const OUString& i_rPropertyName, sal_Int32 i_nValue )
+ {
+ int nNewTag = mnNextTag++;
+ maTagToPropertyName[ nNewTag ] = i_rPropertyName;
+ maTagToValueInt[ nNewTag ] = i_nValue;
+ return nNewTag;
+ }
+
+ void addObservedControl( NSObject* i_pView )
+ {
+ maViews.push_back( i_pView );
+ }
+
+ void addViewPair( NSView* i_pLeft, NSView* i_pRight )
+ {
+ maViewPairMap[ i_pLeft ] = i_pRight;
+ maViewPairMap[ i_pRight ] = i_pLeft;
+ }
+
+ NSView* getPair( NSView* i_pLeft ) const
+ {
+ NSView* pRight = nil;
+ std::map< NSView*, NSView* >::const_iterator it = maViewPairMap.find( i_pLeft );
+ if( it != maViewPairMap.end() )
+ pRight = it->second;
+ return pRight;
+ }
+
+ void changePropertyWithIntValue( int i_nTag )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ std::map< int, sal_Int32 >::const_iterator value_it = maTagToValueInt.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() && value_it != maTagToValueInt.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= value_it->second;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithIntValue( int i_nTag, sal_Int64 i_nValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= i_nValue;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithBoolValue( int i_nTag, bool i_bValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ // ugly
+ if( name_it->second == "PrintContent" )
+ pVal->Value <<= i_bValue ? sal_Int32(2) : sal_Int32(0);
+ else
+ pVal->Value <<= i_bValue;
+
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void changePropertyWithStringValue( int i_nTag, const OUString& i_rValue )
+ {
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag );
+ if( name_it != maTagToPropertyName.end() )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ PropertyValue* pVal = mpController->getValue( name_it->second );
+ if( pVal )
+ {
+ pVal->Value <<= i_rValue;
+ mnLastPageCount = [mpAccessoryController updatePrintOperation: mnLastPageCount];
+ }
+ }
+ }
+
+ void updateEnableState()
+ {
+ for( std::vector< NSObject* >::iterator it = maViews.begin(); it != maViews.end(); ++it )
+ {
+ NSObject* pObj = *it;
+ NSControl* pCtrl = nil;
+ NSCell* pCell = nil;
+ if( [pObj isKindOfClass: [NSControl class]] )
+ pCtrl = static_cast<NSControl*>(pObj);
+ else if( [pObj isKindOfClass: [NSCell class]] )
+ pCell = static_cast<NSCell*>(pObj);
+
+ int nTag = pCtrl ? [pCtrl tag] :
+ pCell ? [pCell tag] :
+ -1;
+
+ std::map< int, OUString >::const_iterator name_it = maTagToPropertyName.find( nTag );
+ if( name_it != maTagToPropertyName.end() && name_it->second != "PrintContent" )
+ {
+ vcl::PrinterController * mpController = [mpAccessoryController printerController];
+ bool bEnabled = mpController->isUIOptionEnabled( name_it->second ) ? YES : NO;
+ if( pCtrl )
+ {
+ [pCtrl setEnabled: bEnabled];
+ NSView* pOther = getPair( pCtrl );
+ if( pOther && [pOther isKindOfClass: [NSControl class]] )
+ [static_cast<NSControl*>(pOther) setEnabled: bEnabled];
+ }
+ else if( pCell )
+ [pCell setEnabled: bEnabled];
+ }
+ }
+ }
+
+};
+
+}
+
+static OUString filterAccelerator( OUString const & rText )
+{
+ OUStringBuffer aBuf( rText.getLength() );
+ for( sal_Int32 nIndex = 0; nIndex != -1; )
+ aBuf.append( o3tl::getToken( rText, 0, '~', nIndex ) );
+ return aBuf.makeStringAndClear();
+}
+
+@implementation ControlTarget
+
+-(id)initWithControllerMap: (ControllerProperties*)pController
+{
+ if( (self = [super init]) )
+ {
+ mpController = pController;
+ }
+ return self;
+}
+
+-(void)triggered:(id)pSender
+{
+ if( [pSender isMemberOfClass: [NSPopUpButton class]] )
+ {
+ NSPopUpButton* pBtn = static_cast<NSPopUpButton*>(pSender);
+ NSMenuItem* pSelected = [pBtn selectedItem];
+ if( pSelected )
+ {
+ int nTag = [pSelected tag];
+ mpController->changePropertyWithIntValue( nTag );
+ }
+ }
+ else if( [pSender isMemberOfClass: [NSButton class]] )
+ {
+ NSButton* pBtn = static_cast<NSButton*>(pSender);
+ int nTag = [pBtn tag];
+ mpController->changePropertyWithBoolValue( nTag, [pBtn state] == NSControlStateValueOn );
+ }
+ else if( [pSender isMemberOfClass: [NSMatrix class]] )
+ {
+ NSObject* pObj = [static_cast<NSMatrix*>(pSender) selectedCell];
+ if( [pObj isMemberOfClass: [NSButtonCell class]] )
+ {
+ NSButtonCell* pCell = static_cast<NSButtonCell*>(pObj);
+ int nTag = [pCell tag];
+ mpController->changePropertyWithIntValue( nTag );
+ }
+ }
+ else if( [pSender isMemberOfClass: [NSTextField class]] )
+ {
+ NSTextField* pField = static_cast<NSTextField*>(pSender);
+ int nTag = [pField tag];
+ OUString aValue = GetOUString( [pSender stringValue] );
+ mpController->changePropertyWithStringValue( nTag, aValue );
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported class" <<
+ ( [pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil" ) );
+ }
+ mpController->updateEnableState();
+}
+
+-(void)triggeredNumeric:(id)pSender
+{
+ if( [pSender isMemberOfClass: [NSTextField class]] )
+ {
+ NSTextField* pField = static_cast<NSTextField*>(pSender);
+ int nTag = [pField tag];
+ sal_Int64 nValue = [pField intValue];
+
+ NSView* pOther = mpController->getPair( pField );
+ if( pOther )
+ [static_cast<NSControl*>(pOther) setIntValue: nValue];
+
+ mpController->changePropertyWithIntValue( nTag, nValue );
+ }
+ else if( [pSender isMemberOfClass: [NSStepper class]] )
+ {
+ NSStepper* pStep = static_cast<NSStepper*>(pSender);
+ int nTag = [pStep tag];
+ sal_Int64 nValue = [pStep intValue];
+
+ NSView* pOther = mpController->getPair( pStep );
+ if( pOther )
+ [static_cast<NSControl*>(pOther) setIntValue: nValue];
+
+ mpController->changePropertyWithIntValue( nTag, nValue );
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported class" <<
+ ([pSender class] ? [NSStringFromClass([pSender class]) UTF8String] : "nil") );
+ }
+ mpController->updateEnableState();
+}
+
+-(void)dealloc
+{
+ delete mpController;
+ [super dealloc];
+}
+
+@end
+
+namespace {
+
+struct ColumnItem
+{
+ NSControl* pControl;
+ CGFloat nOffset;
+ NSControl* pSubControl;
+
+ ColumnItem( NSControl* i_pControl = nil, CGFloat i_nOffset = 0, NSControl* i_pSub = nil )
+ : pControl( i_pControl )
+ , nOffset( i_nOffset )
+ , pSubControl( i_pSub )
+ {}
+
+ CGFloat getWidth() const
+ {
+ CGFloat nWidth = 0;
+ if( pControl )
+ {
+ NSRect aCtrlRect = [pControl frame];
+ nWidth = aCtrlRect.size.width;
+ nWidth += nOffset;
+ if( pSubControl )
+ {
+ NSRect aSubRect = [pSubControl frame];
+ nWidth += aSubRect.size.width;
+ nWidth += aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width);
+ }
+ }
+ return nWidth;
+ }
+};
+
+}
+
+static void adjustViewAndChildren( NSView* pNSView, NSSize& rMaxSize,
+ std::vector< ColumnItem >& rLeftColumn,
+ std::vector< ColumnItem >& rRightColumn
+ )
+{
+ // balance columns
+
+ // first get overall column widths
+ CGFloat nLeftWidth = 0;
+ CGFloat nRightWidth = 0;
+ for( size_t i = 0; i < rLeftColumn.size(); i++ )
+ {
+ CGFloat nW = rLeftColumn[i].getWidth();
+ if( nW > nLeftWidth )
+ nLeftWidth = nW;
+ }
+ for( size_t i = 0; i < rRightColumn.size(); i++ )
+ {
+ CGFloat nW = rRightColumn[i].getWidth();
+ if( nW > nRightWidth )
+ nRightWidth = nW;
+ }
+
+ // right align left column
+ for( size_t i = 0; i < rLeftColumn.size(); i++ )
+ {
+ if( rLeftColumn[i].pControl )
+ {
+ NSRect aCtrlRect = [rLeftColumn[i].pControl frame];
+ CGFloat nX = nLeftWidth - aCtrlRect.size.width;
+ if( rLeftColumn[i].pSubControl )
+ {
+ NSRect aSubRect = [rLeftColumn[i].pSubControl frame];
+ nX -= aSubRect.size.width + (aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width));
+ aSubRect.origin.x = nLeftWidth - aSubRect.size.width;
+ [rLeftColumn[i].pSubControl setFrame: aSubRect];
+ }
+ aCtrlRect.origin.x = nX;
+ [rLeftColumn[i].pControl setFrame: aCtrlRect];
+ }
+ }
+
+ // left align right column
+ for( size_t i = 0; i < rRightColumn.size(); i++ )
+ {
+ if( rRightColumn[i].pControl )
+ {
+ NSRect aCtrlRect = [rRightColumn[i].pControl frame];
+ CGFloat nX = nLeftWidth + 3;
+ if( rRightColumn[i].pSubControl )
+ {
+ NSRect aSubRect = [rRightColumn[i].pSubControl frame];
+ aSubRect.origin.x = nX + aSubRect.origin.x - aCtrlRect.origin.x;
+ [rRightColumn[i].pSubControl setFrame: aSubRect];
+ }
+ aCtrlRect.origin.x = nX;
+ [rRightColumn[i].pControl setFrame: aCtrlRect];
+ }
+ }
+
+ NSArray* pSubViews = [pNSView subviews];
+ unsigned int nViews = [pSubViews count];
+ NSRect aUnion = NSZeroRect;
+
+ // get the combined frame of all subviews
+ for( unsigned int n = 0; n < nViews; n++ )
+ {
+ aUnion = NSUnionRect( aUnion, [[pSubViews objectAtIndex: n] frame] );
+ }
+
+ // move everything so it will fit
+ for( unsigned int n = 0; n < nViews; n++ )
+ {
+ NSView* pCurSubView = [pSubViews objectAtIndex: n];
+ NSRect aFrame = [pCurSubView frame];
+ aFrame.origin.x -= aUnion.origin.x - 5;
+ aFrame.origin.y -= aUnion.origin.y - 5;
+ [pCurSubView setFrame: aFrame];
+ }
+
+ // resize the view itself
+ aUnion.size.height += 10;
+ aUnion.size.width += 20;
+ [pNSView setFrameSize: aUnion.size];
+
+ if( aUnion.size.width > rMaxSize.width )
+ rMaxSize.width = aUnion.size.width;
+ if( aUnion.size.height > rMaxSize.height )
+ rMaxSize.height = aUnion.size.height;
+}
+
+static void adjustTabViews( NSTabView* pTabView, NSSize aTabSize )
+{
+ // loop over all contained tab pages
+ NSArray* pTabbedViews = [pTabView tabViewItems];
+ int nViews = [pTabbedViews count];
+ for( int i = 0; i < nViews; i++ )
+ {
+ NSTabViewItem* pItem = static_cast<NSTabViewItem*>([pTabbedViews objectAtIndex: i]);
+ NSView* pNSView = [pItem view];
+ if( pNSView )
+ {
+ NSRect aRect = [pNSView frame];
+ double nDiff = aTabSize.height - aRect.size.height;
+ aRect.size = aTabSize;
+ [pNSView setFrame: aRect];
+
+ NSArray* pSubViews = [pNSView subviews];
+ unsigned int nSubViews = [pSubViews count];
+
+ // move everything up
+ for( unsigned int n = 0; n < nSubViews; n++ )
+ {
+ NSView* pCurSubView = [pSubViews objectAtIndex: n];
+ NSRect aFrame = [pCurSubView frame];
+ aFrame.origin.y += nDiff;
+ // give separators the correct width
+ // separators are currently the only NSBoxes we use
+ if( [pCurSubView isMemberOfClass: [NSBox class]] )
+ {
+ aFrame.size.width = aTabSize.width - aFrame.origin.x - 10;
+ }
+ [pCurSubView setFrame: aFrame];
+ }
+ }
+ }
+}
+
+static NSControl* createLabel( const OUString& i_rText )
+{
+ NSString* pText = CreateNSString( i_rText );
+ NSRect aTextRect = { NSZeroPoint, {20, 15} };
+ NSTextField* pTextView = [[NSTextField alloc] initWithFrame: aTextRect];
+ [pTextView setFont: [NSFont controlContentFontOfSize: 0]];
+ [pTextView setEditable: NO];
+ [pTextView setSelectable: NO];
+ [pTextView setDrawsBackground: NO];
+ [pTextView setBordered: NO];
+ [pTextView setStringValue: pText];
+ [pTextView sizeToFit];
+ [pText release];
+ return pTextView;
+}
+
+static sal_Int32 findBreak( const OUString& i_rText, sal_Int32 i_nPos )
+{
+ sal_Int32 nRet = i_rText.getLength();
+ Reference< i18n::XBreakIterator > xBI( vcl::unohelper::CreateBreakIterator() );
+ if( xBI.is() )
+ {
+ i18n::Boundary aBoundary =
+ xBI->getWordBoundary( i_rText, i_nPos,
+ Application::GetSettings().GetLanguageTag().getLocale(),
+ i18n::WordType::ANYWORD_IGNOREWHITESPACES,
+ true );
+ nRet = aBoundary.endPos;
+ }
+ return nRet;
+}
+
+static void linebreakCell( NSCell* pBtn, const OUString& i_rText )
+{
+ NSString* pText = CreateNSString( i_rText );
+ [pBtn setTitle: pText];
+ [pText release];
+ NSSize aSize = [pBtn cellSize];
+ if( aSize.width > 280 )
+ {
+ // need two lines
+ sal_Int32 nLen = i_rText.getLength();
+ sal_Int32 nIndex = nLen / 2;
+ nIndex = findBreak( i_rText, nIndex );
+ if( nIndex < nLen )
+ {
+ OUStringBuffer aBuf( i_rText );
+ aBuf[nIndex] = '\n';
+ pText = CreateNSString( aBuf.makeStringAndClear() );
+ [pBtn setTitle: pText];
+ [pText release];
+ }
+ }
+}
+
+static void addSubgroup( NSView* pCurParent, CGFloat& rCurY, const OUString& rText )
+{
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+ NSRect aTextRect = [pTextView frame];
+ // move to nCurY
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ NSRect aSepRect = { { aTextRect.size.width + 1, aTextRect.origin.y }, { 100, 6 } };
+ NSBox* pBox = [[NSBox alloc] initWithFrame: aSepRect];
+ [pBox setBoxType: NSBoxSeparator];
+ [pCurParent addSubview: [pBox autorelease]];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+}
+
+static void addBool( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ const OUString& rText, bool bEnabled,
+ const OUString& rProperty, bool bValue,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ NSRect aCheckRect = { { rCurX + nAttachOffset, 0 }, { 0, 15 } };
+ NSButton* pBtn = [[NSButton alloc] initWithFrame: aCheckRect];
+ [pBtn setButtonType: NSButtonTypeSwitch];
+ [pBtn setState: bValue ? NSControlStateValueOn : NSControlStateValueOff];
+ if( ! bEnabled )
+ [pBtn setEnabled: NO];
+ linebreakCell( [pBtn cell], rText );
+ [pBtn sizeToFit];
+
+ rRightColumn.push_back( ColumnItem( pBtn ) );
+
+ // connect target
+ [pBtn setTarget: pCtrlTarget];
+ [pBtn setAction: @selector(triggered:)];
+ int nTag = pControllerProperties->addNameTag( rProperty );
+ pControllerProperties->addObservedControl( pBtn );
+ [pBtn setTag: nTag];
+
+ aCheckRect = [pBtn frame];
+ // #i115837# add a murphy factor; it can apparently occasionally happen
+ // that sizeToFit does not a perfect job and that the button linebreaks again
+ // if - and only if - there is already a '\n' contained in the text and the width
+ // is minimally of
+ aCheckRect.size.width += 1;
+
+ // move to rCurY
+ aCheckRect.origin.y = rCurY - aCheckRect.size.height;
+ [pBtn setFrame: aCheckRect];
+
+ [pCurParent addSubview: [pBtn autorelease]];
+
+ // update rCurY
+ rCurY = aCheckRect.origin.y - 5;
+}
+
+static void addRadio( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ const OUString& rText,
+ const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ CGFloat nOff = 0;
+ if( rText.getLength() )
+ {
+ // add a label
+ NSControl* pTextView = createLabel( rText );
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX + nAttachOffset;
+ [pCurParent addSubview: [pTextView autorelease]];
+
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+
+ // move to nCurY
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+
+ // indent the radio group relative to the text
+ // nOff = 20;
+ }
+
+ // setup radio matrix
+ NSButtonCell* pProto = [[NSButtonCell alloc] init];
+
+ NSRect aRadioRect = { { rCurX + nOff, 0 },
+ { 280 - rCurX,
+ static_cast<CGFloat>(5*rChoices.getLength()) } };
+ [pProto setTitle: @"RadioButtonGroup"];
+ [pProto setButtonType: NSButtonTypeRadio];
+ NSMatrix* pMatrix = [[NSMatrix alloc] initWithFrame: aRadioRect
+ mode: NSRadioModeMatrix
+ prototype: static_cast<NSCell*>(pProto)
+ numberOfRows: rChoices.getLength()
+ numberOfColumns: 1];
+ // set individual titles
+ NSArray* pCells = [pMatrix cells];
+ for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
+ {
+ NSCell* pCell = [pCells objectAtIndex: m];
+ linebreakCell( pCell, filterAccelerator( rChoices[m] ) );
+ // connect target and action
+ [pCell setTarget: pCtrlTarget];
+ [pCell setAction: @selector(triggered:)];
+ int nTag = pControllerProperties->addNameAndValueTag( rProperty, m );
+ pControllerProperties->addObservedControl( pCell );
+ [pCell setTag: nTag];
+ // set current selection
+ if( nSelectValue == m )
+ [pMatrix selectCellAtRow: m column: 0];
+ }
+ [pMatrix sizeToFit];
+ aRadioRect = [pMatrix frame];
+
+ // move it down, so it comes to the correct position
+ aRadioRect.origin.y = rCurY - aRadioRect.size.height;
+ [pMatrix setFrame: aRadioRect];
+ [pCurParent addSubview: [pMatrix autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pMatrix ) );
+
+ // update nCurY
+ rCurY = aRadioRect.origin.y - 5;
+
+ [pProto release];
+}
+
+static void addList( NSView* pCurParent, CGFloat& rCurX, CGFloat& rCurY, CGFloat /*nAttachOffset*/,
+ const OUString& rText,
+ const OUString& rProperty, Sequence<OUString> const & rChoices, sal_Int32 nSelectValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ // don't indent attached lists, looks bad in the existing cases
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX /* + nAttachOffset*/;
+
+ // don't indent attached lists, looks bad in the existing cases
+ NSRect aBtnRect = { { rCurX /*+ nAttachOffset*/ + aTextRect.size.width, 0 }, { 0, 15 } };
+ NSPopUpButton* pBtn = [[NSPopUpButton alloc] initWithFrame: aBtnRect pullsDown: NO];
+
+ // iterate options
+ for( sal_Int32 m = 0; m < rChoices.getLength(); m++ )
+ {
+ NSString* pItemText = CreateNSString( rChoices[m] );
+ [pBtn addItemWithTitle: pItemText];
+ NSMenuItem* pItem = [pBtn itemWithTitle: pItemText];
+ int nTag = pControllerProperties->addNameAndValueTag( rProperty, m );
+ [pItem setTag: nTag];
+ [pItemText release];
+ }
+
+ [pBtn selectItemAtIndex: nSelectValue];
+
+ // add the button to observed controls for enabled state changes
+ // also add a tag just for this purpose
+ pControllerProperties->addObservedControl( pBtn );
+ [pBtn setTag: pControllerProperties->addNameTag( rProperty )];
+
+ [pBtn sizeToFit];
+ [pCurParent addSubview: [pBtn autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pBtn ) );
+
+ // connect target and action
+ [pBtn setTarget: pCtrlTarget];
+ [pBtn setAction: @selector(triggered:)];
+
+ // move to nCurY
+ aBtnRect = [pBtn frame];
+ aBtnRect.origin.y = rCurY - aBtnRect.size.height;
+ [pBtn setFrame: aBtnRect];
+
+ // align label
+ aTextRect.origin.y = aBtnRect.origin.y + (aBtnRect.size.height - aTextRect.size.height)/2;
+ [pTextView setFrame: aTextRect];
+
+ // update rCurY
+ rCurY = aBtnRect.origin.y - 5;
+}
+
+static void addEdit( NSView* pCurParent, CGFloat rCurX, CGFloat& rCurY, CGFloat nAttachOffset,
+ std::u16string_view rCtrlType,
+ const OUString& rText,
+ const OUString& rProperty, const PropertyValue* pValue,
+ sal_Int64 nMinValue, sal_Int64 nMaxValue,
+ std::vector<ColumnItem >& rLeftColumn,
+ std::vector<ColumnItem >& rRightColumn,
+ ControllerProperties* pControllerProperties,
+ ControlTarget* pCtrlTarget
+ )
+{
+ CGFloat nOff = 0;
+ if( rText.getLength() )
+ {
+ // add a label
+ NSControl* pTextView = createLabel( rText );
+ [pCurParent addSubview: [pTextView autorelease]];
+
+ rLeftColumn.push_back( ColumnItem( pTextView ) );
+
+ // move to nCurY
+ NSRect aTextRect = [pTextView frame];
+ aTextRect.origin.x = rCurX + nAttachOffset;
+ aTextRect.origin.y = rCurY - aTextRect.size.height;
+ [pTextView setFrame: aTextRect];
+
+ // update nCurY
+ rCurY = aTextRect.origin.y - 5;
+
+ // and set the offset for the real edit field
+ nOff = aTextRect.size.width + 5;
+ }
+
+ NSRect aFieldRect = { { rCurX + nOff + nAttachOffset, 0 }, { 100, 25 } };
+ NSTextField* pFieldView = [[NSTextField alloc] initWithFrame: aFieldRect];
+ [pFieldView setEditable: YES];
+ [pFieldView setSelectable: YES];
+ [pFieldView setDrawsBackground: YES];
+ [pFieldView sizeToFit]; // FIXME: this does nothing
+ [pCurParent addSubview: [pFieldView autorelease]];
+
+ rRightColumn.push_back( ColumnItem( pFieldView ) );
+
+ // add the field to observed controls for enabled state changes
+ // also add a tag just for this purpose
+ pControllerProperties->addObservedControl( pFieldView );
+ int nTag = pControllerProperties->addNameTag( rProperty );
+ [pFieldView setTag: nTag];
+ // pControllerProperties->addNamedView( pFieldView, aPropertyName );
+
+ // move to nCurY
+ aFieldRect.origin.y = rCurY - aFieldRect.size.height;
+ [pFieldView setFrame: aFieldRect];
+
+ if( rCtrlType == u"Range" )
+ {
+ // add a stepper control
+ NSRect aStepFrame = { { aFieldRect.origin.x + aFieldRect.size.width + 5,
+ aFieldRect.origin.y },
+ { 15, aFieldRect.size.height } };
+ NSStepper* pStep = [[NSStepper alloc] initWithFrame: aStepFrame];
+ [pStep setIncrement: 1];
+ [pStep setValueWraps: NO];
+ [pStep setTag: nTag];
+ [pCurParent addSubview: [pStep autorelease]];
+
+ rRightColumn.back().pSubControl = pStep;
+
+ pControllerProperties->addObservedControl( pStep );
+ [pStep setTarget: pCtrlTarget];
+ [pStep setAction: @selector(triggered:)];
+
+ // constrain the text field to decimal numbers
+ NSNumberFormatter* pFormatter = [[NSNumberFormatter alloc] init];
+ [pFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4];
+ [pFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
+ [pFormatter setAllowsFloats: NO];
+ [pFormatter setMaximumFractionDigits: 0];
+ if( nMinValue != nMaxValue )
+ {
+ [pFormatter setMinimum: [[NSNumber numberWithInt: nMinValue] autorelease]];
+ [pStep setMinValue: nMinValue];
+ [pFormatter setMaximum: [[NSNumber numberWithInt: nMaxValue] autorelease]];
+ [pStep setMaxValue: nMaxValue];
+ }
+ [pFieldView setFormatter: pFormatter];
+
+ sal_Int64 nSelectVal = 0;
+ if( pValue && pValue->Value.hasValue() )
+ pValue->Value >>= nSelectVal;
+
+ [pFieldView setIntValue: nSelectVal];
+ [pStep setIntValue: nSelectVal];
+
+ pControllerProperties->addViewPair( pFieldView, pStep );
+ // connect target and action
+ [pFieldView setTarget: pCtrlTarget];
+ [pFieldView setAction: @selector(triggeredNumeric:)];
+ [pStep setTarget: pCtrlTarget];
+ [pStep setAction: @selector(triggeredNumeric:)];
+ }
+ else
+ {
+ // connect target and action
+ [pFieldView setTarget: pCtrlTarget];
+ [pFieldView setAction: @selector(triggered:)];
+
+ if( pValue && pValue->Value.hasValue() )
+ {
+ OUString aValue;
+ pValue->Value >>= aValue;
+ if( aValue.getLength() )
+ {
+ NSString* pText = CreateNSString( aValue );
+ [pFieldView setStringValue: pText];
+ [pText release];
+ }
+ }
+ }
+
+ // update nCurY
+ rCurY = aFieldRect.origin.y - 5;
+}
+
+@implementation AquaPrintAccessoryView
+
++(NSObject*)setupPrinterPanel: (NSPrintOperation*)pOp
+ withController: (vcl::PrinterController*)pController
+ withState: (PrintAccessoryViewState*)pState
+{
+ const Sequence< PropertyValue >& rOptions( pController->getUIOptions() );
+ if( rOptions.getLength() == 0 )
+ return nil;
+
+ NSRect aViewFrame = { NSZeroPoint, { 600, 400 } };
+ NSRect aTabViewFrame = aViewFrame;
+
+ NSView* pAccessoryView = [[NSView alloc] initWithFrame: aViewFrame];
+ NSTabView* pTabView = [[NSTabView alloc] initWithFrame: aTabViewFrame];
+ [pAccessoryView addSubview: [pTabView autorelease]];
+
+ // create the accessory controller
+ AquaPrintPanelAccessoryController* pAccessoryController =
+ [[AquaPrintPanelAccessoryController alloc] initWithNibName: nil bundle: nil];
+ [pAccessoryController setView: [pAccessoryView autorelease]];
+ [pAccessoryController forPrintOperation: pOp];
+ [pAccessoryController withPrinterController: pController];
+ [pAccessoryController withViewState: pState];
+
+ NSView* pCurParent = nullptr;
+ CGFloat nCurY = 0;
+ CGFloat nCurX = 0;
+ NSSize aMaxTabSize = NSZeroSize;
+
+ ControllerProperties* pControllerProperties = new ControllerProperties( pAccessoryController );
+ ControlTarget* pCtrlTarget = [[ControlTarget alloc] initWithControllerMap: pControllerProperties];
+
+ std::vector< ColumnItem > aLeftColumn, aRightColumn;
+
+ // ugly:
+ // prepend a "selection" checkbox if the properties have such a selection in PrintContent
+ bool bAddSelectionCheckBox = false, bSelectionBoxEnabled = false, bSelectionBoxChecked = false;
+
+ for( const PropertyValue & prop : rOptions )
+ {
+ Sequence< beans::PropertyValue > aOptProp;
+ prop.Value >>= aOptProp;
+
+ OUString aCtrlType;
+ OUString aPropertyName;
+ Sequence< OUString > aChoices;
+ Sequence< sal_Bool > aChoicesDisabled;
+ sal_Int32 aSelectionChecked = 0;
+ for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if( rEntry.Name == "ControlType" )
+ {
+ rEntry.Value >>= aCtrlType;
+ }
+ else if( rEntry.Name == "Choices" )
+ {
+ rEntry.Value >>= aChoices;
+ }
+ else if( rEntry.Name == "ChoicesDisabled" )
+ {
+ rEntry.Value >>= aChoicesDisabled;
+ }
+ else if( rEntry.Name == "Property" )
+ {
+ PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ if( aPropertyName == "PrintContent" )
+ aVal.Value >>= aSelectionChecked;
+ }
+ }
+ if( aCtrlType == "Radio" &&
+ aPropertyName == "PrintContent" &&
+ aChoices.getLength() > 2 )
+ {
+ bAddSelectionCheckBox = true;
+ bSelectionBoxEnabled = aChoicesDisabled.getLength() < 2 || ! aChoicesDisabled[2];
+ bSelectionBoxChecked = (aSelectionChecked==2);
+ break;
+ }
+ }
+
+ for( const PropertyValue & prop : rOptions )
+ {
+ Sequence< beans::PropertyValue > aOptProp;
+ prop.Value >>= aOptProp;
+
+ // extract ui element
+ OUString aCtrlType;
+ OUString aText;
+ OUString aPropertyName;
+ OUString aGroupHint;
+ Sequence< OUString > aChoices;
+ sal_Int64 nMinValue = 0, nMaxValue = 0;
+ CGFloat nAttachOffset = 0;
+ bool bIgnore = false;
+
+ for( const beans::PropertyValue& rEntry : std::as_const(aOptProp) )
+ {
+ if( rEntry.Name == "Text" )
+ {
+ rEntry.Value >>= aText;
+ aText = filterAccelerator( aText );
+ }
+ else if( rEntry.Name == "ControlType" )
+ {
+ rEntry.Value >>= aCtrlType;
+ }
+ else if( rEntry.Name == "Choices" )
+ {
+ rEntry.Value >>= aChoices;
+ }
+ else if( rEntry.Name == "Property" )
+ {
+ PropertyValue aVal;
+ rEntry.Value >>= aVal;
+ aPropertyName = aVal.Name;
+ }
+ else if( rEntry.Name == "MinValue" )
+ {
+ rEntry.Value >>= nMinValue;
+ }
+ else if( rEntry.Name == "MaxValue" )
+ {
+ rEntry.Value >>= nMaxValue;
+ }
+ else if( rEntry.Name == "AttachToDependency" )
+ {
+ nAttachOffset = 20;
+ }
+ else if( rEntry.Name == "InternalUIOnly" )
+ {
+ bool bValue = false;
+ rEntry.Value >>= bValue;
+ bIgnore = bValue;
+ }
+ else if( rEntry.Name == "GroupingHint" )
+ {
+ rEntry.Value >>= aGroupHint;
+ }
+ }
+
+ if( aCtrlType == "Group" ||
+ aCtrlType == "Subgroup" ||
+ aCtrlType == "Radio" ||
+ aCtrlType == "List" ||
+ aCtrlType == "Edit" ||
+ aCtrlType == "Range" ||
+ aCtrlType == "Bool" )
+ {
+ bool bIgnoreSubgroup = false;
+
+ // with `setAccessoryView' method only one accessory view can be set
+ // so create this single accessory view as tabbed for grouping
+ if( aCtrlType == "Group"
+ || ! pCurParent
+ || ( aCtrlType == "Subgroup" && nCurY < -250 && ! bIgnore )
+ )
+ {
+ OUString aGroupTitle( aText );
+ if( aCtrlType == "Subgroup" )
+ aGroupTitle = ControllerProperties::getMoreString();
+
+ // set size of current parent
+ if( pCurParent )
+ adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
+
+ // new tab item
+ if( ! aText.getLength() )
+ aText = "OOo";
+ NSString* pLabel = CreateNSString( aGroupTitle );
+ NSTabViewItem* pItem = [[NSTabViewItem alloc] initWithIdentifier: pLabel ];
+ [pItem setLabel: pLabel];
+ [pTabView addTabViewItem: pItem];
+ pCurParent = [[NSView alloc] initWithFrame: aTabViewFrame];
+ [pItem setView: pCurParent];
+ [pLabel release];
+
+ nCurX = 20; // reset indent
+ nCurY = 0; // reset Y
+ // clear columns
+ aLeftColumn.clear();
+ aRightColumn.clear();
+
+ if( bAddSelectionCheckBox )
+ {
+ addBool( pCurParent, nCurX, nCurY, 0,
+ ControllerProperties::getPrintSelectionString(), bSelectionBoxEnabled,
+ "PrintContent", bSelectionBoxChecked,
+ aRightColumn, pControllerProperties, pCtrlTarget );
+ bAddSelectionCheckBox = false;
+ }
+ }
+
+ if( aCtrlType == "Subgroup" && pCurParent )
+ {
+ bIgnoreSubgroup = bIgnore;
+ if( bIgnore )
+ continue;
+
+ addSubgroup( pCurParent, nCurY, aText );
+ }
+ else if( bIgnoreSubgroup || bIgnore )
+ {
+ continue;
+ }
+ else if( aCtrlType == "Bool" && pCurParent )
+ {
+ bool bVal = false;
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ if( pVal )
+ pVal->Value >>= bVal;
+ addBool( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, true, aPropertyName, bVal,
+ aRightColumn, pControllerProperties, pCtrlTarget );
+ }
+ else if( aCtrlType == "Radio" && pCurParent )
+ {
+ // get currently selected value
+ sal_Int32 nSelectVal = 0;
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= nSelectVal;
+
+ addRadio( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, aPropertyName, aChoices, nSelectVal,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ else if( aCtrlType == "List" && pCurParent )
+ {
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ sal_Int32 aSelectVal = 0;
+ if( pVal && pVal->Value.hasValue() )
+ pVal->Value >>= aSelectVal;
+
+ addList( pCurParent, nCurX, nCurY, nAttachOffset,
+ aText, aPropertyName, aChoices, aSelectVal,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ else if( (aCtrlType == "Edit"
+ || aCtrlType == "Range") && pCurParent )
+ {
+ // current value
+ PropertyValue* pVal = pController->getValue( aPropertyName );
+ addEdit( pCurParent, nCurX, nCurY, nAttachOffset,
+ aCtrlType, aText, aPropertyName, pVal,
+ nMinValue, nMaxValue,
+ aLeftColumn, aRightColumn,
+ pControllerProperties, pCtrlTarget );
+ }
+ }
+ else
+ {
+ SAL_INFO( "vcl.osx.print", "Unsupported UI option \"" << aCtrlType << "\"");
+ }
+ }
+
+ pControllerProperties->updateEnableState();
+ adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn );
+
+ // now reposition everything again so it is upper bound
+ adjustTabViews( pTabView, aMaxTabSize );
+
+ // find the minimum needed tab size
+ NSSize aTabCtrlSize = [pTabView minimumSize];
+ aTabCtrlSize.height += aMaxTabSize.height + 10;
+ if( aTabCtrlSize.width < aMaxTabSize.width + 10 )
+ aTabCtrlSize.width = aMaxTabSize.width + 10;
+ [pTabView setFrameSize: aTabCtrlSize];
+ aViewFrame.size.width = aTabCtrlSize.width + aTabViewFrame.origin.x;
+ aViewFrame.size.height = aTabCtrlSize.height + aTabViewFrame.origin.y;
+ [pAccessoryView setFrameSize: aViewFrame.size];
+
+ // get the print panel
+ NSPrintPanel* pPrintPanel = [pOp printPanel];
+ [pPrintPanel setOptions: [pPrintPanel options] | NSPrintPanelShowsPreview];
+ // add the accessory controller to the panel
+ [pPrintPanel addAccessoryController: [pAccessoryController autorelease]];
+
+ // set the current selected tab item
+ if( pState->nLastPage >= 0 && pState->nLastPage < [pTabView numberOfTabViewItems] )
+ [pTabView selectTabViewItemAtIndex: pState->nLastPage];
+
+ return pCtrlTarget;
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/printview.mm b/vcl/osx/printview.mm
new file mode 100644
index 0000000000..b54e1b0561
--- /dev/null
+++ b/vcl/osx/printview.mm
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/print.hxx>
+
+#include <osx/printview.h>
+#include <osx/salprn.h>
+
+@implementation AquaPrintView
+
+-(id)initWithController: (vcl::PrinterController*)pController
+ withInfoPrinter: (AquaSalInfoPrinter*)pInfoPrinter
+{
+ NSRect aRect = { NSZeroPoint, [pInfoPrinter->getPrintInfo() paperSize] };
+ if( (self = [super initWithFrame: aRect]) != nil )
+ {
+ mpController = pController;
+ mpInfoPrinter = pInfoPrinter;
+ }
+ return self;
+}
+
+-(BOOL)knowsPageRange: (NSRangePointer)range
+{
+ range->location = 1;
+ range->length = mpInfoPrinter->getCurPageRangeCount();
+ return YES;
+}
+
+-(NSRect)rectForPage: (int)page
+{
+ NSSize aPaperSize = [mpInfoPrinter->getPrintInfo() paperSize];
+ int nWidth = static_cast<int>(aPaperSize.width);
+ // #i101108# sanity check
+ if( nWidth < 1 )
+ nWidth = 1;
+ NSRect aRect = { { static_cast<CGFloat>(page % nWidth),
+ static_cast<CGFloat>(page / nWidth) },
+ aPaperSize };
+ return aRect;
+}
+
+-(NSPoint)locationOfPrintRect: (NSRect)aRect
+{
+ (void)aRect;
+ return NSZeroPoint;
+}
+
+-(void)drawRect: (NSRect)rect
+{
+ mpInfoPrinter->setStartPageOffset( static_cast<int>(rect.origin.x),
+ static_cast<int>(rect.origin.y) );
+ NSSize aPaperSize = [mpInfoPrinter->getPrintInfo() paperSize];
+ int nPage = static_cast<int>(aPaperSize.width * rect.origin.y + rect.origin.x);
+
+ // page count is 1 based
+ if( nPage - 1 < (mpInfoPrinter->getCurPageRangeStart() + mpInfoPrinter->getCurPageRangeCount() ) )
+ mpController->printFilteredPage( nPage-1 );
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/res/MainMenu.nib/classes.nib b/vcl/osx/res/MainMenu.nib/classes.nib
new file mode 100644
index 0000000000..b9b4b09f6b
--- /dev/null
+++ b/vcl/osx/res/MainMenu.nib/classes.nib
@@ -0,0 +1,4 @@
+{
+ IBClasses = ({CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; });
+ IBVersion = 1;
+} \ No newline at end of file
diff --git a/vcl/osx/res/MainMenu.nib/info.nib b/vcl/osx/res/MainMenu.nib/info.nib
new file mode 100644
index 0000000000..856429aee5
--- /dev/null
+++ b/vcl/osx/res/MainMenu.nib/info.nib
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>IBDocumentLocation</key>
+ <string>135 107 356 240 0 0 1680 1028 </string>
+ <key>IBEditorPositions</key>
+ <dict>
+ <key>29</key>
+ <string>132 352 141 44 0 0 1680 1028 </string>
+ </dict>
+ <key>IBFramework Version</key>
+ <string>446.1</string>
+ <key>IBOpenObjects</key>
+ <array>
+ <integer>29</integer>
+ </array>
+ <key>IBSystem Version</key>
+ <string>8R2218</string>
+</dict>
+</plist>
diff --git a/vcl/osx/res/MainMenu.nib/keyedobjects.nib b/vcl/osx/res/MainMenu.nib/keyedobjects.nib
new file mode 100644
index 0000000000..d39d10119c
--- /dev/null
+++ b/vcl/osx/res/MainMenu.nib/keyedobjects.nib
Binary files differ
diff --git a/vcl/osx/saldata.cxx b/vcl/osx/saldata.cxx
new file mode 100644
index 0000000000..1f49d1ef1e
--- /dev/null
+++ b/vcl/osx/saldata.cxx
@@ -0,0 +1,281 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+
+#include <osl/diagnose.h>
+#include <osx/saldata.hxx>
+#include <osx/salnsmenu.h>
+#include <osx/salinst.h>
+#include <o3tl/enumarray.hxx>
+#include <tools/stream.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/settings.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <sal/log.hxx>
+#include <bitmaps.hlst>
+#include <cursor_hotspots.hxx>
+#include <quartz/salgdi.h>
+#include <quartz/SystemFontList.hxx>
+
+#import "apple_remote/RemoteMainController.h"
+
+oslThreadKey SalData::s_aAutoReleaseKey = nullptr;
+
+static void releasePool( void* pPool )
+{
+ if( pPool )
+ [static_cast<NSAutoreleasePool*>(pPool) release];
+}
+
+SalData::SalData()
+:
+ mpTimerProc( nullptr ),
+ mpInstance( nullptr ),
+ mpFirstObject( nullptr ),
+ mpFirstVD( nullptr ),
+ mpFirstPrinter( nullptr ),
+ mpStatusItem( nil ),
+ mxRGBSpace( CGColorSpaceCreateWithName(kCGColorSpaceSRGB) ),
+ mxGraySpace( CGColorSpaceCreateWithName(kCGColorSpaceGenericGrayGamma2_2) ),
+ maCursors(),
+ mbIsScrollbarDoubleMax( false ),
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ mpAppleRemoteMainController( nullptr ),
+#endif
+ mpDockIconClickHandler( nil ),
+ mnDPIX( 0 ),
+ mnDPIY( 0 )
+{
+ SetSalData(this);
+ maCursors.fill( INVALID_CURSOR_PTR );
+ if( s_aAutoReleaseKey == nullptr )
+ s_aAutoReleaseKey = osl_createThreadKey( releasePool );
+}
+
+SalData::~SalData()
+{
+ CGColorSpaceRelease( mxRGBSpace );
+ CGColorSpaceRelease( mxGraySpace );
+ for( NSCursor* pCurs : maCursors )
+ {
+ if( pCurs && pCurs != INVALID_CURSOR_PTR )
+ [pCurs release];
+ }
+ if( s_aAutoReleaseKey )
+ {
+ // release the last pool
+ NSAutoreleasePool* pPool = reinterpret_cast<NSAutoreleasePool*>( osl_getThreadKeyData( s_aAutoReleaseKey ) );
+ if( pPool )
+ {
+ osl_setThreadKeyData( s_aAutoReleaseKey, nullptr );
+ [pPool release];
+ }
+
+ osl_destroyThreadKey( s_aAutoReleaseKey );
+ s_aAutoReleaseKey = nullptr;
+ }
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ if ( mpAppleRemoteMainController )
+ [mpAppleRemoteMainController release];
+#endif
+
+ if( mpStatusItem )
+ {
+ [mpStatusItem release];
+ mpStatusItem = nil;
+ }
+ SetSalData( nullptr );
+}
+
+void SalData::ensureThreadAutoreleasePool()
+{
+ NSAutoreleasePool* pPool = nil;
+ if( s_aAutoReleaseKey )
+ {
+ pPool = reinterpret_cast<NSAutoreleasePool*>( osl_getThreadKeyData( s_aAutoReleaseKey ) );
+ if( ! pPool )
+ {
+ pPool = [[NSAutoreleasePool alloc] init];
+ osl_setThreadKeyData( s_aAutoReleaseKey, pPool );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "no autorelease key" );
+ }
+}
+
+namespace {
+
+NSImage* load_icon_by_name(const OUString& rIconName)
+{
+ OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ OUString sUILang = Application::GetSettings().GetUILanguageTag().getBcp47();
+ auto xMemStm = ImageTree::get().getImageStream(rIconName, sIconTheme, sUILang);
+ if (!xMemStm)
+ return nullptr;
+
+ auto data = xMemStm->GetData();
+ auto length = xMemStm->TellEnd();
+ NSData * byteData = [NSData dataWithBytes:data length:length];
+ NSBitmapImageRep * imageRep = [NSBitmapImageRep imageRepWithData:byteData];
+
+ NSImage * image = [[NSImage alloc] initWithSize:imageRep.size];
+ [image addRepresentation:imageRep];
+ return image;
+}
+
+}
+
+#define MAKE_CURSOR( vcl_name, name, name2 ) \
+ case vcl_name: \
+ aHotSpot = NSPoint{name##curs_x_hot, name##curs_y_hot}; \
+ aIconName = name2; \
+ break
+
+NSCursor* SalData::getCursor( PointerStyle i_eStyle )
+{
+ NSCursor* pCurs = maCursors[ i_eStyle ];
+ if( pCurs != INVALID_CURSOR_PTR )
+ return pCurs;
+
+ NSPoint aHotSpot;
+ OUString aIconName;
+
+ switch( i_eStyle )
+ {
+ // TODO
+ MAKE_CURSOR( PointerStyle::Wait, wait_, RID_CURSOR_WAIT );
+ MAKE_CURSOR( PointerStyle::NWSize, nwsize_, RID_CURSOR_NWSIZE );
+ MAKE_CURSOR( PointerStyle::NESize, nesize_, RID_CURSOR_NESIZE );
+ MAKE_CURSOR( PointerStyle::SWSize, swsize_, RID_CURSOR_SWSIZE );
+ MAKE_CURSOR( PointerStyle::SESize, sesize_, RID_CURSOR_SESIZE );
+ MAKE_CURSOR( PointerStyle::WindowNWSize, window_nwsize_, RID_CURSOR_WINDOW_NWSIZE );
+ MAKE_CURSOR( PointerStyle::WindowNESize, window_nesize_, RID_CURSOR_WINDOW_NESIZE );
+ MAKE_CURSOR( PointerStyle::WindowSWSize, window_swsize_, RID_CURSOR_WINDOW_SWSIZE );
+ MAKE_CURSOR( PointerStyle::WindowSESize, window_sesize_, RID_CURSOR_WINDOW_SESIZE );
+
+ MAKE_CURSOR( PointerStyle::Help, help_, RID_CURSOR_HELP );
+ MAKE_CURSOR( PointerStyle::Pen, pen_, RID_CURSOR_PEN );
+ MAKE_CURSOR( PointerStyle::Null, null, RID_CURSOR_NULL );
+ MAKE_CURSOR( PointerStyle::Magnify, magnify_, RID_CURSOR_MAGNIFY );
+ MAKE_CURSOR( PointerStyle::Fill, fill_, RID_CURSOR_FILL );
+ MAKE_CURSOR( PointerStyle::MoveData, movedata_, RID_CURSOR_MOVE_DATA );
+ MAKE_CURSOR( PointerStyle::CopyData, copydata_, RID_CURSOR_COPY_DATA );
+ MAKE_CURSOR( PointerStyle::MoveFile, movefile_, RID_CURSOR_MOVE_FILE );
+ MAKE_CURSOR( PointerStyle::CopyFile, copyfile_, RID_CURSOR_COPY_FILE );
+ MAKE_CURSOR( PointerStyle::MoveFiles, movefiles_, RID_CURSOR_MOVE_FILES );
+ MAKE_CURSOR( PointerStyle::CopyFiles, copyfiles_, RID_CURSOR_COPY_FILES );
+ MAKE_CURSOR( PointerStyle::NotAllowed, nodrop_, RID_CURSOR_NOT_ALLOWED );
+ MAKE_CURSOR( PointerStyle::Rotate, rotate_, RID_CURSOR_ROTATE );
+ MAKE_CURSOR( PointerStyle::HShear, hshear_, RID_CURSOR_H_SHEAR );
+ MAKE_CURSOR( PointerStyle::VShear, vshear_, RID_CURSOR_V_SHEAR );
+ MAKE_CURSOR( PointerStyle::DrawLine, drawline_, RID_CURSOR_DRAW_LINE );
+ MAKE_CURSOR( PointerStyle::DrawRect, drawrect_, RID_CURSOR_DRAW_RECT );
+ MAKE_CURSOR( PointerStyle::DrawPolygon, drawpolygon_, RID_CURSOR_DRAW_POLYGON );
+ MAKE_CURSOR( PointerStyle::DrawBezier, drawbezier_, RID_CURSOR_DRAW_BEZIER );
+ MAKE_CURSOR( PointerStyle::DrawArc, drawarc_, RID_CURSOR_DRAW_ARC );
+ MAKE_CURSOR( PointerStyle::DrawPie, drawpie_, RID_CURSOR_DRAW_PIE );
+ MAKE_CURSOR( PointerStyle::DrawCircleCut, drawcirclecut_, RID_CURSOR_DRAW_CIRCLE_CUT );
+ MAKE_CURSOR( PointerStyle::DrawEllipse, drawellipse_, RID_CURSOR_DRAW_ELLIPSE );
+ MAKE_CURSOR( PointerStyle::DrawConnect, drawconnect_, RID_CURSOR_DRAW_CONNECT );
+ MAKE_CURSOR( PointerStyle::DrawText, drawtext_, RID_CURSOR_DRAW_TEXT );
+ MAKE_CURSOR( PointerStyle::Mirror, mirror_, RID_CURSOR_MIRROR );
+ MAKE_CURSOR( PointerStyle::Crook, crook_, RID_CURSOR_CROOK );
+ MAKE_CURSOR( PointerStyle::Crop, crop_, RID_CURSOR_CROP );
+ MAKE_CURSOR( PointerStyle::MovePoint, movepoint_, RID_CURSOR_MOVE_POINT );
+ MAKE_CURSOR( PointerStyle::MoveBezierWeight, movebezierweight_, RID_CURSOR_MOVE_BEZIER_WEIGHT );
+ MAKE_CURSOR( PointerStyle::DrawFreehand, drawfreehand_, RID_CURSOR_DRAW_FREEHAND );
+ MAKE_CURSOR( PointerStyle::DrawCaption, drawcaption_, RID_CURSOR_DRAW_CAPTION );
+ MAKE_CURSOR( PointerStyle::LinkData, linkdata_, RID_CURSOR_LINK_DATA );
+ MAKE_CURSOR( PointerStyle::MoveDataLink, movedlnk_, RID_CURSOR_MOVE_DATA_LINK );
+ MAKE_CURSOR( PointerStyle::CopyDataLink, copydlnk_, RID_CURSOR_COPY_DATA_LINK );
+ MAKE_CURSOR( PointerStyle::LinkFile, linkfile_, RID_CURSOR_LINK_FILE );
+ MAKE_CURSOR( PointerStyle::MoveFileLink, moveflnk_, RID_CURSOR_MOVE_FILE_LINK );
+ MAKE_CURSOR( PointerStyle::CopyFileLink, copyflnk_, RID_CURSOR_COPY_FILE_LINK );
+ MAKE_CURSOR( PointerStyle::Chart, chart_, RID_CURSOR_CHART );
+ MAKE_CURSOR( PointerStyle::Detective, detective_, RID_CURSOR_DETECTIVE );
+ MAKE_CURSOR( PointerStyle::PivotCol, pivotcol_, RID_CURSOR_PIVOT_COLUMN );
+ MAKE_CURSOR( PointerStyle::PivotRow, pivotrow_, RID_CURSOR_PIVOT_ROW );
+ MAKE_CURSOR( PointerStyle::PivotField, pivotfld_, RID_CURSOR_PIVOT_FIELD );
+ MAKE_CURSOR( PointerStyle::PivotDelete, pivotdel_, RID_CURSOR_PIVOT_DELETE );
+ MAKE_CURSOR( PointerStyle::Chain, chain_, RID_CURSOR_CHAIN );
+ MAKE_CURSOR( PointerStyle::ChainNotAllowed, chainnot_, RID_CURSOR_CHAIN_NOT_ALLOWED );
+ MAKE_CURSOR( PointerStyle::AutoScrollN, asn_, RID_CURSOR_AUTOSCROLL_N );
+ MAKE_CURSOR( PointerStyle::AutoScrollS, ass_, RID_CURSOR_AUTOSCROLL_S );
+ MAKE_CURSOR( PointerStyle::AutoScrollW, asw_, RID_CURSOR_AUTOSCROLL_W );
+ MAKE_CURSOR( PointerStyle::AutoScrollE, ase_, RID_CURSOR_AUTOSCROLL_E );
+ MAKE_CURSOR( PointerStyle::AutoScrollNW, asnw_, RID_CURSOR_AUTOSCROLL_NW );
+ MAKE_CURSOR( PointerStyle::AutoScrollNE, asne_, RID_CURSOR_AUTOSCROLL_NE );
+ MAKE_CURSOR( PointerStyle::AutoScrollSW, assw_, RID_CURSOR_AUTOSCROLL_SW );
+ MAKE_CURSOR( PointerStyle::AutoScrollSE, asse_, RID_CURSOR_AUTOSCROLL_SE );
+ MAKE_CURSOR( PointerStyle::AutoScrollNS, asns_, RID_CURSOR_AUTOSCROLL_NS );
+ MAKE_CURSOR( PointerStyle::AutoScrollWE, aswe_, RID_CURSOR_AUTOSCROLL_WE );
+ MAKE_CURSOR( PointerStyle::AutoScrollNSWE, asnswe_, RID_CURSOR_AUTOSCROLL_NSWE );
+ MAKE_CURSOR( PointerStyle::TextVertical, vertcurs_, RID_CURSOR_TEXT_VERTICAL );
+
+ // #i32329#
+ MAKE_CURSOR( PointerStyle::TabSelectS, tblsels_, RID_CURSOR_TAB_SELECT_S );
+ MAKE_CURSOR( PointerStyle::TabSelectE, tblsele_, RID_CURSOR_TAB_SELECT_E );
+ MAKE_CURSOR( PointerStyle::TabSelectSE, tblselse_, RID_CURSOR_TAB_SELECT_SE );
+ MAKE_CURSOR( PointerStyle::TabSelectW, tblselw_, RID_CURSOR_TAB_SELECT_W );
+ MAKE_CURSOR( PointerStyle::TabSelectSW, tblselsw_, RID_CURSOR_TAB_SELECT_SW );
+
+ MAKE_CURSOR( PointerStyle::HideWhitespace, hidewhitespace_, RID_CURSOR_HIDE_WHITESPACE );
+ MAKE_CURSOR( PointerStyle::ShowWhitespace, showwhitespace_, RID_CURSOR_SHOW_WHITESPACE );
+
+ MAKE_CURSOR( PointerStyle::FatCross, fatcross_, RID_CURSOR_FATCROSS );
+
+ default:
+ SAL_WARN( "vcl", "pointer style " << static_cast<sal_Int32>(i_eStyle) << "not implemented" );
+ assert( false && "pointer style not implemented" );
+ break;
+ }
+
+ NSImage* theImage = load_icon_by_name(aIconName);
+ pCurs = [[NSCursor alloc] initWithImage: theImage hotSpot: aHotSpot];
+
+ maCursors[ i_eStyle ] = pCurs;
+ return pCurs;
+}
+
+NSStatusItem* SalData::getStatusItem()
+{
+ SalData* pData = GetSalData();
+ if( ! pData->mpStatusItem )
+ {
+ NSStatusBar* pStatBar =[NSStatusBar systemStatusBar];
+ if( pStatBar )
+ {
+ pData->mpStatusItem = [pStatBar statusItemWithLength: NSVariableStatusItemLength];
+ [pData->mpStatusItem retain];
+ OOStatusItemView* pView = [[OOStatusItemView alloc] init];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'setView:' is deprecated: first deprecated in macOS 10.14 - Use the standard
+ // button property instead"
+ [pData->mpStatusItem setView: pView ];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [pView display];
+ }
+ }
+ return pData->mpStatusItem;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salframe.cxx b/vcl/osx/salframe.cxx
new file mode 100644
index 0000000000..b02dbc5958
--- /dev/null
+++ b/vcl/osx/salframe.cxx
@@ -0,0 +1,2027 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string>
+
+#include <comphelper/fileurl.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <tools/long.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <osl/file.h>
+
+#include <vcl/event.hxx>
+#include <vcl/inputctx.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/syswin.hxx>
+#include <vcl/settings.hxx>
+
+#include <osx/saldata.hxx>
+#include <quartz/salgdi.h>
+#include <osx/salframe.h>
+#include <osx/salmenu.h>
+#include <osx/salinst.h>
+#include <osx/salframeview.h>
+#include <osx/a11yfactory.h>
+#include <osx/runinmain.hxx>
+#include <quartz/utils.h>
+
+#include <salwtype.hxx>
+
+#include <premac.h>
+#include <objc/objc-runtime.h>
+// needed for theming
+// FIXME: move theming code to salnativewidgets.cxx
+#include <Carbon/Carbon.h>
+#include <quartz/CGHelpers.hxx>
+#include <postmac.h>
+
+
+const int nMinBlinkCursorDelay = 500;
+
+AquaSalFrame* AquaSalFrame::s_pCaptureFrame = nullptr;
+
+AquaSalFrame::AquaSalFrame( SalFrame* pParent, SalFrameStyleFlags salFrameStyle ) :
+ mpNSWindow(nil),
+ mpNSView(nil),
+ mpDockMenuEntry(nil),
+ mpGraphics(nullptr),
+ mpParent(nullptr),
+ mnMinWidth(0),
+ mnMinHeight(0),
+ mnMaxWidth(0),
+ mnMaxHeight(0),
+ mbGraphics(false),
+ mbFullScreen( false ),
+ mbShown(false),
+ mbInitShow(true),
+ mbPositioned(false),
+ mbSized(false),
+ mbPresentation( false ),
+ mnStyle( salFrameStyle ),
+ mnStyleMask( 0 ),
+ mnLastEventTime( 0 ),
+ mnLastModifierFlags( 0 ),
+ mpMenu( nullptr ),
+ mnExtStyle( 0 ),
+ mePointerStyle( PointerStyle::Arrow ),
+ mrClippingPath( nullptr ),
+ mnICOptions( InputContextFlags::NONE ),
+ mnBlinkCursorDelay( nMinBlinkCursorDelay ),
+ mbForceFlush( false )
+{
+ mpParent = dynamic_cast<AquaSalFrame*>(pParent);
+
+ initWindowAndView();
+
+ SalData* pSalData = GetSalData();
+ pSalData->mpInstance->insertFrame( this );
+ NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
+
+ // tdf#150177 Limit minimum blink cursor rate
+ // This bug occurs when the values for NSTextInsertionPointBlinkPeriodOn or
+ // NSTextInsertionPointBlinkPeriodOff are set to zero or close to zero.
+ // LibreOffice becomes very sluggish opening documents when either is set
+ // at 100 milliseconds or less so set the blink rate to the maximum of
+ // nMinBlinkCursorDelay, NSTextInsertionPointBlinkPeriodOn, and
+ // NSTextInsertionPointBlinkPeriodOff.
+ mnBlinkCursorDelay = nMinBlinkCursorDelay;
+ if (userDefaults != nil)
+ {
+ id setting = [userDefaults objectForKey: @"NSTextInsertionPointBlinkPeriodOn"];
+ if (setting && [setting isKindOfClass:[NSNumber class]])
+ mnBlinkCursorDelay = std::max(mnBlinkCursorDelay, [setting intValue]);
+
+ setting = [userDefaults objectForKey: @"NSTextInsertionPointBlinkPeriodOff"];
+ if (setting && [setting isKindOfClass:[NSNumber class]])
+ mnBlinkCursorDelay = std::max(mnBlinkCursorDelay, [setting intValue]);
+ }
+}
+
+AquaSalFrame::~AquaSalFrame()
+{
+ if (mbFullScreen)
+ doShowFullScreen(false, maGeometry.screen());
+
+ assert( GetSalData()->mpInstance->IsMainThread() );
+
+ // if the frame is destroyed and has the current menubar
+ // set the default menubar
+ if( mpMenu && mpMenu->mbMenuBar && AquaSalMenu::pCurrentMenuBar == mpMenu )
+ AquaSalMenu::setDefaultMenu();
+
+ // cleanup clipping stuff
+ doResetClipRegion();
+
+ [SalFrameView unsetMouseFrame: this];
+
+ SalData* pSalData = GetSalData();
+ pSalData->mpInstance->eraseFrame( this );
+ pSalData->maPresentationFrames.remove( this );
+
+ SAL_WARN_IF( this == s_pCaptureFrame, "vcl", "capture frame destroyed" );
+ if( this == s_pCaptureFrame )
+ s_pCaptureFrame = nullptr;
+
+ delete mpGraphics;
+
+ if( mpDockMenuEntry )
+ {
+ NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
+ // life cycle comment: the menu has ownership of the item, so no release
+ [pDock removeItem: mpDockMenuEntry];
+ if ([pDock numberOfItems] != 0
+ && [[pDock itemAtIndex: 0] isSeparatorItem])
+ {
+ [pDock removeItemAtIndex: 0];
+ }
+ }
+ if ( mpNSView ) {
+ if ([mpNSView isKindOfClass:[SalFrameView class]])
+ [static_cast<SalFrameView*>(mpNSView) revokeWrapper];
+ [mpNSView release];
+ }
+ if ( mpNSWindow )
+ [mpNSWindow release];
+}
+
+void AquaSalFrame::initWindowAndView()
+{
+ OSX_SALDATA_RUNINMAIN( initWindowAndView() )
+
+ // initialize mirroring parameters
+ // FIXME: screens changing
+ NSScreen* pNSScreen = [mpNSWindow screen];
+ if( pNSScreen == nil )
+ pNSScreen = [NSScreen mainScreen];
+ maScreenRect = [pNSScreen frame];
+
+ // calculate some default geometry
+ NSRect aVisibleRect = [pNSScreen visibleFrame];
+ CocoaToVCL( aVisibleRect );
+
+ maGeometry.setX(static_cast<sal_Int32>(aVisibleRect.origin.x + aVisibleRect.size.width / 10));
+ maGeometry.setY(static_cast<sal_Int32>(aVisibleRect.origin.y + aVisibleRect.size.height / 10));
+ maGeometry.setWidth(static_cast<sal_uInt32>(aVisibleRect.size.width * 0.8));
+ maGeometry.setHeight(static_cast<sal_uInt32>(aVisibleRect.size.height * 0.8));
+
+ // calculate style mask
+ if( (mnStyle & SalFrameStyleFlags::FLOAT) ||
+ (mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION) )
+ mnStyleMask = NSWindowStyleMaskBorderless;
+ else if( mnStyle & SalFrameStyleFlags::DEFAULT )
+ {
+ mnStyleMask = NSWindowStyleMaskTitled |
+ NSWindowStyleMaskMiniaturizable |
+ NSWindowStyleMaskResizable |
+ NSWindowStyleMaskClosable;
+ // make default window "maximized"
+ maGeometry.setX(static_cast<sal_Int32>(aVisibleRect.origin.x));
+ maGeometry.setY(static_cast<sal_Int32>(aVisibleRect.origin.y));
+ maGeometry.setWidth(static_cast<sal_uInt32>(aVisibleRect.size.width));
+ maGeometry.setHeight(static_cast<sal_uInt32>(aVisibleRect.size.height));
+ mbPositioned = mbSized = true;
+ }
+ else
+ {
+ if( mnStyle & SalFrameStyleFlags::MOVEABLE )
+ {
+ mnStyleMask |= NSWindowStyleMaskTitled;
+ if( mpParent == nullptr )
+ mnStyleMask |= NSWindowStyleMaskMiniaturizable;
+ }
+ if( mnStyle & SalFrameStyleFlags::SIZEABLE )
+ mnStyleMask |= NSWindowStyleMaskResizable;
+ if( mnStyle & SalFrameStyleFlags::CLOSEABLE )
+ mnStyleMask |= NSWindowStyleMaskClosable;
+ // documentation says anything other than NSWindowStyleMaskBorderless (=0)
+ // should also include NSWindowStyleMaskTitled;
+ if( mnStyleMask != 0 )
+ mnStyleMask |= NSWindowStyleMaskTitled;
+ }
+
+ if (Application::IsBitmapRendering())
+ return;
+
+ // #i91990# support GUI-less (daemon) execution
+ @try
+ {
+ mpNSWindow = [[SalFrameWindow alloc] initWithSalFrame: this];
+ mpNSView = [[SalFrameView alloc] initWithSalFrame: this];
+ }
+ @catch ( id )
+ {
+ std::abort();
+ }
+
+ if( mnStyle & SalFrameStyleFlags::TOOLTIP )
+ [mpNSWindow setIgnoresMouseEvents: YES];
+ else
+ // Related: tdf#155092 mouse events are now handled by tracking areas
+ [mpNSWindow setAcceptsMouseMovedEvents: NO];
+ [mpNSWindow setHasShadow: YES];
+
+ [mpNSWindow setDelegate: static_cast<id<NSWindowDelegate> >(mpNSWindow)];
+
+ [mpNSWindow setRestorable:NO];
+
+ // tdf#155092 use tracking areas instead of tracking rectangles
+ // Apparently, the older, tracking rectangles selectors cause
+ // unexpected window resizing upon the first mouse down after the
+ // window has been manually resized so switch to the newer,
+ // tracking areas selectors. Also, the NSTrackingInVisibleRect
+ // option allows us to create one single tracking area that
+ // resizes itself automatically over the lifetime of the view.
+ // Note: for some unknown reason, both NSTrackingMouseMoved and
+ // NSTrackingAssumeInside are necessary options for this fix
+ // to work.
+ NSTrackingArea *pTrackingArea = [[NSTrackingArea alloc] initWithRect: [mpNSView bounds] options: ( NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingAssumeInside | NSTrackingInVisibleRect ) owner: mpNSView userInfo: nil];
+ [mpNSView addTrackingArea: pTrackingArea];
+ [pTrackingArea release];
+
+ maSysData.mpNSView = mpNSView;
+
+ UpdateFrameGeometry();
+
+ [mpNSWindow setContentView: mpNSView];
+}
+
+void AquaSalFrame::CocoaToVCL( NSRect& io_rRect, bool bRelativeToScreen )
+{
+ if( bRelativeToScreen )
+ io_rRect.origin.y = maScreenRect.size.height - (io_rRect.origin.y+io_rRect.size.height);
+ else
+ io_rRect.origin.y = maGeometry.height() - (io_rRect.origin.y+io_rRect.size.height);
+}
+
+void AquaSalFrame::VCLToCocoa( NSRect& io_rRect, bool bRelativeToScreen )
+{
+ if( bRelativeToScreen )
+ io_rRect.origin.y = maScreenRect.size.height - (io_rRect.origin.y+io_rRect.size.height);
+ else
+ io_rRect.origin.y = maGeometry.height() - (io_rRect.origin.y+io_rRect.size.height);
+}
+
+void AquaSalFrame::CocoaToVCL( NSPoint& io_rPoint, bool bRelativeToScreen )
+{
+ if( bRelativeToScreen )
+ io_rPoint.y = maScreenRect.size.height - io_rPoint.y;
+ else
+ io_rPoint.y = maGeometry.height() - io_rPoint.y;
+}
+
+void AquaSalFrame::VCLToCocoa( NSPoint& io_rPoint, bool bRelativeToScreen )
+{
+ if( bRelativeToScreen )
+ io_rPoint.y = maScreenRect.size.height - io_rPoint.y;
+ else
+ io_rPoint.y = maGeometry.height() - io_rPoint.y;
+}
+
+void AquaSalFrame::screenParametersChanged()
+{
+ OSX_SALDATA_RUNINMAIN( screenParametersChanged() )
+
+ sal::aqua::resetTotalScreenBounds();
+ sal::aqua::resetWindowScaling();
+
+ UpdateFrameGeometry();
+
+ if( mpGraphics )
+ mpGraphics->updateResolution();
+
+ if (!mbGeometryDidChange)
+ return;
+
+ CallCallback( SalEvent::DisplayChanged, nullptr );
+}
+
+SalGraphics* AquaSalFrame::AcquireGraphics()
+{
+ if ( mbGraphics )
+ return nullptr;
+
+ if ( !mpGraphics )
+ {
+ mpGraphics = new AquaSalGraphics;
+ mpGraphics->SetWindowGraphics( this );
+ }
+
+ mbGraphics = true;
+ return mpGraphics;
+}
+
+void AquaSalFrame::ReleaseGraphics( SalGraphics *pGraphics )
+{
+ SAL_WARN_IF( pGraphics != mpGraphics, "vcl", "graphics released on wrong frame" );
+ mbGraphics = false;
+}
+
+bool AquaSalFrame::PostEvent(std::unique_ptr<ImplSVEvent> pData)
+{
+ GetSalData()->mpInstance->PostEvent( this, pData.release(), SalEvent::UserEvent );
+ return true;
+}
+
+void AquaSalFrame::SetTitle(const OUString& rTitle)
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetTitle(rTitle) )
+
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ NSString* pTitle = CreateNSString( rTitle );
+ [mpNSWindow setTitle: pTitle];
+
+ // create an entry in the dock menu
+ const SalFrameStyleFlags nAppWindowStyle = SalFrameStyleFlags::CLOSEABLE | SalFrameStyleFlags::MOVEABLE;
+ if( mpParent == nullptr &&
+ (mnStyle & nAppWindowStyle) == nAppWindowStyle )
+ {
+ if( mpDockMenuEntry == nullptr )
+ {
+ NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
+
+ if ([pDock numberOfItems] != 0) {
+ NSMenuItem* pTopItem = [pDock itemAtIndex: 0];
+ if ( [pTopItem hasSubmenu] )
+ [pDock insertItem: [NSMenuItem separatorItem] atIndex: 0];
+ }
+
+ mpDockMenuEntry = [pDock insertItemWithTitle: pTitle
+ action: @selector(dockMenuItemTriggered:)
+ keyEquivalent: @""
+ atIndex: 0];
+ [mpDockMenuEntry setTarget: mpNSWindow];
+
+ // TODO: image (either the generic window image or an icon
+ // check mark (for "main" window ?)
+ }
+ else
+ [mpDockMenuEntry setTitle: pTitle];
+ }
+
+ if (pTitle)
+ [pTitle release];
+}
+
+void AquaSalFrame::SetIcon( sal_uInt16 )
+{
+}
+
+void AquaSalFrame::SetRepresentedURL( const OUString& i_rDocURL )
+{
+ OSX_SALDATA_RUNINMAIN( SetRepresentedURL( i_rDocURL ) )
+
+ if( comphelper::isFileUrl(i_rDocURL) )
+ {
+ OUString aSysPath;
+ osl_getSystemPathFromFileURL( i_rDocURL.pData, &aSysPath.pData );
+ NSString* pStr = CreateNSString( aSysPath );
+ if( pStr )
+ {
+ [pStr autorelease];
+ [mpNSWindow setRepresentedFilename: pStr];
+ }
+ }
+}
+
+void AquaSalFrame::initShow()
+{
+ OSX_SALDATA_RUNINMAIN( initShow() )
+
+ mbInitShow = false;
+ if( ! mbPositioned && ! mbFullScreen )
+ {
+ AbsoluteScreenPixelRectangle aScreenRect;
+ GetWorkArea( aScreenRect );
+ if( mpParent ) // center relative to parent
+ {
+ // center on parent
+ tools::Long nNewX = mpParent->maGeometry.x() + (static_cast<tools::Long>(mpParent->maGeometry.width()) - static_cast<tools::Long>(maGeometry.width())) / 2;
+ if( nNewX < aScreenRect.Left() )
+ nNewX = aScreenRect.Left();
+ if (static_cast<tools::Long>(nNewX + maGeometry.width()) > aScreenRect.Right())
+ nNewX = aScreenRect.Right() - maGeometry.width() - 1;
+ tools::Long nNewY = mpParent->maGeometry.y() + (static_cast<tools::Long>(mpParent->maGeometry.height()) - static_cast<tools::Long>(maGeometry.height())) / 2;
+ if( nNewY < aScreenRect.Top() )
+ nNewY = aScreenRect.Top();
+ if( nNewY > aScreenRect.Bottom() )
+ nNewY = aScreenRect.Bottom() - maGeometry.height() - 1;
+ SetPosSize( nNewX - mpParent->maGeometry.x(),
+ nNewY - mpParent->maGeometry.y(),
+ 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y );
+ }
+ else if( ! (mnStyle & SalFrameStyleFlags::SIZEABLE) )
+ {
+ // center on screen
+ tools::Long nNewX = (aScreenRect.GetWidth() - maGeometry.width()) / 2;
+ tools::Long nNewY = (aScreenRect.GetHeight() - maGeometry.height()) / 2;
+ SetPosSize( nNewX, nNewY, 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y );
+ }
+ }
+
+ // make sure the view is present in the wrapper list before any children receive focus
+ if (mpNSView && [mpNSView isKindOfClass:[SalFrameView class]])
+ [static_cast<SalFrameView*>(mpNSView) registerWrapper];
+}
+
+void AquaSalFrame::SendPaintEvent( const tools::Rectangle* pRect )
+{
+ OSX_SALDATA_RUNINMAIN( SendPaintEvent( pRect ) )
+
+ SalPaintEvent aPaintEvt(0, 0, maGeometry.width(), maGeometry.height(), true);
+ if( pRect )
+ {
+ aPaintEvt.mnBoundX = pRect->Left();
+ aPaintEvt.mnBoundY = pRect->Top();
+ aPaintEvt.mnBoundWidth = pRect->GetWidth();
+ aPaintEvt.mnBoundHeight = pRect->GetHeight();
+ }
+
+ CallCallback(SalEvent::Paint, &aPaintEvt);
+}
+
+void AquaSalFrame::Show(bool bVisible, bool bNoActivate)
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( Show(bVisible, bNoActivate) )
+
+ // tdf#152173 Don't display tooltip windows when application is inactive
+ // Starting with macOS 13 Ventura, inactive applications receive mouse
+ // move events so when LibreOffice is inactive, a mouse move event causes
+ // a tooltip to be displayed. Since the tooltip window is attached to its
+ // parent window (to ensure that the tooltip is above the parent window),
+ // displaying a tooltip pulls the parent window in front of the windows
+ // of all other inactive applications.
+ // Also, don't display tooltips when mousing over non-key windows even if
+ // the application is active as the tooltip window will pull the non-key
+ // window in front of the key window.
+ if (bVisible && (mnStyle & SalFrameStyleFlags::TOOLTIP))
+ {
+ if (![NSApp isActive])
+ return;
+
+ if (mpParent)
+ {
+ // tdf#157565 show tooltip if any parent window is the key window
+ bool bKeyWindowFound = false;
+ NSWindow *pParent = mpParent->mpNSWindow;
+ while (pParent)
+ {
+ if ([pParent isKeyWindow])
+ {
+ bKeyWindowFound = true;
+ break;
+ }
+ pParent = [pParent parentWindow];
+ }
+ if (!bKeyWindowFound)
+ return;
+ }
+ }
+
+ mbShown = bVisible;
+ if(bVisible)
+ {
+ if( mbInitShow )
+ initShow();
+
+ CallCallback(SalEvent::Resize, nullptr);
+ // trigger filling our backbuffer
+ SendPaintEvent();
+
+ if( bNoActivate || [mpNSWindow canBecomeKeyWindow] == NO )
+ [mpNSWindow orderFront: NSApp];
+ else
+ [mpNSWindow makeKeyAndOrderFront: NSApp];
+
+ if( mpParent )
+ {
+ /* #i92674# #i96433# we do not want an invisible parent to show up (which adding a visible
+ child implicitly does). However we also do not want a parentless toolbar.
+
+ HACK: try to decide when we should not insert a child to its parent
+ floaters and ownerdraw windows have not yet shown up in cases where
+ we don't want the parent to become visible
+ */
+ if( mpParent->mbShown || (mnStyle & (SalFrameStyleFlags::OWNERDRAWDECORATION | SalFrameStyleFlags::FLOAT) ) )
+ {
+ [mpParent->mpNSWindow addChildWindow: mpNSWindow ordered: NSWindowAbove];
+ }
+ }
+
+ if( mbPresentation )
+ [mpNSWindow makeMainWindow];
+ }
+ else
+ {
+ // if the frame holding the current menubar gets hidden
+ // show the default menubar
+ if( mpMenu && mpMenu->mbMenuBar && AquaSalMenu::pCurrentMenuBar == mpMenu )
+ AquaSalMenu::setDefaultMenu();
+
+ // #i90440# #i94443# work around the focus going back to some other window
+ // if a child gets hidden for a parent window
+ if( mpParent && mpParent->mbShown && [mpNSWindow isKeyWindow] )
+ [mpParent->mpNSWindow makeKeyAndOrderFront: NSApp];
+
+ [SalFrameView unsetMouseFrame: this];
+ if( mpParent && [mpNSWindow parentWindow] == mpParent->mpNSWindow )
+ [mpParent->mpNSWindow removeChildWindow: mpNSWindow];
+
+ [mpNSWindow orderOut: NSApp];
+ }
+}
+
+void AquaSalFrame::SetMinClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ OSX_SALDATA_RUNINMAIN( SetMinClientSize( nWidth, nHeight ) )
+
+ mnMinWidth = nWidth;
+ mnMinHeight = nHeight;
+
+ if( mpNSWindow )
+ {
+ // Always add the decoration as the dimension concerns only
+ // the content rectangle
+ nWidth += maGeometry.leftDecoration() + maGeometry.rightDecoration();
+ nHeight += maGeometry.topDecoration() + maGeometry.bottomDecoration();
+
+ NSSize aSize = { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) };
+
+ // Size of full window (content+structure) although we only
+ // have the client size in arguments
+ [mpNSWindow setMinSize: aSize];
+ }
+}
+
+void AquaSalFrame::SetMaxClientSize( tools::Long nWidth, tools::Long nHeight )
+{
+ OSX_SALDATA_RUNINMAIN( SetMaxClientSize( nWidth, nHeight ) )
+
+ mnMaxWidth = nWidth;
+ mnMaxHeight = nHeight;
+
+ if( mpNSWindow )
+ {
+ // Always add the decoration as the dimension concerns only
+ // the content rectangle
+ nWidth += maGeometry.leftDecoration() + maGeometry.rightDecoration();
+ nHeight += maGeometry.topDecoration() + maGeometry.bottomDecoration();
+
+ // Carbon windows can't have a size greater than 32767x32767
+ if (nWidth>32767) nWidth=32767;
+ if (nHeight>32767) nHeight=32767;
+
+ NSSize aSize = { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) };
+
+ // Size of full window (content+structure) although we only
+ // have the client size in arguments
+ [mpNSWindow setMaxSize: aSize];
+ }
+}
+
+void AquaSalFrame::GetClientSize( tools::Long& rWidth, tools::Long& rHeight )
+{
+ if (mbShown || mbInitShow || Application::IsBitmapRendering())
+ {
+ rWidth = maGeometry.width();
+ rHeight = maGeometry.height();
+ }
+ else
+ {
+ rWidth = 0;
+ rHeight = 0;
+ }
+}
+
+SalEvent AquaSalFrame::PreparePosSize(tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags)
+{
+ SalEvent nEvent = SalEvent::NONE;
+ assert(mpNSWindow || Application::IsBitmapRendering());
+
+ if (nFlags & (SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y))
+ {
+ mbPositioned = true;
+ nEvent = SalEvent::Move;
+ }
+
+ if (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT))
+ {
+ mbSized = true;
+ nEvent = (nEvent == SalEvent::Move) ? SalEvent::MoveResize : SalEvent::Resize;
+ }
+
+ if (Application::IsBitmapRendering())
+ {
+ if (nFlags & SAL_FRAME_POSSIZE_X)
+ maGeometry.setX(nX);
+ if (nFlags & SAL_FRAME_POSSIZE_Y)
+ maGeometry.setY(nY);
+ if (nFlags & SAL_FRAME_POSSIZE_WIDTH)
+ {
+ maGeometry.setWidth(nWidth);
+ if (mnMaxWidth > 0 && maGeometry.width() > mnMaxWidth)
+ maGeometry.setWidth(mnMaxWidth);
+ if (mnMinWidth > 0 && maGeometry.width() < mnMinWidth)
+ maGeometry.setWidth(mnMinWidth);
+ }
+ if (nFlags & SAL_FRAME_POSSIZE_HEIGHT)
+ {
+ maGeometry.setHeight(nHeight);
+ if (mnMaxHeight > 0 && maGeometry.height() > mnMaxHeight)
+ maGeometry.setHeight(mnMaxHeight);
+ if (mnMinHeight > 0 && maGeometry.height() < mnMinHeight)
+ maGeometry.setHeight(mnMinHeight);
+ }
+ if (nEvent != SalEvent::NONE)
+ CallCallback(nEvent, nullptr);
+ }
+
+ return nEvent;
+}
+
+void AquaSalFrame::SetWindowState(const vcl::WindowData* pState)
+{
+ if (!mpNSWindow && !Application::IsBitmapRendering())
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetWindowState( pState ) )
+
+ sal_uInt16 nFlags = 0;
+ nFlags |= ((pState->mask() & vcl::WindowDataMask::X) ? SAL_FRAME_POSSIZE_X : 0);
+ nFlags |= ((pState->mask() & vcl::WindowDataMask::Y) ? SAL_FRAME_POSSIZE_Y : 0);
+ nFlags |= ((pState->mask() & vcl::WindowDataMask::Width) ? SAL_FRAME_POSSIZE_WIDTH : 0);
+ nFlags |= ((pState->mask() & vcl::WindowDataMask::Height) ? SAL_FRAME_POSSIZE_HEIGHT : 0);
+
+ SalEvent nEvent = PreparePosSize(pState->x(), pState->y(), pState->width(), pState->height(), nFlags);
+ if (Application::IsBitmapRendering())
+ return;
+
+ // set normal state
+ NSRect aStateRect = [mpNSWindow frame];
+ aStateRect = [NSWindow contentRectForFrameRect: aStateRect styleMask: mnStyleMask];
+ CocoaToVCL(aStateRect);
+ if (pState->mask() & vcl::WindowDataMask::X)
+ aStateRect.origin.x = float(pState->x());
+ if (pState->mask() & vcl::WindowDataMask::Y)
+ aStateRect.origin.y = float(pState->y());
+ if (pState->mask() & vcl::WindowDataMask::Width)
+ aStateRect.size.width = float(pState->width());
+ if (pState->mask() & vcl::WindowDataMask::Height)
+ aStateRect.size.height = float(pState->height());
+ VCLToCocoa(aStateRect);
+ aStateRect = [NSWindow frameRectForContentRect: aStateRect styleMask: mnStyleMask];
+ [mpNSWindow setFrame: aStateRect display: NO];
+
+ if (pState->state() == vcl::WindowState::Minimized)
+ [mpNSWindow miniaturize: NSApp];
+ else if ([mpNSWindow isMiniaturized])
+ [mpNSWindow deminiaturize: NSApp];
+
+ /* ZOOMED is not really maximized (actually it toggles between a user set size and
+ the program specified one), but comes closest since the default behavior is
+ "maximized" if the user did not intervene
+ */
+ if (pState->state() == vcl::WindowState::Maximized)
+ {
+ if (![mpNSWindow isZoomed])
+ [mpNSWindow zoom: NSApp];
+ }
+ else
+ {
+ if ([mpNSWindow isZoomed])
+ [mpNSWindow zoom: NSApp];
+ }
+
+ // get new geometry
+ UpdateFrameGeometry();
+
+ // send event that we were moved/sized
+ if( nEvent != SalEvent::NONE )
+ CallCallback( nEvent, nullptr );
+
+ if (mbShown)
+ {
+ // trigger filling our backbuffer
+ SendPaintEvent();
+
+ // tell the system the views need to be updated
+ [mpNSWindow display];
+ }
+}
+
+bool AquaSalFrame::GetWindowState(vcl::WindowData* pState)
+{
+ if (!mpNSWindow)
+ {
+ if (Application::IsBitmapRendering())
+ {
+ pState->setMask(vcl::WindowDataMask::PosSizeState);
+ pState->setPosSize(maGeometry.posSize());
+ pState->setState(vcl::WindowState::Normal);
+ return true;
+ }
+ return false;
+ }
+
+ OSX_SALDATA_RUNINMAIN_UNION( GetWindowState( pState ), boolean )
+
+ pState->setMask(vcl::WindowDataMask::PosSizeState);
+
+ NSRect aStateRect = [mpNSWindow frame];
+ aStateRect = [NSWindow contentRectForFrameRect: aStateRect styleMask: mnStyleMask];
+ CocoaToVCL( aStateRect );
+ pState->setX(static_cast<sal_Int32>(aStateRect.origin.x));
+ pState->setY(static_cast<sal_Int32>(aStateRect.origin.y));
+ pState->setWidth(static_cast<sal_uInt32>(aStateRect.size.width));
+ pState->setHeight(static_cast<sal_uInt32>(aStateRect.size.height));
+
+ if( [mpNSWindow isMiniaturized] )
+ pState->setState(vcl::WindowState::Minimized);
+ else if( ! [mpNSWindow isZoomed] )
+ pState->setState(vcl::WindowState::Normal);
+ else
+ pState->setState(vcl::WindowState::Maximized);
+
+ return true;
+}
+
+void AquaSalFrame::SetScreenNumber(unsigned int nScreen)
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetScreenNumber( nScreen ) )
+
+ NSArray* pScreens = [NSScreen screens];
+ NSScreen* pScreen = nil;
+ if( pScreens && nScreen < [pScreens count] )
+ {
+ // get new screen frame
+ pScreen = [pScreens objectAtIndex: nScreen];
+ NSRect aNewScreen = [pScreen frame];
+
+ // get current screen frame
+ pScreen = [mpNSWindow screen];
+ if( pScreen )
+ {
+ NSRect aCurScreen = [pScreen frame];
+ if( aCurScreen.origin.x != aNewScreen.origin.x ||
+ aCurScreen.origin.y != aNewScreen.origin.y )
+ {
+ NSRect aFrameRect = [mpNSWindow frame];
+ aFrameRect.origin.x += aNewScreen.origin.x - aCurScreen.origin.x;
+ aFrameRect.origin.y += aNewScreen.origin.y - aCurScreen.origin.y;
+ [mpNSWindow setFrame: aFrameRect display: NO];
+ UpdateFrameGeometry();
+ }
+ }
+ }
+}
+
+void AquaSalFrame::SetApplicationID( const OUString &/*rApplicationID*/ )
+{
+}
+
+void AquaSalFrame::ShowFullScreen( bool bFullScreen, sal_Int32 nDisplay )
+{
+ doShowFullScreen(bFullScreen, nDisplay);
+}
+
+void AquaSalFrame::doShowFullScreen( bool bFullScreen, sal_Int32 nDisplay )
+{
+ if (!mpNSWindow)
+ {
+ if (Application::IsBitmapRendering() && bFullScreen)
+ SetPosSize(0, 0, 1024, 768, SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT);
+ return;
+ }
+
+ SAL_INFO("vcl.osx", __func__ << ": mbFullScreen=" << mbFullScreen << ", bFullScreen=" << bFullScreen);
+
+ if( mbFullScreen == bFullScreen )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( ShowFullScreen( bFullScreen, nDisplay ) )
+
+ mbFullScreen = bFullScreen;
+
+ if( bFullScreen )
+ {
+ // hide the dock and the menubar if we are on the menu screen
+ // which is always on index 0 according to documentation
+ bool bHideMenu = (nDisplay == 0);
+
+ NSRect aNewContentRect = NSZeroRect;
+ // get correct screen
+ NSScreen* pScreen = nil;
+ NSArray* pScreens = [NSScreen screens];
+ if( pScreens )
+ {
+ if( nDisplay >= 0 && o3tl::make_unsigned(nDisplay) < [pScreens count] )
+ pScreen = [pScreens objectAtIndex: nDisplay];
+ else
+ {
+ // this means span all screens
+ bHideMenu = true;
+ NSEnumerator* pEnum = [pScreens objectEnumerator];
+ while( (pScreen = [pEnum nextObject]) != nil )
+ {
+ NSRect aScreenRect = [pScreen frame];
+ if( aScreenRect.origin.x < aNewContentRect.origin.x )
+ {
+ aNewContentRect.size.width += aNewContentRect.origin.x - aScreenRect.origin.x;
+ aNewContentRect.origin.x = aScreenRect.origin.x;
+ }
+ if( aScreenRect.origin.y < aNewContentRect.origin.y )
+ {
+ aNewContentRect.size.height += aNewContentRect.origin.y - aScreenRect.origin.y;
+ aNewContentRect.origin.y = aScreenRect.origin.y;
+ }
+ if( aScreenRect.origin.x + aScreenRect.size.width > aNewContentRect.origin.x + aNewContentRect.size.width )
+ aNewContentRect.size.width = aScreenRect.origin.x + aScreenRect.size.width - aNewContentRect.origin.x;
+ if( aScreenRect.origin.y + aScreenRect.size.height > aNewContentRect.origin.y + aNewContentRect.size.height )
+ aNewContentRect.size.height = aScreenRect.origin.y + aScreenRect.size.height - aNewContentRect.origin.y;
+ }
+ }
+ }
+ if( aNewContentRect.size.width == 0 && aNewContentRect.size.height == 0 )
+ {
+ if( pScreen == nil )
+ pScreen = [mpNSWindow screen];
+ if( pScreen == nil )
+ pScreen = [NSScreen mainScreen];
+
+ aNewContentRect = [pScreen frame];
+ }
+
+ if( bHideMenu )
+ [NSMenu setMenuBarVisible:NO];
+
+ maFullScreenRect = [mpNSWindow frame];
+
+ [mpNSWindow setFrame: [NSWindow frameRectForContentRect: aNewContentRect styleMask: mnStyleMask] display: mbShown ? YES : NO];
+ }
+ else
+ {
+ [mpNSWindow setFrame: maFullScreenRect display: mbShown ? YES : NO];
+
+ // show the dock and the menubar
+ [NSMenu setMenuBarVisible:YES];
+ }
+
+ UpdateFrameGeometry();
+ if (mbShown)
+ {
+ CallCallback(SalEvent::MoveResize, nullptr);
+
+ // trigger filling our backbuffer
+ SendPaintEvent();
+ }
+}
+
+void AquaSalFrame::StartPresentation( bool bStart )
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( StartPresentation( bStart ) )
+
+ if( bStart )
+ {
+ GetSalData()->maPresentationFrames.push_back( this );
+ IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
+ kIOPMAssertionLevelOn,
+ CFSTR("LibreOffice presentation running"),
+ &mnAssertionID);
+ [mpNSWindow setLevel: NSPopUpMenuWindowLevel];
+ if( mbShown )
+ [mpNSWindow makeMainWindow];
+ }
+ else
+ {
+ GetSalData()->maPresentationFrames.remove( this );
+ IOPMAssertionRelease(mnAssertionID);
+ [mpNSWindow setLevel: NSNormalWindowLevel];
+ }
+}
+
+void AquaSalFrame::SetAlwaysOnTop( bool )
+{
+}
+
+void AquaSalFrame::ToTop(SalFrameToTop nFlags)
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( ToTop( nFlags ) )
+
+ if( ! (nFlags & SalFrameToTop::RestoreWhenMin) )
+ {
+ if( ! [mpNSWindow isVisible] || [mpNSWindow isMiniaturized] )
+ return;
+ }
+ if( nFlags & SalFrameToTop::GrabFocus )
+ [mpNSWindow makeKeyAndOrderFront: NSApp];
+ else
+ [mpNSWindow orderFront: NSApp];
+}
+
+NSCursor* AquaSalFrame::getCurrentCursor()
+{
+ OSX_SALDATA_RUNINMAIN_POINTER( getCurrentCursor(), NSCursor* )
+
+ NSCursor* pCursor = nil;
+ switch( mePointerStyle )
+ {
+ case PointerStyle::Text: pCursor = [NSCursor IBeamCursor]; break;
+ case PointerStyle::Cross: pCursor = [NSCursor crosshairCursor]; break;
+ case PointerStyle::Hand:
+ case PointerStyle::Move: pCursor = [NSCursor openHandCursor]; break;
+ case PointerStyle::NSize: pCursor = [NSCursor resizeUpCursor]; break;
+ case PointerStyle::SSize: pCursor = [NSCursor resizeDownCursor]; break;
+ case PointerStyle::ESize: pCursor = [NSCursor resizeRightCursor]; break;
+ case PointerStyle::WSize: pCursor = [NSCursor resizeLeftCursor]; break;
+ case PointerStyle::Arrow: pCursor = [NSCursor arrowCursor]; break;
+ case PointerStyle::VSplit:
+ case PointerStyle::VSizeBar:
+ case PointerStyle::WindowNSize:
+ case PointerStyle::WindowSSize:
+ pCursor = [NSCursor resizeUpDownCursor]; break;
+ case PointerStyle::HSplit:
+ case PointerStyle::HSizeBar:
+ case PointerStyle::WindowESize:
+ case PointerStyle::WindowWSize:
+ pCursor = [NSCursor resizeLeftRightCursor]; break;
+ case PointerStyle::RefHand: pCursor = [NSCursor pointingHandCursor]; break;
+
+ default:
+ pCursor = GetSalData()->getCursor( mePointerStyle );
+ if( pCursor == nil )
+ {
+ assert( false && "unmapped cursor" );
+ pCursor = [NSCursor arrowCursor];
+ }
+ break;
+ }
+ return pCursor;
+}
+
+void AquaSalFrame::SetPointer( PointerStyle ePointerStyle )
+{
+ if ( !mpNSWindow )
+ return;
+ if( ePointerStyle == mePointerStyle )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetPointer( ePointerStyle ) )
+
+ mePointerStyle = ePointerStyle;
+
+ [mpNSWindow invalidateCursorRectsForView: mpNSView];
+}
+
+void AquaSalFrame::SetPointerPos( tools::Long nX, tools::Long nY )
+{
+ OSX_SALDATA_RUNINMAIN( SetPointerPos( nX, nY ) )
+
+ // FIXME: use Cocoa functions
+ // FIXME: multiscreen support
+ CGPoint aPoint = { static_cast<CGFloat>(nX + maGeometry.x()), static_cast<CGFloat>(nY + maGeometry.y()) };
+ CGDirectDisplayID mainDisplayID = CGMainDisplayID();
+ CGDisplayMoveCursorToPoint( mainDisplayID, aPoint );
+}
+
+void AquaSalFrame::Flush()
+{
+ if( !(mbGraphics && mpGraphics && mpNSView && mbShown) )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( Flush() )
+
+ [mpNSView setNeedsDisplay: YES];
+
+ // outside of the application's event loop (e.g. IntroWindow)
+ // nothing would trigger paint event handling
+ // => fall back to synchronous painting
+ if( mbForceFlush || ImplGetSVData()->maAppData.mnDispatchLevel <= 0 )
+ {
+ mbForceFlush = false;
+ mpGraphics->Flush();
+ // Related: tdf#155266 skip redisplay of the view when forcing flush
+ // It appears that calling -[NSView display] overwhelms some Intel Macs
+ // so only flush the graphics and skip immediate redisplay of the view.
+ if( ImplGetSVData()->maAppData.mnDispatchLevel <= 0 )
+ [mpNSView display];
+ }
+}
+
+void AquaSalFrame::Flush( const tools::Rectangle& rRect )
+{
+ if( !(mbGraphics && mpGraphics && mpNSView && mbShown) )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( Flush( rRect ) )
+
+ NSRect aNSRect = { { static_cast<CGFloat>(rRect.Left()), static_cast<CGFloat>(rRect.Top()) }, { static_cast<CGFloat>(rRect.GetWidth()), static_cast<CGFloat>(rRect.GetHeight()) } };
+ VCLToCocoa( aNSRect, false );
+ [mpNSView setNeedsDisplayInRect: aNSRect];
+
+ // outside of the application's event loop (e.g. IntroWindow)
+ // nothing would trigger paint event handling
+ // => fall back to synchronous painting
+ if( mbForceFlush || ImplGetSVData()->maAppData.mnDispatchLevel <= 0 )
+ {
+ mbForceFlush = false;
+ mpGraphics->Flush( rRect );
+ // Related: tdf#155266 skip redisplay of the view when forcing flush
+ // It appears that calling -[NSView display] overwhelms some Intel Macs
+ // so only flush the graphics and skip immediate redisplay of the view.
+ if( ImplGetSVData()->maAppData.mnDispatchLevel <= 0 )
+ [mpNSView display];
+ }
+}
+
+void AquaSalFrame::SetInputContext( SalInputContext* pContext )
+{
+ if (!pContext)
+ {
+ mnICOptions = InputContextFlags::NONE;
+ return;
+ }
+
+ mnICOptions = pContext->mnOptions;
+
+ if(!(pContext->mnOptions & InputContextFlags::Text))
+ return;
+}
+
+void AquaSalFrame::EndExtTextInput( EndExtTextInputFlags nFlags )
+{
+ // tdf#82115 Commit uncommitted text when a popup menu is opened
+ // The Windows implementation of this method commits or discards the native
+ // input method session. It appears that very few, if any, macOS
+ // applications discard the uncommitted text when cancelling a session so
+ // always commit the uncommitted text.
+ SalFrameWindow *pWindow = static_cast<SalFrameWindow*>(mpNSWindow);
+ if (pWindow && [pWindow isKindOfClass:[SalFrameWindow class]])
+ [pWindow endExtTextInput:nFlags];
+}
+
+OUString AquaSalFrame::GetKeyName( sal_uInt16 nKeyCode )
+{
+ static std::map< sal_uInt16, OUString > aKeyMap;
+ if( aKeyMap.empty() )
+ {
+ sal_uInt16 i;
+ for( i = KEY_A; i <= KEY_Z; i++ )
+ aKeyMap[ i ] = OUString( sal_Unicode( 'A' + (i - KEY_A) ) );
+ for( i = KEY_0; i <= KEY_9; i++ )
+ aKeyMap[ i ] = OUString( sal_Unicode( '0' + (i - KEY_0) ) );
+ for( i = KEY_F1; i <= KEY_F26; i++ )
+ {
+ aKeyMap[ i ] = "F" + OUString::number(i - KEY_F1 + 1);
+ }
+
+ aKeyMap[ KEY_DOWN ] = OUString( u'\x21e3' );
+ aKeyMap[ KEY_UP ] = OUString( u'\x21e1' );
+ aKeyMap[ KEY_LEFT ] = OUString( u'\x21e0' );
+ aKeyMap[ KEY_RIGHT ] = OUString( u'\x21e2' );
+ aKeyMap[ KEY_HOME ] = OUString( u'\x2196' );
+ aKeyMap[ KEY_END ] = OUString( u'\x2198' );
+ aKeyMap[ KEY_PAGEUP ] = OUString( u'\x21de' );
+ aKeyMap[ KEY_PAGEDOWN ] = OUString( u'\x21df' );
+ aKeyMap[ KEY_RETURN ] = OUString( u'\x21a9' );
+ aKeyMap[ KEY_ESCAPE ] = "esc";
+ aKeyMap[ KEY_TAB ] = OUString( u'\x21e5' );
+ aKeyMap[ KEY_BACKSPACE ]= OUString( u'\x232b' );
+ aKeyMap[ KEY_SPACE ] = OUString( u'\x2423' );
+ aKeyMap[ KEY_DELETE ] = OUString( u'\x2326' );
+ aKeyMap[ KEY_ADD ] = "+";
+ aKeyMap[ KEY_SUBTRACT ] = "-";
+ aKeyMap[ KEY_DIVIDE ] = "/";
+ aKeyMap[ KEY_MULTIPLY ] = "*";
+ aKeyMap[ KEY_POINT ] = ".";
+ aKeyMap[ KEY_COMMA ] = ",";
+ aKeyMap[ KEY_LESS ] = "<";
+ aKeyMap[ KEY_GREATER ] = ">";
+ aKeyMap[ KEY_EQUAL ] = "=";
+ aKeyMap[ KEY_OPEN ] = OUString( u'\x23cf' );
+ aKeyMap[ KEY_TILDE ] = "~";
+ aKeyMap[ KEY_BRACKETLEFT ] = "[";
+ aKeyMap[ KEY_BRACKETRIGHT ] = "]";
+ aKeyMap[ KEY_SEMICOLON ] = ";";
+ aKeyMap[ KEY_QUOTERIGHT ] = "'";
+ aKeyMap[ KEY_RIGHTCURLYBRACKET ] = "}";
+ aKeyMap[ KEY_NUMBERSIGN ] = "#";
+ aKeyMap[ KEY_COLON ] = ":";
+
+ /* yet unmapped KEYCODES:
+ aKeyMap[ KEY_INSERT ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_CUT ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_COPY ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_PASTE ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_UNDO ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_REPEAT ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_FIND ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_PROPERTIES ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_FRONT ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_CONTEXTMENU ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_MENU ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_HELP ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_HANGUL_HANJA ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_DECIMAL ] = OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_QUOTELEFT ]= OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_CAPSLOCK ]= OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_NUMLOCK ]= OUString( sal_Unicode( ) );
+ aKeyMap[ KEY_SCROLLLOCK ]= OUString( sal_Unicode( ) );
+ */
+
+ }
+
+ OUStringBuffer aResult( 16 );
+
+ sal_uInt16 nUnmodifiedCode = (nKeyCode & KEY_CODE_MASK);
+ std::map< sal_uInt16, OUString >::const_iterator it = aKeyMap.find( nUnmodifiedCode );
+ if( it != aKeyMap.end() )
+ {
+ if( (nKeyCode & KEY_SHIFT) != 0 )
+ aResult.append( u'\x21e7' ); // shift
+ if( (nKeyCode & KEY_MOD1) != 0 )
+ aResult.append( u'\x2318' ); // command
+ if( (nKeyCode & KEY_MOD2) != 0 )
+ aResult.append( u'\x2325' ); // alternate
+ if( (nKeyCode & KEY_MOD3) != 0 )
+ aResult.append( u'\x2303' ); // control
+
+ aResult.append( it->second );
+ }
+
+ return aResult.makeStringAndClear();
+}
+
+static void getAppleScrollBarVariant(StyleSettings &rSettings)
+{
+ bool bIsScrollbarDoubleMax = true; // default is DoubleMax
+
+ CFStringRef AppleScrollBarType = CFSTR("AppleScrollBarVariant");
+ if( AppleScrollBarType )
+ {
+ CFStringRef ScrollBarVariant = static_cast<CFStringRef>(CFPreferencesCopyAppValue( AppleScrollBarType, kCFPreferencesCurrentApplication ));
+ if( ScrollBarVariant )
+ {
+ if( CFGetTypeID( ScrollBarVariant ) == CFStringGetTypeID() )
+ {
+ // TODO: check for the less important variants "DoubleMin" and "DoubleBoth" too
+ CFStringRef DoubleMax = CFSTR("DoubleMax");
+ if (DoubleMax)
+ {
+ if ( !CFStringCompare(ScrollBarVariant, DoubleMax, kCFCompareCaseInsensitive) )
+ bIsScrollbarDoubleMax = true;
+ else
+ bIsScrollbarDoubleMax = false;
+ CFRelease(DoubleMax);
+ }
+ }
+ CFRelease( ScrollBarVariant );
+ }
+ CFRelease(AppleScrollBarType);
+ }
+
+ GetSalData()->mbIsScrollbarDoubleMax = bIsScrollbarDoubleMax;
+
+ CFStringRef jumpScroll = CFSTR("AppleScrollerPagingBehavior");
+ if( jumpScroll )
+ {
+ CFBooleanRef jumpStr = static_cast<CFBooleanRef>(CFPreferencesCopyAppValue( jumpScroll, kCFPreferencesCurrentApplication ));
+ if( jumpStr )
+ {
+ if( CFGetTypeID( jumpStr ) == CFBooleanGetTypeID() )
+ rSettings.SetPrimaryButtonWarpsSlider(jumpStr == kCFBooleanTrue);
+ CFRelease( jumpStr );
+ }
+ CFRelease( jumpScroll );
+ }
+}
+
+static Color getNSBoxBackgroundColor(NSColor* pSysColor)
+{
+ // Figuring out what a NSBox will draw for windowBackground, etc. seems very difficult.
+ // So just draw to a 1x1 surface and read what actually gets drawn
+ // This is similar to getPixel
+#if defined OSL_BIGENDIAN
+ struct
+ {
+ unsigned char b, g, r, a;
+ } aPixel;
+#else
+ struct
+ {
+ unsigned char a, r, g, b;
+ } aPixel;
+#endif
+
+ // create a one-pixel bitmap context
+ CGContextRef xOnePixelContext = CGBitmapContextCreate(
+ &aPixel, 1, 1, 8, 32, GetSalData()->mxRGBSpace,
+ uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Big));
+
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:xOnePixelContext flipped:NO];
+
+ NSRect rect = { NSZeroPoint, NSMakeSize(1, 1) };
+ NSBox* pBox = [[NSBox alloc] initWithFrame: rect];
+
+ [pBox setBoxType: NSBoxCustom];
+ [pBox setFillColor: pSysColor];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH // setBorderType first deprecated in macOS 10.15
+ [pBox setBorderType: NSNoBorder];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ [pBox displayRectIgnoringOpacity: rect inContext: graphicsContext];
+
+ [pBox release];
+
+ CGContextRelease(xOnePixelContext);
+
+ return Color(aPixel.r, aPixel.g, aPixel.b);
+}
+
+static Color getColor( NSColor* pSysColor, const Color& rDefault, NSWindow* pWin )
+{
+ Color aRet( rDefault );
+ if( pSysColor )
+ {
+ // transform to RGB
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'colorUsingColorSpaceName:device:' is deprecated: first deprecated in macOS 10.14 -
+ // Use -colorUsingType: or -colorUsingColorSpace: instead"
+ NSColor* pRBGColor = [pSysColor colorUsingColorSpaceName: NSDeviceRGBColorSpace device: [pWin deviceDescription]];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ if( pRBGColor )
+ {
+ CGFloat r = 0, g = 0, b = 0, a = 0;
+ [pRBGColor getRed: &r green: &g blue: &b alpha: &a];
+ aRet = Color( int(r*255.999), int(g*255.999), int(b*255.999) );
+ }
+ }
+ return aRet;
+}
+
+static vcl::Font getFont( NSFont* pFont, sal_Int32 nDPIY, const vcl::Font& rDefault )
+{
+ vcl::Font aResult( rDefault );
+ if( pFont )
+ {
+ aResult.SetFamilyName( GetOUString( [pFont familyName] ) );
+ aResult.SetFontHeight( static_cast<int>(ceil([pFont pointSize] * 72.0 / static_cast<float>(nDPIY))) );
+ aResult.SetItalic( ([pFont italicAngle] != 0.0) ? ITALIC_NORMAL : ITALIC_NONE );
+ // FIMXE: bold ?
+ }
+
+ return aResult;
+}
+
+void AquaSalFrame::getResolution( sal_Int32& o_rDPIX, sal_Int32& o_rDPIY )
+{
+ OSX_SALDATA_RUNINMAIN( getResolution( o_rDPIX, o_rDPIY ) )
+
+ if( ! mpGraphics )
+ {
+ AcquireGraphics();
+ ReleaseGraphics( mpGraphics );
+ }
+ mpGraphics->GetResolution( o_rDPIX, o_rDPIY );
+}
+
+void AquaSalFrame::UpdateDarkMode()
+{
+ if (@available(macOS 10.14, iOS 13, *))
+ {
+ switch (MiscSettings::GetDarkMode())
+ {
+ case 0: // auto
+ default:
+ [NSApp setAppearance: nil];
+ break;
+ case 1: // light
+ [NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameAqua]];
+ break;
+ case 2: // dark
+ [NSApp setAppearance: [NSAppearance appearanceNamed: NSAppearanceNameDarkAqua]];
+ break;
+ }
+ }
+}
+
+bool AquaSalFrame::GetUseDarkMode() const
+{
+ if (!mpNSView)
+ return false;
+ bool bUseDarkMode(false);
+ if (@available(macOS 10.14, iOS 13, *))
+ {
+ NSAppearanceName match = [mpNSView.effectiveAppearance bestMatchFromAppearancesWithNames: @[
+ NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
+ bUseDarkMode = [match isEqualToString: NSAppearanceNameDarkAqua];
+ }
+ return bUseDarkMode;
+}
+
+bool AquaSalFrame::GetUseReducedAnimation() const
+{
+ return [[NSWorkspace sharedWorkspace] accessibilityDisplayShouldReduceMotion];
+}
+
+// on OSX-Aqua the style settings are independent of the frame, so it does
+// not really belong here. Since the connection to the Appearance_Manager
+// is currently done in salnativewidgets.cxx this would be a good place.
+// On the other hand VCL's platform independent code currently only asks
+// SalFrames for system settings anyway, so moving the code somewhere else
+// doesn't make the anything cleaner for now
+void AquaSalFrame::UpdateSettings( AllSettings& rSettings )
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( UpdateSettings( rSettings ) )
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'lockFocus' is deprecated: first deprecated in macOS 10.14 - To draw, subclass NSView
+ // and implement -drawRect:; AppKit's automatic deferred display mechanism will call
+ // -drawRect: as necessary to display the view."
+ if (![mpNSView lockFocusIfCanDraw])
+ return;
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'setCurrentAppearance:' is deprecated: first deprecated in macOS 12.0 - Use
+ // -performAsCurrentDrawingAppearance: to temporarily set the drawing appearance, or
+ // +currentDrawingAppearance to access the currently drawing appearance."
+ [NSAppearance setCurrentAppearance: mpNSView.effectiveAppearance];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ StyleSettings aStyleSettings = rSettings.GetStyleSettings();
+
+ bool bUseDarkMode(GetUseDarkMode());
+ OUString sThemeName(!bUseDarkMode ? u"sukapura" : u"sukapura_dark");
+ aStyleSettings.SetPreferredIconTheme(sThemeName, bUseDarkMode);
+
+ Color aControlBackgroundColor(getNSBoxBackgroundColor([NSColor controlBackgroundColor]));
+ Color aWindowBackgroundColor(getNSBoxBackgroundColor([NSColor windowBackgroundColor]));
+ Color aUnderPageBackgroundColor(getNSBoxBackgroundColor([NSColor underPageBackgroundColor]));
+
+ // Background Color
+ aStyleSettings.BatchSetBackgrounds( aWindowBackgroundColor, false );
+ aStyleSettings.SetLightBorderColor( aWindowBackgroundColor );
+
+ aStyleSettings.SetActiveTabColor(aWindowBackgroundColor);
+ Color aInactiveTabColor( aWindowBackgroundColor );
+ aInactiveTabColor.DecreaseLuminance( 32 );
+ aStyleSettings.SetInactiveTabColor( aInactiveTabColor );
+
+ Color aShadowColor = getColor( [NSColor systemGrayColor ],
+ aStyleSettings.GetShadowColor(), mpNSWindow );
+ aStyleSettings.SetShadowColor( aShadowColor );
+
+ // tdf#152284 for DarkMode brighten it, while darken for BrightMode
+ NSColor* pDarkColor = bUseDarkMode ? [[NSColor systemGrayColor] highlightWithLevel: 0.5]
+ : [[NSColor systemGrayColor] shadowWithLevel: 0.5];
+ Color aDarkShadowColor = getColor( pDarkColor, aStyleSettings.GetDarkShadowColor(), mpNSWindow );
+ aStyleSettings.SetDarkShadowColor(aDarkShadowColor);
+
+ // get the system font settings
+ vcl::Font aAppFont = aStyleSettings.GetAppFont();
+ sal_Int32 nDPIX = 72, nDPIY = 72;
+ getResolution( nDPIX, nDPIY );
+ aAppFont = getFont( [NSFont systemFontOfSize: 0], nDPIY, aAppFont );
+
+ aStyleSettings.SetToolbarIconSize( ToolbarIconSize::Large );
+
+ // TODO: better mapping of macOS<->LibreOffice font settings
+ vcl::Font aLabelFont( getFont( [NSFont labelFontOfSize: 0], nDPIY, aAppFont ) );
+ aStyleSettings.BatchSetFonts( aAppFont, aLabelFont );
+ vcl::Font aMenuFont( getFont( [NSFont menuFontOfSize: 0], nDPIY, aAppFont ) );
+ aStyleSettings.SetMenuFont( aMenuFont );
+
+ vcl::Font aTitleFont( getFont( [NSFont titleBarFontOfSize: 0], nDPIY, aAppFont ) );
+ aStyleSettings.SetTitleFont( aTitleFont );
+ aStyleSettings.SetFloatTitleFont( aTitleFont );
+
+ vcl::Font aTooltipFont(getFont([NSFont toolTipsFontOfSize: 0], nDPIY, aAppFont));
+ aStyleSettings.SetHelpFont(aTooltipFont);
+
+ Color aAccentColor( getColor( [NSColor controlAccentColor],
+ aStyleSettings.GetAccentColor(), mpNSWindow ) );
+ aStyleSettings.SetAccentColor( aAccentColor );
+
+ Color aHighlightColor( getColor( [NSColor selectedTextBackgroundColor],
+ aStyleSettings.GetHighlightColor(), mpNSWindow ) );
+ aStyleSettings.SetHighlightColor( aHighlightColor );
+ Color aHighlightTextColor( getColor( [NSColor selectedTextColor],
+ aStyleSettings.GetHighlightTextColor(), mpNSWindow ) );
+ aStyleSettings.SetHighlightTextColor( aHighlightTextColor );
+
+ aStyleSettings.SetLinkColor(getColor( [NSColor linkColor],
+ aStyleSettings.GetLinkColor(), mpNSWindow ) );
+ aStyleSettings.SetVisitedLinkColor(getColor( [NSColor purpleColor],
+ aStyleSettings.GetVisitedLinkColor(), mpNSWindow ) );
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ Color aMenuHighlightColor( getColor( [NSColor selectedMenuItemColor],
+ aStyleSettings.GetMenuHighlightColor(), mpNSWindow ) );
+#pragma clang diagnostic pop
+ aStyleSettings.SetMenuHighlightColor( aMenuHighlightColor );
+ Color aMenuHighlightTextColor( getColor( [NSColor selectedMenuItemTextColor],
+ aStyleSettings.GetMenuHighlightTextColor(), mpNSWindow ) );
+ aStyleSettings.SetMenuHighlightTextColor( aMenuHighlightTextColor );
+
+ aStyleSettings.SetMenuColor( aWindowBackgroundColor );
+ Color aMenuTextColor( getColor( [NSColor textColor],
+ aStyleSettings.GetMenuTextColor(), mpNSWindow ) );
+ aStyleSettings.SetMenuTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarRolloverTextColor( aMenuTextColor );
+ aStyleSettings.SetMenuBarHighlightTextColor(aStyleSettings.GetMenuHighlightTextColor());
+
+ aStyleSettings.SetListBoxWindowBackgroundColor( aWindowBackgroundColor );
+ aStyleSettings.SetListBoxWindowTextColor( aMenuTextColor );
+ aStyleSettings.SetListBoxWindowHighlightColor( aMenuHighlightColor );
+ aStyleSettings.SetListBoxWindowHighlightTextColor( aMenuHighlightTextColor );
+
+ // FIXME: Starting with macOS Big Sur, coloring has changed. Currently there is no documentation which system color should be
+ // used for some button states and for selected tab text. As a workaround the current OS version has to be considered. This code
+ // has to be reviewed once issue is covered by documentation.
+
+ // Set text colors for buttons and their different status according to OS settings, typically white for selected buttons,
+ // black otherwise
+
+ NSOperatingSystemVersion aOSVersion = { .majorVersion = 10, .minorVersion = 16, .patchVersion = 0 };
+ Color aControlTextColor(getColor([NSColor controlTextColor], COL_BLACK, mpNSWindow ));
+ Color aSelectedControlTextColor(getColor([NSColor selectedControlTextColor], COL_BLACK, mpNSWindow ));
+ Color aAlternateSelectedControlTextColor(getColor([NSColor alternateSelectedControlTextColor], COL_WHITE, mpNSWindow ));
+ aStyleSettings.SetWindowColor(aWindowBackgroundColor);
+ aStyleSettings.SetListBoxWindowBackgroundColor(aWindowBackgroundColor);
+
+ aStyleSettings.SetDialogTextColor(aControlTextColor);
+ aStyleSettings.SetButtonTextColor(aControlTextColor);
+ aStyleSettings.SetActionButtonTextColor(aControlTextColor);
+ aStyleSettings.SetRadioCheckTextColor(aControlTextColor);
+ aStyleSettings.SetGroupTextColor(aControlTextColor);
+ aStyleSettings.SetLabelTextColor(aControlTextColor);
+ aStyleSettings.SetWindowTextColor(aControlTextColor);
+ aStyleSettings.SetFieldTextColor(aControlTextColor);
+
+ aStyleSettings.SetFieldRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetFieldColor(aControlBackgroundColor);
+ aStyleSettings.SetDefaultActionButtonTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetFlatButtonTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultButtonRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultActionButtonRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetActionButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetFlatButtonRolloverTextColor(aControlTextColor);
+ aStyleSettings.SetDefaultButtonPressedRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetDefaultActionButtonPressedRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetFlatButtonPressedRolloverTextColor(aControlTextColor);
+ if ([NSProcessInfo.processInfo isOperatingSystemAtLeastVersion: aOSVersion])
+ {
+ aStyleSettings.SetDefaultButtonTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetButtonPressedRolloverTextColor(aSelectedControlTextColor);
+ aStyleSettings.SetActionButtonPressedRolloverTextColor(aSelectedControlTextColor);
+ }
+ else
+ {
+ aStyleSettings.SetButtonPressedRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetActionButtonPressedRolloverTextColor(aAlternateSelectedControlTextColor);
+ aStyleSettings.SetDefaultButtonTextColor(aSelectedControlTextColor);
+ }
+
+ aStyleSettings.SetWorkspaceColor(aUnderPageBackgroundColor);
+
+ aStyleSettings.SetHelpColor(aControlBackgroundColor);
+ aStyleSettings.SetHelpTextColor(aControlTextColor);
+ aStyleSettings.SetToolTextColor(aControlTextColor);
+
+ // Set text colors for tabs according to OS settings
+
+ aStyleSettings.SetTabTextColor(aControlTextColor);
+ aStyleSettings.SetTabRolloverTextColor(aControlTextColor);
+ if ([NSProcessInfo.processInfo isOperatingSystemAtLeastVersion: aOSVersion])
+ aStyleSettings.SetTabHighlightTextColor(aSelectedControlTextColor);
+ else
+ aStyleSettings.SetTabHighlightTextColor(aAlternateSelectedControlTextColor);
+
+ aStyleSettings.SetCursorBlinkTime( mnBlinkCursorDelay );
+ aStyleSettings.SetCursorSize(1);
+
+ // no mnemonics on macOS
+ aStyleSettings.SetOptions( aStyleSettings.GetOptions() | StyleSettingsOptions::NoMnemonics );
+
+ getAppleScrollBarVariant(aStyleSettings);
+
+ // set scrollbar size
+ aStyleSettings.SetScrollBarSize( static_cast<tools::Long>([NSScroller scrollerWidthForControlSize:NSControlSizeRegular scrollerStyle:NSScrollerStyleLegacy]) );
+ // images in menus false for MacOSX
+ aStyleSettings.SetPreferredUseImagesInMenus( false );
+ aStyleSettings.SetHideDisabledMenuItems( true );
+ aStyleSettings.SetPreferredContextMenuShortcuts( false );
+
+ rSettings.SetStyleSettings( aStyleSettings );
+
+ // don't draw frame around each and every toolbar
+ ImplGetSVData()->maNWFData.mbDockingAreaAvoidTBFrames = true;
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'unlockFocus' is deprecated: first deprecated in macOS 10.14 - To draw, subclass NSView
+ // and implement -drawRect:; AppKit's automatic deferred display mechanism will call
+ // -drawRect: as necessary to display the view."
+ [mpNSView unlockFocus];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+const SystemEnvData* AquaSalFrame::GetSystemData() const
+{
+ return &maSysData;
+}
+
+void AquaSalFrame::Beep()
+{
+ OSX_SALDATA_RUNINMAIN( Beep() )
+ NSBeep();
+}
+
+void AquaSalFrame::SetPosSize(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight, sal_uInt16 nFlags)
+{
+ if (!mpNSWindow && !Application::IsBitmapRendering())
+ return;
+
+ OSX_SALDATA_RUNINMAIN( SetPosSize( nX, nY, nWidth, nHeight, nFlags ) )
+
+ SalEvent nEvent = PreparePosSize(nX, nY, nWidth, nHeight, nFlags);
+ if (Application::IsBitmapRendering())
+ return;
+
+ if( [mpNSWindow isMiniaturized] )
+ [mpNSWindow deminiaturize: NSApp]; // expand the window
+
+ NSRect aFrameRect = [mpNSWindow frame];
+ NSRect aContentRect = [NSWindow contentRectForFrameRect: aFrameRect styleMask: mnStyleMask];
+
+ // position is always relative to parent frame
+ NSRect aParentContentRect;
+
+ if( mpParent )
+ {
+ if( AllSettings::GetLayoutRTL() )
+ {
+ if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 )
+ nX = static_cast<tools::Long>(mpParent->maGeometry.width()) - nWidth - 1 - nX;
+ else
+ nX = static_cast<tools::Long>(mpParent->maGeometry.width()) - aContentRect.size.width - 1 - nX;
+ }
+ NSRect aParentFrameRect = [mpParent->mpNSWindow frame];
+ aParentContentRect = [NSWindow contentRectForFrameRect: aParentFrameRect styleMask: mpParent->mnStyleMask];
+ }
+ else
+ aParentContentRect = maScreenRect; // use screen if no parent
+
+ CocoaToVCL( aContentRect );
+ CocoaToVCL( aParentContentRect );
+
+ bool bPaint = false;
+ if( (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT)) != 0 )
+ {
+ if( nWidth != aContentRect.size.width || nHeight != aContentRect.size.height )
+ bPaint = true;
+ }
+
+ // use old window pos if no new pos requested
+ if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 )
+ aContentRect.origin.x = nX + aParentContentRect.origin.x;
+ if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0)
+ aContentRect.origin.y = nY + aParentContentRect.origin.y;
+
+ // use old size if no new size requested
+ if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 )
+ aContentRect.size.width = nWidth;
+ if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0)
+ aContentRect.size.height = nHeight;
+
+ VCLToCocoa( aContentRect );
+
+ // do not display yet, we need to update our backbuffer
+ {
+ [mpNSWindow setFrame: [NSWindow frameRectForContentRect: aContentRect styleMask: mnStyleMask] display: NO];
+ }
+
+ UpdateFrameGeometry();
+
+ if (nEvent != SalEvent::NONE)
+ CallCallback(nEvent, nullptr);
+
+ if( mbShown && bPaint )
+ {
+ // trigger filling our backbuffer
+ SendPaintEvent();
+
+ // now inform the system that the views need to be drawn
+ [mpNSWindow display];
+ }
+}
+
+void AquaSalFrame::GetWorkArea( AbsoluteScreenPixelRectangle& rRect )
+{
+ if (!mpNSWindow)
+ {
+ if (Application::IsBitmapRendering())
+ rRect = AbsoluteScreenPixelRectangle(AbsoluteScreenPixelPoint(0, 0), AbsoluteScreenPixelSize(1024, 768));
+ return;
+ }
+
+ OSX_SALDATA_RUNINMAIN( GetWorkArea( rRect ) )
+
+ NSScreen* pScreen = [mpNSWindow screen];
+ if( pScreen == nil )
+ pScreen = [NSScreen mainScreen];
+ NSRect aRect = [pScreen visibleFrame];
+ CocoaToVCL( aRect );
+ rRect.SetLeft( static_cast<tools::Long>(aRect.origin.x) );
+ rRect.SetTop( static_cast<tools::Long>(aRect.origin.y) );
+ rRect.SetRight( static_cast<tools::Long>(aRect.origin.x + aRect.size.width - 1) );
+ rRect.SetBottom( static_cast<tools::Long>(aRect.origin.y + aRect.size.height - 1) );
+}
+
+SalFrame::SalPointerState AquaSalFrame::GetPointerState()
+{
+ OSX_SALDATA_RUNINMAIN_UNION( GetPointerState(), state )
+
+ SalPointerState state;
+ state.mnState = 0;
+
+ // get position
+ NSPoint aPt = [mpNSWindow mouseLocationOutsideOfEventStream];
+ CocoaToVCL( aPt, false );
+ state.maPos = Point(static_cast<tools::Long>(aPt.x), static_cast<tools::Long>(aPt.y));
+
+ NSEvent* pCur = [NSApp currentEvent];
+ bool bMouseEvent = false;
+ if( pCur )
+ {
+ bMouseEvent = true;
+ switch( [pCur type] )
+ {
+ case NSEventTypeLeftMouseDown:
+ state.mnState |= MOUSE_LEFT;
+ break;
+ case NSEventTypeLeftMouseUp:
+ break;
+ case NSEventTypeRightMouseDown:
+ state.mnState |= MOUSE_RIGHT;
+ break;
+ case NSEventTypeRightMouseUp:
+ break;
+ case NSEventTypeOtherMouseDown:
+ state.mnState |= ([pCur buttonNumber] == 2) ? MOUSE_MIDDLE : 0;
+ break;
+ case NSEventTypeOtherMouseUp:
+ break;
+ case NSEventTypeMouseMoved:
+ break;
+ case NSEventTypeLeftMouseDragged:
+ state.mnState |= MOUSE_LEFT;
+ break;
+ case NSEventTypeRightMouseDragged:
+ state.mnState |= MOUSE_RIGHT;
+ break;
+ case NSEventTypeOtherMouseDragged:
+ state.mnState |= ([pCur buttonNumber] == 2) ? MOUSE_MIDDLE : 0;
+ break;
+ default:
+ bMouseEvent = false;
+ break;
+ }
+ }
+ if( bMouseEvent )
+ {
+ unsigned int nMask = static_cast<unsigned int>([pCur modifierFlags]);
+ if( (nMask & NSEventModifierFlagShift) != 0 )
+ state.mnState |= KEY_SHIFT;
+ if( (nMask & NSEventModifierFlagControl) != 0 )
+ state.mnState |= KEY_MOD3;
+ if( (nMask & NSEventModifierFlagOption) != 0 )
+ state.mnState |= KEY_MOD2;
+ if( (nMask & NSEventModifierFlagCommand) != 0 )
+ state.mnState |= KEY_MOD1;
+
+ }
+ else
+ {
+ // FIXME: replace Carbon by Cocoa
+ // Cocoa does not have an equivalent for GetCurrentEventButtonState
+ // and GetCurrentEventKeyModifiers.
+ // we could try to get away with tracking all events for modifierKeys
+ // and all mouse events for button state in VCL_NSApplication::sendEvent,
+ // but it is unclear whether this will get us the same result.
+ // leave in GetCurrentEventButtonState and GetCurrentEventKeyModifiers for now
+
+ // fill in button state
+ UInt32 nState = GetCurrentEventButtonState();
+ state.mnState = 0;
+ if( nState & 1 )
+ state.mnState |= MOUSE_LEFT; // primary button
+ if( nState & 2 )
+ state.mnState |= MOUSE_RIGHT; // secondary button
+ if( nState & 4 )
+ state.mnState |= MOUSE_MIDDLE; // tertiary button
+
+ // fill in modifier state
+ nState = GetCurrentEventKeyModifiers();
+ if( nState & shiftKey )
+ state.mnState |= KEY_SHIFT;
+ if( nState & controlKey )
+ state.mnState |= KEY_MOD3;
+ if( nState & optionKey )
+ state.mnState |= KEY_MOD2;
+ if( nState & cmdKey )
+ state.mnState |= KEY_MOD1;
+ }
+
+ return state;
+}
+
+KeyIndicatorState AquaSalFrame::GetIndicatorState()
+{
+ return KeyIndicatorState::NONE;
+}
+
+void AquaSalFrame::SimulateKeyPress( sal_uInt16 /*nKeyCode*/ )
+{
+}
+
+void AquaSalFrame::SetPluginParent( SystemParentData* )
+{
+ // plugin parent may be killed unexpectedly by
+ // plugging process;
+
+ //TODO: implement
+}
+
+bool AquaSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , vcl::KeyCode& )
+{
+ // not supported yet
+ return false;
+}
+
+LanguageType AquaSalFrame::GetInputLanguage()
+{
+ //TODO: implement
+ return LANGUAGE_DONTKNOW;
+}
+
+void AquaSalFrame::SetMenu( SalMenu* pSalMenu )
+{
+ OSX_SALDATA_RUNINMAIN( SetMenu( pSalMenu ) )
+
+ AquaSalMenu* pMenu = static_cast<AquaSalMenu*>(pSalMenu);
+ SAL_WARN_IF( pMenu && !pMenu->mbMenuBar, "vcl", "setting non menubar on frame" );
+ mpMenu = pMenu;
+ if( mpMenu )
+ mpMenu->setMainMenu();
+}
+
+void AquaSalFrame::SetExtendedFrameStyle( SalExtStyle nStyle )
+{
+ if ( !mpNSWindow )
+ {
+ mnExtStyle = nStyle;
+ return;
+ }
+
+ OSX_SALDATA_RUNINMAIN( SetExtendedFrameStyle( nStyle ) )
+
+ if( (mnExtStyle & SAL_FRAME_EXT_STYLE_DOCMODIFIED) != (nStyle & SAL_FRAME_EXT_STYLE_DOCMODIFIED) )
+ [mpNSWindow setDocumentEdited: (nStyle & SAL_FRAME_EXT_STYLE_DOCMODIFIED) ? YES : NO];
+
+ mnExtStyle = nStyle;
+}
+
+SalFrame* AquaSalFrame::GetParent() const
+{
+ return mpParent;
+}
+
+void AquaSalFrame::SetParent( SalFrame* pNewParent )
+{
+ bool bShown = mbShown;
+ // remove from child list
+ if (bShown)
+ Show(false);
+ mpParent = static_cast<AquaSalFrame*>(pNewParent);
+ // insert to correct parent and paint
+ Show( bShown );
+}
+
+void AquaSalFrame::UpdateFrameGeometry()
+{
+ mbGeometryDidChange = false;
+
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( UpdateFrameGeometry() )
+
+ // keep in mind that view and window coordinates are lower left
+ // whereas vcl's are upper left
+
+ // update screen rect
+ NSScreen * pScreen = [mpNSWindow screen];
+ if( pScreen )
+ {
+ NSRect aNewScreenRect = [pScreen frame];
+ if (!NSEqualRects(maScreenRect, aNewScreenRect))
+ {
+ mbGeometryDidChange = true;
+ maScreenRect = aNewScreenRect;
+ }
+ NSArray* pScreens = [NSScreen screens];
+ if( pScreens )
+ {
+ unsigned int nNewDisplayScreenNumber = [pScreens indexOfObject: pScreen];
+ if (maGeometry.screen() != nNewDisplayScreenNumber)
+ {
+ mbGeometryDidChange = true;
+ maGeometry.setScreen(nNewDisplayScreenNumber);
+ }
+ }
+ }
+
+ NSRect aFrameRect = [mpNSWindow frame];
+ NSRect aContentRect = [NSWindow contentRectForFrameRect: aFrameRect styleMask: mnStyleMask];
+
+ NSRect aTrackRect = { NSZeroPoint, aContentRect.size };
+
+ if (!NSEqualRects(maTrackingRect, aTrackRect))
+ {
+ mbGeometryDidChange = true;
+ maTrackingRect = aTrackRect;
+ }
+
+ // convert to vcl convention
+ CocoaToVCL( aFrameRect );
+ CocoaToVCL( aContentRect );
+
+ if (!NSEqualRects(maContentRect, aContentRect) || !NSEqualRects(maFrameRect, aFrameRect))
+ {
+ mbGeometryDidChange = true;
+
+ maContentRect = aContentRect;
+ maFrameRect = aFrameRect;
+
+ maGeometry.setX(static_cast<sal_Int32>(aContentRect.origin.x));
+ maGeometry.setY(static_cast<sal_Int32>(aContentRect.origin.y));
+ maGeometry.setWidth(static_cast<sal_uInt32>(aContentRect.size.width));
+ maGeometry.setHeight(static_cast<sal_uInt32>(aContentRect.size.height));
+
+ maGeometry.setLeftDecoration(static_cast<sal_uInt32>(aContentRect.origin.x - aFrameRect.origin.x));
+ maGeometry.setRightDecoration(static_cast<sal_uInt32>((aFrameRect.origin.x + aFrameRect.size.width) -
+ (aContentRect.origin.x + aContentRect.size.width)));
+ maGeometry.setTopDecoration(static_cast<sal_uInt32>(aContentRect.origin.y - aFrameRect.origin.y));
+ maGeometry.setBottomDecoration(static_cast<sal_uInt32>((aFrameRect.origin.y + aFrameRect.size.height) -
+ (aContentRect.origin.y + aContentRect.size.height)));
+ }
+}
+
+void AquaSalFrame::CaptureMouse( bool bCapture )
+{
+ /* Remark:
+ we'll try to use a pidgin version of capture mouse
+ on MacOSX (neither carbon nor cocoa) there is a
+ CaptureMouse equivalent (in Carbon there is TrackMouseLocation
+ but this is useless to use since it is blocking)
+
+ However on cocoa the active frame seems to get mouse events
+ also outside the window, so we'll try to forward mouse events
+ to the capture frame in the hope that one of our frames
+ gets a mouse event.
+
+ This will break as soon as the user activates another app, but
+ a mouse click will normally lead to a release of the mouse anyway.
+
+ Let's see how far we get this way. Alternatively we could use one
+ large overlay window like we did for the carbon implementation,
+ however that is resource intensive.
+ */
+
+ if( bCapture )
+ s_pCaptureFrame = this;
+ else if( ! bCapture && s_pCaptureFrame == this )
+ s_pCaptureFrame = nullptr;
+}
+
+void AquaSalFrame::ResetClipRegion()
+{
+ doResetClipRegion();
+}
+
+void AquaSalFrame::doResetClipRegion()
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( ResetClipRegion() )
+
+ // release old path and indicate no clipping
+ CGPathRelease( mrClippingPath );
+ mrClippingPath = nullptr;
+
+ if( mpNSView && mbShown )
+ [mpNSView setNeedsDisplay: YES];
+ [mpNSWindow setOpaque: YES];
+ [mpNSWindow invalidateShadow];
+}
+
+void AquaSalFrame::BeginSetClipRegion( sal_uInt32 nRects )
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( BeginSetClipRegion( nRects ) )
+
+ // release old path
+ if( mrClippingPath )
+ {
+ CGPathRelease( mrClippingPath );
+ mrClippingPath = nullptr;
+ }
+
+ if( maClippingRects.size() > SAL_CLIPRECT_COUNT && nRects < maClippingRects.size() )
+ {
+ std::vector<CGRect>().swap(maClippingRects);
+ }
+ maClippingRects.clear();
+ maClippingRects.reserve( nRects );
+}
+
+void AquaSalFrame::UnionClipRegion(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ if( nWidth && nHeight )
+ {
+ NSRect aRect = { { static_cast<CGFloat>(nX), static_cast<CGFloat>(nY) }, { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) } };
+ VCLToCocoa( aRect, false );
+ maClippingRects.push_back( CGRectMake(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height) );
+ }
+}
+
+void AquaSalFrame::EndSetClipRegion()
+{
+ if ( !mpNSWindow )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( EndSetClipRegion() )
+
+ if( ! maClippingRects.empty() )
+ {
+ mrClippingPath = CGPathCreateMutable();
+ CGPathAddRects( mrClippingPath, nullptr, maClippingRects.data(), maClippingRects.size() );
+ }
+ if( mpNSView && mbShown )
+ [mpNSView setNeedsDisplay: YES];
+ [mpNSWindow setOpaque: (mrClippingPath != nullptr) ? NO : YES];
+ [mpNSWindow setBackgroundColor: [NSColor clearColor]];
+ // shadow is invalidated when view gets drawn again
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm
new file mode 100644
index 0000000000..995eeb5749
--- /dev/null
+++ b/vcl/osx/salframeview.mm
@@ -0,0 +1,2595 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <memory>
+
+#include <sal/macros.h>
+#include <tools/helpers.hxx>
+#include <tools/long.hxx>
+#include <vcl/event.hxx>
+#include <vcl/inputctx.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/commandevent.hxx>
+
+#include <osx/a11yfactory.h>
+#include <osx/salframe.h>
+#include <osx/salframeview.h>
+#include <osx/salinst.h>
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+
+#define WHEEL_EVENT_FACTOR 1.5
+
+static sal_uInt16 ImplGetModifierMask( unsigned int nMask )
+{
+ sal_uInt16 nRet = 0;
+ if( (nMask & NSEventModifierFlagShift) != 0 )
+ nRet |= KEY_SHIFT;
+ if( (nMask & NSEventModifierFlagControl) != 0 )
+ nRet |= KEY_MOD3;
+ if( (nMask & NSEventModifierFlagOption) != 0 )
+ nRet |= KEY_MOD2;
+ if( (nMask & NSEventModifierFlagCommand) != 0 )
+ nRet |= KEY_MOD1;
+ return nRet;
+}
+
+static sal_uInt16 ImplMapCharCode( sal_Unicode aCode )
+{
+ static sal_uInt16 aKeyCodeMap[ 128 ] =
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ KEY_BACKSPACE, KEY_TAB, KEY_RETURN, 0, 0, KEY_RETURN, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, KEY_TAB, 0, KEY_ESCAPE, 0, 0, 0, 0,
+ KEY_SPACE, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, KEY_MULTIPLY, KEY_ADD, KEY_COMMA, KEY_SUBTRACT, KEY_POINT, KEY_DIVIDE,
+ KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7,
+ KEY_8, KEY_9, 0, 0, KEY_LESS, KEY_EQUAL, KEY_GREATER, 0,
+ 0, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G,
+ KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O,
+ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W,
+ KEY_X, KEY_Y, KEY_Z, 0, 0, 0, 0, 0,
+ KEY_QUOTELEFT, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G,
+ KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O,
+ KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W,
+ KEY_X, KEY_Y, KEY_Z, 0, 0, 0, KEY_TILDE, KEY_BACKSPACE
+ };
+
+ // Note: the mapping 0x7f should by rights be KEY_DELETE
+ // however if you press "backspace" 0x7f is reported
+ // whereas for "delete" 0xf728 gets reported
+
+ // Note: the mapping of 0x19 to KEY_TAB is because for unknown reasons
+ // tab alone is reported as 0x09 (as expected) but shift-tab is
+ // reported as 0x19 (end of medium)
+
+ static sal_uInt16 aFunctionKeyCodeMap[ 128 ] =
+ {
+ KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_F1, KEY_F2, KEY_F3, KEY_F4,
+ KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12,
+ KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20,
+ KEY_F21, KEY_F22, KEY_F23, KEY_F24, KEY_F25, KEY_F26, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, KEY_INSERT,
+ KEY_DELETE, KEY_HOME, 0, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, 0, 0,
+ 0, 0, 0, 0, 0, KEY_MENU, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, KEY_UNDO, KEY_REPEAT, KEY_FIND, KEY_HELP, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0
+ };
+
+ sal_uInt16 nKeyCode = 0;
+ if( aCode < SAL_N_ELEMENTS( aKeyCodeMap) )
+ nKeyCode = aKeyCodeMap[ aCode ];
+ else if( aCode >= 0xf700 && aCode < 0xf780 )
+ nKeyCode = aFunctionKeyCodeMap[ aCode - 0xf700 ];
+ return nKeyCode;
+}
+
+static sal_uInt16 ImplMapKeyCode(sal_uInt16 nKeyCode)
+{
+ /*
+ http://stackoverflow.com/questions/2080312/where-can-i-find-a-list-of-key-codes-for-use-with-cocoas-nsevent-class/2080324#2080324
+ /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
+ */
+
+ static sal_uInt16 aKeyCodeMap[ 0x80 ] =
+ {
+ KEY_A, KEY_S, KEY_D, KEY_F, KEY_H, KEY_G, KEY_Z, KEY_X,
+ KEY_C, KEY_V, 0, KEY_B, KEY_Q, KEY_W, KEY_E, KEY_R,
+ KEY_Y, KEY_T, KEY_1, KEY_2, KEY_3, KEY_4, KEY_6, KEY_5,
+ KEY_EQUAL, KEY_9, KEY_7, KEY_SUBTRACT, KEY_8, KEY_0, KEY_BRACKETRIGHT, KEY_RIGHTCURLYBRACKET,
+ KEY_U, KEY_BRACKETLEFT, KEY_I, KEY_P, KEY_RETURN, KEY_L, KEY_J, KEY_QUOTERIGHT,
+ KEY_K, KEY_SEMICOLON, 0, KEY_COMMA, KEY_DIVIDE, KEY_N, KEY_M, KEY_POINT,
+ KEY_TAB, KEY_SPACE, KEY_QUOTELEFT, KEY_DELETE, 0, KEY_ESCAPE, 0, 0,
+ 0, KEY_CAPSLOCK, 0, 0, 0, 0, 0, 0,
+ KEY_F17, KEY_DECIMAL, 0, KEY_MULTIPLY, 0, KEY_ADD, 0, 0,
+ 0, 0, 0, KEY_DIVIDE, KEY_RETURN, 0, KEY_SUBTRACT, KEY_F18,
+ KEY_F19, KEY_EQUAL, 0, 0, 0, 0, 0, 0,
+ 0, 0, KEY_F20, 0, 0, 0, 0, 0,
+ KEY_F5, KEY_F6, KEY_F7, KEY_F3, KEY_F8, KEY_F9, 0, KEY_F11,
+ 0, KEY_F13, KEY_F16, KEY_F14, 0, KEY_F10, 0, KEY_F12,
+ 0, KEY_F15, KEY_HELP, KEY_HOME, KEY_PAGEUP, KEY_DELETE, KEY_F4, KEY_END,
+ KEY_F2, KEY_PAGEDOWN, KEY_F1, KEY_LEFT, KEY_RIGHT, KEY_DOWN, KEY_UP, 0
+ };
+
+ if (nKeyCode < SAL_N_ELEMENTS(aKeyCodeMap))
+ return aKeyCodeMap[nKeyCode];
+ return 0;
+}
+
+// store the frame the mouse last entered
+static AquaSalFrame* s_pMouseFrame = nullptr;
+// store the last pressed button for enter/exit events
+// which lack that information
+static sal_uInt16 s_nLastButton = 0;
+
+static AquaSalFrame* getMouseContainerFrame()
+{
+ AquaSalFrame* pDispatchFrame = nullptr;
+ NSArray* aWindows = [NSWindow windowNumbersWithOptions:0];
+ for(NSUInteger i = 0; i < [aWindows count] && ! pDispatchFrame; i++ )
+ {
+ NSWindow* pWin = [NSApp windowWithWindowNumber:[[aWindows objectAtIndex:i] integerValue]];
+ if( pWin && [pWin isMemberOfClass: [SalFrameWindow class]] && [static_cast<SalFrameWindow*>(pWin) containsMouse] )
+ pDispatchFrame = [static_cast<SalFrameWindow*>(pWin) getSalFrame];
+ }
+ return pDispatchFrame;
+}
+
+static NSArray *getMergedAccessibilityChildren(NSArray *pDefaultChildren, NSArray *pUnignoredChildrenToAdd)
+{
+ NSArray *pRet = pDefaultChildren;
+
+ if (pUnignoredChildrenToAdd && [pUnignoredChildrenToAdd count])
+ {
+ NSMutableArray *pNewChildren = [NSMutableArray arrayWithCapacity:(pRet ? [pRet count] : 0) + 1];
+ if (pNewChildren)
+ {
+ if (pRet)
+ [pNewChildren addObjectsFromArray:pRet];
+
+ for (AquaA11yWrapper *pWrapper : pUnignoredChildrenToAdd)
+ {
+ if (pWrapper && ![pNewChildren containsObject:pWrapper])
+ [pNewChildren addObject:pWrapper];
+ }
+
+ pRet = pNewChildren;
+ }
+ else
+ {
+ pRet = pUnignoredChildrenToAdd;
+ }
+ }
+
+ return pRet;
+}
+
+// Update ImplGetSVData()->mpWinData->mbIsLiveResize
+static void updateWinDataInLiveResize(bool bInLiveResize)
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData );
+ if ( pSVData )
+ {
+ if ( pSVData->mpWinData->mbIsLiveResize != bInLiveResize )
+ {
+ pSVData->mpWinData->mbIsLiveResize = bInLiveResize;
+ Scheduler::Wakeup();
+ }
+ }
+}
+
+@interface NSResponder (SalFrameWindow)
+-(BOOL)accessibilityIsIgnored;
+@end
+
+@implementation SalFrameWindow
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame
+{
+ mDraggingDestinationHandler = nil;
+ mbInWindowDidResize = NO;
+ mpLiveResizeTimer = nil;
+ mpFrame = pFrame;
+ NSRect aRect = { { static_cast<CGFloat>(pFrame->maGeometry.x()), static_cast<CGFloat>(pFrame->maGeometry.y()) },
+ { static_cast<CGFloat>(pFrame->maGeometry.width()), static_cast<CGFloat>(pFrame->maGeometry.height()) } };
+ pFrame->VCLToCocoa( aRect );
+ NSWindow* pNSWindow = [super initWithContentRect: aRect
+ styleMask: mpFrame->getStyleMask()
+ backing: NSBackingStoreBuffered
+ defer: Application::IsHeadlessModeEnabled()];
+
+ // Disallow full-screen mode on macOS >= 10.11 where it is enabled by default. We don't want it
+ // for now as it will just be confused with LibreOffice's home-grown full-screen concept, with
+ // which it has nothing to do, and one can get into all kinds of weird states by using them
+ // intermixedly.
+
+ // Ideally we should use the system full-screen mode and adapt the code for the home-grown thing
+ // to be in sync with that instead. (And we would then not need the button to get out of
+ // full-screen mode, as the normal way to get out of it is to either click on the green bubble
+ // again, or invoke the keyboard command again.)
+
+ // (Confusingly, at the moment the home-grown full-screen mode is bound to Cmd+Shift+F, which is
+ // the keyboard command normally used in apps to get in and out of the system full-screen mode.)
+
+ // Disabling system full-screen mode makes the green button on the title bar (on macOS >= 10.11)
+ // show a plus sign instead, and clicking it becomes identical to double-clicking the title bar,
+ // i.e. it maximizes / unmaximises the window. Sure, that state can also be confused with LO's
+ // home-grown full-screen mode. Oh well.
+
+ [pNSWindow setCollectionBehavior: NSWindowCollectionBehaviorFullScreenNone];
+
+ // Disable window restoration until we support it directly
+ [pNSWindow setRestorable: NO];
+
+ // tdf#137468: Restrict to 24-bit RGB as that is all that we can
+ // handle anyway. HDR is far off in the future for LibreOffice.
+ [pNSWindow setDynamicDepthLimit: NO];
+ [pNSWindow setDepthLimit: NSWindowDepthTwentyfourBitRGB];
+
+ return static_cast<SalFrameWindow *>(pNSWindow);
+}
+
+-(void)clearLiveResizeTimer
+{
+ if ( mpLiveResizeTimer )
+ {
+ [mpLiveResizeTimer invalidate];
+ [mpLiveResizeTimer release];
+ mpLiveResizeTimer = nil;
+ }
+}
+
+-(void)dealloc
+{
+ [self clearLiveResizeTimer];
+ [super dealloc];
+}
+
+-(AquaSalFrame*)getSalFrame
+{
+ return mpFrame;
+}
+
+-(void)displayIfNeeded
+{
+ if( GetSalData() && GetSalData()->mpInstance )
+ {
+ SolarMutexGuard aGuard;
+ [super displayIfNeeded];
+ }
+}
+
+-(BOOL)containsMouse
+{
+ // is this event actually inside that NSWindow ?
+ NSPoint aPt = [NSEvent mouseLocation];
+ NSRect aFrameRect = [self frame];
+ bool bInRect = NSPointInRect( aPt, aFrameRect );
+ return bInRect;
+}
+
+-(BOOL)canBecomeKeyWindow
+{
+ if( (mpFrame->mnStyle &
+ ( SalFrameStyleFlags::FLOAT |
+ SalFrameStyleFlags::TOOLTIP |
+ SalFrameStyleFlags::INTRO
+ )) == SalFrameStyleFlags::NONE )
+ return YES;
+ if( mpFrame->mnStyle & SalFrameStyleFlags::OWNERDRAWDECORATION )
+ return YES;
+ if( mpFrame->mbFullScreen )
+ return YES;
+ return [super canBecomeKeyWindow];
+}
+
+-(void)windowDidBecomeKey: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ static const SalFrameStyleFlags nGuessDocument = SalFrameStyleFlags::MOVEABLE|
+ SalFrameStyleFlags::SIZEABLE|
+ SalFrameStyleFlags::CLOSEABLE;
+
+ // Reset dark mode colors in HITheme controls after printing
+ // In dark mode, after an NSPrintOperation has completed, macOS draws
+ // HITheme controls with light mode colors so reset all dark mode
+ // colors when an NSWindow gains focus.
+ mpFrame->UpdateDarkMode();
+
+ if( mpFrame->mpMenu )
+ mpFrame->mpMenu->setMainMenu();
+ else if( ! mpFrame->mpParent &&
+ ( (mpFrame->mnStyle & nGuessDocument) == nGuessDocument || // set default menu for e.g. help
+ mpFrame->mbFullScreen ) ) // set default menu for e.g. presentation
+ {
+ AquaSalMenu::setDefaultMenu();
+ }
+ mpFrame->CallCallback( SalEvent::GetFocus, nullptr );
+ mpFrame->SendPaintEvent(); // repaint controls as active
+ }
+
+ // Prevent the same native input method popup that was cancelled in a
+ // previous call to [self windowDidResignKey:] from reappearing
+ [self endExtTextInput];
+}
+
+-(void)windowDidResignKey: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ // Commit any uncommitted text and cancel the native input method session
+ // whenever a window loses focus like in Safari, Firefox, and Excel
+ [self endExtTextInput];
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->CallCallback(SalEvent::LoseFocus, nullptr);
+ mpFrame->SendPaintEvent(); // repaint controls as inactive
+ }
+}
+
+-(void)windowDidChangeScreen: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->screenParametersChanged();
+}
+
+-(void)windowDidMove: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Move, nullptr );
+ }
+}
+
+-(void)windowDidResize: (NSNotification*)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ if ( mbInWindowDidResize )
+ return;
+
+ mbInWindowDidResize = YES;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Resize, nullptr );
+
+ updateWinDataInLiveResize( [self inLiveResize] );
+ if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
+ {
+#if HAVE_FEATURE_SKIA
+ // Related: tdf#152703 Eliminate empty window with Skia/Metal while resizing
+ // The window will clear its background so when Skia/Metal is
+ // enabled, explicitly flush the Skia graphics to the window
+ // during live resizing or else nothing will be drawn until after
+ // live resizing has ended.
+ // Also, flushing during [self windowDidResize:] eliminates flicker
+ // by forcing this window's SkSurface to recreate its underlying
+ // CAMetalLayer with the new size. Flushing in
+ // [self displayIfNeeded] does not eliminate flicker so apparently
+ // [self windowDidResize:] is called earlier.
+ if ( SkiaHelper::isVCLSkiaEnabled() )
+ {
+ AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
+ if ( pGraphics )
+ pGraphics->Flush();
+ }
+#endif
+
+ // tdf#152703 Force relayout during live resizing of window
+ // During a live resize, macOS floods the application with
+ // windowDidResize: notifications so sending a paint event does
+ // not trigger redrawing with the new size.
+ // Instead, force relayout by dispatching all pending internal
+ // events and firing any pending timers.
+ // Also, Application::Reschedule() can potentially display a
+ // modal dialog which will cause a hang so temporarily disable
+ // live resize by clamping the window's minimum and maximum sizes
+ // to the current frame size which in Application::Reschedule().
+ NSRect aFrame = [self frame];
+ NSSize aMinSize = [self minSize];
+ NSSize aMaxSize = [self maxSize];
+ [self setMinSize:aFrame.size];
+ [self setMaxSize:aFrame.size];
+ Application::Reschedule( true );
+ [self setMinSize:aMinSize];
+ [self setMaxSize:aMaxSize];
+
+ if ( ImplGetSVData()->mpWinData->mbIsLiveResize )
+ {
+ // tdf#152703 Force repaint after live resizing ends
+ // Repost this notification so that this selector will be called
+ // at least once after live resizing ends
+ if ( !mpLiveResizeTimer )
+ {
+ mpLiveResizeTimer = [NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(windowDidResizeWithTimer:) userInfo:pNotification repeats:YES];
+ if ( mpLiveResizeTimer )
+ {
+ [mpLiveResizeTimer retain];
+
+ // The timer won't fire without a call to
+ // Application::Reschedule() unless we copy the fix for
+ // #i84055# from vcl/osx/saltimer.cxx and add the timer
+ // to the NSEventTrackingRunLoopMode run loop mode
+ [[NSRunLoop currentRunLoop] addTimer:mpLiveResizeTimer forMode:NSEventTrackingRunLoopMode];
+ }
+ }
+ }
+ }
+ else
+ {
+ [self clearLiveResizeTimer];
+ }
+
+ // tdf#158461 eliminate flicker during live resizing
+ // When using Skia/Metal, the window content will flicker while
+ // live resizing a window if we don't send a paint event.
+ mpFrame->SendPaintEvent();
+ }
+
+ mbInWindowDidResize = NO;
+}
+
+-(void)windowDidMiniaturize: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mbShown = false;
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Resize, nullptr );
+ }
+}
+
+-(void)windowDidDeminiaturize: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mbShown = true;
+ mpFrame->UpdateFrameGeometry();
+ mpFrame->CallCallback( SalEvent::Resize, nullptr );
+ }
+}
+
+-(BOOL)windowShouldClose: (NSNotification*)pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ bool bRet = true;
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ // #i84461# end possible input
+ [self endExtTextInput];
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->CallCallback( SalEvent::Close, nullptr );
+ bRet = false; // application will close the window or not, AppKit shouldn't
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ assert( pTimer );
+ pTimer->handleWindowShouldClose();
+ }
+ }
+
+ return bRet;
+}
+
+-(void)windowDidEnterFullScreen: (NSNotification*)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ if( !mpFrame || !AquaSalFrame::isAlive( mpFrame))
+ return;
+ mpFrame->mbFullScreen = true;
+ (void)pNotification;
+}
+
+-(void)windowDidExitFullScreen: (NSNotification*)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ if( !mpFrame || !AquaSalFrame::isAlive( mpFrame))
+ return;
+ mpFrame->mbFullScreen = false;
+ (void)pNotification;
+}
+
+-(void)windowDidChangeBackingProperties:(NSNotification *)pNotification
+{
+ (void)pNotification;
+#if HAVE_FEATURE_SKIA
+ SolarMutexGuard aGuard;
+
+ sal::aqua::resetWindowScaling();
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ // tdf#147342 Notify Skia that the window's backing properties changed
+ if ( SkiaHelper::isVCLSkiaEnabled() )
+ {
+ AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
+ if ( pGraphics )
+ pGraphics->WindowBackingPropertiesChanged();
+ }
+ }
+#endif
+}
+
+-(void)windowWillStartLiveResize:(NSNotification *)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ updateWinDataInLiveResize(true);
+}
+
+-(void)windowDidEndLiveResize:(NSNotification *)pNotification
+{
+ SolarMutexGuard aGuard;
+
+ updateWinDataInLiveResize(false);
+}
+
+-(void)dockMenuItemTriggered: (id)sender
+{
+ (void)sender;
+ SolarMutexGuard aGuard;
+
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->ToTop( SalFrameToTop::RestoreWhenMin | SalFrameToTop::GrabFocus );
+}
+
+-(css::uno::Reference < css::accessibility::XAccessibleContext >)accessibleContext
+{
+ return mpFrame -> GetWindow() -> GetAccessible() -> getAccessibleContext();
+}
+
+-(BOOL)isIgnoredWindow
+{
+ SolarMutexGuard aGuard;
+
+ // Treat tooltip windows as ignored
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ return (mpFrame->mnStyle & SalFrameStyleFlags::TOOLTIP) != SalFrameStyleFlags::NONE;
+ return YES;
+}
+
+-(id)accessibilityApplicationFocusedUIElement
+{
+ return [self accessibilityFocusedUIElement];
+}
+
+-(id)accessibilityFocusedUIElement
+{
+ // Treat tooltip windows as ignored
+ if ([self isIgnoredWindow])
+ return nil;
+
+ return [super accessibilityFocusedUIElement];
+}
+
+-(BOOL)accessibilityIsIgnored
+{
+ // Treat tooltip windows as ignored
+ if ([self isIgnoredWindow])
+ return YES;
+
+ return [super accessibilityIsIgnored];
+}
+
+-(BOOL)isAccessibilityElement
+{
+ return ![self accessibilityIsIgnored];
+}
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler draggingEntered: sender];
+}
+
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler draggingUpdated: sender];
+}
+
+-(void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ [mDraggingDestinationHandler draggingExited: sender];
+}
+
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler prepareForDragOperation: sender];
+}
+
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler performDragOperation: sender];
+}
+
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
+{
+ [mDraggingDestinationHandler concludeDragOperation: sender];
+}
+
+-(void)registerDraggingDestinationHandler:(id)theHandler
+{
+ mDraggingDestinationHandler = theHandler;
+}
+
+-(void)unregisterDraggingDestinationHandler:(id)theHandler
+{
+ (void)theHandler;
+ mDraggingDestinationHandler = nil;
+}
+
+-(void)endExtTextInput
+{
+ [self endExtTextInput:EndExtTextInputFlags::Complete];
+}
+
+-(void)endExtTextInput:(EndExtTextInputFlags)nFlags
+{
+ SalFrameView *pView = static_cast<SalFrameView*>([self firstResponder]);
+ if (pView && [pView isKindOfClass:[SalFrameView class]])
+ [pView endExtTextInput:nFlags];
+}
+
+-(void)windowDidResizeWithTimer:(NSTimer *)pTimer
+{
+ if ( pTimer )
+ [self windowDidResize:[pTimer userInfo]];
+}
+
+@end
+
+@implementation SalFrameView
++(void)unsetMouseFrame: (AquaSalFrame*)pFrame
+{
+ if( pFrame == s_pMouseFrame )
+ s_pMouseFrame = nullptr;
+}
+
+-(id)initWithSalFrame: (AquaSalFrame*)pFrame
+{
+ if ((self = [super initWithFrame: [NSWindow contentRectForFrameRect: [pFrame->getNSWindow() frame] styleMask: pFrame->mnStyleMask]]) != nil)
+ {
+ mDraggingDestinationHandler = nil;
+ mpFrame = pFrame;
+ mpChildWrapper = nil;
+ mbNeedChildWrapper = NO;
+ mpLastEvent = nil;
+ mMarkedRange = NSMakeRange(NSNotFound, 0);
+ mSelectedRange = NSMakeRange(NSNotFound, 0);
+ mpMouseEventListener = nil;
+ mpLastSuperEvent = nil;
+ mfLastMagnifyTime = 0.0;
+
+ mbInEndExtTextInput = NO;
+ mbInCommitMarkedText = NO;
+ mpLastMarkedText = nil;
+ mbTextInputWantsNonRepeatKeyDown = NO;
+ }
+
+ return self;
+}
+
+-(void)dealloc
+{
+ [self clearLastEvent];
+ [self clearLastMarkedText];
+ [self revokeWrapper];
+
+ [super dealloc];
+}
+
+-(AquaSalFrame*)getSalFrame
+{
+ return mpFrame;
+}
+
+-(void)resetCursorRects
+{
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ // FIXME: does this leak the returned NSCursor of getCurrentCursor ?
+ const NSRect aRect = { NSZeroPoint, NSMakeSize(mpFrame->maGeometry.width(), mpFrame->maGeometry.height()) };
+ [self addCursorRect: aRect cursor: mpFrame->getCurrentCursor()];
+ }
+}
+
+-(BOOL)acceptsFirstResponder
+{
+ return YES;
+}
+
+-(BOOL)acceptsFirstMouse: (NSEvent*)pEvent
+{
+ (void)pEvent;
+ return YES;
+}
+
+-(BOOL)isOpaque
+{
+ if( !mpFrame)
+ return YES;
+ if( !AquaSalFrame::isAlive( mpFrame))
+ return YES;
+ if( !mpFrame->getClipPath())
+ return YES;
+ return NO;
+}
+
+-(void)drawRect: (NSRect)aRect
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ assert( pSVData );
+ if ( !pSVData )
+ return;
+
+ SolarMutexGuard aGuard;
+ if (!mpFrame || !AquaSalFrame::isAlive(mpFrame))
+ return;
+
+ updateWinDataInLiveResize([self inLiveResize]);
+
+ AquaSalGraphics* pGraphics = mpFrame->mpGraphics;
+ if (pGraphics)
+ {
+ pGraphics->UpdateWindow(aRect);
+ if (mpFrame->getClipPath())
+ [mpFrame->getNSWindow() invalidateShadow];
+ }
+}
+
+-(void)sendMouseEventToFrame: (NSEvent*)pEvent button:(sal_uInt16)nButton eventtype:(SalEvent)nEvent
+{
+ SolarMutexGuard aGuard;
+
+ AquaSalFrame* pDispatchFrame = AquaSalFrame::GetCaptureFrame();
+ bool bIsCaptured = false;
+ if( pDispatchFrame )
+ {
+ bIsCaptured = true;
+ if( nEvent == SalEvent::MouseLeave ) // no leave events if mouse is captured
+ nEvent = SalEvent::MouseMove;
+ }
+ else if( s_pMouseFrame )
+ pDispatchFrame = s_pMouseFrame;
+ else
+ pDispatchFrame = mpFrame;
+
+ /* #i81645# Cocoa reports mouse events while a button is pressed
+ to the window in which it was first pressed. This is reasonable and fine and
+ gets one around most cases where on other platforms one uses CaptureMouse or XGrabPointer,
+ however vcl expects mouse events to occur in the window the mouse is over, unless the
+ mouse is explicitly captured. So we need to find the window the mouse is actually
+ over for conformance with other platforms.
+ */
+ if( ! bIsCaptured && nButton && pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) )
+ {
+ // is this event actually inside that NSWindow ?
+ NSPoint aPt = [NSEvent mouseLocation];
+ NSRect aFrameRect = [pDispatchFrame->getNSWindow() frame];
+
+ if ( ! NSPointInRect( aPt, aFrameRect ) )
+ {
+ // no, it is not
+ // now we need to find the one it may be in
+ /* #i93756# we ant to get enumerate the application windows in z-order
+ to check if any contains the mouse. This could be elegantly done with this
+ code:
+
+ // use NSApp to check windows in ZOrder whether they contain the mouse pointer
+ NSWindow* pWindow = [NSApp makeWindowsPerform: @selector(containsMouse) inOrder: YES];
+ if( pWindow && [pWindow isMemberOfClass: [SalFrameWindow class]] )
+ pDispatchFrame = [(SalFrameWindow*)pWindow getSalFrame];
+
+ However if a non SalFrameWindow is on screen (like e.g. the file dialog)
+ it can be hit with the containsMouse selector, which it doesn't support.
+ Sadly NSApplication:makeWindowsPerform does not check (for performance reasons
+ I assume) whether a window supports a selector before sending it.
+ */
+ AquaSalFrame* pMouseFrame = getMouseContainerFrame();
+ if( pMouseFrame )
+ pDispatchFrame = pMouseFrame;
+ }
+ }
+
+ if( pDispatchFrame && AquaSalFrame::isAlive( pDispatchFrame ) )
+ {
+ pDispatchFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ pDispatchFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ NSPoint aPt = [NSEvent mouseLocation];
+ pDispatchFrame->CocoaToVCL( aPt );
+
+ sal_uInt16 nModMask = ImplGetModifierMask( [pEvent modifierFlags] );
+ // #i82284# emulate ctrl left
+ if( nModMask == KEY_MOD3 && nButton == MOUSE_LEFT )
+ {
+ nModMask = 0;
+ nButton = MOUSE_RIGHT;
+ }
+
+ SalMouseEvent aEvent;
+ aEvent.mnTime = pDispatchFrame->mnLastEventTime;
+ aEvent.mnX = static_cast<tools::Long>(aPt.x) - pDispatchFrame->maGeometry.x();
+ aEvent.mnY = static_cast<tools::Long>(aPt.y) - pDispatchFrame->maGeometry.y();
+ aEvent.mnButton = nButton;
+ aEvent.mnCode = aEvent.mnButton | nModMask;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = pDispatchFrame->maGeometry.width() - 1 - aEvent.mnX;
+
+ pDispatchFrame->CallCallback( nEvent, &aEvent );
+
+ // tdf#155266 force flush after scrolling
+ if (nButton == MOUSE_LEFT && nEvent == SalEvent::MouseMove)
+ mpFrame->mbForceFlush = true;
+ }
+}
+
+-(void)mouseDown: (NSEvent*)pEvent
+{
+ if ( mpMouseEventListener != nil &&
+ [mpMouseEventListener respondsToSelector: @selector(mouseDown:)])
+ {
+ [mpMouseEventListener mouseDown: [pEvent copyWithZone: nullptr]];
+ }
+
+ s_nLastButton = MOUSE_LEFT;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonDown];
+}
+
+-(void)mouseDragged: (NSEvent*)pEvent
+{
+ if ( mpMouseEventListener != nil &&
+ [mpMouseEventListener respondsToSelector: @selector(mouseDragged:)])
+ {
+ [mpMouseEventListener mouseDragged: [pEvent copyWithZone: nullptr]];
+ }
+ s_nLastButton = MOUSE_LEFT;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseMove];
+}
+
+-(void)mouseUp: (NSEvent*)pEvent
+{
+ s_nLastButton = 0;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_LEFT eventtype:SalEvent::MouseButtonUp];
+}
+
+-(void)mouseMoved: (NSEvent*)pEvent
+{
+ s_nLastButton = 0;
+ [self sendMouseEventToFrame:pEvent button:0 eventtype:SalEvent::MouseMove];
+}
+
+-(void)mouseEntered: (NSEvent*)pEvent
+{
+ s_pMouseFrame = mpFrame;
+
+ // #i107215# the only mouse events we get when inactive are enter/exit
+ // actually we would like to have all of them, but better none than some
+ if( [NSApp isActive] )
+ [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseMove];
+}
+
+-(void)mouseExited: (NSEvent*)pEvent
+{
+ if( s_pMouseFrame == mpFrame )
+ s_pMouseFrame = nullptr;
+
+ // #i107215# the only mouse events we get when inactive are enter/exit
+ // actually we would like to have all of them, but better none than some
+ if( [NSApp isActive] )
+ [self sendMouseEventToFrame:pEvent button:s_nLastButton eventtype:SalEvent::MouseLeave];
+}
+
+-(void)rightMouseDown: (NSEvent*)pEvent
+{
+ s_nLastButton = MOUSE_RIGHT;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonDown];
+}
+
+-(void)rightMouseDragged: (NSEvent*)pEvent
+{
+ s_nLastButton = MOUSE_RIGHT;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseMove];
+}
+
+-(void)rightMouseUp: (NSEvent*)pEvent
+{
+ s_nLastButton = 0;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_RIGHT eventtype:SalEvent::MouseButtonUp];
+}
+
+-(void)otherMouseDown: (NSEvent*)pEvent
+{
+ if( [pEvent buttonNumber] == 2 )
+ {
+ s_nLastButton = MOUSE_MIDDLE;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonDown];
+ }
+ else
+ s_nLastButton = 0;
+}
+
+-(void)otherMouseDragged: (NSEvent*)pEvent
+{
+ if( [pEvent buttonNumber] == 2 )
+ {
+ s_nLastButton = MOUSE_MIDDLE;
+ [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseMove];
+ }
+ else
+ s_nLastButton = 0;
+}
+
+-(void)otherMouseUp: (NSEvent*)pEvent
+{
+ s_nLastButton = 0;
+ if( [pEvent buttonNumber] == 2 )
+ [self sendMouseEventToFrame:pEvent button:MOUSE_MIDDLE eventtype:SalEvent::MouseButtonUp];
+}
+
+- (void)magnifyWithEvent: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ // TODO: ?? -(float)magnification;
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ const NSTimeInterval fMagnifyTime = [pEvent timestamp];
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( fMagnifyTime * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ // check if this is a new series of magnify events
+ static const NSTimeInterval fMaxDiffTime = 0.3;
+ const bool bNewSeries = (fMagnifyTime - mfLastMagnifyTime > fMaxDiffTime);
+
+ if( bNewSeries )
+ mfMagnifyDeltaSum = 0.0;
+ mfMagnifyDeltaSum += [pEvent magnification];
+
+ mfLastMagnifyTime = [pEvent timestamp];
+// TODO: change to 0.1 when CommandWheelMode::ZOOM handlers allow finer zooming control
+ static const float fMagnifyFactor = 0.25*500; // steps are 500 times smaller for -magnification
+ static const float fMinMagnifyStep = 15.0 / fMagnifyFactor;
+ if( fabs(mfMagnifyDeltaSum) <= fMinMagnifyStep )
+ return;
+
+ // adapt NSEvent-sensitivity to application expectations
+ // TODO: rather make CommandWheelMode::ZOOM handlers smarter
+ const float fDeltaZ = mfMagnifyDeltaSum * fMagnifyFactor;
+ int nDeltaZ = FRound( fDeltaZ );
+ if( !nDeltaZ )
+ {
+ // handle new series immediately
+ if( !bNewSeries )
+ return;
+ nDeltaZ = (fDeltaZ >= 0.0) ? +1 : -1;
+ }
+ // eventually give credit for delta sum
+ mfMagnifyDeltaSum -= nDeltaZ / fMagnifyFactor;
+
+ NSPoint aPt = [NSEvent mouseLocation];
+ mpFrame->CocoaToVCL( aPt );
+
+ SalWheelMouseEvent aEvent;
+ aEvent.mnTime = mpFrame->mnLastEventTime;
+ aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->maGeometry.x();
+ aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->maGeometry.y();
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mnCode |= KEY_MOD1; // we want zooming, no scrolling
+ aEvent.mbDeltaIsPixel = true;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.width() - 1 - aEvent.mnX;
+
+ aEvent.mnDelta = nDeltaZ;
+ aEvent.mnNotchDelta = (nDeltaZ >= 0) ? +1 : -1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = false;
+ sal_uInt32 nScrollLines = nDeltaZ;
+ if (nScrollLines == 0)
+ nScrollLines = 1;
+ aEvent.mnScrollLines = nScrollLines;
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+}
+
+- (void)rotateWithEvent: (NSEvent*)pEvent
+{
+ //Rotation : -(float)rotation;
+ // TODO: create new CommandType so rotation is available to the applications
+ (void)pEvent;
+}
+
+- (void)swipeWithEvent: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ // merge pending scroll wheel events
+ CGFloat dX = 0.0;
+ CGFloat dY = 0.0;
+ for(;;)
+ {
+ dX += [pEvent deltaX];
+ dY += [pEvent deltaY];
+ NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskScrollWheel
+ untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ];
+ if( !pNextEvent )
+ break;
+ pEvent = pNextEvent;
+ }
+
+ NSPoint aPt = [NSEvent mouseLocation];
+ mpFrame->CocoaToVCL( aPt );
+
+ SalWheelMouseEvent aEvent;
+ aEvent.mnTime = mpFrame->mnLastEventTime;
+ aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->maGeometry.x();
+ aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->maGeometry.y();
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mbDeltaIsPixel = true;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.width() - 1 - aEvent.mnX;
+
+ if( dX != 0.0 )
+ {
+ aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX));
+ aEvent.mnNotchDelta = (dX < 0) ? -1 : +1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = true;
+ aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+ if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ))
+ {
+ aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY));
+ aEvent.mnNotchDelta = (dY < 0) ? -1 : +1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = false;
+ aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL;
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+ }
+}
+
+-(void)scrollWheel: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ // merge pending scroll wheel events
+ CGFloat dX = 0.0;
+ CGFloat dY = 0.0;
+ for(;;)
+ {
+ dX += [pEvent deltaX];
+ dY += [pEvent deltaY];
+ NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSEventMaskScrollWheel
+ untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ];
+ if( !pNextEvent )
+ break;
+ pEvent = pNextEvent;
+ }
+
+ NSPoint aPt = [NSEvent mouseLocation];
+ mpFrame->CocoaToVCL( aPt );
+
+ SalWheelMouseEvent aEvent;
+ aEvent.mnTime = mpFrame->mnLastEventTime;
+ aEvent.mnX = static_cast<tools::Long>(aPt.x) - mpFrame->maGeometry.x();
+ aEvent.mnY = static_cast<tools::Long>(aPt.y) - mpFrame->maGeometry.y();
+ aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags );
+ aEvent.mbDeltaIsPixel = false;
+
+ if( AllSettings::GetLayoutRTL() )
+ aEvent.mnX = mpFrame->maGeometry.width() - 1 - aEvent.mnX;
+
+ if( dX != 0.0 )
+ {
+ aEvent.mnDelta = static_cast<tools::Long>(dX < 0 ? floor(dX) : ceil(dX));
+ aEvent.mnNotchDelta = (dX < 0) ? -1 : +1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = true;
+ sal_uInt32 nScrollLines = fabs(dX) / WHEEL_EVENT_FACTOR;
+ if (nScrollLines == 0)
+ nScrollLines = 1;
+ aEvent.mnScrollLines = nScrollLines;
+
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+ if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ aEvent.mnDelta = static_cast<tools::Long>(dY < 0 ? floor(dY) : ceil(dY));
+ aEvent.mnNotchDelta = (dY < 0) ? -1 : +1;
+ if( aEvent.mnDelta == 0 )
+ aEvent.mnDelta = aEvent.mnNotchDelta;
+ aEvent.mbHorz = false;
+ sal_uInt32 nScrollLines = fabs(dY) / WHEEL_EVENT_FACTOR;
+ if (nScrollLines == 0)
+ nScrollLines = 1;
+ aEvent.mnScrollLines = nScrollLines;
+
+ mpFrame->CallCallback( SalEvent::WheelMouse, &aEvent );
+ }
+
+ // tdf#155266 force flush after scrolling
+ mpFrame->mbForceFlush = true;
+ }
+}
+
+
+-(void)keyDown: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ // Retain the event as it will be released sometime before a key up
+ // event is dispatched
+ [self clearLastEvent];
+ mpLastEvent = [pEvent retain];
+
+ mbInKeyInput = true;
+ mbNeedSpecialKeyHandle = false;
+ mbKeyHandled = false;
+
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+
+ if( ! [self handleKeyDownException: pEvent] )
+ {
+ sal_uInt16 nKeyCode = ImplMapKeyCode( [pEvent keyCode] );
+ if ( nKeyCode == KEY_DELETE && mbTextInputWantsNonRepeatKeyDown )
+ {
+ // tdf#42437 Enable press-and-hold special character input method
+ // Emulate the press-and-hold behavior of the TextEdit
+ // application by deleting the marked text when only the
+ // Delete key is pressed and keep the marked text when the
+ // Backspace key or Fn-Delete keys are pressed.
+ if ( [pEvent keyCode] == 51 )
+ {
+ [self deleteTextInputWantsNonRepeatKeyDown];
+ }
+ else
+ {
+ [self unmarkText];
+ mbKeyHandled = true;
+ mbInKeyInput = false;
+ }
+
+ [self endExtTextInput];
+ return;
+ }
+
+ NSArray* pArray = [NSArray arrayWithObject: pEvent];
+ [self interpretKeyEvents: pArray];
+
+ // Handle repeat key events by explicitly inserting the text if
+ // -[NSResponder interpretKeyEvents:] does not insert or mark any
+ // text. Note: do not do this step if there is uncommitted text.
+ // Related: tdf#42437 Skip special press-and-hold handling for action keys
+ // Pressing and holding action keys such as arrow keys must not be
+ // handled like pressing and holding a character key as it will
+ // insert unexpected text.
+ if ( !mbKeyHandled && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] )
+ {
+ NSString *pChars = [mpLastEvent characters];
+ if ( pChars )
+ [self insertText:pChars replacementRange:NSMakeRange( 0, [pChars length] )];
+ }
+ // tdf#42437 Enable press-and-hold special character input method
+ // Emulate the press-and-hold behavior of the TextEdit application
+ // by committing an empty string for key down events dispatched
+ // while the special character input method popup is displayed.
+ else if ( mpLastMarkedText && mbTextInputWantsNonRepeatKeyDown && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && ![mpLastEvent isARepeat] )
+ {
+ // If the escape or return key is pressed, unmark the text to
+ // skip deletion of marked text
+ if ( nKeyCode == KEY_ESCAPE || nKeyCode == KEY_RETURN )
+ [self unmarkText];
+ [self insertText:[NSString string] replacementRange:NSMakeRange( NSNotFound, 0 )];
+ }
+ }
+
+ mbInKeyInput = false;
+ }
+}
+
+-(BOOL)handleKeyDownException:(NSEvent*)pEvent
+{
+ // check for a very special set of modified characters
+ NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers];
+
+ if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
+ {
+ /* #i103102# key events with command and alternate don't make it through
+ interpretKeyEvents (why?). Try to dispatch them here first,
+ if not successful continue normally
+ */
+ if( (mpFrame->mnLastModifierFlags & (NSEventModifierFlagOption | NSEventModifierFlagCommand))
+ == (NSEventModifierFlagOption | NSEventModifierFlagCommand) )
+ {
+ if( [self sendSingleCharacter: mpLastEvent] )
+ return YES;
+ }
+ }
+ return NO;
+}
+
+-(void)flagsChanged: (NSEvent*)pEvent
+{
+ SolarMutexGuard aGuard;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ mpFrame->mnLastEventTime = static_cast<sal_uInt64>( [pEvent timestamp] * 1000.0 );
+ mpFrame->mnLastModifierFlags = [pEvent modifierFlags];
+ }
+}
+
+-(void)insertText:(id)aString replacementRange:(NSRange)replacementRange
+{
+ (void) replacementRange; // FIXME: surely it must be used
+
+ SolarMutexGuard aGuard;
+
+ [self deleteTextInputWantsNonRepeatKeyDown];
+
+ // Ignore duplicate events that are sometimes posted during cancellation
+ // of the native input method session. This usually happens when
+ // [self endExtTextInput] is called from [self windowDidBecomeKey:] and,
+ // if the native input method popup, that was cancelled in a
+ // previous call to [self windowDidResignKey:], has reappeared. In such
+ // cases, the native input context posts the reappearing popup's
+ // uncommitted text.
+ if (mbInEndExtTextInput && !mbInCommitMarkedText)
+ return;
+
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ NSString* pInsert = nil;
+ if( [aString isKindOfClass: [NSAttributedString class]] )
+ pInsert = [aString string];
+ else
+ pInsert = aString;
+
+ int nLen = 0;
+ if( pInsert && ( nLen = [pInsert length] ) > 0 )
+ {
+ OUString aInsertString( GetOUString( pInsert ) );
+ // aCharCode initializer is safe since aInsertString will at least contain '\0'
+ sal_Unicode aCharCode = *aInsertString.getStr();
+
+ if( nLen == 1 &&
+ aCharCode < 0x80 &&
+ aCharCode > 0x1f &&
+ ! [self hasMarkedText ]
+ )
+ {
+ sal_uInt16 nKeyCode = ImplMapCharCode( aCharCode );
+ unsigned int nLastModifiers = mpFrame->mnLastModifierFlags;
+
+ // #i99567#
+ // find out the unmodified key code
+
+ // sanity check
+ if( mpLastEvent && ( [mpLastEvent type] == NSEventTypeKeyDown || [mpLastEvent type] == NSEventTypeKeyUp ) )
+ {
+ // get unmodified string
+ NSString* pUnmodifiedString = [mpLastEvent charactersIgnoringModifiers];
+ if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
+ {
+ // map the unmodified key code
+ unichar keyChar = [pUnmodifiedString characterAtIndex: 0];
+ nKeyCode = ImplMapCharCode( keyChar );
+ }
+ nLastModifiers = [mpLastEvent modifierFlags];
+
+ }
+ // #i99567#
+ // applications and vcl's edit fields ignore key events with ALT
+ // however we're at a place where we know text should be inserted
+ // so it seems we need to strip the Alt modifier here
+ if( (nLastModifiers & (NSEventModifierFlagControl | NSEventModifierFlagOption | NSEventModifierFlagCommand))
+ == NSEventModifierFlagOption )
+ {
+ nLastModifiers = 0;
+ }
+ [self sendKeyInputAndReleaseToFrame: nKeyCode character: aCharCode modifiers: nLastModifiers];
+ }
+ else
+ {
+ SalExtTextInputEvent aEvent;
+ aEvent.maText = aInsertString;
+ aEvent.mpTextAttr = nullptr;
+ aEvent.mnCursorPos = aInsertString.getLength();
+ aEvent.mnCursorFlags = 0;
+ mpFrame->CallCallback( SalEvent::ExtTextInput, &aEvent );
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ }
+ }
+ else
+ {
+ SalExtTextInputEvent aEvent;
+ aEvent.maText.clear();
+ aEvent.mpTextAttr = nullptr;
+ aEvent.mnCursorPos = 0;
+ aEvent.mnCursorFlags = 0;
+ mpFrame->CallCallback( SalEvent::ExtTextInput, &aEvent );
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+
+ }
+ [self unmarkText];
+ }
+
+ // Mark event as handled even if the frame isn't valid like is done in
+ // [self setMarkedText:selectedRange:replacementRange:] and
+ // [self doCommandBySelector:]
+ mbKeyHandled = true;
+}
+
+-(void)insertTab: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_TAB character: '\t' modifiers: 0];
+}
+
+-(void)insertBacktab: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: (KEY_TAB | KEY_SHIFT) character: '\t' modifiers: 0];
+}
+
+-(void)moveLeft: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: 0];
+}
+
+-(void)moveLeftAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: NSEventModifierFlagShift];
+}
+
+-(void)moveBackwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveRight: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: 0];
+}
+
+-(void)moveRightAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_RIGHT character: 0 modifiers: NSEventModifierFlagShift];
+}
+
+-(void)moveForwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordLeft: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordBackward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordBackwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordLeftAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordRight: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordForward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordForwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveWordRightAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToRightEndOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfLineAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToRightEndOfLineAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToLeftEndOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfLineAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToLeftEndOfLineAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfParagraphAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveParagraphForward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveParagraphForwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveParagraphBackward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfParagraphAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveParagraphBackwardAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfDocument: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)scrollToEndOfDocument: (id)aSender
+{
+ (void)aSender;
+ // this is not exactly what we should do, but it makes "End" and "Shift-End" behave consistent
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)moveToEndOfDocumentAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_END_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfDocument: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)scrollToBeginningOfDocument: (id)aSender
+{
+ (void)aSender;
+ // this is not exactly what we should do, but it makes "Home" and "Shift-Home" behave consistent
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)moveToBeginningOfDocumentAndModifySelection: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0];
+}
+
+-(void)moveUp: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_UP character: 0 modifiers: 0];
+}
+
+-(void)moveDown: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_DOWN character: 0 modifiers: 0];
+}
+
+-(void)insertNewline: (id)aSender
+{
+ (void)aSender;
+ // #i91267# make enter and shift-enter work by evaluating the modifiers
+ [self sendKeyInputAndReleaseToFrame: KEY_RETURN character: '\n' modifiers: mpFrame->mnLastModifierFlags];
+}
+
+-(void)deleteBackward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '\b' modifiers: 0];
+}
+
+-(void)deleteForward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_DELETE character: 0x7f modifiers: 0];
+}
+
+-(void)deleteBackwardByDecomposingPreviousCharacter: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_BACKSPACE character: '\b' modifiers: 0];
+}
+
+-(void)deleteWordBackward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_WORD_BACKWARD character: 0 modifiers: 0];
+}
+
+-(void)deleteWordForward: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_WORD_FORWARD character: 0 modifiers: 0];
+}
+
+-(void)deleteToBeginningOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_BEGIN_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)deleteToEndOfLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_END_OF_LINE character: 0 modifiers: 0];
+}
+
+-(void)deleteToBeginningOfParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)deleteToEndOfParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::DELETE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)insertLineBreak: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::INSERT_LINEBREAK character: 0 modifiers: 0];
+}
+
+-(void)insertParagraphSeparator: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::INSERT_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)selectWord: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_WORD character: 0 modifiers: 0];
+}
+
+-(void)selectLine: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_LINE character: 0 modifiers: 0];
+}
+
+-(void)selectParagraph: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_PARAGRAPH character: 0 modifiers: 0];
+}
+
+-(void)selectAll: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: css::awt::Key::SELECT_ALL character: 0 modifiers: 0];
+}
+
+-(void)cancelOperation: (id)aSender
+{
+ (void)aSender;
+ [self sendKeyInputAndReleaseToFrame: KEY_ESCAPE character: 0x1b modifiers: 0];
+}
+
+-(void)noop: (id)aSender
+{
+ (void)aSender;
+ if( ! mbKeyHandled )
+ {
+ if( ! [self sendSingleCharacter:mpLastEvent] )
+ {
+ /* prevent recursion */
+ if( mpLastEvent != mpLastSuperEvent && [NSApp respondsToSelector: @selector(sendSuperEvent:)] )
+ {
+ id pLastSuperEvent = mpLastSuperEvent;
+ mpLastSuperEvent = mpLastEvent;
+ [NSApp performSelector:@selector(sendSuperEvent:) withObject: mpLastEvent];
+ mpLastSuperEvent = pLastSuperEvent;
+
+ std::map< NSEvent*, bool >::iterator it = GetSalData()->maKeyEventAnswer.find( mpLastEvent );
+ if( it != GetSalData()->maKeyEventAnswer.end() )
+ it->second = true;
+ }
+ }
+ }
+}
+
+-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar
+{
+ return [self sendKeyInputAndReleaseToFrame: nKeyCode character: aChar modifiers: mpFrame->mnLastModifierFlags];
+}
+
+-(BOOL)sendKeyInputAndReleaseToFrame: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod
+{
+ return [self sendKeyToFrameDirect: nKeyCode character: aChar modifiers: nMod] ||
+ [self sendSingleCharacter: mpLastEvent];
+}
+
+-(BOOL)sendKeyToFrameDirect: (sal_uInt16)nKeyCode character: (sal_Unicode)aChar modifiers: (unsigned int)nMod
+{
+ SolarMutexGuard aGuard;
+
+ bool nRet = false;
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ SalKeyEvent aEvent;
+ aEvent.mnCode = nKeyCode | ImplGetModifierMask( nMod );
+ aEvent.mnCharCode = aChar;
+ aEvent.mnRepeat = FALSE;
+ nRet = mpFrame->CallCallback( SalEvent::KeyInput, &aEvent );
+ std::map< NSEvent*, bool >::iterator it = GetSalData()->maKeyEventAnswer.find( mpLastEvent );
+ if( it != GetSalData()->maKeyEventAnswer.end() )
+ it->second = nRet;
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::KeyUp, &aEvent );
+ }
+ return nRet;
+}
+
+
+-(BOOL)sendSingleCharacter: (NSEvent *)pEvent
+{
+ NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers];
+
+ if( pUnmodifiedString && [pUnmodifiedString length] == 1 )
+ {
+ unichar keyChar = [pUnmodifiedString characterAtIndex: 0];
+ sal_uInt16 nKeyCode = ImplMapCharCode( keyChar );
+ if (nKeyCode == 0)
+ {
+ sal_uInt16 nOtherKeyCode = [pEvent keyCode];
+ nKeyCode = ImplMapKeyCode(nOtherKeyCode);
+ }
+ if( nKeyCode != 0 )
+ {
+ // don't send code points in the private use area
+ if( keyChar >= 0xf700 && keyChar < 0xf780 )
+ keyChar = 0;
+ bool bRet = [self sendKeyToFrameDirect: nKeyCode character: keyChar modifiers: mpFrame->mnLastModifierFlags];
+ mbInKeyInput = false;
+
+ return bRet;
+ }
+ }
+ return NO;
+}
+
+
+// NSTextInput/NSTextInputClient protocol
+- (NSArray *)validAttributesForMarkedText
+{
+ return [NSArray arrayWithObjects:NSUnderlineStyleAttributeName, nil];
+}
+
+- (BOOL)hasMarkedText
+{
+ bool bHasMarkedText;
+
+ bHasMarkedText = ( mMarkedRange.location != NSNotFound ) &&
+ ( mMarkedRange.length != 0 );
+ // hack to check keys like "Control-j"
+ if( mbInKeyInput )
+ {
+ mbNeedSpecialKeyHandle = true;
+ }
+
+ // FIXME:
+ // #i106901#
+ // if we come here outside of mbInKeyInput, this is likely to be because
+ // of the keyboard viewer. For unknown reasons having no marked range
+ // in this case causes a crash. So we say we have a marked range anyway
+ // This is a hack, since it is not understood what a) causes that crash
+ // and b) why we should have a marked range at this point.
+ if( ! mbInKeyInput )
+ bHasMarkedText = true;
+
+ return bHasMarkedText;
+}
+
+- (NSRange)markedRange
+{
+ // FIXME:
+ // #i106901#
+ // if we come here outside of mbInKeyInput, this is likely to be because
+ // of the keyboard viewer. For unknown reasons having no marked range
+ // in this case causes a crash. So we say we have a marked range anyway
+ // This is a hack, since it is not understood what a) causes that crash
+ // and b) why we should have a marked range at this point. Stop the native
+ // input method popup from appearing in the bottom left corner of the
+ // screen by returning the marked range if is valid when called outside of
+ // mbInKeyInput. If a zero length range is returned, macOS won't call
+ // [self firstRectForCharacterRange:actualRange:] for any newly appended
+ // uncommitted text.
+ if( ! mbInKeyInput )
+ return mMarkedRange.location != NSNotFound ? mMarkedRange : NSMakeRange( 0, 0 );
+
+ return [self hasMarkedText] ? mMarkedRange : NSMakeRange( NSNotFound, 0 );
+}
+
+- (NSRange)selectedRange
+{
+ // tdf#42437 Enable press-and-hold special character input method
+ // Always return a valid range location. If the range location is
+ // NSNotFound, -[NSResponder interpretKeyEvents:] will not call
+ // [self firstRectForCharacterRange:actualRange:] and will not display the
+ // special character input method popup.
+ return ( mSelectedRange.location == NSNotFound ? NSMakeRange( 0, 0 ) : mSelectedRange );
+}
+
+- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange replacementRange:(NSRange)replacementRange
+{
+ (void) replacementRange; // FIXME - use it!
+
+ SolarMutexGuard aGuard;
+
+ [self deleteTextInputWantsNonRepeatKeyDown];
+
+ if( ![aString isKindOfClass:[NSAttributedString class]] )
+ aString = [[[NSAttributedString alloc] initWithString:aString] autorelease];
+
+ // Reset cached state
+ [self unmarkText];
+
+ int len = [aString length];
+ SalExtTextInputEvent aInputEvent;
+ if( len > 0 ) {
+ // Set the marked and selected ranges to the marked text and selected
+ // range parameters
+ mMarkedRange = NSMakeRange( 0, [aString length] );
+ if (selRange.location == NSNotFound || selRange.location >= mMarkedRange.length)
+ mSelectedRange = NSMakeRange( NSNotFound, 0 );
+ else
+ mSelectedRange = NSMakeRange( selRange.location, selRange.location + selRange.length > mMarkedRange.length ? mMarkedRange.length - selRange.location : selRange.length );
+
+ // If we are going to post uncommitted text, cache the string parameter
+ // as is needed in both [self endExtTextInput] and
+ // [self attributedSubstringForProposedRange:actualRange:]
+ mpLastMarkedText = [aString retain];
+
+ NSString *pString = [aString string];
+ OUString aInsertString( GetOUString( pString ) );
+ std::vector<ExtTextInputAttr> aInputFlags( std::max( 1, len ), ExtTextInputAttr::NONE );
+ int nSelectionStart = (mSelectedRange.location == NSNotFound ? len : mSelectedRange.location);
+ int nSelectionEnd = (mSelectedRange.location == NSNotFound ? len : mSelectedRange.location + selRange.length);
+ for ( int i = 0; i < len; i++ )
+ {
+ // Highlight all characters in the selected range. Normally
+ // uncommitted text is underlined but when an item is selected in
+ // the native input method popup or selecting a subblock of
+ // uncommitted text using the left or right arrow keys, the
+ // selection range is set and the selected range is either
+ // highlighted like in Excel or is bold underlined like in
+ // Safari. Highlighting the selected range was chosen because
+ // using bold and double underlines can get clipped making the
+ // selection range indistinguishable from the rest of the
+ // uncommitted text.
+ if (i >= nSelectionStart && i < nSelectionEnd)
+ {
+ aInputFlags[i] = ExtTextInputAttr::Highlight;
+ continue;
+ }
+
+ unsigned int nUnderlineValue;
+ NSRange effectiveRange;
+
+ effectiveRange = NSMakeRange(i, 1);
+ nUnderlineValue = [[aString attribute:NSUnderlineStyleAttributeName atIndex:i effectiveRange:&effectiveRange] unsignedIntValue];
+
+ switch (nUnderlineValue & 0xff) {
+ case NSUnderlineStyleSingle:
+ aInputFlags[i] = ExtTextInputAttr::Underline;
+ break;
+ case NSUnderlineStyleThick:
+ aInputFlags[i] = ExtTextInputAttr::BoldUnderline;
+ break;
+ case NSUnderlineStyleDouble:
+ aInputFlags[i] = ExtTextInputAttr::DoubleUnderline;
+ break;
+ default:
+ aInputFlags[i] = ExtTextInputAttr::Highlight;
+ break;
+ }
+ }
+
+ aInputEvent.maText = aInsertString;
+ aInputEvent.mnCursorPos = nSelectionStart;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.mpTextAttr = aInputFlags.data();
+ mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
+ } else {
+ aInputEvent.maText.clear();
+ aInputEvent.mnCursorPos = 0;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.mpTextAttr = nullptr;
+ mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+ }
+ mbKeyHandled= true;
+}
+
+- (void)unmarkText
+{
+ [self clearLastMarkedText];
+
+ mSelectedRange = mMarkedRange = NSMakeRange(NSNotFound, 0);
+}
+
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
+{
+ (void) aRange;
+ (void) actualRange;
+
+ // FIXME - Implement
+ return nil;
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
+{
+ (void)thePoint;
+ // FIXME
+ return 0;
+}
+
+- (NSInteger)conversationIdentifier
+{
+ return reinterpret_cast<long>(self);
+}
+
+- (void)doCommandBySelector:(SEL)aSelector
+{
+ if( AquaSalFrame::isAlive( mpFrame ) )
+ {
+ if( (mpFrame->mnICOptions & InputContextFlags::Text) &&
+ aSelector != nullptr && [self respondsToSelector: aSelector] )
+ {
+ [self performSelector: aSelector];
+ }
+ else
+ {
+ [self sendSingleCharacter:mpLastEvent];
+ }
+ }
+
+ mbKeyHandled = true;
+}
+
+-(void)clearLastEvent
+{
+ if (mpLastEvent)
+ {
+ [mpLastEvent release];
+ mpLastEvent = nil;
+ }
+}
+
+-(void)clearLastMarkedText
+{
+ if (mpLastMarkedText)
+ {
+ [mpLastMarkedText release];
+ mpLastMarkedText = nil;
+ }
+
+ mbTextInputWantsNonRepeatKeyDown = NO;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange
+{
+ // FIXME - These should probably be used?
+ (void) aRange;
+ (void) actualRange;
+
+ SolarMutexGuard aGuard;
+
+ // tdf#42437 Enable press-and-hold special character input method
+ // Some text entry controls, such as Writer comments or the cell editor in
+ // Calc's Formula Bar, need to have an input method session open or else
+ // the returned position won't be anywhere near the text cursor. So,
+ // dispatch an empty SalEvent::ExtTextInput event, fetch the position,
+ // and then dispatch a SalEvent::EndExtTextInput event.
+ NSString *pNewMarkedText = nullptr;
+ NSString *pChars = [mpLastEvent characters];
+ bool bNeedsExtTextInput = ( pChars && mbInKeyInput && !mpLastMarkedText && mpLastEvent && [mpLastEvent type] == NSEventTypeKeyDown && [mpLastEvent isARepeat] );
+ if ( bNeedsExtTextInput )
+ {
+ // tdf#154708 Preserve selection for repeating Shift-arrow on Japanese keyboard
+ // Skip the posting of SalEvent::ExtTextInput and
+ // SalEvent::EndExtTextInput events for private use area characters.
+ NSUInteger nLen = [pChars length];
+ auto const pBuf = std::make_unique<unichar[]>( nLen + 1 );
+ NSUInteger nBufLen = 0;
+ for ( NSUInteger i = 0; i < nLen; i++ )
+ {
+ unichar aChar = [pChars characterAtIndex:i];
+ if ( aChar >= 0xf700 && aChar < 0xf780 )
+ continue;
+
+ pBuf[nBufLen++] = aChar;
+ }
+ pBuf[nBufLen] = 0;
+
+ pNewMarkedText = [NSString stringWithCharacters:pBuf.get() length:nBufLen];
+ if (!pNewMarkedText || ![pNewMarkedText length])
+ bNeedsExtTextInput = false;
+ }
+
+ if ( bNeedsExtTextInput )
+ {
+ SalExtTextInputEvent aInputEvent;
+ aInputEvent.maText.clear();
+ aInputEvent.mnCursorPos = 0;
+ aInputEvent.mnCursorFlags = 0;
+ aInputEvent.mpTextAttr = nullptr;
+ if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::ExtTextInput, static_cast<void *>(&aInputEvent) );
+ }
+
+ SalExtTextInputPosEvent aPosEvent;
+ if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::ExtTextInputPos, static_cast<void *>(&aPosEvent) );
+
+ if ( bNeedsExtTextInput )
+ {
+ if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ mpFrame->CallCallback( SalEvent::EndExtTextInput, nullptr );
+
+ // tdf#42437 Enable press-and-hold special character input method
+ // Emulate the press-and-hold behavior of the TextEdit application by
+ // setting the marked text to the last key down event's characters. The
+ // characters will already have been committed by the special character
+ // input method so set the mbTextInputWantsNonRepeatKeyDown flag to
+ // indicate that the characters need to be deleted if the input method
+ // replaces the committed characters.
+ if ( pNewMarkedText )
+ {
+ [self unmarkText];
+ mpLastMarkedText = [[NSAttributedString alloc] initWithString:pNewMarkedText];
+ mSelectedRange = mMarkedRange = NSMakeRange( 0, [mpLastMarkedText length] );
+ mbTextInputWantsNonRepeatKeyDown = YES;
+ }
+ }
+
+ NSRect rect;
+
+ if ( mpFrame && AquaSalFrame::isAlive( mpFrame ) )
+ {
+ rect.origin.x = aPosEvent.mnX + mpFrame->maGeometry.x();
+ rect.origin.y = aPosEvent.mnY + mpFrame->maGeometry.y() + 4; // add some space for underlines
+ rect.size.width = aPosEvent.mnWidth;
+ rect.size.height = aPosEvent.mnHeight;
+
+ mpFrame->VCLToCocoa( rect );
+ }
+ else
+ {
+ rect = NSMakeRect( aPosEvent.mnX, aPosEvent.mnY, aPosEvent.mnWidth, aPosEvent.mnHeight );
+ }
+
+ return rect;
+}
+
+-(id)parentAttribute {
+ return reinterpret_cast<NSView*>(mpFrame->getNSWindow());
+ //TODO: odd cast really needed for fdo#74121?
+}
+
+-(css::accessibility::XAccessibleContext *)accessibleContext
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibleContext];
+
+ return nil;
+}
+
+-(NSWindow*)windowForParent
+{
+ return mpFrame->getNSWindow();
+}
+
+-(void)registerMouseEventListener: (id)theListener
+{
+ mpMouseEventListener = theListener;
+}
+
+-(void)unregisterMouseEventListener: (id)theListener
+{
+ (void)theListener;
+ mpMouseEventListener = nil;
+}
+
+-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler draggingEntered: sender];
+}
+
+-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler draggingUpdated: sender];
+}
+
+-(void)draggingExited:(id <NSDraggingInfo>)sender
+{
+ [mDraggingDestinationHandler draggingExited: sender];
+}
+
+-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler prepareForDragOperation: sender];
+}
+
+-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender
+{
+ return [mDraggingDestinationHandler performDragOperation: sender];
+}
+
+-(void)concludeDragOperation:(id <NSDraggingInfo>)sender
+{
+ [mDraggingDestinationHandler concludeDragOperation: sender];
+}
+
+-(void)registerDraggingDestinationHandler:(id)theHandler
+{
+ mDraggingDestinationHandler = theHandler;
+}
+
+-(void)unregisterDraggingDestinationHandler:(id)theHandler
+{
+ (void)theHandler;
+ mDraggingDestinationHandler = nil;
+}
+
+-(void)endExtTextInput
+{
+ [self endExtTextInput:EndExtTextInputFlags::Complete];
+}
+
+-(void)endExtTextInput:(EndExtTextInputFlags)nFlags
+{
+ // Prevent recursion from any additional [self insertText:] calls that
+ // may be called when cancelling the native input method session
+ if (mbInEndExtTextInput)
+ return;
+
+ mbInEndExtTextInput = YES;
+
+ SolarMutexGuard aGuard;
+
+ NSTextInputContext *pInputContext = [NSTextInputContext currentInputContext];
+ if (pInputContext)
+ {
+ // Cancel the native input method session
+ [pInputContext discardMarkedText];
+
+ // Commit any uncommitted text. Note: when the delete key is used to
+ // remove all uncommitted characters, the marked range will be zero
+ // length but a SalEvent::EndExtTextInput must still be dispatched.
+ if (mpLastMarkedText && [mpLastMarkedText length] && mMarkedRange.location != NSNotFound && mpFrame && AquaSalFrame::isAlive(mpFrame))
+ {
+ // If there is any marked text, SalEvent::EndExtTextInput may leave
+ // the cursor hidden so commit the marked text to force the cursor
+ // to be visible.
+ mbInCommitMarkedText = YES;
+ if (nFlags & EndExtTextInputFlags::Complete)
+ {
+ // Retain the last marked text as it will be released in
+ // [self insertText:replacementText:]
+ NSAttributedString *pText = [mpLastMarkedText retain];
+ [self insertText:pText replacementRange:NSMakeRange(0, [mpLastMarkedText length])];
+ [pText release];
+ }
+ else
+ {
+ [self insertText:[NSString string] replacementRange:NSMakeRange(0, 0)];
+ }
+ mbInCommitMarkedText = NO;
+ }
+
+ [self unmarkText];
+
+ // If a different view is the input context's client, commit that
+ // view's uncommitted text as well
+ id<NSTextInputClient> pClient = [pInputContext client];
+ if (pClient != self)
+ {
+ SalFrameView *pView = static_cast<SalFrameView*>(pClient);
+ if ([pView isKindOfClass:[SalFrameView class]])
+ [pView endExtTextInput];
+ else
+ [pClient unmarkText];
+ }
+ }
+
+ mbInEndExtTextInput = NO;
+}
+
+-(void)deleteTextInputWantsNonRepeatKeyDown
+{
+ SolarMutexGuard aGuard;
+
+ // tdf#42437 Enable press-and-hold special character input method
+ // Emulate the press-and-hold behavior of the TextEdit application by
+ // dispatching backspace events to delete any marked characters. The
+ // special character input method commits the marked characters so we must
+ // delete the marked characters before the input method calls
+ // [self insertText:replacementRange:].
+ if (mbTextInputWantsNonRepeatKeyDown)
+ {
+ if ( mpLastMarkedText )
+ {
+ NSString *pChars = [mpLastMarkedText string];
+ if ( pChars )
+ {
+ NSUInteger nLength = [pChars length];
+ for ( NSUInteger i = 0; i < nLength; i++ )
+ [self deleteBackward:self];
+ }
+ }
+
+ [self unmarkText];
+ }
+}
+
+-(void)insertRegisteredWrapperIntoWrapperRepository
+{
+ SolarMutexGuard aGuard;
+
+ if (!mbNeedChildWrapper)
+ return;
+
+ vcl::Window *pWindow = mpFrame->GetWindow();
+ if (!pWindow)
+ return;
+
+ mbNeedChildWrapper = NO;
+
+ ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext > xAccessibleContext( pWindow->GetAccessible()->getAccessibleContext() );
+ assert(!mpChildWrapper);
+ mpChildWrapper = [[SalFrameViewA11yWrapper alloc] initWithParent:self accessibleContext:xAccessibleContext];
+ [AquaA11yFactory insertIntoWrapperRepository:mpChildWrapper forAccessibleContext:xAccessibleContext];
+}
+
+-(void)registerWrapper
+{
+ [self revokeWrapper];
+
+ mbNeedChildWrapper = YES;
+}
+
+-(void)revokeWrapper
+{
+ mbNeedChildWrapper = NO;
+
+ if (mpChildWrapper)
+ {
+ [AquaA11yFactory revokeWrapper:mpChildWrapper];
+ [mpChildWrapper setAccessibilityParent:nil];
+ [mpChildWrapper release];
+ mpChildWrapper = nil;
+ }
+}
+
+-(id)accessibilityAttributeValue:(NSString *)pAttribute
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityAttributeValue:pAttribute];
+ else
+ return nil;
+}
+
+-(BOOL)accessibilityIsIgnored
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityIsIgnored];
+ else
+ return YES;
+}
+
+-(NSArray *)accessibilityAttributeNames
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityAttributeNames];
+ else
+ return [NSArray array];
+}
+
+-(BOOL)accessibilityIsAttributeSettable:(NSString *)pAttribute
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityIsAttributeSettable:pAttribute];
+ else
+ return NO;
+}
+
+-(NSArray *)accessibilityParameterizedAttributeNames
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityParameterizedAttributeNames];
+ else
+ return [NSArray array];
+}
+
+-(BOOL)accessibilitySetOverrideValue:(id)pValue forAttribute:(NSString *)pAttribute
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilitySetOverrideValue:pValue forAttribute:pAttribute];
+ else
+ return NO;
+}
+
+-(void)accessibilitySetValue:(id)pValue forAttribute:(NSString *)pAttribute
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ [mpChildWrapper accessibilitySetValue:pValue forAttribute:pAttribute];
+}
+
+-(id)accessibilityAttributeValue:(NSString *)pAttribute forParameter:(id)pParameter
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityAttributeValue:pAttribute forParameter:pParameter];
+ else
+ return nil;
+}
+
+-(id)accessibilityFocusedUIElement
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityFocusedUIElement];
+ else
+ return nil;
+}
+
+-(NSString *)accessibilityActionDescription:(NSString *)pAction
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityActionDescription:pAction];
+ else
+ return nil;
+}
+
+-(void)accessibilityPerformAction:(NSString *)pAction
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ [mpChildWrapper accessibilityPerformAction:pAction];
+}
+
+-(NSArray *)accessibilityActionNames
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityActionNames];
+ else
+ return [NSArray array];
+}
+
+-(id)accessibilityHitTest:(NSPoint)aPoint
+{
+ SolarMutexGuard aGuard;
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ return [mpChildWrapper accessibilityHitTest:aPoint];
+ else
+ return nil;
+}
+
+-(id)accessibilityParent
+{
+ return [self window];
+}
+
+-(NSArray *)accessibilityVisibleChildren
+{
+ return [self accessibilityChildren];
+}
+
+-(NSArray *)accessibilitySelectedChildren
+{
+ SolarMutexGuard aGuard;
+
+ NSArray *pRet = [super accessibilityChildren];
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilitySelectedChildren]);
+
+ return pRet;
+}
+
+-(NSArray *)accessibilityChildren
+{
+ SolarMutexGuard aGuard;
+
+ NSArray *pRet = [super accessibilityChildren];
+
+ [self insertRegisteredWrapperIntoWrapperRepository];
+ if (mpChildWrapper)
+ pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilityChildren]);
+
+ return pRet;
+}
+
+-(NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder
+{
+ return [self accessibilityChildren];
+}
+
+@end
+
+@implementation SalFrameViewA11yWrapper
+
+-(id)initWithParent:(SalFrameView *)pParentView accessibleContext:(::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >&)rxAccessibleContext
+{
+ [super init];
+
+ maReferenceWrapper.rAccessibleContext = rxAccessibleContext;
+
+ mpParentView = pParentView;
+ if (mpParentView)
+ {
+ [mpParentView retain];
+ [self setAccessibilityParent:mpParentView];
+ }
+
+ return self;
+}
+
+-(void)dealloc
+{
+ if (mpParentView)
+ [mpParentView release];
+
+ [super dealloc];
+}
+
+-(id)parentAttribute
+{
+ if (mpParentView)
+ return NSAccessibilityUnignoredAncestor(mpParentView);
+ else
+ return nil;
+}
+
+-(void)setAccessibilityParent:(id)pObject
+{
+ if (mpParentView)
+ {
+ [mpParentView release];
+ mpParentView = nil;
+ }
+
+ if (pObject && [pObject isKindOfClass:[SalFrameView class]])
+ {
+ mpParentView = static_cast<SalFrameView *>(pObject);
+ [mpParentView retain];
+ }
+
+ [super setAccessibilityParent:mpParentView];
+}
+
+-(id)windowAttribute
+{
+ if (mpParentView)
+ return [mpParentView window];
+ else
+ return nil;
+}
+
+-(NSWindow *)windowForParent
+{
+ return [self windowAttribute];
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salgdiutils.cxx b/vcl/osx/salgdiutils.cxx
new file mode 100644
index 0000000000..a944529321
--- /dev/null
+++ b/vcl/osx/salgdiutils.cxx
@@ -0,0 +1,362 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cstdint>
+
+#include <sal/log.hxx>
+
+#include <basegfx/polygon/b2dpolygon.hxx>
+#include <basegfx/polygon/b2dpolygontools.hxx>
+#include <basegfx/range/b2drectangle.hxx>
+#include <basegfx/range/b2irange.hxx>
+#include <basegfx/vector/b2ivector.hxx>
+#include <vcl/svapp.hxx>
+
+#include <quartz/salgdi.h>
+#include <quartz/utils.h>
+#include <osx/salframe.h>
+#include <osx/saldata.hxx>
+
+#if HAVE_FEATURE_SKIA
+#include <tools/sk_app/mac/WindowContextFactory_mac.h>
+#include <vcl/skia/SkiaHelper.hxx>
+#endif
+
+static bool bTotalScreenBounds = false;
+static NSRect aTotalScreenBounds = NSZeroRect;
+
+// TODO: Scale will be set to 2.0f as default after implementation of full scaled display support . This will allow moving of
+// windows between non retina and retina displays without blurry text and graphics. Static variables have to be removed thereafter.
+
+// Currently scaled display support is not implemented for bitmaps. This will cause a slight performance degradation on displays
+// with single precision. To preserve performance for now, window scaling is only activated if at least one display with double
+// precision is present. Moving windows between displays is then possible without blurry text and graphics too. Adapting window
+// scaling when displays are added while application is running is not supported.
+
+static bool bWindowScaling = false;
+static float fWindowScale = 1.0f;
+
+namespace sal::aqua
+{
+NSRect getTotalScreenBounds()
+{
+ if (!bTotalScreenBounds)
+ {
+ aTotalScreenBounds = NSZeroRect;
+
+ NSArray *aScreens = [NSScreen screens];
+ if (aScreens != nullptr)
+ {
+ for (NSScreen *aScreen : aScreens)
+ {
+ // Calculate total screen bounds
+ NSRect aScreenFrame = [aScreen frame];
+ if (!NSIsEmptyRect(aScreenFrame))
+ {
+ if (NSIsEmptyRect(aTotalScreenBounds))
+ aTotalScreenBounds = aScreenFrame;
+ else
+ aTotalScreenBounds = NSUnionRect( aScreenFrame, aTotalScreenBounds );
+ }
+ }
+ bTotalScreenBounds = true;
+ }
+ }
+ return aTotalScreenBounds;
+}
+
+void resetTotalScreenBounds()
+{
+ bTotalScreenBounds = false;
+ getTotalScreenBounds();
+}
+
+float getWindowScaling()
+{
+ // Related: tdf#147342 Any changes to this function must be copied to the
+ // sk_app::GetBackingScaleFactor() function in the following file:
+ // workdir/UnpackedTarball/skia/tools/sk_app/mac/WindowContextFactory_mac.h
+ if (!bWindowScaling)
+ {
+ NSArray *aScreens = [NSScreen screens];
+ if (aScreens != nullptr)
+ {
+ for (NSScreen *aScreen : aScreens)
+ {
+ float fScale = [aScreen backingScaleFactor];
+ if (fScale > fWindowScale)
+ fWindowScale = fScale;
+ }
+ bWindowScaling = true;
+ }
+ if( const char* env = getenv("SAL_FORCE_HIDPI_SCALING"))
+ {
+ fWindowScale = atof(env);
+ bWindowScaling = true;
+ }
+ }
+ return fWindowScale;
+}
+
+void resetWindowScaling()
+{
+ // Related: tdf#147342 Force recalculation of the window scaling but keep
+ // the previous window scaling as the minimum so that we don't lose the
+ // resolution in cached images if a HiDPI monitor is disconnected and
+ // then reconnected.
+#if HAVE_FEATURE_SKIA
+ if ( SkiaHelper::isVCLSkiaEnabled() )
+ sk_app::ResetBackingScaleFactor();
+#endif
+ bWindowScaling = false;
+ getWindowScaling();
+}
+} // end aqua
+
+void AquaSalGraphics::SetWindowGraphics( AquaSalFrame* pFrame )
+{
+ maShared.mpFrame = pFrame;
+ maShared.mbWindow = true;
+ maShared.mbPrinter = false;
+ maShared.mbVirDev = false;
+ mpBackend->UpdateGeometryProvider(pFrame);
+}
+
+void AquaSalGraphics::SetPrinterGraphics( CGContextRef xContext, sal_Int32 nDPIX, sal_Int32 nDPIY )
+{
+ maShared.mbWindow = false;
+ maShared.mbPrinter = true;
+ maShared.mbVirDev = false;
+
+ maShared.maContextHolder.set(xContext);
+ mnRealDPIX = nDPIX;
+ mnRealDPIY = nDPIY;
+
+ // a previously set clip path is now invalid
+ maShared.unsetClipPath();
+
+ if (maShared.maContextHolder.isSet())
+ {
+ CGContextSetFillColorSpace( maShared.maContextHolder.get(), GetSalData()->mxRGBSpace );
+ CGContextSetStrokeColorSpace( maShared.maContextHolder.get(), GetSalData()->mxRGBSpace );
+ CGContextSaveGState( maShared.maContextHolder.get() );
+ maShared.setState();
+ }
+
+ mpBackend->UpdateGeometryProvider(nullptr);
+}
+
+void AquaSalGraphics::InvalidateContext()
+{
+ UnsetState();
+
+ CGContextRelease(maShared.maContextHolder.get());
+ CGContextRelease(maShared.maBGContextHolder.get());
+ CGContextRelease(maShared.maCSContextHolder.get());
+
+ maShared.maContextHolder.set(nullptr);
+ maShared.maCSContextHolder.set(nullptr);
+ maShared.maBGContextHolder.set(nullptr);
+}
+
+void AquaSalGraphics::UnsetState()
+{
+ if (maShared.maBGContextHolder.isSet())
+ {
+ CGContextRelease(maShared.maBGContextHolder.get());
+ maShared.maBGContextHolder.set(nullptr);
+ }
+ if (maShared.maCSContextHolder.isSet())
+ {
+ CGContextRelease(maShared.maCSContextHolder.get());
+ maShared.maBGContextHolder.set(nullptr);
+ }
+ if (maShared.maContextHolder.isSet())
+ {
+ maShared.maContextHolder.restoreState();
+ maShared.maContextHolder.set(nullptr);
+ }
+ maShared.unsetState();
+}
+
+/**
+ * (re-)create the off-screen maLayer we render everything to if
+ * necessary: eg. not initialized yet, or it has an incorrect size.
+ */
+bool AquaSharedAttributes::checkContext()
+{
+ if (mbWindow && mpFrame && (mpFrame->getNSWindow() || Application::IsBitmapRendering()))
+ {
+ const unsigned int nWidth = mpFrame->maGeometry.width();
+ const unsigned int nHeight = mpFrame->maGeometry.height();
+ const float fScale = sal::aqua::getWindowScaling();
+ CGLayerRef rReleaseLayer = nullptr;
+
+ // check if a new drawing context is needed (e.g. after a resize)
+ if( (unsigned(mnWidth) != nWidth) || (unsigned(mnHeight) != nHeight) )
+ {
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ // prepare to release the corresponding resources
+ if (maLayer.isSet())
+ {
+ rReleaseLayer = maLayer.get();
+ }
+ else if (maContextHolder.isSet())
+ {
+ CGContextRelease(maContextHolder.get());
+ }
+ CGContextRelease(maBGContextHolder.get());
+ CGContextRelease(maCSContextHolder.get());
+
+ maContextHolder.set(nullptr);
+ maBGContextHolder.set(nullptr);
+ maCSContextHolder.set(nullptr);
+ maLayer.set(nullptr);
+ }
+
+ if (!maContextHolder.isSet())
+ {
+ const int nBitmapDepth = 32;
+
+ float nScaledWidth = mnWidth * fScale;
+ float nScaledHeight = mnHeight * fScale;
+
+ const CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
+
+ const int nBytesPerRow = (nBitmapDepth * nScaledWidth) / 8;
+ std::uint32_t nFlags = std::uint32_t(kCGImageAlphaNoneSkipFirst)
+ | std::uint32_t(kCGBitmapByteOrder32Host);
+ maBGContextHolder.set(CGBitmapContextCreate(
+ nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
+
+ maLayer.set(CGLayerCreateWithContext(maBGContextHolder.get(), aLayerSize, nullptr));
+ maLayer.setScale(fScale);
+
+ nFlags = std::uint32_t(kCGImageAlphaPremultipliedFirst)
+ | std::uint32_t(kCGBitmapByteOrder32Host);
+ maCSContextHolder.set(CGBitmapContextCreate(
+ nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, GetSalData()->mxRGBSpace, nFlags));
+
+ CGContextRef xDrawContext = CGLayerGetContext(maLayer.get());
+ maContextHolder = xDrawContext;
+
+ if (rReleaseLayer)
+ {
+ // copy original layer to resized layer
+ if (maContextHolder.isSet())
+ {
+ CGContextDrawLayerAtPoint(maContextHolder.get(), CGPointZero, rReleaseLayer);
+ }
+ CGLayerRelease(rReleaseLayer);
+ }
+
+ if (maContextHolder.isSet())
+ {
+ CGContextTranslateCTM(maContextHolder.get(), 0, nScaledHeight);
+ CGContextScaleCTM(maContextHolder.get(), 1.0, -1.0);
+ CGContextSetFillColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
+ CGContextSetStrokeColorSpace(maContextHolder.get(), GetSalData()->mxRGBSpace);
+ // apply a scale matrix so everything is auto-magically scaled
+ CGContextScaleCTM(maContextHolder.get(), fScale, fScale);
+ maContextHolder.saveState();
+ setState();
+
+ // re-enable XOR emulation for the new context
+ if (mpXorEmulation)
+ mpXorEmulation->SetTarget(mnWidth, mnHeight, mnBitmapDepth, maContextHolder.get(), maLayer.get());
+ }
+ }
+ }
+
+ SAL_WARN_IF(!maContextHolder.isSet() && !mbPrinter, "vcl", "<<<WARNING>>> AquaSalGraphics::CheckContext() FAILED!!!!");
+
+ return maContextHolder.isSet();
+}
+
+/**
+ * Blit the contents of our internal maLayer state to the
+ * associated window, if any; cf. drawRect event handling
+ * on the frame.
+ */
+void AquaSalGraphics::UpdateWindow( NSRect& )
+{
+ if (!maShared.mpFrame)
+ {
+ return;
+ }
+
+ NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
+ if (maShared.maLayer.isSet() && pContext != nullptr)
+ {
+ CGContextHolder rCGContextHolder([pContext CGContext]);
+
+ rCGContextHolder.saveState();
+
+ // Related: tdf#155092 translate Y coordinate for height differences
+ // When in live resize, the NSView's height may have changed before
+ // the CGLayer has been resized. This causes the CGLayer's content
+ // to be drawn just above or below the top left corner of the view
+ // so translate the Y coordinate by any difference between the
+ // NSView's height and the CGLayer's height.
+ NSView *pView = maShared.mpFrame->mpNSView;
+ if (pView)
+ {
+ // Use the NSView's bounds, not its frame, to properly handle
+ // any rotation and/or scaling that might have been already
+ // applied to the view
+ CGFloat fTranslateY = [pView bounds].size.height - maShared.maLayer.getSizePoints().height;
+ CGContextTranslateCTM(rCGContextHolder.get(), 0, fTranslateY);
+ }
+
+ CGMutablePathRef rClip = maShared.mpFrame->getClipPath();
+ if (rClip)
+ {
+ CGContextBeginPath(rCGContextHolder.get());
+ CGContextAddPath(rCGContextHolder.get(), rClip );
+ CGContextClip(rCGContextHolder.get());
+ }
+
+ maShared.applyXorContext();
+
+ const CGSize aSize = maShared.maLayer.getSizePoints();
+ const CGRect aRect = CGRectMake(0, 0, aSize.width, aSize.height);
+ const CGRect aRectPoints = { CGPointZero, maShared.maLayer.getSizePixels() };
+ CGContextSetBlendMode(maShared.maCSContextHolder.get(), kCGBlendModeCopy);
+ CGContextDrawLayerInRect(maShared.maCSContextHolder.get(), aRectPoints, maShared.maLayer.get());
+
+ CGImageRef img = CGBitmapContextCreateImage(maShared.maCSContextHolder.get());
+ CGImageRef displayColorSpaceImage = CGImageCreateCopyWithColorSpace(img, [[maShared.mpFrame->getNSWindow() colorSpace] CGColorSpace]);
+ CGContextSetBlendMode(rCGContextHolder.get(), kCGBlendModeCopy);
+ CGContextDrawImage(rCGContextHolder.get(), aRect, displayColorSpaceImage);
+
+ CGImageRelease(img);
+ CGImageRelease(displayColorSpaceImage);
+
+ rCGContextHolder.restoreState();
+ }
+ else
+ {
+ SAL_WARN_IF(!maShared.mpFrame->mbInitShow, "vcl", "UpdateWindow called on uneligible graphics");
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx
new file mode 100644
index 0000000000..7f755bb618
--- /dev/null
+++ b/vcl/osx/salinst.cxx
@@ -0,0 +1,1093 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <condition_variable>
+#include <mutex>
+#include <utility>
+
+#include <config_features.h>
+
+#include <stdio.h>
+
+#include <comphelper/solarmutex.hxx>
+
+#include <comphelper/lok.hxx>
+
+#include <osl/process.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <vclpluginapi.h>
+#include <vcl/QueueInfo.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/svmain.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/event.hxx>
+
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#include <osx/salframe.h>
+#include <osx/salobj.h>
+#include <osx/salsys.h>
+#include <quartz/salvd.h>
+#include <quartz/salbmp.h>
+#include <quartz/utils.h>
+#include <osx/salprn.h>
+#include <osx/saltimer.h>
+#include <osx/vclnsapp.h>
+#include <osx/runinmain.hxx>
+
+#include <print.h>
+#include <strings.hrc>
+
+#include <comphelper/processfactory.hxx>
+
+#include <com/sun/star/uri/ExternalUriReferenceTranslator.hpp>
+#include <com/sun/star/uno/XComponentContext.hpp>
+
+#include <premac.h>
+#include <Foundation/Foundation.h>
+#include <ApplicationServices/ApplicationServices.h>
+#import "apple_remote/RemoteMainController.h"
+#include <apple_remote/RemoteControl.h>
+#include <postmac.h>
+
+#if HAVE_FEATURE_SKIA
+#include <vcl/skia/SkiaHelper.hxx>
+#include <skia/salbmp.hxx>
+#include <skia/osx/gdiimpl.hxx>
+#include <skia/osx/bitmap.hxx>
+#endif
+
+extern "C" {
+#include <crt_externs.h>
+}
+
+using namespace ::com::sun::star;
+
+static int* gpnInit = nullptr;
+static NSMenu* pDockMenu = nil;
+static bool bLeftMain = false;
+
+namespace {
+
+class AquaDelayedSettingsChanged : public Idle
+{
+ bool mbInvalidate;
+
+public:
+ AquaDelayedSettingsChanged( bool bInvalidate ) :
+ Idle("AquaSalInstance AquaDelayedSettingsChanged"),
+ mbInvalidate( bInvalidate )
+ {
+ }
+
+ virtual void Invoke() override
+ {
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ SalFrame *pAnyFrame = pInst->anyFrame();
+ if( pAnyFrame )
+ pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
+
+ if( mbInvalidate )
+ {
+ for( auto pSalFrame : pInst->getFrames() )
+ {
+ AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
+ if( pFrame->mbShown )
+ pFrame->SendPaintEvent();
+ }
+ }
+ delete this;
+ }
+};
+
+}
+
+static OUString& getFallbackPrinterName()
+{
+ static OUString aFallbackPrinter;
+
+ if ( aFallbackPrinter.isEmpty() )
+ {
+ aFallbackPrinter = VclResId( SV_PRINT_DEFPRT_TXT );
+ if ( aFallbackPrinter.isEmpty() )
+ aFallbackPrinter = "Printer";
+ }
+
+ return aFallbackPrinter;
+}
+
+void AquaSalInstance::delayedSettingsChanged( bool bInvalidate )
+{
+ osl::Guard< comphelper::SolarMutex > aGuard( *GetYieldMutex() );
+ AquaDelayedSettingsChanged* pIdle = new AquaDelayedSettingsChanged( bInvalidate );
+ pIdle->Start();
+}
+
+// the std::list<const ApplicationEvent*> must be available before any SalData/SalInst/etc. objects are ready
+std::list<const ApplicationEvent*> AquaSalInstance::aAppEventList;
+
+NSMenu* AquaSalInstance::GetDynamicDockMenu()
+{
+ if( ! pDockMenu && ! bLeftMain )
+ pDockMenu = [[NSMenu alloc] initWithTitle: @""];
+ return pDockMenu;
+}
+
+bool AquaSalInstance::isOnCommandLine( const OUString& rArg )
+{
+ sal_uInt32 nArgs = osl_getCommandArgCount();
+ for( sal_uInt32 i = 0; i < nArgs; i++ )
+ {
+ OUString aArg;
+ osl_getCommandArg( i, &aArg.pData );
+ if( aArg.equals( rArg ) )
+ return true;
+ }
+ return false;
+}
+
+void AquaSalInstance::AfterAppInit()
+{
+ [[NSNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(systemColorsChanged:)
+ name: NSSystemColorsDidChangeNotification
+ object: nil ];
+ [[NSNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(screenParametersChanged:)
+ name: NSApplicationDidChangeScreenParametersNotification
+ object: nil ];
+ // add observers for some settings changes that affect vcl's settings
+ // scrollbar variant
+ [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(scrollbarVariantChanged:)
+ name: @"AppleAquaScrollBarVariantChanged"
+ object: nil ];
+ // scrollbar page behavior ("jump to here" or not)
+ [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(scrollbarSettingsChanged:)
+ name: @"AppleNoRedisplayAppearancePreferenceChanged"
+ object: nil ];
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ // Initialize Apple Remote
+ GetSalData()->mpAppleRemoteMainController = [[AppleRemoteMainController alloc] init];
+
+ [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(applicationWillBecomeActive:)
+ name: @"AppleRemoteWillBecomeActive"
+ object: nil ];
+
+ [[NSDistributedNotificationCenter defaultCenter] addObserver: NSApp
+ selector: @selector(applicationWillResignActive:)
+ name: @"AppleRemoteWillResignActive"
+ object: nil ];
+#endif
+
+ // HACK: When the first call to [NSSpellChecker sharedSpellChecker] (in
+ // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm) is done both on a thread other
+ // than the main thread and with the SolarMutex erroneously locked, then that can lead to
+ // deadlock as [NSSpellChecker sharedSpellChecker] internally calls
+ // AppKit`-[NSSpellChecker init] ->
+ // AppKit`-[NSSpellChecker _fillSpellCheckerPopupButton:] ->
+ // AppKit`-[NSApplication(NSServicesMenuPrivate) _fillSpellCheckerPopupButton:] ->
+ // AppKit`-[NSMenu insertItem:atIndex:] ->
+ // Foundation`-[NSNotificationCenter postNotificationName:object:userInfo:] ->
+ // CoreFoundation`_CFXNotificationPost ->
+ // Foundation`-[NSOperation waitUntilFinished]
+ // waiting for work to be done on the main thread, but the main thread is typically already
+ // blocked (in some event handling loop) waiting to acquire the SolarMutex. The real solution
+ // would be to fix all the cases where a call to [NSSpellChecker sharedSpellChecker] in
+ // lingucomponent/source/spellcheck/macosxspell/macspellimp.mm is done while the SolarMutex is
+ // locked (somewhere up the call chain), but that appears to be rather difficult (see e.g.
+ // <https://bugs.documentfoundation.org/show_bug.cgi?id=151894> "FILEOPEN a Base Document with
+ // customized event for open a startform by 'open document' LO stuck"). So, at least for now,
+ // chicken out and do that first call to [NSSpellChecker sharedSpellChecker] upfront in a
+ // controlled environment:
+ [NSSpellChecker sharedSpellChecker];
+}
+
+SalYieldMutex::SalYieldMutex()
+ : m_aCodeBlock( nullptr )
+{
+}
+
+SalYieldMutex::~SalYieldMutex()
+{
+}
+
+void SalYieldMutex::doAcquire( sal_uInt32 nLockCount )
+{
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if ( pInst && pInst->IsMainThread() )
+ {
+ if ( pInst->mbNoYieldLock )
+ return;
+ do {
+ RuninmainBlock block = nullptr;
+ {
+ std::unique_lock<std::mutex> g(m_runInMainMutex);
+ if (m_aMutex.tryToAcquire()) {
+ assert(m_aCodeBlock == nullptr);
+ m_wakeUpMain = false;
+ break;
+ }
+ // wait for doRelease() or RUNINMAIN_* to set the condition
+ m_aInMainCondition.wait(g, [this]() { return m_wakeUpMain; });
+ m_wakeUpMain = false;
+ std::swap(block, m_aCodeBlock);
+ }
+ if ( block )
+ {
+ assert( !pInst->mbNoYieldLock );
+ pInst->mbNoYieldLock = true;
+ block();
+ pInst->mbNoYieldLock = false;
+ Block_release( block );
+ std::scoped_lock<std::mutex> g(m_runInMainMutex);
+ assert(!m_resultReady);
+ m_resultReady = true;
+ m_aResultCondition.notify_all();
+ }
+ }
+ while ( true );
+ }
+ else
+ m_aMutex.acquire();
+ ++m_nCount;
+ --nLockCount;
+
+ comphelper::SolarMutex::doAcquire( nLockCount );
+}
+
+sal_uInt32 SalYieldMutex::doRelease( const bool bUnlockAll )
+{
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if ( pInst->mbNoYieldLock && pInst->IsMainThread() )
+ return 1;
+ sal_uInt32 nCount;
+ {
+ std::scoped_lock<std::mutex> g(m_runInMainMutex);
+ // read m_nCount before doRelease
+ bool const isReleased(bUnlockAll || m_nCount == 1);
+ nCount = comphelper::SolarMutex::doRelease( bUnlockAll );
+ if (isReleased && !pInst->IsMainThread()) {
+ m_wakeUpMain = true;
+ m_aInMainCondition.notify_all();
+ }
+ }
+ return nCount;
+}
+
+bool SalYieldMutex::IsCurrentThread() const
+{
+ if ( !GetSalData()->mpInstance->mbNoYieldLock )
+ return comphelper::SolarMutex::IsCurrentThread();
+ else
+ return GetSalData()->mpInstance->IsMainThread();
+}
+
+// some convenience functions regarding the yield mutex, aka solar mutex
+
+bool ImplSalYieldMutexTryToAcquire()
+{
+ AquaSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst )
+ return pInst->GetYieldMutex()->tryToAcquire();
+ else
+ return false;
+}
+
+void ImplSalYieldMutexRelease()
+{
+ AquaSalInstance* pInst = GetSalData()->mpInstance;
+ if ( pInst )
+ pInst->GetYieldMutex()->release();
+}
+
+extern "C" {
+VCLPLUG_OSX_PUBLIC SalInstance* create_SalInstance()
+{
+ SalData* pSalData = new SalData;
+
+ NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
+ unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.plist", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
+ unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.txt", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]);
+ [ pool drain ];
+
+ // create our cocoa NSApplication
+ [VCL_NSApplication sharedApplication];
+
+ SalData::ensureThreadAutoreleasePool();
+
+ // put cocoa into multithreaded mode
+ [NSThread detachNewThreadSelector:@selector(enableCocoaThreads:) toTarget:[[CocoaThreadEnabler alloc] init] withObject:nil];
+
+ // activate our delegate methods
+ [NSApp setDelegate: NSApp];
+
+ SAL_WARN_IF( pSalData->mpInstance != nullptr, "vcl", "more than one instance created" );
+ AquaSalInstance* pInst = new AquaSalInstance;
+
+ // init instance (only one instance in this version !!!)
+ pSalData->mpInstance = pInst;
+ // this one is for outside AquaSalInstance::Yield
+ SalData::ensureThreadAutoreleasePool();
+ // no focus rects on NWF
+ ImplGetSVData()->maNWFData.mbNoFocusRects = true;
+ ImplGetSVData()->maNWFData.mbNoActiveTabTextRaise = true;
+ ImplGetSVData()->maNWFData.mbCenteredTabs = true;
+ ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset = 10;
+
+ return pInst;
+}
+}
+
+AquaSalInstance::AquaSalInstance()
+ : SalInstance(std::make_unique<SalYieldMutex>())
+ , mnActivePrintJobs( 0 )
+ , mbNoYieldLock( false )
+ , mbTimerProcessed( false )
+{
+ maMainThread = osl::Thread::getCurrentIdentifier();
+
+ ImplSVData* pSVData = ImplGetSVData();
+ pSVData->maAppData.mxToolkitName = OUString("osx");
+ m_bSupportsOpenGL = true;
+
+ mpButtonCell = [[NSButtonCell alloc] init];
+ mpCheckCell = [[NSButtonCell alloc] init];
+ mpRadioCell = [[NSButtonCell alloc] init];
+ mpTextFieldCell = [[NSTextFieldCell alloc] initTextCell:@""];
+ mpComboBoxCell = [[NSComboBoxCell alloc] initTextCell:@""];
+ mpPopUpButtonCell = [[NSPopUpButtonCell alloc] init];
+ mpStepperCell = [[NSStepperCell alloc] init];
+ mpListNodeCell = [[NSButtonCell alloc] init];
+
+#if HAVE_FEATURE_SKIA
+ AquaSkiaSalGraphicsImpl::prepareSkia();
+#endif
+}
+
+AquaSalInstance::~AquaSalInstance()
+{
+ [NSApp stop: NSApp];
+ bLeftMain = true;
+ if( pDockMenu )
+ {
+ [pDockMenu release];
+ pDockMenu = nil;
+ }
+
+ [mpListNodeCell release];
+ [mpStepperCell release];
+ [mpPopUpButtonCell release];
+ [mpComboBoxCell release];
+ [mpTextFieldCell release];
+ [mpRadioCell release];
+ [mpCheckCell release];
+ [mpButtonCell release];
+
+#if HAVE_FEATURE_SKIA
+ SkiaHelper::cleanup();
+#endif
+}
+
+void AquaSalInstance::TriggerUserEventProcessing()
+{
+ dispatch_async(dispatch_get_main_queue(),^{
+ ImplNSAppPostEvent( AquaSalInstance::YieldWakeupEvent, NO );
+ });
+}
+
+void AquaSalInstance::ProcessEvent( SalUserEvent aEvent )
+{
+ aEvent.m_pFrame->CallCallback( aEvent.m_nEvent, aEvent.m_pData );
+ maWaitingYieldCond.set();
+}
+
+bool AquaSalInstance::IsMainThread() const
+{
+ return osl::Thread::getCurrentIdentifier() == maMainThread;
+}
+
+void AquaSalInstance::handleAppDefinedEvent( NSEvent* pEvent )
+{
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ int nSubtype = [pEvent subtype];
+ switch( nSubtype )
+ {
+ case AppStartTimerEvent:
+ if ( pTimer )
+ pTimer->handleStartTimerEvent( pEvent );
+ break;
+ case AppExecuteSVMain:
+ {
+ int nRet = ImplSVMain();
+ if (gpnInit)
+ *gpnInit = nRet;
+ [NSApp stop: NSApp];
+ break;
+ }
+ case DispatchTimerEvent:
+ {
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if ( pTimer && pInst )
+ pInst->mbTimerProcessed = pTimer->handleDispatchTimerEvent( pEvent );
+ break;
+ }
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+ case AppleRemoteControlEvent: // Defined in <apple_remote/RemoteMainController.h>
+ {
+ MediaCommand nCommand;
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ bool bIsFullScreenMode = false;
+
+ for( auto pSalFrame : pInst->getFrames() )
+ {
+ const AquaSalFrame* pFrame = static_cast<const AquaSalFrame*>( pSalFrame );
+ if ( pFrame->mbFullScreen )
+ {
+ bIsFullScreenMode = true;
+ break;
+ }
+ }
+
+ switch ([pEvent data1])
+ {
+ case kRemoteButtonPlay:
+ nCommand = bIsFullScreenMode ? MediaCommand::PlayPause : MediaCommand::Play;
+ break;
+
+ // kept for experimentation purpose (scheduled for future implementation)
+ // case kRemoteButtonMenu: nCommand = MediaCommand::Menu; break;
+
+ case kRemoteButtonPlus: nCommand = MediaCommand::VolumeUp; break;
+
+ case kRemoteButtonMinus: nCommand = MediaCommand::VolumeDown; break;
+
+ case kRemoteButtonRight: nCommand = MediaCommand::NextTrack; break;
+
+ case kRemoteButtonRight_Hold: nCommand = MediaCommand::NextTrackHold; break;
+
+ case kRemoteButtonLeft: nCommand = MediaCommand::PreviousTrack; break;
+
+ case kRemoteButtonLeft_Hold: nCommand = MediaCommand::Rewind; break;
+
+ case kRemoteButtonPlay_Hold: nCommand = MediaCommand::PlayHold; break;
+
+ case kRemoteButtonMenu_Hold: nCommand = MediaCommand::Stop; break;
+
+ // FIXME : not detected
+ case kRemoteButtonPlus_Hold:
+ case kRemoteButtonMinus_Hold:
+ break;
+
+ default:
+ break;
+ }
+ AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pInst->anyFrame() );
+ vcl::Window* pWindow = pFrame ? pFrame->GetWindow() : nullptr;
+ if( pWindow )
+ {
+ const Point aPoint;
+ CommandMediaData aMediaData(nCommand);
+ CommandEvent aCEvt( aPoint, CommandEventId::Media, false, &aMediaData );
+ NotifyEvent aNCmdEvt( NotifyEventType::COMMAND, pWindow, &aCEvt );
+
+ if ( !ImplCallPreNotify( aNCmdEvt ) )
+ pWindow->Command( aCEvt );
+ }
+
+ }
+ break;
+#endif
+
+ case YieldWakeupEvent:
+ // do nothing, fall out of Yield
+ break;
+
+ default:
+ OSL_FAIL( "unhandled NSEventTypeApplicationDefined event" );
+ break;
+ }
+}
+
+bool AquaSalInstance::RunInMainYield( bool bHandleAllCurrentEvents )
+{
+ OSX_SALDATA_RUNINMAIN_UNION( DoYield( false, bHandleAllCurrentEvents), boolean )
+
+ // PrinterController::removeTransparencies() calls this frequently on the
+ // main thread so reduce the severity from an assert so that printing still
+ // works in a debug builds
+ SAL_WARN_IF( true, "vcl", "Don't call this from the main thread!" );
+ return false;
+
+}
+
+static bool isWakeupEvent( NSEvent *pEvent )
+{
+ return NSEventTypeApplicationDefined == [pEvent type]
+ && AquaSalInstance::YieldWakeupEvent == static_cast<int>([pEvent subtype]);
+}
+
+bool AquaSalInstance::DoYield(bool bWait, bool bHandleAllCurrentEvents)
+{
+ // ensure that the per thread autorelease pool is top level and
+ // will therefore not be destroyed by cocoa implicitly
+ SalData::ensureThreadAutoreleasePool();
+
+ // NSAutoreleasePool documentation suggests we should have
+ // an own pool for each yield level
+ ReleasePoolHolder aReleasePool;
+
+ // first, process current user events
+ // Related: tdf#152703 Eliminate potential blocking during live resize
+ // Only native events and timers need to be dispatched to redraw
+ // the window so skip dispatching user events when a window is in
+ // live resize
+ bool bHadEvent = ( !ImplGetSVData()->mpWinData->mbIsLiveResize && DispatchUserEvents( bHandleAllCurrentEvents ) );
+ if ( !bHandleAllCurrentEvents && bHadEvent )
+ return true;
+
+ // handle cocoa event queue
+ // cocoa events may be only handled in the thread the NSApp was created
+ if( IsMainThread() && mnActivePrintJobs == 0 )
+ {
+ // handle available events
+ NSEvent* pEvent = nil;
+ NSTimeInterval now = [[NSProcessInfo processInfo] systemUptime];
+ mbTimerProcessed = false;
+
+ int noLoops = 0;
+ do
+ {
+ SolarMutexReleaser aReleaser;
+
+ pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: [NSDate distantPast]
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ if( pEvent )
+ {
+ // tdf#155092 don't dispatch left mouse up events during live resizing
+ // If this is a left mouse up event, dispatching this event
+ // will trigger tdf#155092 to occur in the next mouse down
+ // event. So do not dispatch this event and push it back onto
+ // the front of the event queue so no more events will be
+ // dispatched until live resizing ends. Surprisingly, live
+ // resizing appears to end in the next mouse down event.
+ if ( ImplGetSVData()->mpWinData->mbIsLiveResize && [pEvent type] == NSEventTypeLeftMouseUp )
+ {
+ [NSApp postEvent: pEvent atStart: YES];
+ return false;
+ }
+
+ [NSApp sendEvent: pEvent];
+ if ( isWakeupEvent( pEvent ) )
+ continue;
+ bHadEvent = true;
+ }
+
+ [NSApp updateWindows];
+
+ if ( !bHandleAllCurrentEvents || !pEvent || now < [pEvent timestamp] )
+ break;
+ // noelgrandin: I see sporadic hangs on the macos jenkins boxes, and the backtrace
+ // points to the this loop - let us see if breaking out of here after too many
+ // trips around helps.
+ noLoops++;
+ if (noLoops == 100)
+ break;
+ }
+ while( true );
+
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if ( !mbTimerProcessed && pTimer && pTimer->IsDirectTimeout() )
+ {
+ pTimer->handleTimerElapsed();
+ bHadEvent = true;
+ }
+
+ // if we had no event yet, wait for one if requested
+ // Related: tdf#152703 Eliminate potential blocking during live resize
+ // Some events and timers call Application::Reschedule() or
+ // Application::Yield() so don't block and wait for events when a
+ // window is in live resize
+ if( bWait && ! bHadEvent && !ImplGetSVData()->mpWinData->mbIsLiveResize )
+ {
+ SolarMutexReleaser aReleaser;
+
+ // attempt to fix macos jenkins hangs - part 3
+ // oox::xls::WorkbookFragment::finalizeImport() calls
+ // AquaSalInstance::DoYield() with bWait set to true. But
+ // since unit tests generally have no expected user generated
+ // events, we can end up blocking and waiting forever so
+ // don't block and wait when running unit tests.
+ pEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: SalInstance::IsRunningUnitTest() ? [NSDate distantPast] : [NSDate distantFuture]
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ if( pEvent )
+ {
+ [NSApp sendEvent: pEvent];
+ if ( !isWakeupEvent( pEvent ) )
+ bHadEvent = true;
+ }
+ [NSApp updateWindows];
+ }
+
+ // collect update rectangles
+ for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
+ {
+ AquaSalFrame* pFrame = static_cast<AquaSalFrame*>( pSalFrame );
+ if( pFrame->mbShown && ! pFrame->maInvalidRect.IsEmpty() )
+ {
+ pFrame->Flush( pFrame->maInvalidRect );
+ pFrame->maInvalidRect.SetEmpty();
+ }
+ }
+
+ if ( bHadEvent )
+ maWaitingYieldCond.set();
+ }
+ else
+ {
+ bHadEvent = RunInMainYield( bHandleAllCurrentEvents );
+ if ( !bHadEvent && bWait )
+ {
+ // #i103162#
+ // wait until the main thread has dispatched an event
+ maWaitingYieldCond.reset();
+ SolarMutexReleaser aReleaser;
+ maWaitingYieldCond.wait();
+ }
+ }
+
+ // we get some apple events way too early
+ // before the application is ready to handle them,
+ // so their corresponding application events need to be delayed
+ // now is a good time to handle at least one of them
+ if( bWait && !aAppEventList.empty() && ImplGetSVData()->maAppData.mbInAppExecute )
+ {
+ // make sure that only one application event is active at a time
+ static bool bInAppEvent = false;
+ if( !bInAppEvent )
+ {
+ bInAppEvent = true;
+ // get the next delayed application event
+ const ApplicationEvent* pAppEvent = aAppEventList.front();
+ aAppEventList.pop_front();
+ // handle one application event (no recursion)
+ const ImplSVData* pSVData = ImplGetSVData();
+ pSVData->mpApp->AppEvent( *pAppEvent );
+ delete pAppEvent;
+ // allow the next delayed application event
+ bInAppEvent = false;
+ }
+ }
+
+ return bHadEvent;
+}
+
+bool AquaSalInstance::AnyInput( VclInputFlags nType )
+{
+ if( nType & VclInputFlags::APPEVENT )
+ {
+ if( ! aAppEventList.empty() )
+ return true;
+ if( nType == VclInputFlags::APPEVENT )
+ return false;
+ }
+
+ OSX_INST_RUNINMAIN_UNION( AnyInput( nType ), boolean )
+
+ if( nType & VclInputFlags::TIMER )
+ {
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if (pTimer && pTimer->IsTimerElapsed())
+ return true;
+ }
+
+ unsigned/*NSUInteger*/ nEventMask = 0;
+ if( nType & VclInputFlags::MOUSE)
+ {
+ nEventMask |=
+ NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown |
+ NSEventMaskLeftMouseUp | NSEventMaskRightMouseUp | NSEventMaskOtherMouseUp |
+ NSEventMaskLeftMouseDragged | NSEventMaskRightMouseDragged | NSEventMaskOtherMouseDragged |
+ NSEventMaskScrollWheel |
+ // NSEventMaskMouseMoved |
+ NSEventMaskMouseEntered | NSEventMaskMouseExited;
+
+ // Related: tdf#155266 stop delaying painting timer while swiping
+ // After fixing several flushing issues in tdf#155266, scrollbars
+ // still will not redraw until swiping has ended or paused when
+ // using Skia/Raster or Skia disabled. So, stop the delay by only
+ // including NSEventMaskScrollWheel if the current event type is
+ // not NSEventTypeScrollWheel.
+ NSEvent* pCurrentEvent = [NSApp currentEvent];
+ if( pCurrentEvent && [pCurrentEvent type] == NSEventTypeScrollWheel )
+ nEventMask &= ~NSEventMaskScrollWheel;
+ }
+
+ if( nType & VclInputFlags::KEYBOARD)
+ nEventMask |= NSEventMaskKeyDown | NSEventMaskKeyUp | NSEventMaskFlagsChanged;
+ if( nType & VclInputFlags::OTHER)
+ nEventMask |= NSEventMaskTabletPoint | NSEventMaskApplicationDefined;
+ // TODO: VclInputFlags::PAINT / more VclInputFlags::OTHER
+ if( !bool(nType) )
+ return false;
+
+ NSEvent* pEvent = [NSApp nextEventMatchingMask: nEventMask untilDate: [NSDate distantPast]
+ inMode: NSDefaultRunLoopMode dequeue: NO];
+ return (pEvent != nullptr);
+}
+
+SalFrame* AquaSalInstance::CreateChildFrame( SystemParentData*, SalFrameStyleFlags /*nSalFrameStyle*/ )
+{
+ return nullptr;
+}
+
+SalFrame* AquaSalInstance::CreateFrame( SalFrame* pParent, SalFrameStyleFlags nSalFrameStyle )
+{
+ OSX_INST_RUNINMAIN_POINTER( CreateFrame( pParent, nSalFrameStyle ), SalFrame* )
+ return new AquaSalFrame( pParent, nSalFrameStyle );
+}
+
+void AquaSalInstance::DestroyFrame( SalFrame* pFrame )
+{
+ OSX_INST_RUNINMAIN( DestroyFrame( pFrame ) )
+ delete pFrame;
+}
+
+SalObject* AquaSalInstance::CreateObject( SalFrame* pParent, SystemWindowData* pWindowData, bool /* bShow */ )
+{
+ if ( !pParent )
+ return nullptr;
+
+ OSX_INST_RUNINMAIN_POINTER( CreateObject( pParent, pWindowData, false ), SalObject* )
+ return new AquaSalObject( static_cast<AquaSalFrame*>(pParent), pWindowData );
+}
+
+void AquaSalInstance::DestroyObject( SalObject* pObject )
+{
+ OSX_INST_RUNINMAIN( DestroyObject( pObject ) )
+ delete pObject;
+}
+
+std::unique_ptr<SalPrinter> AquaSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter )
+{
+ return std::unique_ptr<SalPrinter>(new AquaSalPrinter( dynamic_cast<AquaSalInfoPrinter*>(pInfoPrinter) ));
+}
+
+void AquaSalInstance::GetPrinterQueueInfo( ImplPrnQueueList* pList )
+{
+ NSArray* pNames = [NSPrinter printerNames];
+ NSArray* pTypes = [NSPrinter printerTypes];
+ unsigned int nNameCount = pNames ? [pNames count] : 0;
+ unsigned int nTypeCount = pTypes ? [pTypes count] : 0;
+ SAL_WARN_IF( nTypeCount != nNameCount, "vcl", "type count not equal to printer count" );
+ for( unsigned int i = 0; i < nNameCount; i++ )
+ {
+ NSString* pName = [pNames objectAtIndex: i];
+ NSString* pType = i < nTypeCount ? [pTypes objectAtIndex: i] : nil;
+ if( pName )
+ {
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = GetOUString( pName );
+ if( pType )
+ pInfo->maDriver = GetOUString( pType );
+ pInfo->mnStatus = PrintQueueFlags::NONE;
+ pInfo->mnJobs = 0;
+
+ pList->Add( std::move(pInfo) );
+ }
+ }
+
+ // tdf#151700 Prevent the non-native LibreOffice PrintDialog from
+ // displaying by creating a fake printer if there are no printers. This
+ // will allow the LibreOffice printing code to proceed with native
+ // NSPrintOperation which will display the native print panel.
+ if ( !nNameCount )
+ {
+ std::unique_ptr<SalPrinterQueueInfo> pInfo(new SalPrinterQueueInfo);
+ pInfo->maPrinterName = getFallbackPrinterName();
+ pInfo->mnStatus = PrintQueueFlags::NONE;
+ pInfo->mnJobs = 0;
+
+ pList->Add( std::move(pInfo) );
+ }
+}
+
+void AquaSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* )
+{
+}
+
+OUString AquaSalInstance::GetDefaultPrinter()
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ // WinSalInstance::GetDefaultPrinter() fetches current default printer
+ // on every call so do the same here
+ OUString aDefaultPrinter;
+ {
+ NSPrintInfo* pPI = [NSPrintInfo sharedPrintInfo];
+ SAL_WARN_IF( !pPI, "vcl", "no print info" );
+ if( pPI )
+ {
+ NSPrinter* pPr = [pPI printer];
+ SAL_WARN_IF( !pPr, "vcl", "no printer in default info" );
+ if( pPr )
+ {
+ // Related: tdf#151700 Return the name of the fake printer if
+ // there are no printers so that the LibreOffice printing code
+ // will be able to find the fake printer returned by
+ // AquaSalInstance::GetPrinterQueueInfo()
+ NSString* pDefName = [pPr name];
+ SAL_WARN_IF( !pDefName, "vcl", "printer has no name" );
+ if ( pDefName && [pDefName length])
+ aDefaultPrinter = GetOUString( pDefName );
+ else
+ aDefaultPrinter = getFallbackPrinterName();
+ }
+ }
+ }
+ return aDefaultPrinter;
+}
+
+SalInfoPrinter* AquaSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo,
+ ImplJobSetup* pSetupData )
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ SalInfoPrinter* pNewInfoPrinter = nullptr;
+ if( pQueueInfo )
+ {
+ pNewInfoPrinter = new AquaSalInfoPrinter( *pQueueInfo );
+ if( pSetupData )
+ pNewInfoPrinter->SetPrinterData( pSetupData );
+ }
+
+ return pNewInfoPrinter;
+}
+
+void AquaSalInstance::DestroyInfoPrinter( SalInfoPrinter* pPrinter )
+{
+ // #i113170# may not be the main thread if called from UNO API
+ SalData::ensureThreadAutoreleasePool();
+
+ delete pPrinter;
+}
+
+OUString AquaSalInstance::GetConnectionIdentifier()
+{
+ return OUString();
+}
+
+// We need to re-encode file urls because osl_getFileURLFromSystemPath converts
+// to UTF-8 before encoding non ascii characters, which is not what other apps expect.
+static OUString translateToExternalUrl(const OUString& internalUrl)
+{
+ uno::Reference< uno::XComponentContext > context(
+ comphelper::getProcessComponentContext());
+ return uri::ExternalUriReferenceTranslator::create(context)->translateToExternal(internalUrl);
+}
+
+// #i104525# many versions of OSX have problems with some URLs:
+// when an app requests OSX to add one of these URLs to the "Recent Items" list
+// then this app gets killed (TextEdit, Preview, etc. and also OOo)
+static bool isDangerousUrl( const OUString& rUrl )
+{
+ // use a heuristic that detects all known cases since there is no official comment
+ // on the exact impact and root cause of the OSX bug
+ const int nLen = rUrl.getLength();
+ const sal_Unicode* p = rUrl.getStr();
+ for( int i = 0; i < nLen-3; ++i, ++p ) {
+ if( p[0] != '%' )
+ continue;
+ // escaped percent?
+ if( (p[1] == '2') && (p[2] == '5') )
+ return true;
+ // escapes are considered to be UTF-8 encoded
+ // => check for invalid UTF-8 leading byte
+ if( (p[1] != 'f') && (p[1] != 'F') )
+ continue;
+ int cLowNibble = p[2];
+ if( (cLowNibble >= '0' ) && (cLowNibble <= '9'))
+ return false;
+ if( cLowNibble >= 'a' )
+ cLowNibble -= 'a' - 'A';
+ if( (cLowNibble < 'A') || (cLowNibble >= 'C'))
+ return true;
+ }
+
+ return false;
+}
+
+void AquaSalInstance::AddToRecentDocumentList(const OUString& rFileUrl, const OUString& /*rMimeType*/, const OUString& /*rDocumentService*/)
+{
+ // Convert file URL for external use (see above)
+ OUString externalUrl = translateToExternalUrl(rFileUrl);
+ if( externalUrl.isEmpty() )
+ externalUrl = rFileUrl;
+
+ if( !externalUrl.isEmpty() && !isDangerousUrl( externalUrl ) )
+ {
+ NSString* pString = CreateNSString( externalUrl );
+ NSURL* pURL = [NSURL URLWithString: pString];
+
+ if( pURL )
+ {
+ NSDocumentController* pCtrl = [NSDocumentController sharedDocumentController];
+ [pCtrl noteNewRecentDocumentURL: pURL];
+ }
+ if( pString )
+ [pString release];
+ }
+}
+
+SalTimer* AquaSalInstance::CreateSalTimer()
+{
+ return new AquaSalTimer();
+}
+
+SalSystem* AquaSalInstance::CreateSalSystem()
+{
+ return new AquaSalSystem();
+}
+
+std::shared_ptr<SalBitmap> AquaSalInstance::CreateSalBitmap()
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return std::make_shared<SkiaSalBitmap>();
+ else
+#endif
+ return std::make_shared<QuartzSalBitmap>();
+}
+
+OUString AquaSalInstance::getOSVersion()
+{
+ NSString * versionString = nullptr;
+ NSDictionary * sysVersionDict = [ NSDictionary dictionaryWithContentsOfFile: @"/System/Library/CoreServices/SystemVersion.plist" ];
+ if ( sysVersionDict )
+ versionString = [ sysVersionDict valueForKey: @"ProductVersion" ];
+
+ OUString aVersion = "macOS ";
+ if ( versionString )
+ aVersion += OUString::fromUtf8( [ versionString UTF8String ] );
+ else
+ aVersion += "(unknown)";
+
+ return aVersion;
+}
+
+CGImageRef CreateCGImage( const Image& rImage )
+{
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled())
+ return SkiaHelper::createCGImage( rImage );
+#endif
+
+ BitmapEx aBmpEx( rImage.GetBitmapEx() );
+ Bitmap aBmp( aBmpEx.GetBitmap() );
+
+ if( aBmp.IsEmpty() || ! aBmp.ImplGetSalBitmap() )
+ return nullptr;
+
+ // simple case, no transparency
+ QuartzSalBitmap* pSalBmp = static_cast<QuartzSalBitmap*>(aBmp.ImplGetSalBitmap().get());
+
+ if( ! pSalBmp )
+ return nullptr;
+
+ CGImageRef xImage = nullptr;
+ if( !aBmpEx.IsAlpha() )
+ xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
+ else
+ {
+ AlphaMask aAlphaMask( aBmpEx.GetAlphaMask() );
+ Bitmap aMask( aAlphaMask.GetBitmap() );
+ QuartzSalBitmap* pMaskBmp = static_cast<QuartzSalBitmap*>(aMask.ImplGetSalBitmap().get());
+ if( pMaskBmp )
+ xImage = pSalBmp->CreateWithMask( *pMaskBmp, 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
+ else
+ xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight );
+ }
+
+ return xImage;
+}
+
+NSImage* CreateNSImage( const Image& rImage )
+{
+ CGImageRef xImage = CreateCGImage( rImage );
+
+ if( ! xImage )
+ return nil;
+
+ Size aSize( rImage.GetSizePixel() );
+ NSImage* pImage = [[NSImage alloc] initWithSize: NSMakeSize( aSize.Width(), aSize.Height() )];
+ if( pImage )
+ {
+ [pImage lockFocusFlipped:YES];
+ NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
+ CGContextRef rCGContext = [pContext CGContext];
+
+ const CGRect aDstRect = { {0, 0}, { static_cast<CGFloat>(aSize.Width()), static_cast<CGFloat>(aSize.Height()) } };
+ CGContextDrawImage( rCGContext, aDstRect, xImage );
+
+ [pImage unlockFocus];
+ }
+
+ CGImageRelease( xImage );
+
+ return pImage;
+}
+
+bool AquaSalInstance::SVMainHook(int* pnInit)
+{
+ gpnInit = pnInit;
+
+ OUString aExeURL, aExe;
+ osl_getExecutableFile( &aExeURL.pData );
+ osl_getSystemPathFromFileURL( aExeURL.pData, &aExe.pData );
+ OString aByteExe( OUStringToOString( aExe, osl_getThreadTextEncoding() ) );
+
+#ifdef DEBUG
+ aByteExe += OString ( " NSAccessibilityDebugLogLevel 1" );
+ const char* pArgv[] = { aByteExe.getStr(), NULL };
+ NSApplicationMain( 3, pArgv );
+#else
+ const char* pArgv[] = { aByteExe.getStr(), nullptr };
+ NSApplicationMain( 1, pArgv );
+#endif
+
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salmacos.cxx b/vcl/osx/salmacos.cxx
new file mode 100644
index 0000000000..700b252cf4
--- /dev/null
+++ b/vcl/osx/salmacos.cxx
@@ -0,0 +1,529 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+// This file contains the macOS-specific versions of the functions which were touched in the commit
+// to fix tdf#138122. The iOS-specific versions of these functions are kept (for now, when this
+// comment is written) as they were before that commit in vcl/ios/salios.cxx.
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/bitmap.hxx>
+
+#include <quartz/salbmp.h>
+#include <quartz/salgdi.h>
+#include <quartz/salvd.h>
+#include <quartz/utils.h>
+
+#include <osx/saldata.hxx>
+
+// From salbmp.cxx
+
+bool QuartzSalBitmap::Create(CGLayerHolder const & rLayerHolder, int nBitmapBits, int nX, int nY, int nWidth, int nHeight, bool bFlipped)
+{
+
+ // TODO: Bitmaps from scaled layers are reverted to single precision. This is a workaround only unless bitmaps with precision of
+ // source layer are implemented.
+
+ SAL_WARN_IF(!rLayerHolder.isSet(), "vcl", "QuartzSalBitmap::Create() from non-layered context");
+
+ // sanitize input parameters
+ if( nX < 0 ) {
+ nWidth += nX;
+ nX = 0;
+ }
+
+ if( nY < 0 ) {
+ nHeight += nY;
+ nY = 0;
+ }
+
+ CGSize aLayerSize = CGLayerGetSize(rLayerHolder.get());
+ const float fScale = rLayerHolder.getScale();
+ aLayerSize.width /= fScale;
+ aLayerSize.height /= fScale;
+
+ if( nWidth >= static_cast<int>(aLayerSize.width) - nX )
+ nWidth = static_cast<int>(aLayerSize.width) - nX;
+
+ if( nHeight >= static_cast<int>(aLayerSize.height) - nY )
+ nHeight = static_cast<int>(aLayerSize.height) - nY;
+
+ if( (nWidth < 0) || (nHeight < 0) )
+ nWidth = nHeight = 0;
+
+ // initialize properties
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ mnBits = nBitmapBits ? nBitmapBits : 32;
+
+ // initialize drawing context
+ CreateContext();
+
+ // copy layer content into the bitmap buffer
+ const CGPoint aSrcPoint = { static_cast<CGFloat>(-nX * fScale), static_cast<CGFloat>(-nY * fScale) };
+ if (maGraphicContext.isSet())
+ {
+ if( bFlipped )
+ {
+ CGContextTranslateCTM(maGraphicContext.get(), 0, +mnHeight);
+ CGContextScaleCTM(maGraphicContext.get(), +1, -1);
+ }
+ maGraphicContext.saveState();
+ CGContextScaleCTM(maGraphicContext.get(), 1 / fScale, 1 / fScale);
+ CGContextDrawLayerAtPoint(maGraphicContext.get(), aSrcPoint, rLayerHolder.get());
+ maGraphicContext.restoreState();
+ }
+ return true;
+}
+
+// From salgdicommon.cxx
+
+void AquaGraphicsBackend::copyBits(const SalTwoRect &rPosAry, SalGraphics *pSrcGraphics)
+{
+ AquaSharedAttributes* pSrcShared = nullptr;
+
+ if (pSrcGraphics)
+ {
+ AquaSalGraphics* pSrc = static_cast<AquaSalGraphics*>(pSrcGraphics);
+ pSrcShared = &pSrc->getAquaGraphicsBackend()->GetShared();
+ }
+ else
+ pSrcShared = &mrShared;
+
+ if (rPosAry.mnSrcWidth <= 0 || rPosAry.mnSrcHeight <= 0 || rPosAry.mnDestWidth <= 0 || rPosAry.mnDestHeight <= 0)
+ return;
+ if (!mrShared.maContextHolder.isSet())
+ return;
+
+ SAL_WARN_IF (!pSrcShared->maLayer.isSet(), "vcl.quartz", "AquaSalGraphics::copyBits() from non-layered graphics this=" << this);
+
+ // Layered graphics are copied by AquaSalGraphics::copyScaledArea() which is able to consider the layer's scaling.
+
+ if (pSrcShared->maLayer.isSet())
+ copyScaledArea(rPosAry.mnDestX, rPosAry.mnDestY, rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, pSrcShared);
+ else
+ {
+ mrShared.applyXorContext();
+ pSrcShared->applyXorContext();
+ std::shared_ptr<SalBitmap> pBitmap = pSrcGraphics->GetImpl()->getBitmap(rPosAry.mnSrcX, rPosAry.mnSrcY,
+ rPosAry.mnSrcWidth, rPosAry.mnSrcHeight);
+ if (pBitmap)
+ {
+ SalTwoRect aPosAry(rPosAry);
+ aPosAry.mnSrcX = 0;
+ aPosAry.mnSrcY = 0;
+ drawBitmap(aPosAry, *pBitmap);
+ }
+ }
+}
+
+void AquaGraphicsBackend::copyArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, bool)
+{
+ if (!mrShared.maContextHolder.isSet())
+ return;
+
+ // Functionality is implemented in protected member function AquaSalGraphics::copyScaledArea() which requires an additional
+ // parameter of type SalGraphics to be used in AquaSalGraphics::copyBits() too.
+
+ copyScaledArea(nDstX, nDstY, nSrcX, nSrcY, nSrcWidth, nSrcHeight, &mrShared);
+}
+
+void AquaGraphicsBackend::copyScaledArea(tools::Long nDstX, tools::Long nDstY,tools::Long nSrcX, tools::Long nSrcY,
+ tools::Long nSrcWidth, tools::Long nSrcHeight, AquaSharedAttributes* pSrcShared)
+{
+ SAL_WARN_IF(!mrShared.maLayer.isSet(), "vcl.quartz",
+ "AquaSalGraphics::copyScaledArea() without graphics context or for non-layered graphics this=" << this);
+
+ if (!mrShared.maContextHolder.isSet() || !mrShared.maLayer.isSet())
+ return;
+
+ // Determine scaled geometry of source and target area assuming source and target area have the same scale
+
+ float fScale = mrShared.maLayer.getScale();
+ CGFloat nScaledSourceX = nSrcX * fScale;
+ CGFloat nScaledSourceY = nSrcY * fScale;
+ CGFloat nScaledTargetX = nDstX * fScale;
+ CGFloat nScaledTargetY = nDstY * fScale;
+ CGFloat nScaledSourceWidth = nSrcWidth * fScale;
+ CGFloat nScaledSourceHeight = nSrcHeight * fScale;
+
+ // Apply XOR context and get copy context from current graphics context or XOR context
+
+ mrShared.applyXorContext();
+ mrShared.maContextHolder.saveState();
+ CGContextRef xCopyContext = mrShared.maContextHolder.get();
+ if (mrShared.mpXorEmulation && mrShared.mpXorEmulation->IsEnabled())
+ xCopyContext = mrShared.mpXorEmulation->GetTargetContext();
+
+ // Set scale matrix of copy context to consider layer scaling
+
+ CGContextScaleCTM(xCopyContext, 1 / fScale, 1 / fScale);
+
+ // Creating an additional layer is required for drawing with the required scale and extent at the drawing destination
+ // thereafter.
+
+ CGLayerHolder aSourceLayerHolder(pSrcShared->maLayer);
+ const CGSize aSourceSize = CGSizeMake(nScaledSourceWidth, nScaledSourceHeight);
+ aSourceLayerHolder.set(CGLayerCreateWithContext(xCopyContext, aSourceSize, nullptr));
+ const CGContextRef xSourceContext = CGLayerGetContext(aSourceLayerHolder.get());
+ CGPoint aSrcPoint = CGPointMake(-nScaledSourceX, -nScaledSourceY);
+ if (pSrcShared->isFlipped())
+ {
+ CGContextTranslateCTM(xSourceContext, 0, nScaledSourceHeight);
+ CGContextScaleCTM(xSourceContext, 1, -1);
+ aSrcPoint.y = nScaledSourceY + nScaledSourceHeight - mrShared.mnHeight * fScale;
+ }
+ CGContextSetBlendMode(xSourceContext, kCGBlendModeCopy);
+ CGContextDrawLayerAtPoint(xSourceContext, aSrcPoint, pSrcShared->maLayer.get());
+
+ // Copy source area from additional layer to target area
+
+ const CGRect aTargetRect = CGRectMake(nScaledTargetX, nScaledTargetY, nScaledSourceWidth, nScaledSourceHeight);
+ CGContextSetBlendMode(xCopyContext, kCGBlendModeCopy);
+ CGContextDrawLayerInRect(xCopyContext, aTargetRect, aSourceLayerHolder.get());
+
+ // Housekeeping on exit
+
+ mrShared.maContextHolder.restoreState();
+ if (aSourceLayerHolder.get() != mrShared.maLayer.get())
+ CGLayerRelease(aSourceLayerHolder.get());
+
+ mrShared.refreshRect(nDstX, nDstY, nSrcWidth, nSrcHeight);
+}
+
+void AquaSalGraphics::SetVirDevGraphics(SalVirtualDevice* pVirDev, CGLayerHolder const &rLayer, CGContextRef xContext, int nBitmapDepth)
+{
+ SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this << " layer=" << rLayer.get() << " context=" << xContext);
+
+ // Set member variables
+
+ InvalidateContext();
+ maShared.mbWindow = false;
+ maShared.mbPrinter = false;
+ maShared.mbVirDev = true;
+ maShared.maLayer = rLayer;
+ maShared.mnBitmapDepth = nBitmapDepth;
+
+ mpBackend->UpdateGeometryProvider(pVirDev);
+
+ // Get size and scale from layer if set else from bitmap and sal::aqua::getWindowScaling(), which is used to determine
+ // scaling for direct graphics output too
+
+ CGSize aSize;
+ float fScale;
+ if (maShared.maLayer.isSet())
+ {
+ maShared.maContextHolder.set(CGLayerGetContext(maShared.maLayer.get()));
+ aSize = CGLayerGetSize(maShared.maLayer.get());
+ fScale = maShared.maLayer.getScale();
+ }
+ else
+ {
+ maShared.maContextHolder.set(xContext);
+ if (!xContext)
+ return;
+ aSize.width = CGBitmapContextGetWidth(xContext);
+ aSize.height = CGBitmapContextGetHeight(xContext);
+ fScale = sal::aqua::getWindowScaling();
+ }
+ maShared.mnWidth = aSize.width / fScale;
+ maShared.mnHeight = aSize.height / fScale;
+
+ // Set color space for fill and stroke
+
+ CGColorSpaceRef aColorSpace = GetSalData()->mxRGBSpace;
+ CGContextSetFillColorSpace(maShared.maContextHolder.get(), aColorSpace);
+ CGContextSetStrokeColorSpace(maShared.maContextHolder.get(), aColorSpace);
+
+ // Apply scale matrix to virtual device graphics
+
+ CGContextScaleCTM(maShared.maContextHolder.get(), fScale, fScale);
+
+ // Apply XOR emulation if required
+
+ if (maShared.mpXorEmulation)
+ {
+ maShared.mpXorEmulation->SetTarget(maShared.mnWidth, maShared.mnHeight, maShared.mnBitmapDepth, maShared.maContextHolder.get(), maShared.maLayer.get());
+ if (maShared.mpXorEmulation->IsEnabled())
+ maShared.maContextHolder.set(maShared.mpXorEmulation->GetMaskContext());
+ }
+
+ // Housekeeping on exit
+
+ maShared.maContextHolder.saveState();
+ maShared.setState();
+
+ SAL_INFO("vcl.quartz", "SetVirDevGraphics() this=" << this <<
+ " (" << maShared.mnWidth << "x" << maShared.mnHeight << ") fScale=" << fScale << " mnBitmapDepth=" << maShared.mnBitmapDepth);
+}
+
+void XorEmulation::SetTarget(int nWidth, int nHeight, int nTargetDepth, CGContextRef xTargetContext, CGLayerRef xTargetLayer)
+{
+ SAL_INFO("vcl.quartz", "XorEmulation::SetTarget() this=" << this <<
+ " (" << nWidth << "x" << nHeight << ") depth=" << nTargetDepth <<
+ " context=" << xTargetContext << " layer=" << xTargetLayer);
+
+ // Prepare to replace old mask and temporary context
+
+ if (m_xMaskContext)
+ {
+ CGContextRelease(m_xMaskContext);
+ delete[] m_pMaskBuffer;
+ m_xMaskContext = nullptr;
+ m_pMaskBuffer = nullptr;
+ if (m_xTempContext)
+ {
+ CGContextRelease(m_xTempContext);
+ delete[] m_pTempBuffer;
+ m_xTempContext = nullptr;
+ m_pTempBuffer = nullptr;
+ }
+ }
+
+ // Return early if there is nothing more to do
+
+ if (!xTargetContext)
+ return;
+
+ // Retarget drawing operations to the XOR mask
+
+ m_xTargetLayer = xTargetLayer;
+ m_xTargetContext = xTargetContext;
+
+ // Prepare creation of matching bitmaps
+
+ CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace;
+ CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst;
+ int nBitDepth = nTargetDepth;
+ if (!nBitDepth)
+ nBitDepth = 32;
+ int nBytesPerRow = 4;
+ const size_t nBitsPerComponent = (nBitDepth == 16) ? 5 : 8;
+ if (nBitDepth <= 8)
+ {
+ aCGColorSpace = GetSalData()->mxGraySpace;
+ aCGBmpInfo = kCGImageAlphaNone;
+ nBytesPerRow = 1;
+ }
+ float fScale = sal::aqua::getWindowScaling();
+ size_t nScaledWidth = nWidth * fScale;
+ size_t nScaledHeight = nHeight * fScale;
+ nBytesPerRow *= nScaledWidth;
+ m_nBufferLongs = (nScaledHeight * nBytesPerRow + sizeof(sal_uLong) - 1) / sizeof(sal_uLong);
+
+ // Create XOR mask context
+
+ m_pMaskBuffer = new sal_uLong[m_nBufferLongs];
+ m_xMaskContext = CGBitmapContextCreate(m_pMaskBuffer, nScaledWidth, nScaledHeight,
+ nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
+ SAL_WARN_IF(!m_xMaskContext, "vcl.quartz", "mask context creation failed");
+
+ // Reset XOR mask to black
+
+ memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
+
+ // Create bitmap context for manual XOR unless target context is a bitmap context
+
+ if (nTargetDepth)
+ m_pTempBuffer = static_cast<sal_uLong*>(CGBitmapContextGetData(m_xTargetContext));
+ if (!m_pTempBuffer)
+ {
+ m_pTempBuffer = new sal_uLong[m_nBufferLongs];
+ m_xTempContext = CGBitmapContextCreate(m_pTempBuffer, nScaledWidth, nScaledHeight,
+ nBitsPerComponent, nBytesPerRow, aCGColorSpace, aCGBmpInfo);
+ SAL_WARN_IF(!m_xTempContext, "vcl.quartz", "temp context creation failed");
+ }
+
+ // Initialize XOR mask context for drawing
+
+ CGContextSetFillColorSpace(m_xMaskContext, aCGColorSpace);
+ CGContextSetStrokeColorSpace(m_xMaskContext, aCGColorSpace);
+ CGContextSetShouldAntialias(m_xMaskContext, false);
+
+ // Improve XOR emulation for monochrome contexts
+
+ if (aCGColorSpace == GetSalData()->mxGraySpace)
+ CGContextSetBlendMode(m_xMaskContext, kCGBlendModeDifference);
+
+ // Initialize XOR mask transformation matrix and apply scale matrix to consider layer scaling
+
+ const CGAffineTransform aCTM = CGContextGetCTM(xTargetContext);
+ CGContextConcatCTM(m_xMaskContext, aCTM);
+ if (m_xTempContext)
+ {
+ CGContextConcatCTM( m_xTempContext, aCTM );
+ CGContextScaleCTM(m_xTempContext, 1 / fScale, 1 / fScale);
+ }
+ CGContextSaveGState(m_xMaskContext);
+}
+
+bool XorEmulation::UpdateTarget()
+{
+ SAL_INFO("vcl.quartz", "XorEmulation::UpdateTarget() this=" << this);
+
+ if (!IsEnabled())
+ return false;
+
+ // Update temporary bitmap buffer
+
+ if (m_xTempContext)
+ {
+ SAL_WARN_IF(m_xTargetContext == nullptr, "vcl.quartz", "Target layer is NULL");
+ CGContextDrawLayerAtPoint(m_xTempContext, CGPointZero, m_xTargetLayer);
+ }
+
+ // XOR using XOR mask (sufficient for simple color manipulations as well as for complex XOR clipping used in metafiles)
+
+ const sal_uLong *pSrc = m_pMaskBuffer;
+ sal_uLong *pDst = m_pTempBuffer;
+ for (int i = m_nBufferLongs; --i >= 0;)
+ *(pDst++) ^= *(pSrc++);
+
+ // Write back XOR results to target context
+
+ if (m_xTempContext)
+ {
+ CGImageRef xXorImage = CGBitmapContextCreateImage(m_xTempContext);
+ size_t nWidth = CGImageGetWidth(xXorImage);
+ size_t nHeight = CGImageGetHeight(xXorImage);
+
+ // Set scale matrix of target context to consider layer scaling and update target context
+ // TODO: Update minimal change rectangle
+
+ const CGRect aFullRect = CGRectMake(0, 0, nWidth, nHeight);
+ CGContextSaveGState(m_xTargetContext);
+ float fScale = sal::aqua::getWindowScaling();
+ CGContextScaleCTM(m_xTargetContext, 1 / fScale, 1 / fScale);
+ CGContextDrawImage(m_xTargetContext, aFullRect, xXorImage);
+ CGContextRestoreGState(m_xTargetContext);
+ CGImageRelease(xXorImage);
+ }
+
+ // Reset XOR mask to black again
+ // TODO: Not needed for last update
+
+ memset(m_pMaskBuffer, 0, m_nBufferLongs * sizeof(sal_uLong));
+
+ // TODO: Return FALSE if target was not changed
+
+ return true;
+}
+
+// From salvd.cxx
+
+void AquaSalVirtualDevice::Destroy()
+{
+ SAL_INFO( "vcl.virdev", "AquaSalVirtualDevice::Destroy() this=" << this << " mbForeignContext=" << mbForeignContext );
+
+ if (mbForeignContext)
+ {
+ // Do not delete mxContext that we have received from outside VCL
+ maLayer.set(nullptr);
+ return;
+ }
+
+ if (maLayer.isSet())
+ {
+ if( mpGraphics )
+ {
+ mpGraphics->SetVirDevGraphics(this, nullptr, nullptr);
+ }
+ CGLayerRelease(maLayer.get());
+ maLayer.set(nullptr);
+ }
+
+ if (maBitmapContext.isSet())
+ {
+ CGContextRelease(maBitmapContext.get());
+ maBitmapContext.set(nullptr);
+ }
+}
+
+bool AquaSalVirtualDevice::SetSize(tools::Long nDX, tools::Long nDY)
+{
+ SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " (" << nDX << "x" << nDY << ") mbForeignContext=" << (mbForeignContext ? "YES" : "NO"));
+
+ // Do not delete/resize graphics context if it has been received from outside VCL
+
+ if (mbForeignContext)
+ return true;
+
+ // Do not delete/resize graphics context if no change of geometry has been requested
+
+ float fScale;
+ if (maLayer.isSet())
+ {
+ fScale = maLayer.getScale();
+ const CGSize aSize = CGLayerGetSize(maLayer.get());
+ if ((nDX == aSize.width / fScale) && (nDY == aSize.height / fScale))
+ return true;
+ }
+
+ // Destroy graphics context if change of geometry has been requested
+
+ Destroy();
+
+ // Prepare new graphics context for initialization, use scaling independent of prior graphics context calculated by
+ // sal::aqua::getWindowScaling(), which is used to determine scaling for direct graphics output too
+
+ mnWidth = nDX;
+ mnHeight = nDY;
+ fScale = sal::aqua::getWindowScaling();
+ CGColorSpaceRef aColorSpace;
+ uint32_t nFlags;
+ if (mnBitmapDepth && (mnBitmapDepth < 16))
+ {
+ mnBitmapDepth = 8;
+ aColorSpace = GetSalData()->mxGraySpace;
+ nFlags = kCGImageAlphaNone;
+ }
+ else
+ {
+ mnBitmapDepth = 32;
+ aColorSpace = GetSalData()->mxRGBSpace;
+
+ nFlags = uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Host);
+ }
+
+ // Allocate buffer for virtual device graphics as bitmap context to store graphics with highest required (scaled) resolution
+
+ size_t nScaledWidth = mnWidth * fScale;
+ size_t nScaledHeight = mnHeight * fScale;
+ size_t nBytesPerRow = mnBitmapDepth * nScaledWidth / 8;
+ maBitmapContext.set(CGBitmapContextCreate(nullptr, nScaledWidth, nScaledHeight, 8, nBytesPerRow, aColorSpace, nFlags));
+
+ SAL_INFO("vcl.virdev", "AquaSalVirtualDevice::SetSize() this=" << this <<
+ " fScale=" << fScale << " mnBitmapDepth=" << mnBitmapDepth);
+
+ CGSize aLayerSize = { static_cast<CGFloat>(nScaledWidth), static_cast<CGFloat>(nScaledHeight) };
+ maLayer.set(CGLayerCreateWithContext(maBitmapContext.get(), aLayerSize, nullptr));
+ maLayer.setScale(fScale);
+ mpGraphics->SetVirDevGraphics(this, maLayer, CGLayerGetContext(maLayer.get()), mnBitmapDepth);
+
+ SAL_WARN_IF(!maBitmapContext.isSet(), "vcl.quartz", "No context");
+
+ return maLayer.isSet();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salmenu.cxx b/vcl/osx/salmenu.cxx
new file mode 100644
index 0000000000..b3d02587f4
--- /dev/null
+++ b/vcl/osx/salmenu.cxx
@@ -0,0 +1,888 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+#include <osl/diagnose.h>
+
+#include <objc/objc-runtime.h>
+
+#include <rtl/ustrbuf.hxx>
+#include <tools/debug.hxx>
+#include <tools/long.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/toolkit/floatwin.hxx>
+#include <vcl/window.hxx>
+#include <vcl/svapp.hxx>
+
+#include <osx/runinmain.hxx>
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#include <osx/salmenu.h>
+#include <osx/salnsmenu.h>
+#include <osx/salframe.h>
+#include <osx/a11ywrapper.h>
+#include <quartz/utils.h>
+#include <strings.hrc>
+#include <window.h>
+#include <vcl/mnemonic.hxx>
+
+namespace {
+
+void releaseButtonEntry( AquaSalMenu::MenuBarButtonEntry& i_rEntry )
+{
+ if( i_rEntry.mpNSImage )
+ {
+ [i_rEntry.mpNSImage release];
+ i_rEntry.mpNSImage = nil;
+ }
+ if( i_rEntry.mpToolTipString )
+ {
+ [i_rEntry.mpToolTipString release];
+ i_rEntry.mpToolTipString = nil;
+ }
+}
+
+}
+
+const AquaSalMenu* AquaSalMenu::pCurrentMenuBar = nullptr;
+
+@interface MainMenuSelector : NSObject
+{
+}
+-(void)showDialog: (ShowDialogId)nDialog;
+-(void)showPreferences: (id)sender;
+-(void)showAbout: (id)sender;
+@end
+
+@implementation MainMenuSelector
+-(void)showDialog: (ShowDialogId)nDialog
+{
+ if( AquaSalMenu::pCurrentMenuBar )
+ {
+ const AquaSalFrame* pFrame = AquaSalMenu::pCurrentMenuBar->mpFrame;
+ if( pFrame && AquaSalFrame::isAlive( pFrame ) )
+ {
+ pFrame->CallCallback( SalEvent::ShowDialog, reinterpret_cast<void*>(nDialog) );
+ }
+ }
+ else
+ {
+ OUString aDialog;
+ if( nDialog == ShowDialogId::About )
+ aDialog = "ABOUT";
+ else if( nDialog == ShowDialogId::Preferences )
+ aDialog = "PREFERENCES";
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(
+ ApplicationEvent::Type::ShowDialog, aDialog);
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ }
+}
+
+-(void)showPreferences: (id) sender
+{
+ (void)sender;
+ SolarMutexGuard aGuard;
+
+ [self showDialog: ShowDialogId::Preferences];
+}
+-(void)showAbout: (id) sender
+{
+ (void)sender;
+ SolarMutexGuard aGuard;
+
+ [self showDialog: ShowDialogId::About];
+}
+@end
+
+// FIXME: currently this is leaked
+static MainMenuSelector* pMainMenuSelector = nil;
+
+static void initAppMenu()
+{
+ static bool bInitialized = false;
+ if (bInitialized)
+ return;
+ OSX_SALDATA_RUNINMAIN(initAppMenu())
+ bInitialized = true;
+
+ NSMenu* pAppMenu = nil;
+ NSMenuItem* pNewItem = nil;
+
+ NSMenu* pMainMenu = [[[NSMenu alloc] initWithTitle: @"Main Menu"] autorelease];
+ pNewItem = [pMainMenu addItemWithTitle: @"Application"
+ action: nil
+ keyEquivalent: @""];
+ pAppMenu = [[[NSMenu alloc] initWithTitle: @"Application"] autorelease];
+ [pNewItem setSubmenu: pAppMenu];
+ [NSApp setMainMenu: pMainMenu];
+
+ pMainMenuSelector = [[MainMenuSelector alloc] init];
+
+ // about
+ NSString* pString = CreateNSString(VclResId(SV_STDTEXT_ABOUT));
+ pNewItem = [pAppMenu addItemWithTitle: pString
+ action: @selector(showAbout:)
+ keyEquivalent: @""];
+ [pString release];
+ [pNewItem setTarget: pMainMenuSelector];
+
+ [pAppMenu addItem:[NSMenuItem separatorItem]];
+
+ // preferences
+ pString = CreateNSString(VclResId(SV_STDTEXT_PREFERENCES));
+ pNewItem = [pAppMenu addItemWithTitle: pString
+ action: @selector(showPreferences:)
+ keyEquivalent: @","];
+ [pString release];
+ [pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand];
+ [pNewItem setTarget: pMainMenuSelector];
+
+ [pAppMenu addItem:[NSMenuItem separatorItem]];
+
+ // Services item and menu
+ pString = CreateNSString(VclResId(SV_MENU_MAC_SERVICES));
+ pNewItem = [pAppMenu addItemWithTitle: pString
+ action: nil
+ keyEquivalent: @""];
+ NSMenu *servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
+ [pNewItem setSubmenu: servicesMenu];
+ [NSApp setServicesMenu: servicesMenu];
+
+ [pAppMenu addItem:[NSMenuItem separatorItem]];
+
+ // Hide Application
+ pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEAPP));
+ [pAppMenu addItemWithTitle: pString
+ action:@selector(hide:)
+ keyEquivalent:@"h"];
+ [pString release];
+
+ // Hide Others
+ pString = CreateNSString(VclResId(SV_MENU_MAC_HIDEALL));
+ [pAppMenu addItemWithTitle: pString
+ action:@selector(hideOtherApplications:)
+ keyEquivalent:@"h"];
+ [pString release];
+ [pNewItem setKeyEquivalentModifierMask: NSEventModifierFlagCommand | NSEventModifierFlagOption];
+
+ // Show All
+ pString = CreateNSString(VclResId(SV_MENU_MAC_SHOWALL));
+ [pAppMenu addItemWithTitle: pString
+ action:@selector(unhideAllApplications:)
+ keyEquivalent:@""];
+ [pString release];
+
+ [pAppMenu addItem:[NSMenuItem separatorItem]];
+
+ // Quit
+ pString = CreateNSString(VclResId(SV_MENU_MAC_QUITAPP));
+ [pAppMenu addItemWithTitle: pString
+ action:@selector(terminate:)
+ keyEquivalent:@"q"];
+ [pString release];
+}
+
+std::unique_ptr<SalMenu> AquaSalInstance::CreateMenu( bool bMenuBar, Menu* pVCLMenu )
+{
+ initAppMenu();
+
+ AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar );
+ pAquaSalMenu->mpVCLMenu = pVCLMenu;
+
+ return std::unique_ptr<SalMenu>(pAquaSalMenu);
+}
+
+std::unique_ptr<SalMenuItem> AquaSalInstance::CreateMenuItem( const SalItemParams & rItemData )
+{
+ AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( &rItemData );
+
+ return std::unique_ptr<SalMenuItem>(pSalMenuItem);
+}
+
+/*
+ * AquaSalMenu
+ */
+
+AquaSalMenu::AquaSalMenu( bool bMenuBar ) :
+ mbMenuBar( bMenuBar ),
+ mpMenu( nil ),
+ mpFrame( nullptr ),
+ mpParentSalMenu( nullptr )
+{
+ if( ! mbMenuBar )
+ {
+ mpMenu = [[SalNSMenu alloc] initWithMenu: this];
+ [mpMenu setDelegate: reinterpret_cast< id<NSMenuDelegate> >(mpMenu)];
+ }
+ else
+ {
+ mpMenu = [NSApp mainMenu];
+ }
+ [mpMenu setAutoenablesItems: NO];
+}
+
+AquaSalMenu::~AquaSalMenu()
+{
+ // actually someone should have done AquaSalFrame::SetMenu( NULL )
+ // on our frame, alas it is not so
+ if( mpFrame && AquaSalFrame::isAlive( mpFrame ) && mpFrame->mpMenu == this )
+ const_cast<AquaSalFrame*>(mpFrame)->mpMenu = nullptr;
+
+ // this should normally be empty already, but be careful...
+ for( size_t i = 0; i < maButtons.size(); i++ )
+ releaseButtonEntry( maButtons[i] );
+ maButtons.clear();
+
+ // is this leaking in some cases ? the release often leads to a duplicate release
+ // it seems the parent item gets ownership of the menu
+ if( mpMenu )
+ {
+ if( mbMenuBar )
+ {
+ if( pCurrentMenuBar == this )
+ {
+ // if the current menubar gets destroyed, set the default menubar
+ setDefaultMenu();
+ }
+ }
+ else
+ // the system may still hold a reference on mpMenu
+ {
+ // so set the pointer to this AquaSalMenu to NULL
+ // to protect from calling a dead object
+
+ // in ! mbMenuBar case our mpMenu is actually a SalNSMenu*
+ // so we can safely cast here
+ [static_cast<SalNSMenu*>(mpMenu) setSalMenu: nullptr];
+ /* #i89860# FIXME:
+ using [autorelease] here (and in AquaSalMenuItem::~AquaSalMenuItem)
+ instead of [release] fixes an occasional crash. That should
+ indicate that we release menus / menu items in the wrong order
+ somewhere, but I could not find that case.
+ */
+ [mpMenu autorelease];
+ }
+ }
+}
+
+bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const tools::Rectangle& rRect, FloatWinPopupFlags nFlags)
+{
+ // set offsets for positioning
+ const float offset = 9.0;
+
+ // get the pointers
+ AquaSalFrame * pParentAquaSalFrame = static_cast<AquaSalFrame *>(pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame());
+ NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow;
+ NSView* pParentNSView = [pParentNSWindow contentView];
+ NSView* pPopupNSView = static_cast<AquaSalFrame *>(pWin->ImplGetWindow()->ImplGetFrame())->mpNSView;
+ NSRect popupFrame = [pPopupNSView frame];
+
+ // create frame rect
+ NSRect displayPopupFrame = NSMakeRect( rRect.Left()+(offset-1), rRect.Top()+(offset+1), popupFrame.size.width, 0 );
+ pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
+
+ // do the same strange semantics as vcl popup windows to arrive at a frame geometry
+ // in mirrored UI case; best done by actually executing the same code
+ sal_uInt16 nArrangeIndex;
+ pWin->SetPosPixel( FloatingWindow::ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) );
+ displayPopupFrame.origin.x = pWin->ImplGetFrame()->maGeometry.x() - pParentAquaSalFrame->maGeometry.x() + offset;
+ displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.y() - pParentAquaSalFrame->maGeometry.y() + offset;
+ pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false);
+
+ // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again
+ if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] )
+ [pParentNSView performSelector:@selector(clearLastEvent)];
+
+ // open popup menu
+ NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO];
+ [pPopUpButtonCell setMenu: mpMenu];
+ [pPopUpButtonCell selectItem:nil];
+ [AquaA11yWrapper setPopupMenuOpen: YES];
+ [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView];
+ [pPopUpButtonCell release];
+ [AquaA11yWrapper setPopupMenuOpen: NO];
+
+ return true;
+}
+
+int AquaSalMenu::getItemIndexByPos( sal_uInt16 nPos ) const
+{
+ int nIndex = 0;
+ if( nPos == MENU_APPEND )
+ nIndex = [mpMenu numberOfItems];
+ else
+ nIndex = sal::static_int_cast<int>( mbMenuBar ? nPos+1 : nPos );
+ return nIndex;
+}
+
+const AquaSalFrame* AquaSalMenu::getFrame() const
+{
+ const AquaSalMenu* pMenu = this;
+ while( pMenu && ! pMenu->mpFrame )
+ pMenu = pMenu->mpParentSalMenu;
+ return pMenu ? pMenu->mpFrame : nullptr;
+}
+
+void AquaSalMenu::unsetMainMenu()
+{
+ pCurrentMenuBar = nullptr;
+
+ // remove items from main menu
+ NSMenu* pMenu = [NSApp mainMenu];
+ for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- )
+ [pMenu removeItemAtIndex: 1];
+}
+
+void AquaSalMenu::setMainMenu()
+{
+ SAL_WARN_IF( !mbMenuBar, "vcl", "setMainMenu on non menubar" );
+ if( mbMenuBar )
+ {
+ if( pCurrentMenuBar != this )
+ {
+ unsetMainMenu();
+ // insert our items
+ for( std::vector<AquaSalMenuItem *>::size_type i = 0; i < maItems.size(); i++ )
+ {
+ NSMenuItem* pItem = maItems[i]->mpMenuItem;
+ [mpMenu insertItem: pItem atIndex: i+1];
+ }
+ pCurrentMenuBar = this;
+
+ // change status item
+ statusLayout();
+ }
+ enableMainMenu( true );
+ }
+}
+
+void AquaSalMenu::setDefaultMenu()
+{
+ NSMenu* pMenu = [NSApp mainMenu];
+
+ unsetMainMenu();
+
+ // insert default items
+ std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
+ for( unsigned int i = 0, nAddItems = rFallbackMenu.size(); i < nAddItems; i++ )
+ {
+ NSMenuItem* pItem = rFallbackMenu[i];
+ if( [pItem menu] == nil )
+ [pMenu insertItem: pItem atIndex: i+1];
+ }
+}
+
+void AquaSalMenu::enableMainMenu( bool bEnable )
+{
+ NSMenu* pMainMenu = [NSApp mainMenu];
+ if( pMainMenu )
+ {
+ // enable/disable items from main menu
+ int nItems = [pMainMenu numberOfItems];
+ for( int n = 1; n < nItems; n++ )
+ {
+ NSMenuItem* pItem = [pMainMenu itemAtIndex: n];
+ [pItem setEnabled: bEnable ? YES : NO];
+ }
+ }
+}
+
+void AquaSalMenu::addFallbackMenuItem( NSMenuItem* pNewItem )
+{
+ initAppMenu();
+
+ std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
+
+ // prevent duplicate insertion
+ int nItems = rFallbackMenu.size();
+ for( int i = 0; i < nItems; i++ )
+ {
+ if( rFallbackMenu[i] == pNewItem )
+ return;
+ }
+
+ // push the item to the back and retain it
+ [pNewItem retain];
+ rFallbackMenu.push_back( pNewItem );
+
+ if( pCurrentMenuBar == nullptr )
+ setDefaultMenu();
+}
+
+void AquaSalMenu::removeFallbackMenuItem( NSMenuItem* pOldItem )
+{
+ std::vector< NSMenuItem* >& rFallbackMenu( GetSalData()->maFallbackMenu );
+
+ // find item
+ unsigned int nItems = rFallbackMenu.size();
+ for( unsigned int i = 0; i < nItems; i++ )
+ {
+ if( rFallbackMenu[i] == pOldItem )
+ {
+ // remove item and release
+ rFallbackMenu.erase( rFallbackMenu.begin() + i );
+ [pOldItem release];
+
+ if( pCurrentMenuBar == nullptr )
+ setDefaultMenu();
+
+ return;
+ }
+ }
+}
+
+bool AquaSalMenu::VisibleMenuBar()
+{
+ return true;
+}
+
+void AquaSalMenu::SetFrame( const SalFrame *pFrame )
+{
+ mpFrame = static_cast<const AquaSalFrame*>(pFrame);
+}
+
+void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos )
+{
+ OSX_SALDATA_RUNINMAIN(InsertItem(pSalMenuItem, nPos))
+
+ AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
+
+ pAquaSalMenuItem->mpParentMenu = this;
+ DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == nullptr ||
+ pAquaSalMenuItem->mpVCLMenu == mpVCLMenu ||
+ mpVCLMenu == nullptr,
+ "resetting menu ?" );
+ if( pAquaSalMenuItem->mpVCLMenu )
+ mpVCLMenu = pAquaSalMenuItem->mpVCLMenu;
+
+ if( nPos == MENU_APPEND || nPos == maItems.size() )
+ maItems.push_back( pAquaSalMenuItem );
+ else if( nPos < maItems.size() )
+ maItems.insert( maItems.begin() + nPos, pAquaSalMenuItem );
+ else
+ {
+ OSL_FAIL( "invalid item index in insert" );
+ return;
+ }
+
+ if( ! mbMenuBar || pCurrentMenuBar == this )
+ [mpMenu insertItem: pAquaSalMenuItem->mpMenuItem atIndex: getItemIndexByPos(nPos)];
+}
+
+void AquaSalMenu::RemoveItem( unsigned nPos )
+{
+ AquaSalMenuItem* pRemoveItem = nullptr;
+ if( nPos == MENU_APPEND || nPos == (maItems.size()-1) )
+ {
+ pRemoveItem = maItems.back();
+ maItems.pop_back();
+ }
+ else if( nPos < maItems.size() )
+ {
+ pRemoveItem = maItems[ nPos ];
+ maItems.erase( maItems.begin()+nPos );
+ }
+ else
+ {
+ OSL_FAIL( "invalid item index in remove" );
+ return;
+ }
+
+ pRemoveItem->mpParentMenu = nullptr;
+
+ if( ! mbMenuBar || pCurrentMenuBar == this )
+ [mpMenu removeItemAtIndex: getItemIndexByPos(nPos)];
+}
+
+void AquaSalMenu::SetSubMenu( SalMenuItem* pSalMenuItem, SalMenu* pSubMenu, unsigned /*nPos*/ )
+{
+ AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem);
+ AquaSalMenu *subAquaSalMenu = static_cast<AquaSalMenu*>(pSubMenu);
+
+ if (subAquaSalMenu)
+ {
+ pAquaSalMenuItem->mpSubMenu = subAquaSalMenu;
+ if( subAquaSalMenu->mpParentSalMenu == nullptr )
+ {
+ subAquaSalMenu->mpParentSalMenu = this;
+ [pAquaSalMenuItem->mpMenuItem setSubmenu: subAquaSalMenu->mpMenu];
+
+ // set title of submenu
+ [subAquaSalMenu->mpMenu setTitle: [pAquaSalMenuItem->mpMenuItem title]];
+ }
+ else if( subAquaSalMenu->mpParentSalMenu != this )
+ {
+ // cocoa doesn't allow menus to be submenus of multiple
+ // menu items, so place a copy in the menu item instead ?
+ // let's hope that NSMenu copy does the right thing
+ NSMenu* pCopy = [subAquaSalMenu->mpMenu copy];
+ [pAquaSalMenuItem->mpMenuItem setSubmenu: pCopy];
+
+ // set title of submenu
+ [pCopy setTitle: [pAquaSalMenuItem->mpMenuItem title]];
+ }
+ }
+ else
+ {
+ if( pAquaSalMenuItem->mpSubMenu )
+ {
+ if( pAquaSalMenuItem->mpSubMenu->mpParentSalMenu == this )
+ pAquaSalMenuItem->mpSubMenu->mpParentSalMenu = nullptr;
+ }
+ pAquaSalMenuItem->mpSubMenu = nullptr;
+ [pAquaSalMenuItem->mpMenuItem setSubmenu: nil];
+ }
+}
+
+void AquaSalMenu::CheckItem( unsigned nPos, bool bCheck )
+{
+ if( nPos < maItems.size() )
+ {
+ NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
+ [pItem setState: bCheck ? NSControlStateValueOn : NSControlStateValueOff];
+ }
+}
+
+void AquaSalMenu::EnableItem( unsigned nPos, bool bEnable )
+{
+ if( nPos < maItems.size() )
+ {
+ NSMenuItem* pItem = maItems[nPos]->mpMenuItem;
+ [pItem setEnabled: bEnable ? YES : NO];
+ }
+}
+
+void AquaSalMenu::SetItemImage( unsigned /*nPos*/, SalMenuItem* pSMI, const Image& rImage )
+{
+ AquaSalMenuItem* pSalMenuItem = static_cast<AquaSalMenuItem*>( pSMI );
+ if( ! pSalMenuItem || ! pSalMenuItem->mpMenuItem )
+ return;
+
+ NSImage* pImage = CreateNSImage( rImage );
+
+ [pSalMenuItem->mpMenuItem setImage: pImage];
+ if( pImage )
+ [pImage release];
+}
+
+void AquaSalMenu::SetItemText( unsigned /*i_nPos*/, SalMenuItem* i_pSalMenuItem, const OUString& i_rText )
+{
+ if (!i_pSalMenuItem)
+ return;
+
+ AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(i_pSalMenuItem);
+
+ // Delete all mnemonics of mbMenuBar and CJK-style mnemonic
+ OUString aText = MnemonicGenerator::EraseAllMnemonicChars(i_rText);
+
+ if (aText.endsWith("...", &aText))
+ aText += u"\u2026";
+
+ NSString* pString = CreateNSString( aText );
+ if (pString)
+ {
+ [pAquaSalMenuItem->mpMenuItem setTitle: pString];
+ // if the menu item has a submenu, change its title as well
+ if (pAquaSalMenuItem->mpSubMenu)
+ [pAquaSalMenuItem->mpSubMenu->mpMenu setTitle: pString];
+ [pString release];
+ }
+}
+
+void AquaSalMenu::SetAccelerator( unsigned /*nPos*/, SalMenuItem* pSalMenuItem, const vcl::KeyCode& rKeyCode, const OUString& /*rKeyName*/ )
+{
+ sal_uInt16 nModifier;
+ sal_Unicode nCommandKey = 0;
+
+ sal_uInt16 nKeyCode=rKeyCode.GetCode();
+ if( nKeyCode )
+ {
+ if ((nKeyCode>=KEY_A) && (nKeyCode<=KEY_Z)) // letter A..Z
+ nCommandKey = nKeyCode-KEY_A + 'a';
+ else if ((nKeyCode>=KEY_0) && (nKeyCode<=KEY_9)) // numbers 0..9
+ nCommandKey = nKeyCode-KEY_0 + '0';
+ else if ((nKeyCode>=KEY_F1) && (nKeyCode<=KEY_F26)) // function keys F1..F26
+ nCommandKey = nKeyCode-KEY_F1 + NSF1FunctionKey;
+ else if( nKeyCode == KEY_REPEAT )
+ nCommandKey = NSRedoFunctionKey;
+ else if( nKeyCode == KEY_SPACE )
+ nCommandKey = ' ';
+ else
+ {
+ switch (nKeyCode)
+ {
+ case KEY_ADD:
+ nCommandKey='+';
+ break;
+ case KEY_SUBTRACT:
+ nCommandKey='-';
+ break;
+ case KEY_MULTIPLY:
+ nCommandKey='*';
+ break;
+ case KEY_DIVIDE:
+ nCommandKey='/';
+ break;
+ case KEY_POINT:
+ nCommandKey='.';
+ break;
+ case KEY_LESS:
+ nCommandKey='<';
+ break;
+ case KEY_GREATER:
+ nCommandKey='>';
+ break;
+ case KEY_EQUAL:
+ nCommandKey='=';
+ break;
+ case KEY_COLON:
+ nCommandKey=':';
+ break;
+ case KEY_NUMBERSIGN:
+ nCommandKey='#';
+ break;
+ case KEY_SEMICOLON:
+ nCommandKey=';';
+ break;
+ case KEY_BACKSPACE:
+ nCommandKey=u'\x232b';
+ break;
+ case KEY_PAGEUP:
+ nCommandKey=u'\x21de';
+ break;
+ case KEY_PAGEDOWN:
+ nCommandKey=u'\x21df';
+ break;
+ case KEY_UP:
+ nCommandKey=u'\x21e1';
+ break;
+ case KEY_DOWN:
+ nCommandKey=u'\x21e3';
+ break;
+ case KEY_RETURN:
+ nCommandKey=u'\x21a9';
+ break;
+ case KEY_BRACKETLEFT:
+ nCommandKey='[';
+ break;
+ case KEY_BRACKETRIGHT:
+ nCommandKey=']';
+ break;
+ }
+ }
+ }
+ else // not even a code ? nonsense -> ignore
+ return;
+
+ SAL_WARN_IF( !nCommandKey, "vcl", "unmapped accelerator key" );
+
+ nModifier=rKeyCode.GetModifier();
+
+ // should always use the command key
+ int nItemModifier = 0;
+
+ if (nModifier & KEY_SHIFT)
+ {
+ nItemModifier |= NSEventModifierFlagShift; // actually useful only for function keys
+ if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z )
+ nCommandKey = nKeyCode - KEY_A + 'A';
+ }
+
+ if (nModifier & KEY_MOD1)
+ nItemModifier |= NSEventModifierFlagCommand;
+
+ if(nModifier & KEY_MOD2)
+ nItemModifier |= NSEventModifierFlagOption;
+
+ if(nModifier & KEY_MOD3)
+ nItemModifier |= NSEventModifierFlagControl;
+
+ AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem *>(pSalMenuItem);
+ NSString* pString = CreateNSString( OUString( &nCommandKey, 1 ) );
+ [pAquaSalMenuItem->mpMenuItem setKeyEquivalent: pString];
+ [pAquaSalMenuItem->mpMenuItem setKeyEquivalentModifierMask: nItemModifier];
+ if (pString)
+ [pString release];
+}
+
+void AquaSalMenu::GetSystemMenuData( SystemMenuData* )
+{
+}
+
+AquaSalMenu::MenuBarButtonEntry* AquaSalMenu::findButtonItem( sal_uInt16 i_nItemId )
+{
+ for( size_t i = 0; i < maButtons.size(); ++i )
+ {
+ if( maButtons[i].maButton.mnId == i_nItemId )
+ return &maButtons[i];
+ }
+ return nullptr;
+}
+
+void AquaSalMenu::statusLayout()
+{
+ if( GetSalData()->mpStatusItem )
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button
+ // property instead"
+ NSView* pNSView = [GetSalData()->mpStatusItem view];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is
+ [static_cast<OOStatusItemView*>(pNSView) layout];
+ else
+ OSL_FAIL( "someone stole our status view" );
+ }
+}
+
+bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem )
+{
+ if( ! mbMenuBar )
+ return false;
+
+ MenuBarButtonEntry* pEntry = findButtonItem( i_rNewItem.mnId );
+ if( pEntry )
+ {
+ releaseButtonEntry( *pEntry );
+ pEntry->maButton = i_rNewItem;
+ pEntry->mpNSImage = CreateNSImage( i_rNewItem.maImage );
+ if( i_rNewItem.maToolTipText.getLength() )
+ pEntry->mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
+ }
+ else
+ {
+ maButtons.push_back( MenuBarButtonEntry( i_rNewItem ) );
+ maButtons.back().mpNSImage = CreateNSImage( i_rNewItem.maImage );
+ maButtons.back().mpToolTipString = CreateNSString( i_rNewItem.maToolTipText );
+ }
+
+ // lazy create status item
+ SalData::getStatusItem();
+
+ if( pCurrentMenuBar == this )
+ statusLayout();
+
+ return true;
+}
+
+void AquaSalMenu::RemoveMenuBarButton( sal_uInt16 i_nId )
+{
+ MenuBarButtonEntry* pEntry = findButtonItem( i_nId );
+ if( pEntry )
+ {
+ releaseButtonEntry( *pEntry );
+ // note: vector guarantees that its contents are in a plain array
+ maButtons.erase( maButtons.begin() + (pEntry - maButtons.data()) );
+ }
+
+ if( pCurrentMenuBar == this )
+ statusLayout();
+}
+
+tools::Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame )
+{
+ if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) )
+ return tools::Rectangle();
+
+ MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId );
+
+ if( ! pEntry )
+ return tools::Rectangle();
+
+ NSStatusItem* pItem = SalData::getStatusItem();
+ if( ! pItem )
+ return tools::Rectangle();
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'view' is deprecated: first deprecated in macOS 10.14 - Use the standard button property
+ // instead"
+ NSView* pNSView = [pItem view];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ if( ! pNSView )
+ return tools::Rectangle();
+ NSWindow* pNSWin = [pNSView window];
+ if( ! pNSWin )
+ return tools::Rectangle();
+
+ NSRect aRect = [pNSWin convertRectToScreen:[pNSWin frame]];
+
+ // make coordinates relative to reference frame
+ static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin );
+ aRect.origin.x -= i_pReferenceFrame->maGeometry.x();
+ aRect.origin.y -= i_pReferenceFrame->maGeometry.y() + aRect.size.height;
+
+ return tools::Rectangle( Point(static_cast<tools::Long>(aRect.origin.x),
+ static_cast<tools::Long>(aRect.origin.y)
+ ),
+ Size( static_cast<tools::Long>(aRect.size.width),
+ static_cast<tools::Long>(aRect.size.height)
+ )
+ );
+}
+
+/*
+ * SalMenuItem
+ */
+
+AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) :
+ mnId( pItemData->nId ),
+ mpVCLMenu( pItemData->pMenu ),
+ mpParentMenu( nullptr ),
+ mpSubMenu( nullptr ),
+ mpMenuItem( nil )
+{
+ if (pItemData->eType == MenuItemType::SEPARATOR)
+ {
+ mpMenuItem = [NSMenuItem separatorItem];
+ // these can go occasionally go in and out of a menu, ensure their lifecycle
+ // also for the release in AquaSalMenuItem destructor
+ [mpMenuItem retain];
+ }
+ else
+ {
+ mpMenuItem = [[SalNSMenuItem alloc] initWithMenuItem: this];
+ [mpMenuItem setEnabled: YES];
+
+ // peel mnemonics because on mac there are no such things for menu items
+ // Delete CJK-style mnemonics for the dropdown menu of the 'New button' and lower menu of 'File > New'
+ NSString* pString = CreateNSString(MnemonicGenerator::EraseAllMnemonicChars(pItemData->aText));
+ if (pString)
+ {
+ [mpMenuItem setTitle: pString];
+ [pString release];
+ }
+ // anything but a separator should set a menu to dispatch to
+ SAL_WARN_IF( !mpVCLMenu, "vcl", "no menu" );
+ }
+}
+
+AquaSalMenuItem::~AquaSalMenuItem()
+{
+ /* #i89860# FIXME:
+ using [autorelease] here (and in AquaSalMenu:::~AquaSalMenu) instead of
+ [release] fixes an occasional crash. That should indicate that we release
+ menus / menu items in the wrong order somewhere, but I
+ could not find that case.
+ */
+ if( mpMenuItem )
+ [mpMenuItem autorelease];
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salnativewidgets.cxx b/vcl/osx/salnativewidgets.cxx
new file mode 100644
index 0000000000..8a7e81fd5d
--- /dev/null
+++ b/vcl/osx/salnativewidgets.cxx
@@ -0,0 +1,1366 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <config_features.h>
+#include <tools/long.hxx>
+#include <vcl/salnativewidgets.hxx>
+#include <vcl/decoview.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/threadex.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/settings.hxx>
+
+#include <quartz/salgdi.h>
+#include <osx/salnativewidgets.h>
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+
+#include <premac.h>
+#include <Carbon/Carbon.h>
+#include <postmac.h>
+
+#include "cuidraw.hxx"
+
+// presentation of native widgets consists of two important methods:
+
+// AquaSalGraphics::getNativeControlRegion to determine native rectangle in pixels to draw the widget
+// AquaSalGraphics::drawNativeControl to do the drawing operation itself
+
+// getNativeControlRegion has to calculate a content rectangle within it is safe to draw the widget. Furthermore a bounding rectangle
+// has to be calculated by getNativeControlRegion to consider adornments like a focus rectangle. As drawNativeControl uses Carbon
+// API calls, all widgets are drawn without text. Drawing of text is done separately by VCL on top of graphical Carbon widget
+// representation. drawNativeControl is called by VCL using content rectangle determined by getNativeControlRegion.
+
+// FIXME: when calculation bounding rectangle larger then content rectangle, text displayed by VCL will become misaligned. To avoid
+// misalignment bounding rectangle and content rectangle are calculated equally including adornments. Reduction of size for content
+// is done by drawNativeControl subsequently. Only exception is editbox: As other widgets have distinct ControlPart::SubEdit control
+// parts, editbox bounding rectangle and content rectangle are both calculated to reflect content area. Extending size for
+// adornments is done by drawNativeControl subsequently.
+
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+
+@interface NSWindow(CoreUIRendererPrivate)
++ (CUIRendererRef)coreUIRenderer;
+@end
+
+#endif
+
+static HIRect ImplGetHIRectFromRectangle(tools::Rectangle aRect)
+{
+ HIRect aHIRect;
+ aHIRect.origin.x = static_cast<float>(aRect.Left());
+ aHIRect.origin.y = static_cast<float>(aRect.Top());
+ aHIRect.size.width = static_cast<float>(aRect.GetWidth());
+ aHIRect.size.height = static_cast<float>(aRect.GetHeight());
+ return aHIRect;
+}
+
+static NSControlStateValue ImplGetButtonValue(ButtonValue aButtonValue)
+{
+ switch (aButtonValue)
+ {
+ case ButtonValue::On:
+ return NSControlStateValueOn;
+ case ButtonValue::Off:
+ case ButtonValue::DontKnow:
+ return NSControlStateValueOff;
+ case ButtonValue::Mixed:
+ default:
+ return NSControlStateValueMixed;
+ }
+}
+
+static bool AquaGetScrollRect(/* TODO: int nScreen, */
+ ControlPart nPart, const tools::Rectangle &rControlRect, tools::Rectangle &rResultRect)
+{
+ bool bRetVal = true;
+ rResultRect = rControlRect;
+ switch (nPart)
+ {
+ case ControlPart::ButtonUp:
+ rResultRect.SetBottom(rResultRect.Top());
+ break;
+ case ControlPart::ButtonDown:
+ rResultRect.SetTop(rResultRect.Bottom());
+ break;
+ case ControlPart::ButtonLeft:
+ rResultRect.SetRight(rResultRect.Left());
+ break;
+ case ControlPart::ButtonRight:
+ rResultRect.SetLeft(rResultRect.Right());
+ break;
+ case ControlPart::TrackHorzArea:
+ case ControlPart::TrackVertArea:
+ case ControlPart::ThumbHorz:
+ case ControlPart::ThumbVert:
+ case ControlPart::TrackHorzLeft:
+ case ControlPart::TrackHorzRight:
+ case ControlPart::TrackVertUpper:
+ case ControlPart::TrackVertLower:
+ break;
+ default:
+ bRetVal = false;
+ }
+ return bRetVal;
+}
+
+bool AquaSalGraphics::isNativeControlSupported(ControlType nType, ControlPart nPart)
+{
+ // native controls are now defaults. If you want to disable native controls, set the environment variable SAL_NO_NWF to
+ // something and VCL controls will be used as default again.
+
+ switch (nType)
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ case ControlType::ListNode:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ case ControlType::Scrollbar:
+ if (nPart == ControlPart::DrawBackgroundHorz || nPart == ControlPart::DrawBackgroundVert
+ || nPart == ControlPart::Entire || nPart == ControlPart::HasThreeButtons)
+ return true;
+ break;
+ case ControlType::Slider:
+ if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
+ return true;
+ break;
+ case ControlType::Editbox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::MultilineEditbox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::Spinbox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::AllButtons || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::SpinButtons:
+ return false;
+ case ControlType::Combobox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::Listbox:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::ListboxWindow || nPart == ControlPart::HasBackgroundTexture
+ || nPart == ControlPart::SubEdit)
+ return true;
+ break;
+ case ControlType::TabItem:
+ case ControlType::TabPane:
+ case ControlType::TabBody:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::TabsDrawRtl || nPart == ControlPart::HasBackgroundTexture)
+ return true;
+ break;
+ case ControlType::Toolbar:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::DrawBackgroundHorz
+ || nPart == ControlPart::DrawBackgroundVert)
+ return true;
+ break;
+ case ControlType::WindowBackground:
+ if (nPart == ControlPart::BackgroundWindow || nPart == ControlPart::BackgroundDialog)
+ return true;
+ break;
+ case ControlType::Menubar:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ case ControlType::Tooltip:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ case ControlType::MenuPopup:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::MenuItemCheckMark
+ || nPart == ControlPart::MenuItemRadioMark)
+ return true;
+ break;
+ case ControlType::LevelBar:
+ case ControlType::Progress:
+ case ControlType::IntroProgress:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ case ControlType::Frame:
+ if (nPart == ControlPart::Border)
+ return true;
+ break;
+ case ControlType::ListNet:
+ if (nPart == ControlPart::Entire)
+ return true;
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool AquaSalGraphics::hitTestNativeControl(ControlType nType, ControlPart nPart, const tools::Rectangle &rControlRegion,
+ const Point &rPos, bool& rIsInside)
+{
+ if (nType == ControlType::Scrollbar)
+ {
+ tools::Rectangle aRect;
+ bool bValid = AquaGetScrollRect(/* TODO: int nScreen, */
+ nPart, rControlRegion, aRect);
+ rIsInside = bValid && aRect.Contains(rPos);
+ return bValid;
+ }
+ return false;
+}
+
+static bool getEnabled(ControlState nState, AquaSalFrame* mpFrame)
+{
+
+ // there are non key windows which are children of key windows, e.g. autofilter configuration dialog or sidebar dropdown dialogs.
+ // To handle these windows correctly, parent frame's key window state is considered here additionally.
+
+ const bool bDrawActive = mpFrame == nullptr || [mpFrame->getNSWindow() isKeyWindow]
+ || mpFrame->mpParent == nullptr || [mpFrame->mpParent->getNSWindow() isKeyWindow];
+ if (!(nState & ControlState::ENABLED) || !bDrawActive)
+ {
+ return false;
+ }
+ return true;
+}
+
+bool AquaSalGraphics::drawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue,
+ const OUString &,
+ const Color&)
+{
+ return mpBackend->drawNativeControl(nType, nPart, rControlRegion, nState, aValue);
+}
+
+static void paintCell(NSCell* pBtn, const NSRect& bounds, bool bShowsFirstResponder, CGContextRef context, NSView* pView)
+{
+ //translate and scale because up side down otherwise
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, bounds.origin.x, bounds.origin.y + bounds.size.height);
+ CGContextScaleCTM(context, 1, -1);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];
+
+ NSRect rect = { NSZeroPoint, bounds.size };
+
+ if ([pBtn isKindOfClass: [NSSliderCell class]])
+ {
+ // NSSliderCell doesn't seem to work with drawWithFrame(?), so draw the elements directly
+ [static_cast<NSSliderCell*>(pBtn)
+ drawBarInside: [static_cast<NSSliderCell*>(pBtn) barRectFlipped: NO] flipped: NO];
+ rect = [static_cast<NSSliderCell*>(pBtn) knobRectFlipped: NO];
+ [static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
+ }
+ else
+ [pBtn drawWithFrame: rect inView: pView];
+
+ // setShowsFirstResponder apparently causes a hang when set on NSComboBoxCell
+ const bool bIsComboBox = [pBtn isMemberOfClass: [NSComboBoxCell class]];
+ if (!bIsComboBox)
+ [pBtn setShowsFirstResponder: bShowsFirstResponder];
+
+ if (bShowsFirstResponder)
+ {
+ NSSetFocusRingStyle(NSFocusRingOnly);
+
+ CGContextBeginTransparencyLayerWithRect(context, rect, nullptr);
+ if ([pBtn isMemberOfClass: [NSTextFieldCell class]])
+ {
+ // I wonder why NSTextFieldCell doesn't work for me in the default else branch.
+ // NSComboBoxCell works, and that derives from NSTextFieldCell, on the other
+ // hand setShowsFirstResponder causes a hangs when set on NSComboBoxCell
+ NSRect out = [pBtn focusRingMaskBoundsForFrame: rect inView: pView];
+ CGContextFillRect(context, out);
+ }
+ else if ([pBtn isKindOfClass: [NSSliderCell class]])
+ {
+ // Not getting anything useful for a NSSliderCell, so use the knob
+ [static_cast<NSSliderCell*>(pBtn) drawKnob: rect];
+ }
+ else
+ [pBtn drawFocusRingMaskWithFrame:rect inView: pView];
+
+ CGContextEndTransparencyLayer(context);
+ }
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+ CGContextRestoreGState(context);
+}
+
+static void paintFocusRect(double radius, const NSRect& rect, CGContextRef context)
+{
+ NSRect bounds = rect;
+
+ CGPathRef path = CGPathCreateWithRoundedRect(bounds, radius, radius, nullptr);
+ CGContextSetStrokeColorWithColor(context, [NSColor keyboardFocusIndicatorColor].CGColor);
+ CGContextSetLineWidth(context, FOCUS_RING_WIDTH);
+ CGContextBeginPath(context);
+ CGContextAddPath(context, path);
+ CGContextStrokePath(context);
+ CFRelease(path);
+}
+
+@interface FixedWidthTabViewItem : NSTabViewItem {
+ int m_nWidth;
+}
+- (NSSize)sizeOfLabel: (BOOL)computeMin;
+- (void)setTabWidth: (int)nWidth;
+@end
+
+@implementation FixedWidthTabViewItem
+- (NSSize)sizeOfLabel: (BOOL)computeMin
+{
+ NSSize size = [super sizeOfLabel: computeMin];
+ size.width = m_nWidth;
+ return size;
+}
+- (void)setTabWidth: (int)nWidth
+{
+ m_nWidth = nWidth;
+}
+@end
+
+bool AquaGraphicsBackend::drawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue)
+{
+ if (!mrShared.checkContext())
+ return false;
+ mrShared.maContextHolder.saveState();
+ bool bOK = performDrawNativeControl(nType, nPart, rControlRegion, nState, aValue,
+ mrShared.maContextHolder.get(), mrShared.mpFrame);
+ mrShared.maContextHolder.restoreState();
+
+ tools::Rectangle buttonRect = rControlRegion;
+
+ // in most cases invalidating the whole control region instead of just the unclipped part of it is sufficient (and probably
+ // faster). However for the window background we should not unnecessarily enlarge the really changed rectangle since the
+ // difference is usually quite high. Background is always drawn as a whole since we don't know anything about its possible
+ // contents (see issue i90291).
+
+ if (nType == ControlType::WindowBackground)
+ {
+ CGRect aRect = {{0, 0}, {0, 0}};
+ if (mrShared.mxClipPath)
+ aRect = CGPathGetBoundingBox(mrShared.mxClipPath);
+ if (aRect.size.width != 0 && aRect.size.height != 0)
+ buttonRect.Intersection(tools::Rectangle(Point(static_cast<tools::Long>(aRect.origin.x),
+ static_cast<tools::Long>(aRect.origin.y)),
+ Size(static_cast<tools::Long>(aRect.size.width),
+ static_cast<tools::Long>(aRect.size.height))));
+ }
+ mrShared.refreshRect(buttonRect.Left(), buttonRect.Top(), buttonRect.GetWidth(), buttonRect.GetHeight());
+ return bOK;
+}
+
+static void drawBox(CGContextRef context, const NSRect& rc, NSColor* pColor)
+{
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y + rc.size.height);
+ CGContextScaleCTM(context, 1, -1);
+
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSBox* pBox = [[NSBox alloc] initWithFrame: rect];
+
+ [pBox setBoxType: NSBoxCustom];
+ [pBox setFillColor: pColor];
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH // setBorderType first deprecated in macOS 10.15
+ [pBox setBorderType: NSNoBorder];
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ [pBox displayRectIgnoringOpacity: rect inContext: graphicsContext];
+
+ [pBox release];
+
+ CGContextRestoreGState(context);
+}
+
+// if I don't crystallize this bg then the InvertCursor using kCGBlendModeDifference doesn't
+// work correctly and the cursor doesn't appear correctly
+static void drawEditableBackground(CGContextRef context, const NSRect& rc)
+{
+ CGContextSaveGState(context);
+ CGContextSetFillColorWithColor(context, [NSColor controlBackgroundColor].CGColor);
+ CGContextFillRect(context, rc);
+ CGContextRestoreGState(context);
+}
+
+// As seen in macOS 12.3.1. All a bit odd really.
+const int RoundedMargin[4] = { 6, 4, 0, 3 };
+
+bool AquaGraphicsBackendBase::performDrawNativeControl(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState nState,
+ const ImplControlValue &aValue,
+ CGContextRef context,
+ AquaSalFrame* mpFrame)
+{
+ bool bOK = false;
+ AquaSalInstance* pInst = GetSalData()->mpInstance;
+ HIRect rc = ImplGetHIRectFromRectangle(rControlRegion);
+ switch (nType)
+ {
+ case ControlType::Toolbar:
+ {
+ drawBox(context, rc, NSColor.windowBackgroundColor);
+ bOK = true;
+ }
+ break;
+ case ControlType::WindowBackground:
+ {
+ drawBox(context, rc, NSColor.windowBackgroundColor);
+ bOK = true;
+ }
+ break;
+ case ControlType::Tooltip:
+ {
+ rc.size.width += 2;
+ rc.size.height += 2;
+ drawBox(context, rc, NSColor.controlBackgroundColor);
+ bOK = true;
+ }
+ break;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ if (nPart == ControlPart::Entire || nPart == ControlPart::MenuItem || nPart == ControlPart::HasBackgroundTexture)
+ {
+ // FIXME: without this magical offset there is a 2 pixel black border on the right
+
+ rc.size.width += 2;
+ HIThemeMenuDrawInfo aMenuInfo;
+ aMenuInfo.version = 0;
+ aMenuInfo.menuType = kThemeMenuTypePullDown;
+ HIThemeMenuItemDrawInfo aMenuItemDrawInfo;
+
+ // grey theme when the item is selected is drawn here.
+
+ aMenuItemDrawInfo.itemType = kThemeMenuItemPlain;
+ if ((nPart == ControlPart::MenuItem) && (nState & ControlState::SELECTED))
+
+ // blue theme when the item is selected is drawn here.
+
+ aMenuItemDrawInfo.state = kThemeMenuSelected;
+ else
+
+ // normal color for non selected item
+
+ aMenuItemDrawInfo.state = kThemeMenuActive;
+
+ // repaints the background of the pull down menu
+
+ HIThemeDrawMenuBackground(&rc, &aMenuInfo, context, kHIThemeOrientationNormal);
+
+ // repaints the item either blue (selected) and/or grey (active only)
+
+ HIThemeDrawMenuItem(&rc, &rc, &aMenuItemDrawInfo, context, kHIThemeOrientationNormal, &rc);
+ bOK = true;
+ }
+ else if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
+ {
+ // checked, else it is not displayed (see vcl/source/window/menu.cxx)
+
+ if (nState & ControlState::PRESSED)
+ {
+ HIThemeTextInfo aTextInfo;
+ aTextInfo.version = 0;
+ aTextInfo.state = (nState & ControlState::ENABLED) ? kThemeStateInactive: kThemeStateActive;
+ aTextInfo.fontID = kThemeMenuItemMarkFont;
+ aTextInfo.horizontalFlushness = kHIThemeTextHorizontalFlushCenter;
+ aTextInfo.verticalFlushness = kHIThemeTextVerticalFlushTop;
+ aTextInfo.options = kHIThemeTextBoxOptionNone;
+ aTextInfo.truncationPosition = kHIThemeTextTruncationNone;
+
+ // aTextInfo.truncationMaxLines unused because of kHIThemeTextTruncationNone item highlighted
+
+ if (nState & ControlState::SELECTED) aTextInfo.state = kThemeStatePressed;
+ UniChar mark=(nPart == ControlPart::MenuItemCheckMark) ? kCheckUnicode: kBulletUnicode;
+ CFStringRef cfString = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &mark, 1, kCFAllocatorNull);
+ HIThemeDrawTextBox(cfString, &rc, &aTextInfo, context, kHIThemeOrientationNormal);
+ if (cfString)
+ CFRelease(cfString);
+ bOK = true;
+ }
+ }
+ break;
+ case ControlType::Pushbutton:
+ {
+ NSControlSize eSizeKind = NSControlSizeRegular;
+ NSBezelStyle eBezelStyle = NSBezelStyleRounded;
+
+ PushButtonValue const *pPBVal = aValue.getType() == ControlType::Pushbutton ?
+ static_cast<PushButtonValue const *>(&aValue) : nullptr;
+
+ SInt32 nPaintHeight = rc.size.height;
+ if (rc.size.height <= PUSH_BUTTON_NORMAL_HEIGHT)
+ {
+ eSizeKind = NSControlSizeMini;
+ GetThemeMetric(kThemeMetricSmallPushButtonHeight, &nPaintHeight);
+ }
+ else if ((pPBVal && pPBVal->mbSingleLine) || rc.size.height < PUSH_BUTTON_NORMAL_HEIGHT * 3 / 2)
+ {
+ GetThemeMetric(kThemeMetricPushButtonHeight, &nPaintHeight);
+ }
+ else
+ {
+ // A simple square bezel style that can scale to any size
+ eBezelStyle = NSBezelStyleSmallSquare;
+ }
+
+ // translate the origin for controls with fixed paint height so content ends up somewhere sensible
+ rc.origin.y += (rc.size.height - nPaintHeight + 1) / 2;
+ rc.size.height = nPaintHeight;
+
+ NSButtonCell* pBtn = pInst->mpButtonCell;
+ pBtn.allowsMixedState = YES;
+
+ [pBtn setTitle: @""];
+ [pBtn setButtonType: NSButtonTypeMomentaryPushIn];
+ [pBtn setBezelStyle: eBezelStyle];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+ [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
+ [pBtn setControlSize: eSizeKind];
+ if (nState & ControlState::DEFAULT)
+ [pBtn setKeyEquivalent: @"\r"];
+ else
+ [pBtn setKeyEquivalent: @""];
+
+ if (eBezelStyle == NSBezelStyleRounded)
+ {
+ int nMargin = RoundedMargin[eSizeKind];
+ rc.origin.x -= nMargin;
+ rc.size.width += nMargin * 2;
+
+ rc.origin.x += FOCUS_RING_WIDTH / 2;
+ rc.size.width -= FOCUS_RING_WIDTH;
+ }
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, nullptr);
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ {
+ rc.size.width -= 2 * FOCUS_RING_WIDTH;
+ rc.size.height = RADIO_BUTTON_SMALL_SIZE;
+ rc.origin.x += FOCUS_RING_WIDTH;
+ rc.origin.y += FOCUS_RING_WIDTH;
+
+ NSButtonCell* pBtn = nType == ControlType::Checkbox ? pInst->mpCheckCell : pInst->mpRadioCell;
+ pBtn.allowsMixedState = YES;
+
+ [pBtn setTitle: @""];
+ [pBtn setButtonType: nType == ControlType::Checkbox ? NSButtonTypeSwitch : NSButtonTypeRadio];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+ [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, nullptr);
+
+ bOK = true;
+ }
+ break;
+ case ControlType::ListNode:
+ {
+ NSButtonCell* pBtn = pInst->mpListNodeCell;
+ pBtn.allowsMixedState = YES;
+
+ [pBtn setTitle: @""];
+ [pBtn setButtonType: NSButtonTypeOnOff];
+ [pBtn setBezelStyle: NSBezelStyleDisclosure];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, nullptr);
+
+ bOK = true;
+ }
+ break;
+ case ControlType::LevelBar:
+ {
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSLevelIndicator* pBox = [[NSLevelIndicator alloc] initWithFrame:rect];
+ [pBox setLevelIndicatorStyle: NSLevelIndicatorStyleContinuousCapacity];
+ [pBox setMinValue: 0];
+ [pBox setMaxValue: rc.size.width];
+ [pBox setCriticalValue: rc.size.width * 35.0 / 100.0];
+ [pBox setWarningValue: rc.size.width * 70.0 / 100.0];
+ [pBox setDoubleValue: aValue.getNumericVal()];
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ [pBox drawRect: rect];
+
+ [NSGraphicsContext setCurrentContext: savedContext];
+
+ CGContextRestoreGState(context);
+
+ [pBox release];
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Progress:
+ case ControlType::IntroProgress:
+ {
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSProgressIndicator* pBox = [[NSProgressIndicator alloc] initWithFrame: rect];
+ [pBox setControlSize: (rc.size.height > MEDIUM_PROGRESS_INDICATOR_HEIGHT) ?
+ NSControlSizeRegular : NSControlSizeSmall];
+ [pBox setMinValue: 0];
+ [pBox setMaxValue: rc.size.width];
+ [pBox setDoubleValue: aValue.getNumericVal()];
+ pBox.usesThreadedAnimation = NO;
+ [pBox setIndeterminate: NO];
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ [pBox drawRect: rect];
+
+ [NSGraphicsContext setCurrentContext: savedContext];
+
+ CGContextRestoreGState(context);
+
+ [pBox release];
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Slider:
+ {
+ const SliderValue *pSliderVal = static_cast<SliderValue const *>(&aValue);
+ if (nPart == ControlPart::TrackHorzArea || nPart == ControlPart::TrackVertArea)
+ {
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSSlider* pBox = [[NSSlider alloc] initWithFrame: rect];
+
+ [pBox setEnabled: getEnabled(nState, mpFrame)];
+ [pBox setVertical: nPart == ControlPart::TrackVertArea];
+ [pBox setMinValue: pSliderVal->mnMin];
+ [pBox setMaxValue: pSliderVal->mnMax];
+ [pBox setIntegerValue: pSliderVal->mnCur];
+ [pBox setSliderType: NSSliderTypeLinear];
+ [pBox setFocusRingType: NSFocusRingTypeExterior];
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBox.cell, rc, bFocused, context, mpFrame->getNSView());
+
+ [pBox release];
+
+ bOK = true;
+ }
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ const ScrollbarValue *pScrollbarVal = (aValue.getType() == ControlType::Scrollbar)
+ ? static_cast<const ScrollbarValue *>(&aValue) : nullptr;
+ if (nPart == ControlPart::DrawBackgroundVert || nPart == ControlPart::DrawBackgroundHorz)
+ {
+ drawBox(context, rc, NSColor.controlBackgroundColor);
+
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSScroller* pBar = [[NSScroller alloc] initWithFrame: rect];
+
+ double range = pScrollbarVal->mnMax - pScrollbarVal->mnVisibleSize - pScrollbarVal->mnMin;
+ double value = range ? (pScrollbarVal->mnCur - pScrollbarVal->mnMin) / range : 0;
+
+ double length = pScrollbarVal->mnMax - pScrollbarVal->mnMin;
+ double proportion = pScrollbarVal->mnVisibleSize / length;
+
+ [pBar setEnabled: getEnabled(nState, mpFrame)];
+ [pBar setScrollerStyle: NSScrollerStyleLegacy];
+ [pBar setFloatValue: value];
+ [pBar setKnobProportion: proportion];
+ bool bPressed = (pScrollbarVal->mnThumbState & ControlState::ENABLED) &&
+ (pScrollbarVal->mnThumbState & ControlState::PRESSED);
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ // For not-pressed first draw without the knob and then
+ // draw just the knob but with 50% opaque which looks sort of
+ // right
+
+ [pBar drawKnobSlotInRect: rect highlight: NO];
+
+ NSBitmapImageRep* pImageRep = [pBar bitmapImageRepForCachingDisplayInRect: rect];
+
+ NSGraphicsContext* imageContext = [NSGraphicsContext graphicsContextWithBitmapImageRep:pImageRep];
+ [NSGraphicsContext setCurrentContext: imageContext];
+
+ [pBar drawKnob];
+
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
+ [pImage addRepresentation: pImageRep]; // takes ownership of pImageRep
+
+ [pImage drawInRect: rect fromRect: rect
+ operation: NSCompositingOperationSourceOver
+ fraction: bPressed ? 1.0 : 0.5];
+
+ [pImage release];
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+
+ CGContextRestoreGState(context);
+
+ bOK = true;
+
+ [pBar release];
+ }
+ }
+ break;
+ case ControlType::TabPane:
+ {
+ NSTabView* pBox = [[NSTabView alloc] initWithFrame: rc];
+
+ SInt32 nOverlap;
+ GetThemeMetric(kThemeMetricTabFrameOverlap, &nOverlap);
+
+ // this calculation is probably more than a little dubious
+ rc.origin.x -= pBox.contentRect.origin.x - FOCUS_RING_WIDTH;
+ rc.size.width += rc.size.width - pBox.contentRect.size.width - 2 * FOCUS_RING_WIDTH;
+ double nTopBorder = pBox.contentRect.origin.y;
+ double nBottomBorder = rc.size.height - pBox.contentRect.size.height - nTopBorder;
+ double nExtraTop = (nTopBorder - nBottomBorder) / 2;
+ rc.origin.y -= (nTopBorder - nExtraTop + nOverlap);
+ rc.size.height += (nTopBorder - nExtraTop + nBottomBorder);
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ rc.origin.x = 0;
+ rc.origin.y = 0;
+
+ [pBox setBoundsOrigin: rc.origin];
+ [pBox setBoundsSize: rc.size];
+
+ // jam this in to force the tab contents area to be left undrawn, the ControlType::TabItem
+ // will be drawn in this space.
+ const TabPaneValue& rValue = static_cast<const TabPaneValue&>(aValue);
+ SInt32 nEndCapWidth;
+ GetThemeMetric(kThemeMetricLargeTabCapsWidth, &nEndCapWidth);
+ FixedWidthTabViewItem* pItem = [[[FixedWidthTabViewItem alloc] initWithIdentifier: @"tab"] autorelease];
+ [pItem setTabWidth: rValue.m_aTabHeaderRect.GetWidth() - 2 * nEndCapWidth];
+ [pBox addTabViewItem: pItem];
+
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ [pBox drawRect: rc];
+
+ [NSGraphicsContext setCurrentContext: savedContext];
+
+ [pBox release];
+
+ CGContextRestoreGState(context);
+
+ bOK = true;
+ }
+ break;
+ case ControlType::TabItem:
+ {
+ // first, last or middle tab
+
+ TabitemValue const * pTabValue = static_cast<TabitemValue const *>(&aValue);
+ TabitemFlags nAlignment = pTabValue->mnAlignment;
+
+ // TabitemFlags::LeftAligned (and TabitemFlags::RightAligned) for the leftmost (or rightmost) tab
+ // when there are several lines of tabs because there is only one first tab and one
+ // last tab and TabitemFlags::FirstInGroup (and TabitemFlags::LastInGroup) because when the
+ // line width is different from window width, there may not be TabitemFlags::RightAligned
+ int nPaintIndex = 1;
+ bool bSolo = false;
+ if (((nAlignment & TabitemFlags::LeftAligned) && (nAlignment & TabitemFlags::RightAligned))
+ || ((nAlignment & TabitemFlags::FirstInGroup) && (nAlignment & TabitemFlags::LastInGroup)))
+ {
+ nPaintIndex = 0;
+ bSolo = true;
+ }
+ else if ((nAlignment & TabitemFlags::LeftAligned) || (nAlignment & TabitemFlags::FirstInGroup))
+ nPaintIndex = !AllSettings::GetLayoutRTL() ? 0 : 2;
+ else if ((nAlignment & TabitemFlags::RightAligned) || (nAlignment & TabitemFlags::LastInGroup))
+ nPaintIndex = !AllSettings::GetLayoutRTL() ? 2 : 0;
+
+ int nCells = !bSolo ? 3 : 1;
+ NSRect ctrlrect = { NSZeroPoint, NSMakeSize(rc.size.width * nCells + FOCUS_RING_WIDTH, rc.size.height) };
+ NSSegmentedControl* pCtrl = [[NSSegmentedControl alloc] initWithFrame: ctrlrect];
+ [pCtrl setSegmentCount: nCells];
+ if (bSolo)
+ [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH forSegment: 0];
+ else
+ {
+ [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 0];
+ [pCtrl setWidth: rc.size.width forSegment: 1];
+ [pCtrl setWidth: rc.size.width + FOCUS_RING_WIDTH/2 forSegment: 2];
+ }
+ [pCtrl setSelected: (nState & ControlState::SELECTED) ? YES : NO forSegment: nPaintIndex];
+ [pCtrl setFocusRingType: NSFocusRingTypeExterior];
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ [NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithCGContext:context flipped:NO]];
+
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSRect tabrect = { NSMakePoint(rc.size.width * nPaintIndex + FOCUS_RING_WIDTH / 2, 0),
+ NSMakeSize(rc.size.width, rc.size.height) };
+ NSBitmapImageRep* pImageRep = [pCtrl bitmapImageRepForCachingDisplayInRect: tabrect];
+ [pCtrl cacheDisplayInRect: tabrect toBitmapImageRep: pImageRep];
+
+ NSImage* pImage = [[NSImage alloc] initWithSize: rect.size];
+ [pImage addRepresentation: pImageRep]; // takes ownership of pImageRep
+
+ [pImage drawInRect: rc fromRect: rect
+ operation: NSCompositingOperationSourceOver
+ fraction: 1.0];
+
+ [pImage release];
+
+ [NSGraphicsContext setCurrentContext:savedContext];
+
+ [pCtrl release];
+
+ if (nState & ControlState::FOCUSED)
+ {
+ if (!bSolo)
+ {
+ if (nPaintIndex == 0)
+ {
+ rc.origin.x += FOCUS_RING_WIDTH / 2;
+ rc.size.width -= FOCUS_RING_WIDTH / 2;
+ }
+ else if (nPaintIndex == 2)
+ {
+ rc.size.width -= FOCUS_RING_WIDTH / 2;
+ rc.size.width -= FOCUS_RING_WIDTH / 2;
+ }
+ }
+
+ paintFocusRect(4.0, rc, context);
+ }
+ bOK=true;
+ }
+ break;
+ case ControlType::Editbox:
+ case ControlType::MultilineEditbox:
+ {
+ rc.size.width += 2 * EDITBOX_INSET_MARGIN;
+ if (nType == ControlType::Editbox)
+ rc.size.height = EDITBOX_HEIGHT;
+ else
+ rc.size.height += 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
+ rc.origin.x -= EDITBOX_INSET_MARGIN;
+ rc.origin.y -= EDITBOX_INSET_MARGIN;
+
+ NSTextFieldCell* pBtn = pInst->mpTextFieldCell;
+
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setBezeled: YES];
+ [pBtn setEditable: YES];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+
+ drawEditableBackground(context, rc);
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Combobox:
+ if (nPart == ControlPart::HasBackgroundTexture || nPart == ControlPart::Entire)
+ {
+ rc.origin.y += (rc.size.height - COMBOBOX_HEIGHT + 1) / 2;
+ rc.size.height = COMBOBOX_HEIGHT;
+
+ NSComboBoxCell* pBtn = pInst->mpComboBoxCell;
+
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setEditable: YES];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+
+ {
+ rc.origin.x += 2;
+ rc.size.width -= 1;
+ }
+
+ drawEditableBackground(context, rc);
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, mpFrame->getNSView());
+
+ bOK = true;
+ }
+ break;
+ case ControlType::Listbox:
+
+ switch (nPart)
+ {
+ case ControlPart::Entire:
+ case ControlPart::ButtonDown:
+ {
+ rc.origin.y += (rc.size.height - LISTBOX_HEIGHT + 1) / 2;
+ rc.size.height = LISTBOX_HEIGHT;
+
+ NSPopUpButtonCell* pBtn = pInst->mpPopUpButtonCell;
+
+ [pBtn setTitle: @""];
+ [pBtn setEnabled: getEnabled(nState, mpFrame)];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+ [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
+ if (nState & ControlState::DEFAULT)
+ [pBtn setKeyEquivalent: @"\r"];
+ else
+ [pBtn setKeyEquivalent: @""];
+
+ {
+ rc.size.width += 1;
+ }
+
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bFocused, context, nullptr);
+
+ bOK = true;
+ break;
+ }
+ case ControlPart::ListboxWindow:
+ {
+ NSRect rect = { NSZeroPoint, NSMakeSize(rc.size.width, rc.size.height) };
+ NSScrollView* pBox = [[NSScrollView alloc] initWithFrame: rect];
+ [pBox setBorderType: NSLineBorder];
+
+ CGContextSaveGState(context);
+ CGContextTranslateCTM(context, rc.origin.x, rc.origin.y);
+
+ NSGraphicsContext* savedContext = [NSGraphicsContext currentContext];
+ NSGraphicsContext* graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:context flipped:NO];
+ [NSGraphicsContext setCurrentContext: graphicsContext];
+
+ [pBox drawRect: rect];
+
+ [NSGraphicsContext setCurrentContext: savedContext];
+
+ CGContextRestoreGState(context);
+
+ [pBox release];
+
+ bOK = true;
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ case ControlType::Spinbox:
+ if (nPart == ControlPart::Entire)
+ {
+ // text field
+
+ rc.size.width -= SPIN_BUTTON_WIDTH + 4 * FOCUS_RING_WIDTH;
+ rc.size.height = EDITBOX_HEIGHT;
+ rc.origin.x += FOCUS_RING_WIDTH;
+ rc.origin.y += FOCUS_RING_WIDTH;
+
+ NSTextFieldCell* pEdit = pInst->mpTextFieldCell;
+
+ [pEdit setEnabled: YES];
+ [pEdit setBezeled: YES];
+ [pEdit setEditable: YES];
+ [pEdit setFocusRingType: NSFocusRingTypeExterior];
+
+ drawEditableBackground(context, rc);
+ const bool bFocused(nState & ControlState::FOCUSED);
+ paintCell(pEdit, rc, bFocused, context, mpFrame->getNSView());
+
+ // buttons
+
+ const SpinbuttonValue *pSpinButtonVal = (aValue.getType() == ControlType::SpinButtons)
+ ? static_cast <const SpinbuttonValue *>(&aValue) : nullptr;
+ if (pSpinButtonVal)
+ {
+ ControlState nUpperState = pSpinButtonVal->mnUpperState;
+ ControlState nLowerState = pSpinButtonVal->mnLowerState;
+
+ rc.origin.x += rc.size.width + FOCUS_RING_WIDTH + 1;
+ rc.origin.y -= 1;
+ rc.size.width = SPIN_BUTTON_WIDTH;
+ rc.size.height = SPIN_LOWER_BUTTON_HEIGHT + SPIN_LOWER_BUTTON_HEIGHT;
+
+ NSStepperCell* pBtn = pInst->mpStepperCell;
+
+ [pBtn setTitle: @""];
+ [pBtn setState: ImplGetButtonValue(aValue.getTristateVal())];
+ [pBtn setEnabled: (nUpperState & ControlState::ENABLED || nLowerState & ControlState::ENABLED) ?
+ YES : NO];
+ [pBtn setFocusRingType: NSFocusRingTypeExterior];
+ [pBtn setHighlighted: (nState & ControlState::PRESSED) ? YES : NO];
+
+ const bool bSpinFocused(nUpperState & ControlState::FOCUSED || nLowerState & ControlState::FOCUSED);
+ paintCell(pBtn, rc, bSpinFocused, context, nullptr);
+ }
+ bOK = true;
+ }
+ break;
+ case ControlType::Frame:
+ {
+ DrawFrameFlags nStyle = static_cast<DrawFrameFlags>(aValue.getNumericVal());
+ if (nPart == ControlPart::Border)
+ {
+ if (!(nStyle & DrawFrameFlags::Menu) && !(nStyle & DrawFrameFlags::WindowBorder))
+ {
+
+ // strange effects start to happen when HIThemeDrawFrame meets the border of the window.
+ // These can be avoided by clipping to the boundary of the frame (see issue 84756)
+
+ if (rc.origin.y + rc.size.height >= mpFrame->maGeometry.height() - 3)
+ {
+ CGMutablePathRef rPath = CGPathCreateMutable();
+ CGPathAddRect(rPath, nullptr,
+ CGRectMake(0, 0, mpFrame->maGeometry.width() - 1, mpFrame->maGeometry.height() - 1));
+ CGContextBeginPath(context);
+ CGContextAddPath(context, rPath);
+ CGContextClip(context);
+ CGPathRelease(rPath);
+ }
+ HIThemeFrameDrawInfo aTextDrawInfo;
+ aTextDrawInfo.version = 0;
+ aTextDrawInfo.kind = kHIThemeFrameListBox;
+ aTextDrawInfo.state = kThemeStateActive;
+ aTextDrawInfo.isFocused = false;
+ HIThemeDrawFrame(&rc, &aTextDrawInfo, context, kHIThemeOrientationNormal);
+ bOK = true;
+ }
+ }
+ }
+ break;
+ case ControlType::ListNet:
+
+ // do nothing as there isn't net for listviews on macOS
+
+ bOK = true;
+ break;
+ default:
+ break;
+ }
+
+ return bOK;
+}
+
+bool AquaSalGraphics::getNativeControlRegion(ControlType nType,
+ ControlPart nPart,
+ const tools::Rectangle &rControlRegion,
+ ControlState,
+ const ImplControlValue &aValue,
+ const OUString &,
+ tools::Rectangle &rNativeBoundingRegion,
+ tools::Rectangle &rNativeContentRegion)
+{
+ bool toReturn = false;
+ tools::Rectangle aCtrlBoundRect(rControlRegion);
+ short x = aCtrlBoundRect.Left();
+ short y = aCtrlBoundRect.Top();
+ short w, h;
+ switch (nType)
+ {
+ case ControlType::Pushbutton:
+ case ControlType::Radiobutton:
+ case ControlType::Checkbox:
+ {
+ if (nType == ControlType::Pushbutton)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = aCtrlBoundRect.GetHeight();
+ }
+ else
+ {
+ w = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH + RADIO_BUTTON_TEXT_SEPARATOR;
+ h = RADIO_BUTTON_SMALL_SIZE + 2 * FOCUS_RING_WIDTH;
+ }
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::LevelBar:
+ case ControlType::Progress:
+ {
+ tools::Rectangle aRect(aCtrlBoundRect);
+ if (aRect.GetHeight() < LARGE_PROGRESS_INDICATOR_HEIGHT)
+ aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
+ else
+ aRect.SetBottom(aRect.Top() + LARGE_PROGRESS_INDICATOR_HEIGHT - 1);
+ rNativeBoundingRegion = aRect;
+ rNativeContentRegion = aRect;
+ toReturn = true;
+ }
+ break;
+ case ControlType::IntroProgress:
+ {
+ tools::Rectangle aRect(aCtrlBoundRect);
+ aRect.SetBottom(aRect.Top() + MEDIUM_PROGRESS_INDICATOR_HEIGHT - 1);
+ rNativeBoundingRegion = aRect;
+ rNativeContentRegion = aRect;
+ toReturn = true;
+ }
+ break;
+ case ControlType::Slider:
+ if (nPart == ControlPart::ThumbHorz)
+ {
+ w = SLIDER_WIDTH;
+ h = aCtrlBoundRect.GetHeight();
+ rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ThumbVert)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = SLIDER_HEIGHT;
+ rNativeBoundingRegion = rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Scrollbar:
+ {
+ tools::Rectangle aRect;
+ if (AquaGetScrollRect(nPart, aCtrlBoundRect, aRect))
+ {
+ toReturn = true;
+ rNativeBoundingRegion = aRect;
+ rNativeContentRegion = aRect;
+ }
+ }
+ break;
+ case ControlType::TabItem:
+ {
+ w = aCtrlBoundRect.GetWidth() + 2 * TAB_TEXT_MARGIN;
+ h = TAB_HEIGHT + 2;
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Editbox:
+ {
+ const tools::Long nBorderThickness = FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
+ // tdf#144241 don't return a negative width, expand the region to the min osx width
+ w = std::max(nBorderThickness * 2, aCtrlBoundRect.GetWidth());
+ h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ w -= 2 * nBorderThickness;
+ h -= 2 * nBorderThickness;
+ x += nBorderThickness;
+ y += nBorderThickness;
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Combobox:
+ if (nPart == ControlPart::Entire)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ w = COMBOBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
+ h = COMBOBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ x += aCtrlBoundRect.GetWidth() - w;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::SubEdit)
+ {
+ w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - COMBOBOX_BUTTON_WIDTH - COMBOBOX_BORDER_WIDTH
+ - 2 * COMBOBOX_TEXT_MARGIN;
+ h = COMBOBOX_HEIGHT - 2 * COMBOBOX_BORDER_WIDTH;
+ x += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH + COMBOBOX_TEXT_MARGIN;
+ y += FOCUS_RING_WIDTH + COMBOBOX_BORDER_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Listbox:
+ if (nPart == ControlPart::Entire)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ w = LISTBOX_BUTTON_WIDTH + FOCUS_RING_WIDTH;
+ h = LISTBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ x += aCtrlBoundRect.GetWidth() - w;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::SubEdit)
+ {
+ w = aCtrlBoundRect.GetWidth() - 2 * FOCUS_RING_WIDTH - LISTBOX_BUTTON_WIDTH - LISTBOX_BORDER_WIDTH
+ - 2 * LISTBOX_TEXT_MARGIN;
+ h = LISTBOX_HEIGHT - 2 * LISTBOX_BORDER_WIDTH;
+ x += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH + LISTBOX_TEXT_MARGIN;
+ y += FOCUS_RING_WIDTH + LISTBOX_BORDER_WIDTH;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ListboxWindow)
+ {
+ w = aCtrlBoundRect.GetWidth() - 2;
+ h = aCtrlBoundRect.GetHeight() - 2;
+ x += 1;
+ y += 1;
+ rNativeBoundingRegion = aCtrlBoundRect;
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Spinbox:
+ if (nPart == ControlPart::Entire)
+ {
+ w = aCtrlBoundRect.GetWidth();
+ h = EDITBOX_HEIGHT + 2 * FOCUS_RING_WIDTH;
+ x += SPINBOX_OFFSET;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::SubEdit)
+ {
+ w = aCtrlBoundRect.GetWidth() - 4 * FOCUS_RING_WIDTH - SPIN_BUTTON_WIDTH - 2 * EDITBOX_BORDER_WIDTH
+ - 2 * EDITBOX_INSET_MARGIN;
+ h = EDITBOX_HEIGHT - 2 * (EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN);
+ x += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN + SPINBOX_OFFSET;
+ y += FOCUS_RING_WIDTH + EDITBOX_BORDER_WIDTH + EDITBOX_INSET_MARGIN;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ButtonUp)
+ {
+ w = SPIN_BUTTON_WIDTH + 2 * FOCUS_RING_WIDTH;
+ h = SPIN_UPPER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
+ x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ else if (nPart == ControlPart::ButtonDown)
+ {
+ w = SPIN_BUTTON_WIDTH + 2 * FOCUS_RING_WIDTH;
+ h = SPIN_LOWER_BUTTON_HEIGHT + FOCUS_RING_WIDTH;
+ x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - 2 * FOCUS_RING_WIDTH + SPINBOX_OFFSET;
+ y += FOCUS_RING_WIDTH + SPIN_UPPER_BUTTON_HEIGHT;
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ case ControlType::Frame:
+ {
+ DrawFrameStyle nStyle = static_cast<DrawFrameStyle>(aValue.getNumericVal() & 0x000f);
+ DrawFrameFlags nFlags = static_cast<DrawFrameFlags>(aValue.getNumericVal() & 0xfff0);
+ if (nPart == ControlPart::Border
+ && !(nFlags & (DrawFrameFlags::Menu | DrawFrameFlags::WindowBorder | DrawFrameFlags::BorderWindowBorder)))
+ {
+ tools::Rectangle aRect(aCtrlBoundRect);
+ if (nStyle == DrawFrameStyle::DoubleIn)
+ {
+ aRect.AdjustLeft(1);
+ aRect.AdjustTop(1);
+ // rRect.Right() -= 1;
+ // rRect.Bottom() -= 1;
+ }
+ else
+ {
+ aRect.AdjustLeft(1);
+ aRect.AdjustTop(1);
+ aRect.AdjustRight(-1);
+ aRect.AdjustBottom(-1);
+ }
+ rNativeContentRegion = aRect;
+ rNativeBoundingRegion = aRect;
+ toReturn = true;
+ }
+ }
+ break;
+ case ControlType::Menubar:
+ case ControlType::MenuPopup:
+ if (nPart == ControlPart::MenuItemCheckMark || nPart == ControlPart::MenuItemRadioMark)
+ {
+ w=10;
+ h=10;
+ rNativeContentRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ rNativeBoundingRegion = tools::Rectangle(Point(x, y), Size(w, h));
+ toReturn = true;
+ }
+ break;
+ default:
+ break;
+ }
+ return toReturn;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salnsmenu.mm b/vcl/osx/salnsmenu.mm
new file mode 100644
index 0000000000..b2df2da7e5
--- /dev/null
+++ b/vcl/osx/salnsmenu.mm
@@ -0,0 +1,261 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <osl/diagnose.h>
+
+#include <vcl/window.hxx>
+
+#include <osx/salinst.h>
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#include <osx/salframeview.h>
+#include <osx/salmenu.h>
+#include <osx/salnsmenu.h>
+
+@implementation SalNSMenu
+-(id)initWithMenu: (AquaSalMenu*)pMenu
+{
+ mpMenu = pMenu;
+ return [super initWithTitle: [NSString string]];
+}
+
+-(void)menuNeedsUpdate: (NSMenu*)pMenu
+{
+ SolarMutexGuard aGuard;
+
+ if( mpMenu )
+ {
+ const AquaSalFrame* pFrame = mpMenu->getFrame();
+ if( pFrame && AquaSalFrame::isAlive( pFrame ) )
+ {
+ SalMenuEvent aMenuEvt;
+ aMenuEvt.mnId = 0;
+ aMenuEvt.mpMenu = mpMenu->mpVCLMenu;
+ if( aMenuEvt.mpMenu )
+ {
+ pFrame->CallCallback(SalEvent::MenuActivate, &aMenuEvt);
+ pFrame->CallCallback(SalEvent::MenuDeactivate, &aMenuEvt);
+ }
+ else
+ OSL_FAIL( "unconnected menu" );
+ }
+ else if( mpMenu->mpVCLMenu )
+ {
+ mpMenu->mpVCLMenu->Activate();
+ mpMenu->mpVCLMenu->Deactivate();
+
+ // Hide disabled items
+ NSArray* elements = [pMenu itemArray];
+ NSEnumerator* it = [elements objectEnumerator];
+ id element;
+ while ( ( element = [it nextObject] ) != nil )
+ {
+ NSMenuItem* item = static_cast< NSMenuItem* >( element );
+ if( ![item isSeparatorItem] )
+ [item setHidden: ![item isEnabled]];
+ }
+ }
+ }
+}
+
+-(void)setSalMenu: (AquaSalMenu*)pMenu
+{
+ mpMenu = pMenu;
+}
+@end
+
+@implementation SalNSMenuItem
+-(id)initWithMenuItem: (AquaSalMenuItem*)pMenuItem
+{
+ mpMenuItem = pMenuItem;
+ id ret = [super initWithTitle: [NSString string]
+ action: @selector(menuItemTriggered:)
+ keyEquivalent: [NSString string]];
+ [ret setTarget: self];
+ return ret;
+}
+-(void)menuItemTriggered: (id)aSender
+{
+ (void)aSender;
+ SolarMutexGuard aGuard;
+
+ // Commit uncommitted text before dispatching the selecting menu item. In
+ // certain cases such as selecting the Insert > Comment menu item in a
+ // Writer document while there is uncommitted text will call
+ // AquaSalFrame::EndExtTextInput() which will dispatch a
+ // SalEvent::EndExtTextInput event. Writer's handler for that event will
+ // delete the uncommitted text and then insert the committed text but
+ // LibreOffice will crash when deleting the uncommitted text because
+ // deletion of the text also removes and deletes the newly inserted
+ // comment.
+ NSWindow* pKeyWin = [NSApp keyWindow];
+ if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
+ [static_cast<SalFrameWindow*>(pKeyWin) endExtTextInput];
+
+ // tdf#49853 Keyboard shortcuts are also handled by the menu bar, but at least some of them
+ // must still end up in the view. This is necessary to handle common edit actions in docked
+ // windows (e.g. in toolbar fields).
+ NSEvent* pEvent = [NSApp currentEvent];
+ if( pEvent && [pEvent type] == NSEventTypeKeyDown )
+ {
+ unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
+ NSString* charactersIgnoringModifiers = [pEvent charactersIgnoringModifiers];
+ if( nModMask == NSEventModifierFlagCommand &&
+ ( [charactersIgnoringModifiers isEqualToString: @"v"] ||
+ [charactersIgnoringModifiers isEqualToString: @"c"] ||
+ [charactersIgnoringModifiers isEqualToString: @"x"] ||
+ [charactersIgnoringModifiers isEqualToString: @"a"] ||
+ [charactersIgnoringModifiers isEqualToString: @"z"] ) )
+ {
+ [[[NSApp keyWindow] contentView] keyDown: pEvent];
+ return;
+ }
+ }
+
+ const AquaSalFrame* pFrame = mpMenuItem->mpParentMenu ? mpMenuItem->mpParentMenu->getFrame() : nullptr;
+ if( pFrame && AquaSalFrame::isAlive( pFrame ) && ! pFrame->GetWindow()->IsInModalMode() )
+ {
+ SalMenuEvent aMenuEvt( mpMenuItem->mnId, mpMenuItem->mpVCLMenu );
+ pFrame->CallCallback(SalEvent::MenuCommand, &aMenuEvt);
+ }
+ else if( mpMenuItem->mpVCLMenu )
+ {
+ // if an item from submenu was selected. the corresponding Window does not exist because
+ // we use native popup menus, so we have to set the selected menuitem directly
+ // incidentally this of course works for top level popup menus, too
+ PopupMenu * pPopupMenu = dynamic_cast<PopupMenu *>(mpMenuItem->mpVCLMenu.get());
+ if( pPopupMenu )
+ {
+ // FIXME: revise this ugly code
+
+ // select handlers in vcl are dispatch on the original menu
+ // if not consumed by the select handler of the current menu
+ // however since only the starting menu ever came into Execute
+ // the hierarchy is not build up. Workaround this by getting
+ // the menu it should have been
+
+ // get started from hierarchy in vcl menus
+ AquaSalMenu* pParentMenu = mpMenuItem->mpParentMenu;
+ Menu* pCurMenu = mpMenuItem->mpVCLMenu;
+ while( pParentMenu && pParentMenu->mpVCLMenu )
+ {
+ pCurMenu = pParentMenu->mpVCLMenu;
+ pParentMenu = pParentMenu->mpParentSalMenu;
+ }
+
+ pPopupMenu->SetSelectedEntry( mpMenuItem->mnId );
+ pPopupMenu->ImplSelectWithStart( pCurMenu );
+ }
+ else
+ OSL_FAIL( "menubar item without frame !" );
+ }
+}
+@end
+
+@implementation OOStatusItemView
+-(void)drawRect: (NSRect)aRect
+{
+ NSGraphicsContext* pContext = [NSGraphicsContext currentContext];
+ [pContext saveGraphicsState];
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'drawStatusBarBackgroundInRect:withHighlight:' is deprecated: first deprecated in macOS
+ // 10.14 - Use the standard button instead which handles highlight drawing, making this
+ // method obsolete"
+ [SalData::getStatusItem() drawStatusBarBackgroundInRect: aRect withHighlight: NO];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ if( AquaSalMenu::pCurrentMenuBar )
+ {
+ const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
+ NSRect aFrame = [self frame];
+ NSRect aImgRect = { { 2, 0 }, { 0, 0 } };
+ for( size_t i = 0; i < rButtons.size(); ++i )
+ {
+ const Size aPixSize = rButtons[i].maButton.maImage.GetSizePixel();
+ const NSRect aFromRect = { NSZeroPoint, NSMakeSize( aPixSize.Width(), aPixSize.Height()) };
+ aImgRect.origin.y = floor((aFrame.size.height - aFromRect.size.height)/2);
+ aImgRect.size = aFromRect.size;
+ if( rButtons[i].mpNSImage )
+ [rButtons[i].mpNSImage drawInRect: aImgRect fromRect: aFromRect operation: NSCompositingOperationSourceOver fraction: 1.0];
+ aImgRect.origin.x += aFromRect.size.width + 2;
+ }
+ }
+ [pContext restoreGraphicsState];
+}
+
+-(void)mouseUp: (NSEvent *)pEvent
+{
+ /* check if button goes up inside one of our status buttons */
+ if( AquaSalMenu::pCurrentMenuBar )
+ {
+ const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
+ NSRect aFrame = [self frame];
+ NSRect aImgRect = { { 2, 0 }, { 0, 0 } };
+ NSPoint aMousePt = [pEvent locationInWindow];
+ for( size_t i = 0; i < rButtons.size(); ++i )
+ {
+ const Size aPixSize = rButtons[i].maButton.maImage.GetSizePixel();
+ const NSRect aFromRect = { NSZeroPoint, NSMakeSize( aPixSize.Width(), aPixSize.Height()) };
+ aImgRect.origin.y = (aFrame.size.height - aFromRect.size.height)/2;
+ aImgRect.size = aFromRect.size;
+ if( aMousePt.x >= aImgRect.origin.x && aMousePt.x <= (aImgRect.origin.x+aImgRect.size.width) &&
+ aMousePt.y >= aImgRect.origin.y && aMousePt.y <= (aImgRect.origin.y+aImgRect.size.height) )
+ {
+ if( AquaSalMenu::pCurrentMenuBar->mpFrame && AquaSalFrame::isAlive( AquaSalMenu::pCurrentMenuBar->mpFrame ) )
+ {
+ SalMenuEvent aMenuEvt( rButtons[i].maButton.mnId, AquaSalMenu::pCurrentMenuBar->mpVCLMenu );
+ AquaSalMenu::pCurrentMenuBar->mpFrame->CallCallback(SalEvent::MenuButtonCommand, &aMenuEvt);
+ }
+ return;
+ }
+
+ aImgRect.origin.x += aFromRect.size.width + 2;
+ }
+ }
+}
+
+-(void)layout
+{
+ NSStatusBar* pStatBar = [NSStatusBar systemStatusBar];
+ NSSize aSize = { 0, [pStatBar thickness] };
+ [self removeAllToolTips];
+ if( AquaSalMenu::pCurrentMenuBar )
+ {
+ const std::vector< AquaSalMenu::MenuBarButtonEntry >& rButtons( AquaSalMenu::pCurrentMenuBar->getButtons() );
+ if( ! rButtons.empty() )
+ {
+ aSize.width = 2;
+ for( size_t i = 0; i < rButtons.size(); ++i )
+ {
+ NSRect aImgRect = { { aSize.width,
+ static_cast<CGFloat>(floor((aSize.height-rButtons[i].maButton.maImage.GetSizePixel().Height())/2)) },
+ { static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Width()),
+ static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Height()) } };
+ if( rButtons[i].mpToolTipString )
+ [self addToolTipRect: aImgRect owner: rButtons[i].mpToolTipString userData: nullptr];
+ aSize.width += 2 + aImgRect.size.width;
+ }
+ }
+ }
+ [self setFrameSize: aSize];
+}
+@end
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salnstimer.mm b/vcl/osx/salnstimer.mm
new file mode 100644
index 0000000000..95be181644
--- /dev/null
+++ b/vcl/osx/salnstimer.mm
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <osx/saltimer.h>
+#include <osx/salnstimer.h>
+#include <osx/salinst.h>
+#include <osx/saldata.hxx>
+#include <svdata.hxx>
+
+@implementation TimerCallbackCaller
+
+-(void)timerElapsed:(NSTimer*)pNSTimer
+{
+ (void) pNSTimer;
+ AquaSalTimer *pTimer = static_cast<AquaSalTimer*>( ImplGetSVData()->maSchedCtx.mpSalTimer );
+ if (pTimer)
+ pTimer->handleTimerElapsed();
+}
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salobj.cxx b/vcl/osx/salobj.cxx
new file mode 100644
index 0000000000..6cf114f20c
--- /dev/null
+++ b/vcl/osx/salobj.cxx
@@ -0,0 +1,444 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <string.h>
+#include <tools/debug.hxx>
+#include <vcl/opengl/OpenGLContext.hxx>
+#include <vcl/opengl/OpenGLHelper.hxx>
+#include <opengl/zone.hxx>
+
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#include <osx/salinst.h>
+#include <osx/salobj.h>
+#include <osx/runinmain.hxx>
+
+#include <AppKit/NSOpenGLView.h>
+
+AquaSalObject::AquaSalObject( AquaSalFrame* pFrame, SystemWindowData const * pWindowData ) :
+ mpFrame( pFrame ),
+ mnClipX( -1 ),
+ mnClipY( -1 ),
+ mnClipWidth( -1 ),
+ mnClipHeight( -1 ),
+ mbClip( false ),
+ mnX( 0 ),
+ mnY( 0 ),
+ mnWidth( 20 ),
+ mnHeight( 20 )
+{
+ maSysData.mpNSView = nullptr;
+ maSysData.mbOpenGL = false;
+
+ NSRect aInitFrame = { NSZeroPoint, { 20, 20 } };
+ mpClipView = [[NSClipView alloc] initWithFrame: aInitFrame ];
+ if( mpClipView )
+ {
+ [mpFrame->getNSView() addSubview: mpClipView];
+ [mpClipView setHidden: YES];
+ }
+ if (pWindowData && pWindowData->bOpenGL)
+ {
+ maSysData.mbOpenGL = true;
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormat' is deprecated: first deprecated in macOS 10.14 - Please use
+ // Metal or MetalKit."
+ NSOpenGLPixelFormat* pixFormat = nullptr;
+SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ if (pWindowData->bLegacy)
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormatAttribute' is deprecated: first deprecated in macOS 10.14"
+ NSOpenGLPixelFormatAttribute const aAttributes[] =
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPFADoubleBuffer' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAAlphaSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAColorSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFADepthSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAMultisample' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFASampleBuffers' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPixelFormatAttribute' is deprecated: first deprecated in macOS
+ // 10.14",
+ // "'NSOpenGLPFASamples' is deprecated: first deprecated in macOS 10.14"
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFADepthSize, 24,
+ NSOpenGLPFAMultisample,
+ NSOpenGLPFASampleBuffers, NSOpenGLPixelFormatAttribute(1),
+ NSOpenGLPFASamples, NSOpenGLPixelFormatAttribute(4),
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 0
+ };
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormat' is deprecated: first deprecated in macOS 10.14 - Please
+ // use Metal or MetalKit."
+ pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:aAttributes];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ else
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormatAttribute' is deprecated: first deprecated in macOS 10.14"
+ NSOpenGLPixelFormatAttribute const aAttributes[] =
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ {
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPFAOpenGLProfile' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLProfileVersion3_2Core' is deprecated: first deprecated in macOS
+ // 10.14",
+ // "'NSOpenGLPFADoubleBuffer' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAAlphaSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAColorSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFADepthSize' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFAMultisample' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPFASampleBuffers' is deprecated: first deprecated in macOS 10.14",
+ // "'NSOpenGLPixelFormatAttribute' is deprecated: first deprecated in macOS
+ // 10.14",
+ // "'NSOpenGLPFASamples' is deprecated: first deprecated in macOS 10.14"
+ NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core,
+ NSOpenGLPFADoubleBuffer,
+ NSOpenGLPFAAlphaSize, 8,
+ NSOpenGLPFAColorSize, 24,
+ NSOpenGLPFADepthSize, 24,
+ NSOpenGLPFAMultisample,
+ NSOpenGLPFASampleBuffers, NSOpenGLPixelFormatAttribute(1),
+ NSOpenGLPFASamples, NSOpenGLPixelFormatAttribute(4),
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ 0
+ };
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLPixelFormat' is deprecated: first deprecated in macOS 10.14 - Please
+ // use Metal or MetalKit."
+ pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:aAttributes];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ maSysData.mpNSView = [[NSOpenGLView alloc] initWithFrame: aInitFrame pixelFormat:pixFormat];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+ else
+ {
+ maSysData.mpNSView = [[NSView alloc] initWithFrame: aInitFrame];
+ }
+
+ if( maSysData.mpNSView )
+ {
+ if( mpClipView )
+ [mpClipView setDocumentView: maSysData.mpNSView];
+ }
+}
+
+AquaSalObject::~AquaSalObject()
+{
+ assert( GetSalData()->mpInstance->IsMainThread() );
+
+ if( maSysData.mpNSView )
+ {
+ NSView *pView = maSysData.mpNSView;
+ [pView removeFromSuperview];
+ [pView release];
+ }
+ if( mpClipView )
+ {
+ [mpClipView removeFromSuperview];
+ [mpClipView release];
+ }
+}
+
+// Please note that the talk about QTMovieView below presumably refers
+// to stuff in the QuickTime avmedia thingie, and that QuickTime is
+// deprecated, not available for 64-bit code, and won't thus be used
+// in a "modern" build of LO anyway. So the relevance of the comment
+// is unclear.
+
+/*
+ sadly there seems to be no way to impose clipping on a child view,
+ especially a QTMovieView which seems to ignore the current context
+ completely. Also there is no real way to shape a window; on Aqua a
+ similar effect to non-rectangular windows is achieved by using a
+ non-opaque window and not painting where one wants the background
+ to shine through.
+
+ With respect to SalObject this leaves us to having an NSClipView
+ containing the child view. Even a QTMovieView respects the boundaries of
+ that, which gives us a clip "region" consisting of one rectangle.
+ This is gives us an 80% solution only, though.
+*/
+
+void AquaSalObject::ResetClipRegion()
+{
+ mbClip = false;
+ setClippedPosSize();
+}
+
+void AquaSalObject::BeginSetClipRegion( sal_uInt32 )
+{
+ mbClip = false;
+}
+
+void AquaSalObject::UnionClipRegion(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ if( mbClip )
+ {
+ if( nX < mnClipX )
+ {
+ mnClipWidth += mnClipX - nX;
+ mnClipX = nX;
+ }
+ if( nX + nWidth > mnClipX + mnClipWidth )
+ mnClipWidth = nX + nWidth - mnClipX;
+ if( nY < mnClipY )
+ {
+ mnClipHeight += mnClipY - nY;
+ mnClipY = nY;
+ }
+ if( nY + nHeight > mnClipY + mnClipHeight )
+ mnClipHeight = nY + nHeight - mnClipY;
+ }
+ else
+ {
+ mnClipX = nX;
+ mnClipY = nY;
+ mnClipWidth = nWidth;
+ mnClipHeight = nHeight;
+ mbClip = true;
+ }
+}
+
+void AquaSalObject::EndSetClipRegion()
+{
+ setClippedPosSize();
+}
+
+void AquaSalObject::SetPosSize(
+ tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight )
+{
+ mnX = nX;
+ mnY = nY;
+ mnWidth = nWidth;
+ mnHeight = nHeight;
+ setClippedPosSize();
+}
+
+void AquaSalObject::setClippedPosSize()
+{
+ OSX_SALDATA_RUNINMAIN( setClippedPosSize() )
+
+ NSRect aViewRect = { NSZeroPoint, NSMakeSize( mnWidth, mnHeight) };
+ if( maSysData.mpNSView )
+ {
+ NSView* pNSView = maSysData.mpNSView;
+ [pNSView setFrame: aViewRect];
+ }
+
+ NSRect aClipViewRect = NSMakeRect( mnX, mnY, mnWidth, mnHeight);
+ NSPoint aClipPt = NSZeroPoint;
+ if( mbClip )
+ {
+ aClipViewRect.origin.x += mnClipX;
+ aClipViewRect.origin.y += mnClipY;
+ aClipViewRect.size.width = mnClipWidth;
+ aClipViewRect.size.height = mnClipHeight;
+ aClipPt.x = mnClipX;
+ if( mnClipY == 0 )
+ aClipPt.y = mnHeight - mnClipHeight;
+ }
+
+ mpFrame->VCLToCocoa( aClipViewRect, false );
+ [mpClipView setFrame: aClipViewRect];
+
+ [mpClipView scrollToPoint: aClipPt];
+}
+
+void AquaSalObject::Show( bool bVisible )
+{
+ if( !mpClipView )
+ return;
+
+ OSX_SALDATA_RUNINMAIN( Show( bVisible ) )
+
+ [mpClipView setHidden: (bVisible ? NO : YES)];
+}
+
+const SystemEnvData* AquaSalObject::GetSystemData() const
+{
+ return &maSysData;
+}
+
+namespace {
+
+class AquaOpenGLContext : public OpenGLContext
+{
+public:
+ virtual void initWindow() override;
+
+private:
+ GLWindow m_aGLWin;
+
+ virtual const GLWindow& getOpenGLWindow() const override { return m_aGLWin; }
+ virtual GLWindow& getModifiableOpenGLWindow() override { return m_aGLWin; }
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ NSOpenGLView* getOpenGLView();
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ virtual bool ImplInit() override;
+ virtual SystemWindowData generateWinData(vcl::Window* pParent, bool bRequestLegacyContext) override;
+ virtual void makeCurrent() override;
+ virtual void destroyCurrentContext() override;
+ virtual void resetCurrent() override;
+ virtual void swapBuffers() override;
+};
+
+}
+
+void AquaOpenGLContext::resetCurrent()
+{
+ OSX_SALDATA_RUNINMAIN( resetCurrent() )
+
+ clearCurrent();
+
+ OpenGLZone aZone;
+
+ (void) this; // loplugin:staticmethods
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLContext' is deprecated: first deprecated in macOS 10.14 - Please use Metal or
+ // MetalKit."
+ [NSOpenGLContext clearCurrentContext];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+void AquaOpenGLContext::makeCurrent()
+{
+ OSX_SALDATA_RUNINMAIN( makeCurrent() )
+
+ if (isCurrent())
+ return;
+
+ OpenGLZone aZone;
+
+ clearCurrent();
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ NSOpenGLView* pView = getOpenGLView();
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [[pView openGLContext] makeCurrentContext];
+
+ registerAsCurrent();
+}
+
+void AquaOpenGLContext::swapBuffers()
+{
+ OSX_SALDATA_RUNINMAIN( swapBuffers() )
+
+ OpenGLZone aZone;
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ NSOpenGLView* pView = getOpenGLView();
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [[pView openGLContext] flushBuffer];
+
+ BuffersSwapped();
+}
+
+SystemWindowData AquaOpenGLContext::generateWinData(vcl::Window* /*pParent*/, bool bRequestLegacyContext)
+{
+ SystemWindowData aWinData;
+ aWinData.bOpenGL = true;
+ aWinData.bLegacy = bRequestLegacyContext;
+ return aWinData;
+}
+
+void AquaOpenGLContext::destroyCurrentContext()
+{
+ OSX_SALDATA_RUNINMAIN( destroyCurrentContext() )
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLContext' is deprecated: first deprecated in macOS 10.14 - Please use Metal or
+ // MetalKit."
+ [NSOpenGLContext clearCurrentContext];
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+void AquaOpenGLContext::initWindow()
+{
+ OSX_SALDATA_RUNINMAIN( initWindow() )
+
+ if( !m_pChildWindow )
+ {
+ SystemWindowData winData = generateWinData(mpWindow, mbRequestLegacyContext);
+ m_pChildWindow = VclPtr<SystemChildWindow>::Create(mpWindow, 0, &winData, false);
+ }
+
+ if (m_pChildWindow)
+ {
+ InitChildWindow(m_pChildWindow.get());
+ }
+}
+
+bool AquaOpenGLContext::ImplInit()
+{
+ OSX_SALDATA_RUNINMAIN_UNION( ImplInit(), boolean )
+
+ OpenGLZone aZone;
+
+ VCL_GL_INFO("OpenGLContext::ImplInit----start");
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ NSOpenGLView* pView = getOpenGLView();
+SAL_WNODEPRECATED_DECLARATIONS_POP
+ [[pView openGLContext] makeCurrentContext];
+
+ bool bRet = InitGL();
+ InitGLDebugging();
+ return bRet;
+}
+
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+NSOpenGLView* AquaOpenGLContext::getOpenGLView()
+SAL_WNODEPRECATED_DECLARATIONS_POP
+{
+SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ // "'NSOpenGLView' is deprecated: first deprecated in macOS 10.14 - Please use MTKView
+ // instead."
+ return reinterpret_cast<NSOpenGLView*>(m_pChildWindow->GetSystemData()->mpNSView);
+SAL_WNODEPRECATED_DECLARATIONS_POP
+}
+
+OpenGLContext* AquaSalInstance::CreateOpenGLContext()
+{
+ OSX_SALDATA_RUNINMAIN_POINTER( CreateOpenGLContext(), OpenGLContext* )
+ return new AquaOpenGLContext;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salprn.cxx b/vcl/osx/salprn.cxx
new file mode 100644
index 0000000000..9f9c8c08f3
--- /dev/null
+++ b/vcl/osx/salprn.cxx
@@ -0,0 +1,677 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <officecfg/Office/Common.hxx>
+
+#include <vcl/gdimtf.hxx>
+#include <vcl/print.hxx>
+#include <sal/macros.h>
+#include <osl/diagnose.h>
+#include <tools/long.hxx>
+
+#include <osx/salinst.h>
+#include <osx/salprn.h>
+#include <osx/printview.h>
+#include <quartz/salgdi.h>
+#include <osx/saldata.hxx>
+#include <quartz/utils.h>
+
+#include <jobset.h>
+#include <salptype.hxx>
+
+#include <com/sun/star/beans/PropertyValue.hpp>
+#include <com/sun/star/awt/Size.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <algorithm>
+#include <cstdlib>
+
+using namespace vcl;
+using namespace com::sun::star;
+using namespace com::sun::star::beans;
+
+AquaSalInfoPrinter::AquaSalInfoPrinter( const SalPrinterQueueInfo& i_rQueue ) :
+ mpGraphics( nullptr ),
+ mbGraphics( false ),
+ mbJob( false ),
+ mpPrinter( nil ),
+ mpPrintInfo( nil ),
+ mePageOrientation( Orientation::Portrait ),
+ mnStartPageOffsetX( 0 ),
+ mnStartPageOffsetY( 0 ),
+ mnCurPageRangeStart( 0 ),
+ mnCurPageRangeCount( 0 )
+{
+ NSString* pStr = CreateNSString( i_rQueue.maPrinterName );
+ mpPrinter = [NSPrinter printerWithName: pStr];
+ [pStr release];
+
+ NSPrintInfo* pShared = [NSPrintInfo sharedPrintInfo];
+ if( pShared )
+ {
+ mpPrintInfo = [pShared copy];
+ [mpPrintInfo setPrinter: mpPrinter];
+ mePageOrientation = ([mpPrintInfo orientation] == NSPaperOrientationLandscape) ? Orientation::Landscape : Orientation::Portrait;
+ [mpPrintInfo setOrientation: NSPaperOrientationPortrait];
+ }
+
+ mpGraphics = new AquaSalGraphics(true);
+
+ const int nWidth = 100, nHeight = 100;
+ mpContextMemory.reset(new (std::nothrow) sal_uInt8[nWidth * 4 * nHeight]);
+
+ if (mpContextMemory)
+ {
+ mrContext = CGBitmapContextCreate(mpContextMemory.get(),
+ nWidth, nHeight, 8, nWidth * 4,
+ GetSalData()->mxRGBSpace, kCGImageAlphaNoneSkipFirst);
+ if( mrContext )
+ SetupPrinterGraphics( mrContext );
+ }
+}
+
+AquaSalInfoPrinter::~AquaSalInfoPrinter()
+{
+ delete mpGraphics;
+ if( mpPrintInfo )
+ [mpPrintInfo release];
+ if( mrContext )
+ CFRelease( mrContext );
+}
+
+void AquaSalInfoPrinter::SetupPrinterGraphics( CGContextRef i_rContext ) const
+{
+ if( mpGraphics )
+ {
+ if( mpPrintInfo )
+ {
+ // FIXME: get printer resolution
+ sal_Int32 nDPIX = 720, nDPIY = 720;
+ NSSize aPaperSize = [mpPrintInfo paperSize];
+
+ NSRect aImageRect = [mpPrintInfo imageablePageBounds];
+ if( mePageOrientation == Orientation::Portrait )
+ {
+ // move mirrored CTM back into paper
+ double dX = 0, dY = aPaperSize.height;
+ // move CTM to reflect imageable area
+ dX += aImageRect.origin.x;
+ dY -= aPaperSize.height - aImageRect.size.height - aImageRect.origin.y;
+ CGContextTranslateCTM( i_rContext, dX + mnStartPageOffsetX, dY - mnStartPageOffsetY );
+ // scale to be top/down and reflect our "virtual" DPI
+ CGContextScaleCTM( i_rContext, 72.0/double(nDPIX), -(72.0/double(nDPIY)) );
+ }
+ else
+ {
+ // move CTM to reflect imageable area
+ double dX = aImageRect.origin.x, dY = aPaperSize.height - aImageRect.size.height - aImageRect.origin.y;
+ CGContextTranslateCTM( i_rContext, -dX, -dY );
+ // turn by 90 degree
+ CGContextRotateCTM( i_rContext, M_PI/2 );
+ // move turned CTM back into paper
+ dX = aPaperSize.height;
+ dY = -aPaperSize.width;
+ CGContextTranslateCTM( i_rContext, dX + mnStartPageOffsetY, dY - mnStartPageOffsetX );
+ // scale to be top/down and reflect our "virtual" DPI
+ CGContextScaleCTM( i_rContext, -(72.0/double(nDPIY)), (72.0/double(nDPIX)) );
+ }
+ mpGraphics->SetPrinterGraphics( i_rContext, nDPIX, nDPIY );
+ }
+ else
+ OSL_FAIL( "no print info in SetupPrinterGraphics" );
+ }
+}
+
+SalGraphics* AquaSalInfoPrinter::AcquireGraphics()
+{
+ SalGraphics* pGraphics = mbGraphics ? nullptr : mpGraphics;
+ mbGraphics = true;
+ return pGraphics;
+}
+
+void AquaSalInfoPrinter::ReleaseGraphics( SalGraphics* )
+{
+ mbGraphics = false;
+}
+
+bool AquaSalInfoPrinter::Setup( weld::Window*, ImplJobSetup* )
+{
+ return false;
+}
+
+bool AquaSalInfoPrinter::SetPrinterData( ImplJobSetup* io_pSetupData )
+{
+ // FIXME: implement driver data
+ if( io_pSetupData && io_pSetupData->GetDriverData() )
+ return SetData( JobSetFlags::ALL, io_pSetupData );
+
+ bool bSuccess = true;
+
+ // set system type
+ io_pSetupData->SetSystem( JOBSETUP_SYSTEM_MAC );
+
+ // get paper format
+ if( mpPrintInfo )
+ {
+ NSSize aPaperSize = [mpPrintInfo paperSize];
+ double width = aPaperSize.width, height = aPaperSize.height;
+ // set paper
+ PaperInfo aInfo( PtTo10Mu( width ), PtTo10Mu( height ) );
+ aInfo.doSloppyFit();
+ io_pSetupData->SetPaperFormat( aInfo.getPaper() );
+ if( io_pSetupData->GetPaperFormat() == PAPER_USER )
+ {
+ io_pSetupData->SetPaperWidth( PtTo10Mu( width ) );
+ io_pSetupData->SetPaperHeight( PtTo10Mu( height ) );
+ }
+ else
+ {
+ io_pSetupData->SetPaperWidth( 0 );
+ io_pSetupData->SetPaperHeight( 0 );
+ }
+
+ // set orientation
+ io_pSetupData->SetOrientation( mePageOrientation );
+
+ io_pSetupData->SetPaperBin( 0 );
+ io_pSetupData->SetDriverData( std::make_unique<sal_uInt8[]>(4), 4 );
+ }
+ else
+ bSuccess = false;
+
+ return bSuccess;
+}
+
+void AquaSalInfoPrinter::setPaperSize( tools::Long i_nWidth, tools::Long i_nHeight, Orientation i_eSetOrientation )
+{
+
+ Orientation ePaperOrientation = Orientation::Portrait;
+ const PaperInfo* pPaper = matchPaper( i_nWidth, i_nHeight, ePaperOrientation );
+
+ if( pPaper )
+ {
+ NSString* pPaperName = [CreateNSString( OStringToOUString(PaperInfo::toPSName(pPaper->getPaper()), RTL_TEXTENCODING_ASCII_US) ) autorelease];
+ [mpPrintInfo setPaperName: pPaperName];
+ }
+ else if( i_nWidth > 0 && i_nHeight > 0 )
+ {
+ NSSize aPaperSize = { static_cast<CGFloat>(TenMuToPt(i_nWidth)), static_cast<CGFloat>(TenMuToPt(i_nHeight)) };
+ [mpPrintInfo setPaperSize: aPaperSize];
+ }
+ // this seems counterintuitive
+ mePageOrientation = i_eSetOrientation;
+}
+
+bool AquaSalInfoPrinter::SetData( JobSetFlags i_nFlags, ImplJobSetup* io_pSetupData )
+{
+ if( ! io_pSetupData || io_pSetupData->GetSystem() != JOBSETUP_SYSTEM_MAC )
+ return false;
+
+ if( mpPrintInfo )
+ {
+ if( i_nFlags & JobSetFlags::ORIENTATION )
+ mePageOrientation = io_pSetupData->GetOrientation();
+
+ if( i_nFlags & JobSetFlags::PAPERSIZE )
+ {
+ // set paper format
+ tools::Long width = 21000, height = 29700;
+ if( io_pSetupData->GetPaperFormat() == PAPER_USER )
+ {
+ // #i101108# sanity check
+ if( io_pSetupData->GetPaperWidth() && io_pSetupData->GetPaperHeight() )
+ {
+ width = io_pSetupData->GetPaperWidth();
+ height = io_pSetupData->GetPaperHeight();
+ }
+ }
+ else
+ {
+ PaperInfo aInfo( io_pSetupData->GetPaperFormat() );
+ width = aInfo.getWidth();
+ height = aInfo.getHeight();
+ }
+
+ setPaperSize( width, height, mePageOrientation );
+ }
+ }
+
+ return mpPrintInfo != nil;
+}
+
+sal_uInt16 AquaSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* )
+{
+ return 0;
+}
+
+OUString AquaSalInfoPrinter::GetPaperBinName( const ImplJobSetup*, sal_uInt16 )
+{
+ return OUString();
+}
+
+sal_uInt32 AquaSalInfoPrinter::GetCapabilities( const ImplJobSetup*, PrinterCapType i_nType )
+{
+ switch( i_nType )
+ {
+ case PrinterCapType::SupportDialog:
+ return 0;
+ case PrinterCapType::Copies:
+ return 0xffff;
+ case PrinterCapType::CollateCopies:
+ return 0xffff;
+ case PrinterCapType::SetOrientation:
+ return 1;
+ case PrinterCapType::SetPaperSize:
+ return 1;
+ case PrinterCapType::SetPaper:
+ return 1;
+ case PrinterCapType::ExternalDialog:
+ return officecfg::Office::Common::Misc::UseSystemPrintDialog::get()
+ ? 1 : 0;
+ case PrinterCapType::PDF:
+ return 1;
+ case PrinterCapType::UsePullModel:
+ return 1;
+ default: break;
+ }
+ return 0;
+}
+
+void AquaSalInfoPrinter::GetPageInfo( const ImplJobSetup*,
+ tools::Long& o_rOutWidth, tools::Long& o_rOutHeight,
+ Point& rPageOffset,
+ Size& rPaperSize )
+{
+ if( mpPrintInfo )
+ {
+ sal_Int32 nDPIX = 72, nDPIY = 72;
+ mpGraphics->GetResolution( nDPIX, nDPIY );
+ const double fXScaling = static_cast<double>(nDPIX)/72.0,
+ fYScaling = static_cast<double>(nDPIY)/72.0;
+
+ NSSize aPaperSize = [mpPrintInfo paperSize];
+ rPaperSize.setWidth( static_cast<tools::Long>( double(aPaperSize.width) * fXScaling ) );
+ rPaperSize.setHeight( static_cast<tools::Long>( double(aPaperSize.height) * fYScaling ) );
+
+ NSRect aImageRect = [mpPrintInfo imageablePageBounds];
+ rPageOffset.setX( static_cast<tools::Long>( aImageRect.origin.x * fXScaling ) );
+ rPageOffset.setY( static_cast<tools::Long>( (aPaperSize.height - aImageRect.size.height - aImageRect.origin.y) * fYScaling ) );
+ o_rOutWidth = static_cast<tools::Long>( aImageRect.size.width * fXScaling );
+ o_rOutHeight = static_cast<tools::Long>( aImageRect.size.height * fYScaling );
+
+ if( mePageOrientation == Orientation::Landscape )
+ {
+ std::swap( o_rOutWidth, o_rOutHeight );
+ // swap width and height
+ tools::Long n = rPaperSize.Width();
+ rPaperSize.setWidth(rPaperSize.Height());
+ rPaperSize.setHeight(n);
+ // swap offset x and y
+ n = rPageOffset.X();
+ rPageOffset.setX(rPageOffset.Y());
+ rPageOffset.setY(n);
+ }
+ }
+}
+
+static Size getPageSize( vcl::PrinterController const & i_rController, sal_Int32 i_nPage )
+{
+ Size aPageSize;
+ uno::Sequence< PropertyValue > const aPageParms( i_rController.getPageParameters( i_nPage ) );
+ for( const PropertyValue & pv : aPageParms )
+ {
+ if ( pv.Name == "PageSize" )
+ {
+ awt::Size aSize;
+ pv.Value >>= aSize;
+ aPageSize.setWidth( aSize.Width );
+ aPageSize.setHeight( aSize.Height );
+ break;
+ }
+ }
+ return aPageSize;
+}
+
+bool AquaSalInfoPrinter::StartJob( const OUString* i_pFileName,
+ const OUString& i_rJobName,
+ ImplJobSetup* i_pSetupData,
+ vcl::PrinterController& i_rController
+ )
+{
+ if( mbJob )
+ return false;
+
+ bool bSuccess = false;
+ bool bWasAborted = false;
+ AquaSalInstance* pInst = GetSalData()->mpInstance;
+ PrintAccessoryViewState aAccViewState;
+ sal_Int32 nAllPages = 0;
+
+ // reset IsLastPage
+ i_rController.setLastPage( false );
+
+ // update job data
+ if( i_pSetupData )
+ SetData( JobSetFlags::ALL, i_pSetupData );
+
+ // do we want a progress panel ?
+ bool bShowProgressPanel = true;
+ beans::PropertyValue* pMonitor = i_rController.getValue( OUString( "MonitorVisible" ) );
+ if( pMonitor )
+ pMonitor->Value >>= bShowProgressPanel;
+ if( ! i_rController.isShowDialogs() )
+ bShowProgressPanel = false;
+
+ // possibly create one job for collated output
+ bool bSinglePrintJobs = i_rController.getPrinter()->IsSinglePrintJobs();
+
+ // FIXME: jobStarted() should be done after the print dialog has ended (if there is one)
+ // how do I know when that might be ?
+ i_rController.jobStarted();
+
+ int nCopies = i_rController.getPrinter()->GetCopyCount();
+ int nJobs = 1;
+ if( bSinglePrintJobs )
+ {
+ nJobs = nCopies;
+ nCopies = 1;
+ }
+
+ for( int nCurJob = 0; nCurJob < nJobs; nCurJob++ )
+ {
+ aAccViewState.bNeedRestart = true;
+ do
+ {
+ if( aAccViewState.bNeedRestart )
+ {
+ mnCurPageRangeStart = 0;
+ mnCurPageRangeCount = 0;
+ nAllPages = i_rController.getFilteredPageCount();
+ }
+
+ aAccViewState.bNeedRestart = false;
+
+ Size aCurSize( 21000, 29700 );
+ if( nAllPages > 0 )
+ {
+ mnCurPageRangeCount = 1;
+ aCurSize = getPageSize( i_rController, mnCurPageRangeStart );
+ Size aNextSize( aCurSize );
+
+ // print pages up to a different size
+ while( mnCurPageRangeCount + mnCurPageRangeStart < nAllPages )
+ {
+ aNextSize = getPageSize( i_rController, mnCurPageRangeStart + mnCurPageRangeCount );
+ if( aCurSize == aNextSize // same page size
+ ||
+ (aCurSize.Width() == aNextSize.Height() && aCurSize.Height() == aNextSize.Width()) // same size, but different orientation
+ )
+ {
+ mnCurPageRangeCount++;
+ }
+ else
+ break;
+ }
+ }
+ else
+ mnCurPageRangeCount = 0;
+
+ // now for the current run
+ mnStartPageOffsetX = mnStartPageOffsetY = 0;
+ // setup the paper size and orientation
+ // do this on our associated Printer object, since that is
+ // out interface to the applications which occasionally rely on the paper
+ // information (e.g. brochure printing scales to the found paper size)
+ // also SetPaperSizeUser has the advantage that we can share a
+ // platform independent paper matching algorithm
+ VclPtr<Printer> pPrinter( i_rController.getPrinter() );
+ pPrinter->SetMapMode( MapMode( MapUnit::Map100thMM ) );
+ pPrinter->SetPaperSizeUser( aCurSize );
+
+ // create view
+ NSView* pPrintView = [[AquaPrintView alloc] initWithController: &i_rController withInfoPrinter: this];
+
+ NSMutableDictionary* pPrintDict = [mpPrintInfo dictionary];
+
+ // set filename
+ if( i_pFileName )
+ {
+ [mpPrintInfo setJobDisposition: NSPrintSaveJob];
+ NSString* pPath = CreateNSString( *i_pFileName );
+ [pPrintDict setObject:[NSURL fileURLWithPath:pPath] forKey:NSPrintJobSavingURL];
+ [pPath release];
+ }
+
+ [pPrintDict setObject: [[NSNumber numberWithInt: nCopies] autorelease] forKey: NSPrintCopies];
+ if( nCopies > 1 )
+ [pPrintDict setObject: [[NSNumber numberWithBool: pPrinter->IsCollateCopy()] autorelease] forKey: NSPrintMustCollate];
+ [pPrintDict setObject: [[NSNumber numberWithBool: YES] autorelease] forKey: NSPrintDetailedErrorReporting];
+ [pPrintDict setObject: [[NSNumber numberWithInt: 1] autorelease] forKey: NSPrintFirstPage];
+ // #i103253# weird: for some reason, autoreleasing the value below like the others above
+ // leads do a double free malloc error. Why this value should behave differently from all the others
+ // is a mystery.
+ [pPrintDict setObject: [NSNumber numberWithInt: mnCurPageRangeCount] forKey: NSPrintLastPage];
+
+ // create print operation
+ NSPrintOperation* pPrintOperation = [NSPrintOperation printOperationWithView: pPrintView printInfo: mpPrintInfo];
+
+ if( pPrintOperation )
+ {
+ NSObject* pReleaseAfterUse = nil;
+ bool bShowPanel = !i_rController.isDirectPrint()
+ && (officecfg::Office::Common::Misc::UseSystemPrintDialog::
+ get())
+ && i_rController.isShowDialogs();
+ [pPrintOperation setShowsPrintPanel: bShowPanel ? YES : NO ];
+ [pPrintOperation setShowsProgressPanel: bShowProgressPanel ? YES : NO];
+
+ // set job title (since MacOSX 10.5)
+ if( [pPrintOperation respondsToSelector: @selector(setJobTitle:)] )
+ [pPrintOperation performSelector: @selector(setJobTitle:) withObject: [CreateNSString( i_rJobName ) autorelease]];
+
+ if( bShowPanel && mnCurPageRangeStart == 0 && nCurJob == 0) // only the first range of pages (in the first job) gets the accessory view
+ pReleaseAfterUse = [AquaPrintAccessoryView setupPrinterPanel: pPrintOperation withController: &i_rController withState: &aAccViewState];
+
+ bSuccess = true;
+ mbJob = true;
+ pInst->startedPrintJob();
+ bool wasSuccessful = [pPrintOperation runOperation];
+ pInst->endedPrintJob();
+ bSuccess = wasSuccessful;
+ bWasAborted = [[[pPrintOperation printInfo] jobDisposition] compare: NSPrintCancelJob] == NSOrderedSame;
+ mbJob = false;
+ if( pReleaseAfterUse )
+ [pReleaseAfterUse release];
+ }
+
+ mnCurPageRangeStart += mnCurPageRangeCount;
+ mnCurPageRangeCount = 1;
+ } while( aAccViewState.bNeedRestart || mnCurPageRangeStart + mnCurPageRangeCount < nAllPages );
+ }
+
+ // inform application that it can release its data
+ // this is awkward, but the XRenderable interface has no method for this,
+ // so we need to call XRenderable::render one last time with IsLastPage = true
+ i_rController.setLastPage( true );
+ GDIMetaFile aPageFile;
+ if( mrContext )
+ SetupPrinterGraphics( mrContext );
+ i_rController.getFilteredPageFile( 0, aPageFile );
+
+ i_rController.setJobState( bWasAborted
+ ? view::PrintableState_JOB_ABORTED
+ : view::PrintableState_JOB_SPOOLED );
+
+ mnCurPageRangeStart = mnCurPageRangeCount = 0;
+
+ return bSuccess;
+}
+
+bool AquaSalInfoPrinter::EndJob()
+{
+ mnStartPageOffsetX = mnStartPageOffsetY = 0;
+ mbJob = false;
+ return true;
+}
+
+bool AquaSalInfoPrinter::AbortJob()
+{
+ mbJob = false;
+
+ // FIXME: implementation
+ return false;
+}
+
+SalGraphics* AquaSalInfoPrinter::StartPage( ImplJobSetup* i_pSetupData, bool i_bNewJobData )
+{
+ if( i_bNewJobData && i_pSetupData )
+ SetPrinterData( i_pSetupData );
+
+ CGContextRef rContext = [[NSGraphicsContext currentContext] CGContext];
+
+ SetupPrinterGraphics( rContext );
+
+ return mpGraphics;
+}
+
+bool AquaSalInfoPrinter::EndPage()
+{
+ mpGraphics->InvalidateContext();
+ return true;
+}
+
+AquaSalPrinter::AquaSalPrinter( AquaSalInfoPrinter* i_pInfoPrinter ) :
+ mpInfoPrinter( i_pInfoPrinter )
+{
+}
+
+AquaSalPrinter::~AquaSalPrinter()
+{
+}
+
+bool AquaSalPrinter::StartJob( const OUString* i_pFileName,
+ const OUString& i_rJobName,
+ const OUString&,
+ ImplJobSetup* i_pSetupData,
+ vcl::PrinterController& i_rController )
+{
+ return mpInfoPrinter->StartJob( i_pFileName, i_rJobName, i_pSetupData, i_rController );
+}
+
+bool AquaSalPrinter::StartJob( const OUString* /*i_pFileName*/,
+ const OUString& /*i_rJobName*/,
+ const OUString& /*i_rAppName*/,
+ sal_uInt32 /*i_nCopies*/,
+ bool /*i_bCollate*/,
+ bool /*i_bDirect*/,
+ ImplJobSetup* )
+{
+ OSL_FAIL( "should never be called" );
+ return false;
+}
+
+bool AquaSalPrinter::EndJob()
+{
+ return mpInfoPrinter->EndJob();
+}
+
+SalGraphics* AquaSalPrinter::StartPage( ImplJobSetup* i_pSetupData, bool i_bNewJobData )
+{
+ return mpInfoPrinter->StartPage( i_pSetupData, i_bNewJobData );
+}
+
+void AquaSalPrinter::EndPage()
+{
+ mpInfoPrinter->EndPage();
+}
+
+void AquaSalInfoPrinter::InitPaperFormats( const ImplJobSetup* )
+{
+ m_aPaperFormats.clear();
+ m_bPapersInit = true;
+
+ if( mpPrinter )
+ {
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH
+ //TODO: 10.9 statusForTable:, stringListForKey:inTable:
+ if( [mpPrinter statusForTable: @"PPD"] == NSPrinterTableOK )
+ {
+ NSArray* pPaperNames = [mpPrinter stringListForKey: @"PageSize" inTable: @"PPD"];
+ if( pPaperNames )
+ {
+ unsigned int nPapers = [pPaperNames count];
+ for( unsigned int i = 0; i < nPapers; i++ )
+ {
+ NSString* pPaper = [pPaperNames objectAtIndex: i];
+ // first try to match the name
+ OString aPaperName( [pPaper UTF8String] );
+ Paper ePaper = PaperInfo::fromPSName( aPaperName );
+ if( ePaper != PAPER_USER )
+ {
+ m_aPaperFormats.push_back( PaperInfo( ePaper ) );
+ }
+ else
+ {
+ NSSize aPaperSize = [mpPrinter pageSizeForPaper: pPaper];
+ if( aPaperSize.width > 0 && aPaperSize.height > 0 )
+ {
+ PaperInfo aInfo( PtTo10Mu( aPaperSize.width ),
+ PtTo10Mu( aPaperSize.height ) );
+ if( aInfo.getPaper() == PAPER_USER )
+ aInfo.doSloppyFit();
+ m_aPaperFormats.push_back( aInfo );
+ }
+ }
+ }
+ }
+ }
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+ }
+}
+
+const PaperInfo* AquaSalInfoPrinter::matchPaper( tools::Long i_nWidth, tools::Long i_nHeight, Orientation& o_rOrientation ) const
+{
+ if( ! m_bPapersInit )
+ const_cast<AquaSalInfoPrinter*>(this)->InitPaperFormats( nullptr );
+
+ const PaperInfo* pMatch = nullptr;
+ o_rOrientation = Orientation::Portrait;
+ for( int n = 0; n < 2 ; n++ )
+ {
+ for( size_t i = 0; i < m_aPaperFormats.size(); i++ )
+ {
+ if( std::abs( m_aPaperFormats[i].getWidth() - i_nWidth ) < 50 &&
+ std::abs( m_aPaperFormats[i].getHeight() - i_nHeight ) < 50 )
+ {
+ pMatch = &m_aPaperFormats[i];
+ return pMatch;
+ }
+ }
+ o_rOrientation = Orientation::Landscape;
+ std::swap( i_nWidth, i_nHeight );
+ }
+ return pMatch;
+}
+
+int AquaSalInfoPrinter::GetLandscapeAngle( const ImplJobSetup* )
+{
+ return 900;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/salsys.cxx b/vcl/osx/salsys.cxx
new file mode 100644
index 0000000000..37ad48e7dc
--- /dev/null
+++ b/vcl/osx/salsys.cxx
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <rtl/ustrbuf.hxx>
+#include <tools/long.hxx>
+#include <vcl/stdtext.hxx>
+
+#include <osx/salsys.h>
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+#include <quartz/utils.h>
+
+#include <strings.hrc>
+
+AquaSalSystem::~AquaSalSystem()
+{
+}
+
+unsigned int AquaSalSystem::GetDisplayScreenCount()
+{
+ NSArray* pScreens = [NSScreen screens];
+ return pScreens ? [pScreens count] : 1;
+}
+
+AbsoluteScreenPixelRectangle AquaSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen )
+{
+ if (Application::IsBitmapRendering())
+ {
+ AbsoluteScreenPixelRectangle aRect;
+ if (nScreen == 0)
+ aRect = AbsoluteScreenPixelRectangle(AbsoluteScreenPixelPoint(0,0), AbsoluteScreenPixelSize(1024, 768));
+ return aRect;
+ }
+
+ NSArray* pScreens = [NSScreen screens];
+ AbsoluteScreenPixelRectangle aRet;
+ NSScreen* pScreen = nil;
+ if( pScreens && nScreen < [pScreens count] )
+ pScreen = [pScreens objectAtIndex: nScreen];
+ else
+ pScreen = [NSScreen mainScreen];
+
+ if( pScreen )
+ {
+ NSRect aFrame = [pScreen frame];
+ aRet = AbsoluteScreenPixelRectangle(
+ AbsoluteScreenPixelPoint( static_cast<tools::Long>(aFrame.origin.x), static_cast<tools::Long>(aFrame.origin.y) ),
+ AbsoluteScreenPixelSize( static_cast<tools::Long>(aFrame.size.width), static_cast<tools::Long>(aFrame.size.height) ) );
+ }
+ return aRet;
+}
+
+static NSString* getStandardString( StandardButtonType nButtonId, bool bUseResources )
+{
+ OUString aText;
+ if( bUseResources )
+ {
+ aText = GetStandardText( nButtonId );
+ }
+ if( aText.isEmpty() ) // this is for bad cases, we might be missing the vcl resource
+ {
+ switch( nButtonId )
+ {
+ case StandardButtonType::OK: aText = "OK";break;
+ case StandardButtonType::Abort: aText = "Abort";break;
+ case StandardButtonType::Cancel: aText = "Cancel";break;
+ case StandardButtonType::Retry: aText = "Retry";break;
+ case StandardButtonType::Yes: aText = "Yes";break;
+ case StandardButtonType::No: aText = "No";break;
+ default: break;
+ }
+ }
+ return aText.isEmpty() ? nil : CreateNSString( aText);
+}
+
+int AquaSalSystem::ShowNativeMessageBox( const OUString& rTitle,
+ const OUString& rMessage )
+{
+ NSString* pTitle = CreateNSString( rTitle );
+ NSString* pMessage = CreateNSString( rMessage );
+
+ NSString* pDefText = getStandardString( StandardButtonType::OK, false/*bUseResources*/ );
+
+ SAL_WNODEPRECATED_DECLARATIONS_PUSH //TODO: 10.10 NSRunAlertPanel
+ int nResult = NSRunAlertPanel( pTitle, @"%@", pDefText, nil, nil, pMessage );
+ SAL_WNODEPRECATED_DECLARATIONS_POP
+
+ if( pTitle )
+ [pTitle release];
+ if( pMessage )
+ [pMessage release];
+ if( pDefText )
+ [pDefText release];
+
+ int nRet = 0;
+ if( nResult == 1 )
+ nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK;
+
+ return nRet;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/saltimer.cxx b/vcl/osx/saltimer.cxx
new file mode 100644
index 0000000000..8af7de2176
--- /dev/null
+++ b/vcl/osx/saltimer.cxx
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <rtl/math.hxx>
+#include <tools/time.hxx>
+
+#include <osx/saltimer.h>
+#include <osx/salnstimer.h>
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#include <osx/salinst.h>
+
+
+void ImplNSAppPostEvent( short nEventId, BOOL bAtStart, int nUserData )
+{
+ ReleasePoolHolder aPool;
+ NSEvent* pEvent = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: [[NSProcessInfo processInfo] systemUptime]
+ windowNumber: 0
+ context: nil
+ subtype: nEventId
+ data1: nUserData
+ data2: 0];
+ assert( pEvent );
+ if ( nil == pEvent )
+ return;
+ if ( NO == bAtStart )
+ {
+ // nextEventMatchingMask has to run in the main thread!
+ assert([NSThread isMainThread]);
+
+ // Posting an event to the end of an empty queue fails,
+ // so we peek the queue and post to the start, if empty.
+ // Some Qt bugs even indicate nextEvent without dequeue
+ // sometimes blocks, so we dequeue and re-add the event.
+ NSEvent* pPeekEvent = [NSApp nextEventMatchingMask: NSEventMaskAny
+ untilDate: nil
+ inMode: NSDefaultRunLoopMode
+ dequeue: YES];
+ if ( nil == pPeekEvent )
+ bAtStart = YES;
+ else
+ [NSApp postEvent: pPeekEvent atStart: YES];
+ }
+ [NSApp postEvent: pEvent atStart: bAtStart];
+}
+
+void AquaSalTimer::queueDispatchTimerEvent( bool bAtStart )
+{
+ Stop();
+ m_bDirectTimeout = true;
+ ImplNSAppPostEvent( AquaSalInstance::DispatchTimerEvent,
+ bAtStart, GetNextEventVersion() );
+}
+
+void AquaSalTimer::Start( sal_uInt64 nMS )
+{
+ SalData* pSalData = GetSalData();
+
+ if( !pSalData->mpInstance->IsMainThread() )
+ {
+ ImplNSAppPostEvent( AquaSalInstance::AppStartTimerEvent, YES, nMS );
+ return;
+ }
+
+ m_bDirectTimeout = (0 == nMS) && !ImplGetSVData()->mpWinData->mbIsLiveResize;
+ if ( m_bDirectTimeout )
+ Stop();
+ else
+ {
+ NSTimeInterval aTI = double(nMS) / 1000.0;
+ if( m_pRunningTimer != nil )
+ {
+ if ([m_pRunningTimer isValid] && rtl::math::approxEqual(
+ [m_pRunningTimer timeInterval], aTI))
+ {
+ // set new fire date
+ [m_pRunningTimer setFireDate: [NSDate dateWithTimeIntervalSinceNow: aTI]];
+ }
+ else
+ Stop();
+ }
+ else
+ Stop();
+ if( m_pRunningTimer == nil )
+ {
+ m_pRunningTimer = [[NSTimer scheduledTimerWithTimeInterval: aTI
+ target: [[[TimerCallbackCaller alloc] init] autorelease]
+ selector: @selector(timerElapsed:)
+ userInfo: nil
+ repeats: NO
+ ] retain];
+ /* #i84055# add timer to tracking run loop mode,
+ so they also elapse while e.g. life resize
+ */
+ [[NSRunLoop currentRunLoop] addTimer: m_pRunningTimer forMode: NSEventTrackingRunLoopMode];
+ }
+ }
+}
+
+void AquaSalTimer::Stop()
+{
+ assert( GetSalData()->mpInstance->IsMainThread() );
+
+ if( m_pRunningTimer != nil )
+ {
+ [m_pRunningTimer invalidate];
+ [m_pRunningTimer release];
+ m_pRunningTimer = nil;
+ }
+ InvalidateEvent();
+}
+
+void AquaSalTimer::callTimerCallback()
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ SolarMutexGuard aGuard;
+ m_bDirectTimeout = false;
+ if( pSVData->maSchedCtx.mpSalTimer )
+ pSVData->maSchedCtx.mpSalTimer->CallCallback();
+}
+
+void AquaSalTimer::handleTimerElapsed()
+{
+ if ( m_bDirectTimeout || ImplGetSVData()->mpWinData->mbIsLiveResize )
+ {
+ // Stop the timer, as it is just invalidated after the firing function
+ Stop();
+ callTimerCallback();
+ }
+ else
+ queueDispatchTimerEvent( true );
+}
+
+bool AquaSalTimer::handleDispatchTimerEvent( NSEvent *pEvent )
+{
+ bool bIsValidEvent = IsValidEventVersion( [pEvent data1] );
+ if ( bIsValidEvent )
+ callTimerCallback();
+ return bIsValidEvent;
+}
+
+void AquaSalTimer::handleStartTimerEvent( NSEvent* pEvent )
+{
+ ImplSVData* pSVData = ImplGetSVData();
+ if( pSVData->maSchedCtx.mpSalTimer )
+ {
+ NSTimeInterval posted = [pEvent timestamp] + NSTimeInterval([pEvent data1])/1000.0;
+ NSTimeInterval current = [NSDate timeIntervalSinceReferenceDate];
+ sal_uLong nTimeoutMS = 0;
+ if( (posted - current) > 0.0 )
+ nTimeoutMS = ceil( (posted - current) * 1000 );
+ Start( nTimeoutMS );
+ }
+}
+
+bool AquaSalTimer::IsTimerElapsed() const
+{
+ assert( !((ExistsValidEvent() || m_bDirectTimeout) && m_pRunningTimer) );
+ if ( ExistsValidEvent() || m_bDirectTimeout )
+ return true;
+ if ( !m_pRunningTimer )
+ return false;
+ NSDate* pDt = [m_pRunningTimer fireDate];
+ return pDt && ([pDt timeIntervalSinceNow] < 0);
+}
+
+AquaSalTimer::AquaSalTimer( )
+ : m_pRunningTimer( nil )
+{
+}
+
+AquaSalTimer::~AquaSalTimer()
+{
+ Stop();
+}
+
+void AquaSalTimer::handleWindowShouldClose()
+{
+ // for whatever reason events get filtered on close, presumably by
+ // timestamp so post a new timeout event, if there was one queued...
+ if ( ExistsValidEvent() && !m_pRunningTimer )
+ queueDispatchTimerEvent( false );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/service_entry.cxx b/vcl/osx/service_entry.cxx
new file mode 100644
index 0000000000..849e73a77d
--- /dev/null
+++ b/vcl/osx/service_entry.cxx
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+
+#include <vcl/svapp.hxx>
+#include <dndhelper.hxx>
+#include <vcl/sysdata.hxx>
+
+#include <osx/saldata.hxx>
+#include <osx/salinst.h>
+
+#include "DragSource.hxx"
+#include "DropTarget.hxx"
+#include "clipboard.hxx"
+
+using namespace ::osl;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::cppu;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::datatransfer::clipboard;
+
+uno::Reference< XInterface > AquaSalInstance::CreateClipboard( const Sequence< Any >& i_rArguments )
+{
+ if ( Application::IsHeadlessModeEnabled() || IsRunningUnitTest() )
+ return SalInstance::CreateClipboard( i_rArguments );
+
+ SalData* pSalData = GetSalData();
+ if( ! pSalData->mxClipboard.is() )
+ pSalData->mxClipboard.set(static_cast< XClipboard* >(new AquaClipboard(nullptr, true)), UNO_QUERY);
+ return pSalData->mxClipboard;
+}
+
+uno::Reference<XInterface> AquaSalInstance::ImplCreateDragSource(const SystemEnvData* pSysEnv)
+{
+ return vcl::OleDnDHelper(new DragSource(), reinterpret_cast<sal_IntPtr>(pSysEnv->mpNSView),
+ vcl::DragOrDrop::Drag);
+}
+
+uno::Reference<XInterface> AquaSalInstance::ImplCreateDropTarget(const SystemEnvData* pSysEnv)
+{
+ return vcl::OleDnDHelper(new DropTarget(), reinterpret_cast<sal_IntPtr>(pSysEnv->mpNSView),
+ vcl::DragOrDrop::Drop);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/osx/vclnsapp.mm b/vcl/osx/vclnsapp.mm
new file mode 100644
index 0000000000..5daf923ce1
--- /dev/null
+++ b/vcl/osx/vclnsapp.mm
@@ -0,0 +1,481 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+#include <config_features.h>
+
+#include <vector>
+
+#include <stdlib.h>
+
+#include <sal/main.h>
+#include <vcl/commandevent.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+
+#include <osx/saldata.hxx>
+#include <osx/salframe.h>
+#include <osx/salframeview.h>
+#include <osx/salinst.h>
+#include <osx/vclnsapp.h>
+#include <quartz/utils.h>
+
+#include <premac.h>
+#include <objc/objc-runtime.h>
+#import "Carbon/Carbon.h"
+#import "apple_remote/RemoteControl.h"
+#include <postmac.h>
+
+
+@implementation CocoaThreadEnabler
+-(void)enableCocoaThreads:(id)param
+{
+ // do nothing, this is just to start an NSThread and therefore put
+ // Cocoa into multithread mode
+ (void)param;
+}
+@end
+
+// If you wonder how this VCL_NSApplication stuff works, one thing you
+// might have missed is that the NSPrincipalClass property in
+// desktop/macosx/Info.plist has the value VCL_NSApplication.
+
+@implementation VCL_NSApplication
+
+-(void)applicationDidFinishLaunching:(NSNotification*)pNotification
+{
+ (void)pNotification;
+
+ NSEvent* pEvent = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
+ location: NSZeroPoint
+ modifierFlags: 0
+ timestamp: [[NSProcessInfo processInfo] systemUptime]
+ windowNumber: 0
+ context: nil
+ subtype: AquaSalInstance::AppExecuteSVMain
+ data1: 0
+ data2: 0 ];
+ assert( pEvent );
+ [NSApp postEvent: pEvent atStart: NO];
+
+ if( [NSWindow respondsToSelector:@selector(allowsAutomaticWindowTabbing)] )
+ {
+ [NSWindow setAllowsAutomaticWindowTabbing:NO];
+ }
+
+ // listen to dark mode change
+ [NSApp addObserver:self forKeyPath:@"effectiveAppearance" options: 0 context: nil];
+}
+
+-(void)sendEvent:(NSEvent*)pEvent
+{
+ NSEventType eType = [pEvent type];
+ if( eType == NSEventTypeApplicationDefined )
+ {
+ AquaSalInstance::handleAppDefinedEvent( pEvent );
+ }
+ else if( eType == NSEventTypeKeyDown && ([pEvent modifierFlags] & NSEventModifierFlagCommand) != 0 )
+ {
+ NSWindow* pKeyWin = [NSApp keyWindow];
+ if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] )
+ {
+ // Commit uncommitted text before dispatching key shortcuts. In
+ // certain cases such as pressing Command-Option-C in a Writer
+ // document while there is uncommitted text will call
+ // AquaSalFrame::EndExtTextInput() which will dispatch a
+ // SalEvent::EndExtTextInput event. Writer's handler for that event
+ // will delete the uncommitted text and then insert the committed
+ // text but LibreOffice will crash when deleting the uncommitted
+ // text because deletion of the text also removes and deletes the
+ // newly inserted comment.
+ [static_cast<SalFrameWindow*>(pKeyWin) endExtTextInput];
+
+ AquaSalFrame* pFrame = [static_cast<SalFrameWindow*>(pKeyWin) getSalFrame];
+ unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
+ /*
+ * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows
+ */
+ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] )
+ {
+ if ( nModMask == NSEventModifierFlagCommand && ([pFrame->getNSWindow() styleMask] & NSWindowStyleMaskMiniaturizable) )
+ {
+ [pFrame->getNSWindow() performMiniaturize: nil];
+ return;
+ }
+
+ if ( nModMask == ( NSEventModifierFlagCommand | NSEventModifierFlagOption ) )
+ {
+ [NSApp miniaturizeAll: nil];
+ return;
+ }
+ }
+
+ // get information whether the event was handled; keyDown returns nothing
+ GetSalData()->maKeyEventAnswer[ pEvent ] = false;
+ bool bHandled = false;
+
+ // dispatch to view directly to avoid the key event being consumed by the menubar
+ // popup windows do not get the focus, so they don't get these either
+ // simplest would be dispatch this to the key window always if it is without parent
+ // however e.g. in document we want the menu shortcut if e.g. the stylist has focus
+ if( pFrame->mpParent && !(pFrame->mnStyle & SalFrameStyleFlags::FLOAT) )
+ {
+ [[pKeyWin contentView] keyDown: pEvent];
+ bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
+ }
+
+ // see whether the main menu consumes this event
+ // if not, we want to dispatch it ourselves. Unless we do this "trick"
+ // the main menu just beeps for an unknown or disabled key equivalent
+ // and swallows the event wholesale
+ NSMenu* pMainMenu = [NSApp mainMenu];
+ if( ! bHandled &&
+ (pMainMenu == nullptr || ! [NSMenu menuBarVisible] || ! [pMainMenu performKeyEquivalent: pEvent]) )
+ {
+ [[pKeyWin contentView] keyDown: pEvent];
+ bHandled = GetSalData()->maKeyEventAnswer[ pEvent ];
+ }
+ else
+ {
+ bHandled = true; // event handled already or main menu just handled it
+ }
+ GetSalData()->maKeyEventAnswer.erase( pEvent );
+
+ if( bHandled )
+ return;
+ }
+ else if( pKeyWin )
+ {
+ // #i94601# a window not of vcl's making has the focus.
+ // Since our menus do not invoke the usual commands
+ // try to play nice with native windows like the file dialog
+ // and emulate them
+ // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are
+ // NOT localized, that is the same in all locales. Should this be
+ // different in any locale, this hack will fail.
+ unsigned int nModMask = ([pEvent modifierFlags] & (NSEventModifierFlagShift|NSEventModifierFlagControl|NSEventModifierFlagOption|NSEventModifierFlagCommand));
+ if( nModMask == NSEventModifierFlagCommand )
+ {
+
+ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] )
+ {
+ if( [NSApp sendAction: @selector(paste:) to: nil from: nil] )
+ return;
+ }
+ else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] )
+ {
+ if( [NSApp sendAction: @selector(copy:) to: nil from: nil] )
+ return;
+ }
+ else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] )
+ {
+ if( [NSApp sendAction: @selector(cut:) to: nil from: nil] )
+ return;
+ }
+ else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"a"] )
+ {
+ if( [NSApp sendAction: @selector(selectAll:) to: nil from: nil] )
+ return;
+ }
+ else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"z"] )
+ {
+ if( [NSApp sendAction: @selector(undo:) to: nil from: nil] )
+ return;
+ }
+ }
+ else if( nModMask == (NSEventModifierFlagCommand|NSEventModifierFlagShift) )
+ {
+ if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"Z"] )
+ {
+ if( [NSApp sendAction: @selector(redo:) to: nil from: nil] )
+ return;
+ }
+ }
+ }
+ }
+ [super sendEvent: pEvent];
+}
+
+-(void)sendSuperEvent:(NSEvent*)pEvent
+{
+ [super sendEvent: pEvent];
+}
+
+-(NSMenu*)applicationDockMenu:(NSApplication *)sender
+{
+ (void)sender;
+ return AquaSalInstance::GetDynamicDockMenu();
+}
+
+-(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile
+{
+ (void)app;
+ std::vector<OUString> aFile { GetOUString( pFile ) };
+ if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) )
+ {
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(aFile));
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if( pInst )
+ pInst->TriggerUserEventProcessing();
+ }
+ return YES;
+}
+
+-(void)application: (NSApplication*) app openFiles: (NSArray*)files
+{
+ (void)app;
+ std::vector<OUString> aFileList;
+
+ NSEnumerator* it = [files objectEnumerator];
+ NSString* pFile = nil;
+
+ while( (pFile = [it nextObject]) != nil )
+ {
+ const OUString aFile( GetOUString( pFile ) );
+ if( ! AquaSalInstance::isOnCommandLine( aFile ) )
+ {
+ aFileList.push_back( aFile );
+ }
+ }
+
+ if( !aFileList.empty() )
+ {
+ // we have no back channel here, we have to assume success, in which case
+ // replyToOpenOrPrint does not need to be called according to documentation
+ // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess];
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Open, std::move(aFileList));
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if( pInst )
+ pInst->TriggerUserEventProcessing();
+ }
+}
+
+-(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile
+{
+ (void)app;
+ std::vector<OUString> aFile { GetOUString(pFile) };
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFile));
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if( pInst )
+ pInst->TriggerUserEventProcessing();
+ return YES;
+}
+-(NSApplicationPrintReply)application: (NSApplication *) app printFiles:(NSArray *)files withSettings: (NSDictionary *)printSettings showPrintPanels:(BOOL)bShowPrintPanels
+{
+ (void)app;
+ (void)printSettings;
+ (void)bShowPrintPanels;
+ // currently ignores print settings a bShowPrintPanels
+ std::vector<OUString> aFileList;
+
+ NSEnumerator* it = [files objectEnumerator];
+ NSString* pFile = nil;
+
+ while( (pFile = [it nextObject]) != nil )
+ {
+ aFileList.push_back( GetOUString( pFile ) );
+ }
+ const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::Type::Print, std::move(aFileList));
+ AquaSalInstance::aAppEventList.push_back( pAppEvent );
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ if( pInst )
+ pInst->TriggerUserEventProcessing();
+ // we have no back channel here, we have to assume success
+ // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint]
+ return NSPrintingSuccess;
+}
+
+-(void)applicationWillTerminate: (NSNotification *) aNotification
+{
+ (void)aNotification;
+ sal_detail_deinitialize();
+ _Exit(0);
+}
+
+-(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app
+{
+ (void)app;
+ NSApplicationTerminateReply aReply = NSTerminateNow;
+ {
+ SolarMutexGuard aGuard;
+
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ SalFrame *pAnyFrame = pInst->anyFrame();
+ if( pAnyFrame )
+ {
+ // the following QueryExit will likely present a message box, activate application
+ [NSApp activateIgnoringOtherApps: YES];
+ aReply = pAnyFrame->CallCallback( SalEvent::Shutdown, nullptr ) ? NSTerminateCancel : NSTerminateNow;
+ }
+
+ if( aReply == NSTerminateNow )
+ {
+ ApplicationEvent aEv(ApplicationEvent::Type::PrivateDoShutdown);
+ GetpApp()->AppEvent( aEv );
+ ImageTree::get().shutdown();
+ // DeInitVCL should be called in ImplSVMain - unless someone exits first which
+ // can occur in Desktop::doShutdown for example
+ }
+ }
+
+ return aReply;
+}
+
+-(void)observeValueForKeyPath: (NSString*) keyPath ofObject:(id)object
+ change: (NSDictionary<NSKeyValueChangeKey, id>*)change
+ context: (void*)context
+{
+ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+ if ([keyPath isEqualToString:@"effectiveAppearance"])
+ [self systemColorsChanged: nil];
+}
+
+-(void)systemColorsChanged: (NSNotification*) pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ AquaSalInstance *pInst = GetSalData()->mpInstance;
+ SalFrame *pAnyFrame = pInst->anyFrame();
+ if( pAnyFrame )
+ pAnyFrame->CallCallback( SalEvent::SettingsChanged, nullptr );
+}
+
+-(void)screenParametersChanged: (NSNotification*) pNotification
+{
+ (void)pNotification;
+ SolarMutexGuard aGuard;
+
+ for( auto pSalFrame : GetSalData()->mpInstance->getFrames() )
+ {
+ AquaSalFrame *pFrame = static_cast<AquaSalFrame*>( pSalFrame );
+ pFrame->screenParametersChanged();
+ }
+}
+
+-(void)scrollbarVariantChanged: (NSNotification*) pNotification
+{
+ (void)pNotification;
+ GetSalData()->mpInstance->delayedSettingsChanged( true );
+}
+
+-(void)scrollbarSettingsChanged: (NSNotification*) pNotification
+{
+ (void)pNotification;
+ GetSalData()->mpInstance->delayedSettingsChanged( false );
+}
+
+-(void)addFallbackMenuItem: (NSMenuItem*)pNewItem
+{
+ AquaSalMenu::addFallbackMenuItem( pNewItem );
+}
+
+-(void)removeFallbackMenuItem: (NSMenuItem*)pItem
+{
+ AquaSalMenu::removeFallbackMenuItem( pItem );
+}
+
+-(void)addDockMenuItem: (NSMenuItem*)pNewItem
+{
+ NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu();
+ [pDock insertItem: pNewItem atIndex: [pDock numberOfItems]];
+}
+
+// for Apple Remote implementation
+
+#if !HAVE_FEATURE_MACOSX_SANDBOX
+- (void)applicationWillBecomeActive:(NSNotification *)pNotification
+{
+ (void)pNotification;
+ SalData* pSalData = GetSalData();
+ AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
+ if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
+ {
+ // [remoteControl startListening: self];
+ // does crash because the right thing to do is
+ // [pAppleRemoteCtrl->remoteControl startListening: self];
+ // but the instance variable 'remoteControl' is declared protected
+ // workaround : declare remoteControl instance variable as public in RemoteMainController.m
+
+ [pAppleRemoteCtrl->remoteControl startListening: self];
+#ifdef DEBUG
+ NSLog(@"Apple Remote will become active - Using remote controls");
+#endif
+ }
+ for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
+ it != pSalData->maPresentationFrames.end(); ++it )
+ {
+ NSWindow* pNSWindow = (*it)->getNSWindow();
+ [pNSWindow setLevel: NSPopUpMenuWindowLevel];
+ if( [pNSWindow isVisible] )
+ [pNSWindow orderFront: NSApp];
+ }
+}
+
+- (void)applicationWillResignActive:(NSNotification *)pNotification
+{
+ (void)pNotification;
+ SalData* pSalData = GetSalData();
+ AppleRemoteMainController* pAppleRemoteCtrl = pSalData->mpAppleRemoteMainController;
+ if( pAppleRemoteCtrl && pAppleRemoteCtrl->remoteControl)
+ {
+ // [remoteControl stopListening: self];
+ // does crash because the right thing to do is
+ // [pAppleRemoteCtrl->remoteControl stopListening: self];
+ // but the instance variable 'remoteControl' is declared protected
+ // workaround : declare remoteControl instance variable as public in RemoteMainController.m
+
+ [pAppleRemoteCtrl->remoteControl stopListening: self];
+#ifdef DEBUG
+ NSLog(@"Apple Remote will resign active - Releasing remote controls");
+#endif
+ }
+ for( std::list< AquaSalFrame* >::const_iterator it = pSalData->maPresentationFrames.begin();
+ it != pSalData->maPresentationFrames.end(); ++it )
+ {
+ [(*it)->getNSWindow() setLevel: NSNormalWindowLevel];
+ }
+}
+#endif
+
+- (BOOL)applicationShouldHandleReopen: (NSApplication*)pApp hasVisibleWindows: (BOOL) bWinVisible
+{
+ (void)pApp;
+ (void)bWinVisible;
+ NSObject* pHdl = GetSalData()->mpDockIconClickHandler;
+ if( pHdl && [pHdl respondsToSelector: @selector(dockIconClicked:)] )
+ {
+ [pHdl performSelector:@selector(dockIconClicked:) withObject: self];
+ }
+ return YES;
+}
+
+-(void)setDockIconClickHandler: (NSObject*)pHandler
+{
+ GetSalData()->mpDockIconClickHandler = pHandler;
+}
+
+
+@end
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */