diff options
Diffstat (limited to 'vcl/osx')
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 Binary files differnew file mode 100644 index 0000000000..d39d10119c --- /dev/null +++ b/vcl/osx/res/MainMenu.nib/keyedobjects.nib 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: */ |