diff options
author | Tor Lillqvist <tml@collabora.com> | 2013-12-05 22:01:05 +0200 |
---|---|---|
committer | Tor Lillqvist <tml@collabora.com> | 2013-12-06 15:05:00 +0200 |
commit | c49721950cb3d897b35f08bf871239308680b18e (patch) | |
tree | 6a76e91557328b9195960d065065b1a6914bf9be /vcl/osx | |
parent | 693eced961a3d3014d15e0a406f4e001ee817522 (diff) |
Re-organize OS X and iOS code in vcl a bit
Now with the ATSUI code gone is a good time for some
re-organisation. Get rid of "aqua" in file names and the separate
"coretext" folders. CoreText is all we use now for OS X (and has
always been so for iOS), so no need for a "coretext" folder, we can
keep the CoreText-using code under "quartz". Keep OS X -specific code
in "osx". Ditto for headers.
Keep "Aqua" as part of class names for now, though.
This is also preparation for planned further unification between OS X
and iOS code.
Change-Id: Ic60bd73fea4ab98183e7c8a09c7d3f66b9a34223
Diffstat (limited to 'vcl/osx')
165 files changed, 20418 insertions, 0 deletions
diff --git a/vcl/osx/DataFlavorMapping.cxx b/vcl/osx/DataFlavorMapping.cxx new file mode 100644 index 000000000000..c5a79b368990 --- /dev/null +++ b/vcl/osx/DataFlavorMapping.cxx @@ -0,0 +1,741 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "sal/config.h" + +#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/uno/Sequence.hxx" +#include "comphelper/processfactory.hxx" + +#include <rtl/ustring.hxx> +#include <osl/endian.h> + +#include <stdio.h> +#include <string.h> + +#include <premac.h> +#include <Cocoa/Cocoa.h> +#include <postmac.h> + +using namespace ::com::sun::star::datatransfer; +using namespace ::com::sun::star::uno; +using namespace com::sun::star::lang; +using namespace cppu; +using namespace std; + + +namespace // private +{ + /* 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 == getCppuType((Sequence<sal_Int8>*)0)) || (dtype == getCppuType( (OUString*)0 )))); + } + + OUString NSStringToOUString(NSString* cfString) + { + BOOST_ASSERT(cfString && "Invalid parameter"); + + const char* utf8Str = [cfString UTF8String]; + unsigned int len = rtl_str_getLength(utf8Str); + + return OUString(utf8Str, len, RTL_TEXTENCODING_UTF8); + } + + NSString* OUStringToNSString(const OUString& ustring) + { + OString utf8Str = OUStringToOString(ustring, RTL_TEXTENCODING_UTF8); + return [NSString stringWithCString: utf8Str.getStr() encoding: NSUTF8StringEncoding]; + } + + NSString* PBTYPE_SODX = @"application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\""; + NSString* PBTYPE_SESX = @"application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""; + NSString* PBTYPE_SLSDX = @"application/x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link Source Descriptor (XML)\""; + NSString* PBTYPE_ESX = @"application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""; + NSString* PBTYPE_LSX = @"application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\""; + NSString* PBTYPE_EOX = @"application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\""; + NSString* PBTYPE_SVXB = @"application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\""; + NSString* PBTYPE_GDIMF = @"application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\""; + NSString* PBTYPE_WMF = @"application/x-openoffice-wmf;windows_formatname=\"Image WMF\""; + NSString* PBTYPE_EMF = @"application/x-openoffice-emf;windows_formatname=\"Image EMF\""; + + NSString* PBTYPE_DUMMY_INTERNAL = @"application/x-openoffice-internal"; + + const char* FLAVOR_SODX = "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star Object Descriptor (XML)\""; + const char* FLAVOR_SESX = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""; + const char* FLAVOR_SLSDX = "application/x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link Source Descriptor (XML)\""; + const char* FLAVOR_ESX = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""; + const char* FLAVOR_LSX = "application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\""; + const char* FLAVOR_EOX = "application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\""; + const char* FLAVOR_SVXB = "application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\""; + const char* FLAVOR_GDIMF = "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\""; + const char* FLAVOR_WMF = "application/x-openoffice-wmf;windows_formatname=\"Image WMF\""; + const char* FLAVOR_EMF = "application/x-openoffice-emf;windows_formatname=\"Image EMF\""; + + const char* FLAVOR_DUMMY_INTERNAL = "application/x-openoffice-internal"; + + + struct FlavorMap + { + NSString* SystemFlavor; + const char* OOoFlavor; + const char* HumanPresentableName; + bool DataTypeOUString; // sequence<byte> otherwise + }; + + /* At the moment it appears as if only MS Office pastes "public.html" to the clipboard. + */ + FlavorMap flavorMap[] = + { + { NSStringPboardType, "text/plain;charset=utf-16", "Unicode Text (UTF-16)", true }, + { NSRTFPboardType, "text/richtext", "Rich Text Format", false }, + { NSTIFFPboardType, "image/png", "Portable Network Graphics", false }, + { NSHTMLPboardType, "text/html", "Plain Html", false }, + { NSFilenamesPboardType, "application/x-openoffice-filelist;windows_formatname=\"FileList\"", "FileList", false }, + { PBTYPE_SESX, FLAVOR_SESX, "Star Embed Source (XML)", false }, + { PBTYPE_SLSDX, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false }, + { PBTYPE_ESX, FLAVOR_ESX, "Star Embed Source (XML)", false }, + { PBTYPE_LSX, FLAVOR_LSX, "Star Link Source (XML)", false }, + { PBTYPE_EOX, FLAVOR_EOX, "Star Embedded Object (XML)", false }, + { PBTYPE_SVXB, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false }, + { PBTYPE_GDIMF, FLAVOR_GDIMF, "GDIMetaFile", false }, + { PBTYPE_WMF, FLAVOR_WMF, "Windows MetaFile", false }, + { PBTYPE_EMF, FLAVOR_EMF, "Windows Enhanced MetaFile", false }, + { PBTYPE_SODX, FLAVOR_SODX, "Star Object Descriptor (XML)", false }, + { PBTYPE_DUMMY_INTERNAL, FLAVOR_DUMMY_INTERNAL, "internal data",false } + }; + + + #define SIZE_FLAVOR_MAP (sizeof(flavorMap)/sizeof(FlavorMap)) + + + inline bool isByteSequenceType(const Type& theType) + { + return (theType == getCppuType((Sequence<sal_Int8>*)0)); + } + + inline bool isOUStringType(const Type& theType) + { + return (theType == getCppuType( (OUString*)0 )); + } + +} // namespace private + + +//########################### + +/* A base class for other data provider. + */ +class DataProviderBaseImpl : public DataProvider +{ +public: + DataProviderBaseImpl(const Any& data); + DataProviderBaseImpl(id data); + virtual ~DataProviderBaseImpl(); + +protected: + Any mData; + //NSData* mSystemData; + id mSystemData; +}; + +DataProviderBaseImpl::DataProviderBaseImpl(const Any& data) : + mData(data), + mSystemData(nil) +{ +} + +DataProviderBaseImpl::DataProviderBaseImpl(id data) : + mSystemData(data) +{ + [mSystemData retain]; +} + + +DataProviderBaseImpl::~DataProviderBaseImpl() +{ + if (mSystemData) + { + [mSystemData release]; + } +} + +//################################# + +class UniDataProvider : public DataProviderBaseImpl +{ +public: + UniDataProvider(const Any& data); + + UniDataProvider(NSData* data); + + virtual NSData* getSystemData(); + + virtual Any getOOoData(); +}; + +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 = makeAny(OUString(reinterpret_cast<const sal_Char*>([mSystemData bytes]), + [mSystemData length], + RTL_TEXTENCODING_UTF8)); + } + else + { + oOOData = mData; + } + + return oOOData; +} + +//########################### + +class ByteSequenceDataProvider : public DataProviderBaseImpl +{ +public: + ByteSequenceDataProvider(const Any& data); + + ByteSequenceDataProvider(NSData* data); + + virtual NSData* getSystemData(); + + virtual Any getOOoData(); +}; + +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 = makeAny(byteSequence); + } + else + { + oOOData = mData; + } + + return oOOData; +} + + +//########################### + +class HTMLFormatDataProvider : public DataProviderBaseImpl +{ +public: + HTMLFormatDataProvider(const Any& data); + + HTMLFormatDataProvider(NSData* data); + + virtual NSData* getSystemData(); + + virtual Any getOOoData(); +}; + +HTMLFormatDataProvider::HTMLFormatDataProvider(const Any& data) : + DataProviderBaseImpl(data) +{ +} + +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 = makeAny(*pPlainHtml); + } + else + { + oOOData = mData; + } + + return oOOData; +} + +//########################### + +class PNGDataProvider : public DataProviderBaseImpl +{ + NSBitmapImageFileType meImageType; +public: + PNGDataProvider( const Any&, NSBitmapImageFileType); + + PNGDataProvider( NSData*, NSBitmapImageFileType); + + virtual NSData* getSystemData(); + + virtual Any getOOoData(); +}; + +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 = NULL; + 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, meImageType)) + oOOData = makeAny( pngData); + } + else + { + oOOData = mData; + } + + return oOOData; +} + +//###################### + +class FileListDataProvider : public DataProviderBaseImpl +{ +public: + FileListDataProvider(const Any& data); + FileListDataProvider(NSArray* data); + + virtual NSData* getSystemData(); + virtual Any getOOoData(); +}; + +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 = makeAny(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(NSString* systemDataFlavor) const +{ + DataFlavor oOOFlavor; + + for (size_t i = 0; i < SIZE_FLAVOR_MAP; i++) + { + if ([systemDataFlavor caseInsensitiveCompare: flavorMap[i].SystemFlavor] == NSOrderedSame) + { + oOOFlavor.MimeType = OUString::createFromAscii(flavorMap[i].OOoFlavor); + oOOFlavor.HumanPresentableName = OUString::createFromAscii(flavorMap[i].HumanPresentableName); + oOOFlavor.DataType = flavorMap[i].DataTypeOUString ? getCppuType( (OUString*)0 ) : getCppuType((Sequence<sal_Int8>*)0); + 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 = OUString(); + oOOFlavor.DataType = getCppuType((Sequence<sal_Int8>*)0); + } + + return oOOFlavor; +} + +NSString* DataFlavorMapper::openOfficeToSystemFlavor(const DataFlavor& oOOFlavor, bool& rbInternal) const +{ + NSString* sysFlavor = NULL; + rbInternal = false; + + for( size_t i = 0; i < SIZE_FLAVOR_MAP; ++i ) + { + if (oOOFlavor.MimeType.startsWith(OUString::createFromAscii(flavorMap[i].OOoFlavor))) + { + sysFlavor = flavorMap[i].SystemFlavor; + } + } + + if(!sysFlavor) + { + rbInternal = true; + OfficeOnlyTypes::const_iterator it = maOfficeOnlyTypes.find( oOOFlavor.MimeType ); + + if( it == maOfficeOnlyTypes.end() ) + sysFlavor = maOfficeOnlyTypes[ oOOFlavor.MimeType ] = OUStringToNSString( oOOFlavor.MimeType ); + else + sysFlavor = it->second; + } + + return sysFlavor; +} + +NSString* DataFlavorMapper::openOfficeImageToSystemFlavor(NSPasteboard* pPasteboard) const +{ + NSArray *supportedTypes = [NSArray arrayWithObjects: NSTIFFPboardType, nil]; + NSString *sysFlavor = [pPasteboard availableTypeFromArray:supportedTypes]; + return sysFlavor; +} + +DataProviderPtr_t DataFlavorMapper::getDataProvider(NSString* systemFlavor, Reference<XTransferable> 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: NSTIFFPboardType] == NSOrderedSame) + { + dp = DataProviderPtr_t( new PNGDataProvider( data, NSTIFFFileType)); + } + else if ([systemFlavor caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame) + { + dp = DataProviderPtr_t(new FileListDataProvider(data)); + } + else + { + dp = DataProviderPtr_t(new ByteSequenceDataProvider(data)); + } + } + else // Must be OUString type + { + SAL_WARN_IF( + !isOUStringType(data.getValueType()), "vcl", + "must be OUString type"); + dp = DataProviderPtr_t(new UniDataProvider(data)); + } + } + catch(UnsupportedFlavorException&) + { + // Somebody violates the contract of the clipboard + // interface @see XTransferable + } + + return dp; +} + +DataProviderPtr_t DataFlavorMapper::getDataProvider(const NSString* /*systemFlavor*/, NSArray* systemData) const +{ + return DataProviderPtr_t(new FileListDataProvider(systemData)); +} + +DataProviderPtr_t DataFlavorMapper::getDataProvider(const NSString* systemFlavor, NSData* systemData) const +{ + DataProviderPtr_t dp; + + if ([systemFlavor caseInsensitiveCompare: NSStringPboardType] == NSOrderedSame) + { + dp = DataProviderPtr_t(new UniDataProvider(systemData)); + } + else if ([systemFlavor caseInsensitiveCompare: NSHTMLPboardType] == NSOrderedSame) + { + dp = DataProviderPtr_t(new HTMLFormatDataProvider(systemData)); + } + else if ([systemFlavor caseInsensitiveCompare: NSTIFFPboardType] == NSOrderedSame) + { + dp = DataProviderPtr_t( new PNGDataProvider(systemData, NSTIFFFileType)); + } + else if ([systemFlavor caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame) + { + //dp = DataProviderPtr_t(new FileListDataProvider(systemData)); + } + else + { + dp = DataProviderPtr_t(new ByteSequenceDataProvider(systemData)); + } + + return dp; +} + +bool DataFlavorMapper::isValidMimeContentType(const OUString& contentType) const +{ + bool result = true; + + try + { + Reference<XMimeContentType> xCntType(mrXMimeCntFactory->createMimeContentType(contentType)); + } + catch( IllegalArgumentException& ) + { + result = false; + } + + return result; +} + +NSArray* DataFlavorMapper::flavorSequenceToTypesArray(const com::sun::star::uno::Sequence<com::sun::star::datatransfer::DataFlavor>& flavors) const +{ + sal_uInt32 nFlavors = flavors.getLength(); + NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity: 1]; + + bool bNeedDummyInternalFlavor(false); + + for (sal_uInt32 i = 0; i < nFlavors; i++) + { + if( flavors[i].MimeType.startsWith("image/bmp") ) + { + [array addObject: NSTIFFPboardType]; + } + else + { + NSString* str = openOfficeToSystemFlavor(flavors[i], bNeedDummyInternalFlavor); + + if (str != NULL) + { + [str retain]; + [array addObject: str]; + } + } + } + + // #i89462# #i90747# + // in case no system flavor was found to report + // report at least one so D&D between OOo targets works + if( [array count] == 0 || bNeedDummyInternalFlavor) + { + [array addObject: PBTYPE_DUMMY_INTERNAL]; + } + + return [array autorelease]; +} + +com::sun::star::uno::Sequence<com::sun::star::datatransfer::DataFlavor> DataFlavorMapper::typesArrayToFlavorSequence(NSArray* types) const +{ + int nFormats = [types count]; + Sequence<DataFlavor> flavors; + + for (int i = 0; i < nFormats; i++) + { + NSString* sysFormat = [types objectAtIndex: i]; + DataFlavor oOOFlavor = systemToOpenOfficeFlavor(sysFormat); + + if (isValidFlavor(oOOFlavor)) + { + flavors.realloc(flavors.getLength() + 1); + flavors[flavors.getLength() - 1] = oOOFlavor; + } + } + + return flavors; +} + + +NSArray* DataFlavorMapper::getAllSupportedPboardTypes() const +{ + NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity: SIZE_FLAVOR_MAP]; + + for (sal_uInt32 i = 0; i < SIZE_FLAVOR_MAP; i++) + { + [array addObject: flavorMap[i].SystemFlavor]; + } + + return [array autorelease]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/DataFlavorMapping.hxx b/vcl/osx/DataFlavorMapping.hxx new file mode 100644 index 000000000000..70f7736b248e --- /dev/null +++ b/vcl/osx/DataFlavorMapping.hxx @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_DATAFLAVORMAPPING_HXX +#define INCLUDED_VCL_OSX_DATAFLAVORMAPPING_HXX + +#include <com/sun/star/datatransfer/DataFlavor.hpp> +#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> + +#include <premac.h> +#import <Cocoa/Cocoa.h> +#include <postmac.h> + +#include <boost/unordered_map.hpp> +#include <memory> +#include <boost/shared_ptr.hpp> + + +/* 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 com::sun::star::uno::Any getOOoData() = 0; +}; + +typedef std::auto_ptr<DataProvider> DataProviderPtr_t; + + +//################################ + + +class DataFlavorMapper +{ +public: + /* Initialialize 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 suiteable + mapping from a system data flavor to a OpenOffice data + flavor. + */ + com::sun::star::datatransfer::DataFlavor systemToOpenOfficeFlavor(NSString* systemDataFlavor) const; + + + /* Map an OpenOffice data flavor to a system data flavor. + If there is no suiteable mapping available NULL will + be returned. + */ + NSString* openOfficeToSystemFlavor(const com::sun::star::datatransfer::DataFlavor& oooDataFlavor, bool& rbInternal) const; + + /* Select the best available image data type + If there is no suiteable mapping available NULL will + be returned. + */ + NSString* openOfficeImageToSystemFlavor(NSPasteboard* pPasteboard) const; + + /* 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(NSString* systemFlavor, + const com::sun::star::uno::Reference< com::sun::star::datatransfer::XTransferable > rTransferable) const; + + + + /* Get a data provider which is able to provide 'systemData' in the OOo expected format. + */ + DataProviderPtr_t getDataProvider(const NSString* systemFlavor, NSArray* systemData) const; + + + /* Get a data provider which is able to provide 'systemData' in the OOo expected format. + */ + DataProviderPtr_t getDataProvider(const NSString* systemFlavor, NSData* systemData) const; + + + /* Translate a sequence of DataFlavors into a NSArray of system types. + Only those DataFlavors for which a suitable mapping to a system + type exist will be contained in the returned types array. + */ + NSArray* flavorSequenceToTypesArray(const com::sun::star::uno::Sequence<com::sun::star::datatransfer::DataFlavor>& flavors) const; + + /* Translate a NSArray of system types into a sequence of DataFlavors. + Only those types for which a suitable mapping to a DataFlavor + exist will be contained in the new DataFlavor Sequence. + */ + com::sun::star::uno::Sequence<com::sun::star::datatransfer::DataFlavor> typesArrayToFlavorSequence(NSArray* types) const; + + /* Returns an NSArray containing all pasteboard types supported by OOo + */ + NSArray* getAllSupportedPboardTypes() const; + +private: + /* Determines if the provided Mime content type is valid. + */ + bool isValidMimeContentType(const OUString& contentType) const; + +private: + ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory; + typedef boost::unordered_map< OUString, NSString*, OUStringHash > OfficeOnlyTypes; + mutable OfficeOnlyTypes maOfficeOnlyTypes; +}; + +typedef boost::shared_ptr<DataFlavorMapper> DataFlavorMapperPtr_t; + +#endif // INCLUDED_VCL_OSX_DATAFLAVORMAPPING_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/DragActionConversion.cxx b/vcl/osx/DragActionConversion.cxx new file mode 100644 index 000000000000..d4bb9e5c3c86 --- /dev/null +++ b/vcl/osx/DragActionConversion.cxx @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "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>com::sun::star::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>com::sun::star::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 000000000000..8c9b057bff0f --- /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 . + */ + + +#include <sal/types.h> + +#include <premac.h> +#import <Cocoa/Cocoa.h> +#include <postmac.h> + + +/* Convert office drag actions as defined in + <type>com::sun::star::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>com::sun::star::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 000000000000..720292854707 --- /dev/null +++ b/vcl/osx/DragSource.cxx @@ -0,0 +1,372 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * 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 "comphelper/makesequence.hxx" + +#include "DragSource.hxx" +#include "DragSourceContext.hxx" +#include "clipboard.hxx" +#include "DragActionConversion.hxx" + +#include "osx/salframe.h" + +#include <memory> + + +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; +using namespace std; + + +// For OOo internal D&D we provide the Transferable without NSDragPboard +// interference as a shortcut +uno::Reference<XTransferable> DragSource::g_XTransferable; +NSView* DragSource::g_DragSourceView = nil; +bool DragSource::g_DropSuccessSet = false; +bool DragSource::g_DropSuccess = false; + + +OUString dragSource_getImplementationName() +{ + return OUString("com.sun.star.comp.datatransfer.dnd.OleDragSource_V1"); +} + +Sequence<OUString> dragSource_getSupportedServiceNames() +{ + return makeSequence(OUString("com.sun.star.datatransfer.dnd.OleDragSource")); +} + + +@implementation DragSourceHelper; + +-(DragSourceHelper*)initWithDragSource: (DragSource*) pds +{ + self = [super init]; + + if (self) + { + mDragSource = pds; + } + + return self; +} + + +-(void)mouseDown: (NSEvent*)theEvent +{ + mDragSource->saveMouseEvent(theEvent); +} + + +-(void)mouseDragged: (NSEvent*)theEvent +{ + mDragSource->saveMouseEvent(theEvent); +} + + +-(unsigned int)draggingSourceOperationMaskForLocal: (BOOL)isLocal +{ + return mDragSource->getSupportedDragOperations(isLocal); +} + + +-(void)draggedImage:(NSImage*)anImage beganAt:(NSPoint)aPoint +{ + (void)anImage; + (void)aPoint; + DragSourceDragEvent dsde(static_cast<OWeakObject*>(mDragSource), + new DragSourceContext, + mDragSource, + DNDConstants::ACTION_COPY, + DNDConstants::ACTION_COPY); + + mDragSource->mXDragSrcListener->dragEnter(dsde); +} + + +-(void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation +{ + (void)anImage; + (void)aPoint; + // an internal drop can accept the drop but fail with dropComplete( false ) + // this is different than the Cocoa API + bool bDropSuccess = operation != NSDragOperationNone; + if( DragSource::g_DropSuccessSet ) + bDropSuccess = DragSource::g_DropSuccess; + + DragSourceDropEvent dsde(static_cast<OWeakObject*>(mDragSource), + new DragSourceContext, + static_cast< XDragSource* >(mDragSource), + SystemToOfficeDragActions(operation), + bDropSuccess ); + + mDragSource->mXDragSrcListener->dragDropEnd(dsde); + mDragSource->mXDragSrcListener = uno::Reference<XDragSourceListener>(); +} + + +-(void)draggedImage:(NSImage *)draggedImage movedTo:(NSPoint)screenPoint +{ + (void)draggedImage; + (void)screenPoint; + DragSourceDragEvent dsde(static_cast<OWeakObject*>(mDragSource), + new DragSourceContext, + mDragSource, + DNDConstants::ACTION_COPY, + DNDConstants::ACTION_COPY); + + mDragSource->mXDragSrcListener->dragOver(dsde); +} + +@end + + +DragSource::DragSource(): + WeakComponentImplHelper3<XDragSource, XInitialization, XServiceInfo>(m_aMutex), + mView(NULL), + mpFrame(NULL), + mLastMouseEventBeforeStartDrag(nil), + m_MouseButton(0) +{ +} + + +DragSource::~DragSource() +{ + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + [(id <MouseEventListener>)mView unregisterMouseEventListener: mDragSourceHelper]; + [mDragSourceHelper release]; +} + + +void SAL_CALL DragSource::initialize(const Sequence< Any >& aArguments) + throw(Exception) +{ + if (aArguments.getLength() < 2) + { + throw Exception("DragSource::initialize: Not enough parameter.", + static_cast<OWeakObject*>(this)); + } + + Any pNSView = aArguments[1]; + sal_uInt64 tmp = 0; + pNSView >>= tmp; + mView = (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 therefor implements + a hook mechanism so that we can get mouse events for our purpose. + */ + if (![mView respondsToSelector: @selector(registerMouseEventListener:)] || + ![mView respondsToSelector: @selector(unregisterMouseEventListener:)]) + { + throw Exception("DragSource::initialize: Provided view doesn't support mouse listener", + static_cast<OWeakObject*>(this)); + } + NSWindow* pWin = [mView window]; + if( ! pWin || ![pWin respondsToSelector: @selector(getSalFrame)] ) + { + throw Exception("DragSource::initialize: Provided view is not attached to a vcl frame", + static_cast<OWeakObject*>(this)); + } + mpFrame = (AquaSalFrame*)[pWin performSelector: @selector(getSalFrame)]; + + mDragSourceHelper = [[DragSourceHelper alloc] initWithDragSource: this]; + + if (mDragSourceHelper == nil) + { + throw Exception("DragSource::initialize: Cannot initialize DragSource", + static_cast<OWeakObject*>(this)); + } + + [(id <MouseEventListener>)mView registerMouseEventListener: mDragSourceHelper]; +} + + +//---------------------------------------------------- +// XDragSource +//---------------------------------------------------- + +sal_Bool SAL_CALL DragSource::isDragImageSupported( ) + throw(RuntimeException) +{ + return true; +} + + +sal_Int32 SAL_CALL DragSource::getDefaultCursor( sal_Int8 /*dragAction*/ ) + throw( IllegalArgumentException, RuntimeException) +{ + 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 ) + throw( RuntimeException) +{ + MutexGuard guard(m_aMutex); + + OSL_ASSERT(listener.is() && "DragSource::startDrag: No XDragSourceListener provided\n"); + OSL_ASSERT(transferable.is() && "DragSource::startDrag: No transferable provided\n"); + + trigger.Event >>= mMouseEvent; + m_MouseButton= mMouseEvent.Buttons; + mXDragSrcListener = listener; + mXCurrentContext = static_cast<XDragSourceContext*>(new DragSourceContext); + auto_ptr<AquaClipboard> clipb(new AquaClipboard(NULL, 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; + + [mView dragImage: dragImage + at: p + offset: NSMakeSize(0,0) + event: mLastMouseEventBeforeStartDrag + pasteboard: clipb->getPasteboard() + source: mDragSourceHelper + slideBack: 1]; + + [dragImage release]; + + g_XTransferable = uno::Reference<XTransferable>(); + g_DragSourceView = nil; + + // reset drop success flags + g_DropSuccessSet = false; + g_DropSuccess = false; +} + + +// In order to initiate a D&D operation we need to +// provide the triggering mouse event which we get +// from the SalFrameView that is associated with +// this DragSource +void DragSource::saveMouseEvent(NSEvent* theEvent) +{ + if (mLastMouseEventBeforeStartDrag != nil) + { + [mLastMouseEventBeforeStartDrag release]; + } + + mLastMouseEventBeforeStartDrag = theEvent; +} + + +/* isLocal indicates whether or not the DnD operation is OOo + internal. + */ +unsigned int DragSource::getSupportedDragOperations(bool isLocal) const +{ + unsigned int srcActions = OfficeToSystemDragActions(mDragSourceActions); + + if (isLocal) + { + // Support NSDragOperation generic which means we can + // decide which D&D operation to choose. We map + // NSDragOperationGenric to DNDConstants::ACTION_DEFAULT + // in SystemToOfficeDragActions to signal this and + // use it in DropTarget::determineDropAction + srcActions |= NSDragOperationGeneric; + } + else + { + // Mask out link and move operations on external DnD + srcActions &= ~(NSDragOperationMove | NSDragOperationLink); + } + + return srcActions; +} + + +//################################ +// XServiceInfo +//################################ + +OUString SAL_CALL DragSource::getImplementationName( ) throw (RuntimeException) +{ + return dragSource_getImplementationName(); +} + + +sal_Bool SAL_CALL DragSource::supportsService( const OUString& ServiceName ) throw (RuntimeException) +{ + return ServiceName == "com.sun.star.datatransfer.dnd.OleDragSource"; +} + + +Sequence< OUString > SAL_CALL DragSource::getSupportedServiceNames() throw (RuntimeException) +{ + 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 000000000000..625388e87a69 --- /dev/null +++ b/vcl/osx/DragSource.hxx @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_DRAGSOURCE_HXX +#define INCLUDED_VCL_OSX_DRAGSOURCE_HXX + +#include <com/sun/star/datatransfer/dnd/XDragSource.hpp> +#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <cppuhelper/compbase3.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 <boost/utility.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::WeakComponentImplHelper3< com::sun::star::datatransfer::dnd::XDragSource, + com::sun::star::lang::XInitialization, + com::sun::star::lang::XServiceInfo >, + private ::boost::noncopyable +{ +public: + DragSource(); + virtual ~DragSource(); + + // XInitialization + virtual void SAL_CALL initialize( const com::sun::star::uno::Sequence< com::sun::star::uno::Any >& aArguments ) + throw(com::sun::star::uno::Exception/*, com::sun::star::uno::RuntimeException*/); + + // XDragSource + virtual sal_Bool SAL_CALL isDragImageSupported( ) throw(com::sun::star::uno::RuntimeException); + + virtual sal_Int32 SAL_CALL getDefaultCursor(sal_Int8 dragAction) + throw(com::sun::star::lang::IllegalArgumentException, com::sun::star::uno::RuntimeException); + + virtual void SAL_CALL startDrag( const com::sun::star::datatransfer::dnd::DragGestureEvent& trigger, + sal_Int8 sourceActions, + sal_Int32 cursor, + sal_Int32 image, + const com::sun::star::uno::Reference< com::sun::star::datatransfer::XTransferable >& transferable, + const com::sun::star::uno::Reference< com::sun::star::datatransfer::dnd::XDragSourceListener >& listener ) + throw(com::sun::star::uno::RuntimeException); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() throw (com::sun::star::uno::RuntimeException); + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) throw (com::sun::star::uno::RuntimeException); + virtual com::sun::star::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() throw (com::sun::star::uno::RuntimeException); + + virtual void saveMouseEvent(NSEvent* theEvent); + virtual unsigned int getSupportedDragOperations(bool isLocal) const; + +public: + // The context notifies the XDragSourceListeners + com::sun::star::uno::Reference< com::sun::star::datatransfer::dnd::XDragSourceContext > mXCurrentContext; + + id mView; + AquaSalFrame* mpFrame; + NSEvent* mLastMouseEventBeforeStartDrag; + DragSourceHelper* mDragSourceHelper; + com::sun::star::awt::MouseEvent mMouseEvent; + com::sun::star::uno::Reference< com::sun::star::datatransfer::XTransferable > mXTransferable; + com::sun::star::uno::Reference< com::sun::star::datatransfer::dnd::XDragSourceListener > mXDragSrcListener; + // The mouse button that set off the drag and drop operation + short m_MouseButton; + sal_Int8 mDragSourceActions; + + static com::sun::star::uno::Reference< com::sun::star::datatransfer::XTransferable > g_XTransferable; + static NSView* g_DragSourceView; + static bool g_DropSuccessSet; + static bool g_DropSuccess; + +}; + +#endif // INCLUDED_VCL_OSX_DRAGSOURCE_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/DragSourceContext.cxx b/vcl/osx/DragSourceContext.cxx new file mode 100644 index 000000000000..1eb4fe3ac78e --- /dev/null +++ b/vcl/osx/DragSourceContext.cxx @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <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() : + WeakComponentImplHelper1<XDragSourceContext>(m_aMutex) +{ +} + +DragSourceContext::~DragSourceContext() +{ +} + +sal_Int32 SAL_CALL DragSourceContext::getCurrentCursor( ) + throw( RuntimeException) +{ + return 0; +} + +void SAL_CALL DragSourceContext::setCursor( sal_Int32 /*cursorId*/ ) + throw( RuntimeException) +{ +} + +void SAL_CALL DragSourceContext::setImage( sal_Int32 /*imageId*/ ) + throw( RuntimeException) +{ +} + +void SAL_CALL DragSourceContext::transferablesFlavorsChanged( ) + throw( RuntimeException) +{ +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/DragSourceContext.hxx b/vcl/osx/DragSourceContext.hxx new file mode 100644 index 000000000000..f27518ee2756 --- /dev/null +++ b/vcl/osx/DragSourceContext.hxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_DRAGSOURCECONTEXT_HXX +#define INCLUDED_VCL_OSX_DRAGSOURCECONTEXT_HXX + +#include <cppuhelper/implbase1.hxx> +#include <com/sun/star/datatransfer/dnd/XDragSourceContext.hpp> +#include <cppuhelper/compbase1.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <boost/utility.hpp> + +// 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::WeakComponentImplHelper1<com::sun::star::datatransfer::dnd::XDragSourceContext>, + private ::boost::noncopyable +{ +public: + DragSourceContext(); + ~DragSourceContext(); + + virtual sal_Int32 SAL_CALL getCurrentCursor( ) + throw( com::sun::star::uno::RuntimeException); + + virtual void SAL_CALL setCursor( sal_Int32 cursorId ) + throw( com::sun::star::uno::RuntimeException); + + virtual void SAL_CALL setImage( sal_Int32 imageId ) + throw( com::sun::star::uno::RuntimeException); + + virtual void SAL_CALL transferablesFlavorsChanged( ) + throw( com::sun::star::uno::RuntimeException); +}; + +#endif // INCLUDED_VCL_OSX_DRAGSOURCECONTEXT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/DropTarget.cxx b/vcl/osx/DropTarget.cxx new file mode 100644 index 000000000000..e4d3a6bc72a4 --- /dev/null +++ b/vcl/osx/DropTarget.cxx @@ -0,0 +1,596 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * 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 "comphelper/makesequence.hxx" +#include <cppuhelper/interfacecontainer.hxx> + +#include "clipboard.hxx" +#include "DropTarget.hxx" +#include "DragActionConversion.hxx" + +#include "DragSource.hxx" + +#include <rtl/ustring.h> +#include <stdio.h> + +#include <premac.h> +#include <Carbon/Carbon.h> +#include <postmac.h> + +#include <osx/salframe.h> +#include <osx/salframeview.h> + +using namespace cppu; +using namespace osl; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::dnd; +using namespace com::sun::star::datatransfer::dnd::DNDConstants; +using namespace com::sun::star::datatransfer::clipboard; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace com::sun::star; +using namespace comphelper; + + +OUString dropTarget_getImplementationName() +{ + return OUString("com.sun.star.comp.datatransfer.dnd.OleDropTarget_V1"); +} + + +Sequence<OUString> dropTarget_getSupportedServiceNames() +{ + return makeSequence(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 + + inline 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 +{ + return mDropTarget->prepareForDragOperation(sender); +} + + +-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender +{ + return mDropTarget->performDragOperation(sender); +} + + +-(void)concludeDragOperation:(id <NSDraggingInfo>)sender +{ + mDropTarget->concludeDragOperation(sender); +} + + +@end + + +DropTarget::DropTarget() : + WeakComponentImplHelper5<XInitialization, XDropTarget, XDropTargetDragContext, XDropTargetDropContext, XServiceInfo>(m_aMutex), + mView(nil), + mpFrame(NULL), + 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 = DataFlavorMapperPtr_t(new DataFlavorMapper()); +} + + +DropTarget::~DropTarget() +{ + if( AquaSalFrame::isAlive( mpFrame ) ) + [(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 interessted in the drag actions + // supported by the source + if (mDragSourceSupportedActions & mDefaultActions) + { + sal_Int8 currentAction = determineDropAction(mDragSourceSupportedActions, sender); + + NSRect bounds = [mView bounds]; + NSPoint dragLocation = [sender draggedImageLocation]; + + CocoaToVCL(dragLocation, bounds); + + sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x); + sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y); + + NSPasteboard* dragPboard = [sender draggingPasteboard]; + mXCurrentDragClipboard = new AquaClipboard(dragPboard, false); + + uno::Reference<XTransferable> xTransferable = DragSource::g_XTransferable.is() ? + DragSource::g_XTransferable : mXCurrentDragClipboard->getContents(); + + DropTargetDragEnterEvent dtdee(static_cast<OWeakObject*>(this), + 0, + this, + currentAction, + posX, + posY, + mDragSourceSupportedActions, + xTransferable->getTransferDataFlavors()); + + fire_dragEnter(dtdee); + } + + return OfficeToSystemDragActions(mSelectedDropAction); +} + + +NSDragOperation DropTarget::draggingUpdated(id sender) +{ + sal_Int8 currentDragSourceActions = + SystemToOfficeDragActions([sender draggingSourceOperationMask]); + NSDragOperation dragOp = NSDragOperationNone; + + if (currentDragSourceActions & mDefaultActions) + { + sal_Int8 currentAction = determineDropAction(currentDragSourceActions, sender); + NSRect bounds = [mView bounds]; + NSPoint dragLocation = [sender draggedImageLocation]; + + CocoaToVCL(dragLocation, bounds); + + sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x); + sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y); + + DropTargetDragEvent dtde(static_cast<OWeakObject*>(this), + 0, + this, + currentAction, + posX, + posY, + mDragSourceSupportedActions); + + fire_dragOver(dtde); + + // drag over callbacks likely have rendered something + [mView setNeedsDisplay: TRUE]; + + dragOp = OfficeToSystemDragActions(mSelectedDropAction); + + //NSLog(@"Drag update: Source actions: %x proposed action %x selected action %x", mDragSourceSupportedActions, currentAction, mSelectedDropAction); + } + +#ifndef __LP64__ + // Weird but it appears as if there is no method in Cocoa + // to create a kThemeCopyArrowCursor hence we have to use + // Carbon to do it + if (dragOp == NSDragOperationNone) + SetThemeCursor(kThemeNotAllowedCursor); + else if (dragOp == NSDragOperationCopy) + SetThemeCursor(kThemeCopyArrowCursor); + else + SetThemeCursor(kThemeArrowCursor); +#else + // FIXME: SetThemeCursor replacement? +#endif + return dragOp; +} + + +void DropTarget::draggingExited(id /*sender*/) +{ + DropTargetEvent dte(static_cast<OWeakObject*>(this), 0); + fire_dragExit(dte); + mDragSourceSupportedActions = DNDConstants::ACTION_NONE; + mSelectedDropAction = DNDConstants::ACTION_NONE; +#ifndef __LP64__ + SetThemeCursor(kThemeArrowCursor); +#endif +} + + +BOOL DropTarget::prepareForDragOperation(id /*sender*/) +{ + return 1; +} + + +BOOL DropTarget::performDragOperation(id sender) +{ + 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 dragLocation = [sender draggedImageLocation]; + + CocoaToVCL(dragLocation, bounds); + + sal_Int32 posX = static_cast<sal_Int32>(dragLocation.x); + sal_Int32 posY = static_cast<sal_Int32>(dragLocation.y); + + DropTargetDropEvent dtde(static_cast<OWeakObject*>(this), + 0, + this, + mSelectedDropAction, + posX, + posY, + mDragSourceSupportedActions, + xTransferable); + + fire_drop(dtde); + + bSuccess = true; + } + + return bSuccess; +} + + +void DropTarget::concludeDragOperation(id /*sender*/) +{ + mDragSourceSupportedActions = DNDConstants::ACTION_NONE; + mSelectedDropAction = DNDConstants::ACTION_NONE; + mXCurrentDragClipboard = uno::Reference<XClipboard>(); +#ifndef __LP64__ + SetThemeCursor(kThemeArrowCursor); +#endif +} + + + // 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) + throw(Exception) + { + if (aArguments.getLength() < 2) + { + throw RuntimeException("DropTarget::initialize: Cannot install window event handler", + static_cast<OWeakObject*>(this)); + } + + Any pNSView = aArguments[0]; + sal_uInt64 tmp = 0; + pNSView >>= tmp; + mView = (id)tmp; + mpFrame = [(SalFrameView*)mView getSalFrame]; + + mDropTargetHelper = [[DropTargetHelper alloc] initWithDropTarget: this]; + + [(id <DraggingDestinationHandler>)mView registerDraggingDestinationHandler:mDropTargetHelper]; + [mView registerForDraggedTypes: mDataFlavorMapper->getAllSupportedPboardTypes()]; + + id wnd = [mView window]; + NSWindow* parentWnd = [wnd parentWindow]; + unsigned int topWndStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask); + unsigned int wndStyles = [wnd styleMask] & topWndStyle; + + if (parentWnd == nil && (wndStyles == topWndStyle)) + { + [wnd registerDraggingDestinationHandler:mDropTargetHelper]; + [wnd registerForDraggedTypes: [NSArray arrayWithObjects: NSFilenamesPboardType, nil]]; + } + } + + + void SAL_CALL DropTarget::addDropTargetListener(const uno::Reference<XDropTargetListener>& dtl) + throw(RuntimeException) + { + rBHelper.addListener(::getCppuType(&dtl), dtl); + } + + + void SAL_CALL DropTarget::removeDropTargetListener(const uno::Reference<XDropTargetListener>& dtl) + throw(RuntimeException) + { + rBHelper.removeListener(::getCppuType(&dtl), dtl); + } + + + sal_Bool SAL_CALL DropTarget::isActive( ) throw(RuntimeException) + { + return mbActive; + } + + + void SAL_CALL DropTarget::setActive(sal_Bool active) throw(RuntimeException) + { + mbActive = active; + } + + + sal_Int8 SAL_CALL DropTarget::getDefaultActions() throw(RuntimeException) + { + return mDefaultActions; + } + + + void SAL_CALL DropTarget::setDefaultActions(sal_Int8 actions) throw(RuntimeException) + { + OSL_ENSURE( actions < 8, "No valid default actions"); + mDefaultActions= actions; + } + + + // XDropTargetDragContext + + void SAL_CALL DropTarget::acceptDrag(sal_Int8 dragOperation) throw (RuntimeException) + { + mSelectedDropAction = dragOperation; + } + + + void SAL_CALL DropTarget::rejectDrag() throw (RuntimeException) + { + mSelectedDropAction = DNDConstants::ACTION_NONE; + } + + + //XDropTargetDropContext + + void SAL_CALL DropTarget::acceptDrop(sal_Int8 dropOperation) throw( RuntimeException) + { + mSelectedDropAction = dropOperation; + } + + + void SAL_CALL DropTarget::rejectDrop() throw (RuntimeException) + { + mSelectedDropAction = DNDConstants::ACTION_NONE; + } + + + void SAL_CALL DropTarget::dropComplete(sal_Bool success) throw (RuntimeException) + { + // Reset the internal transferable used as shortcut in case this is + // an internal D&D operation + DragSource::g_XTransferable = uno::Reference<XTransferable>(); + DragSource::g_DropSuccessSet = true; + DragSource::g_DropSuccess = success; + } + + + void DropTarget::fire_drop( const DropTargetDropEvent& dte) + { + OInterfaceContainerHelper* pContainer= rBHelper.getContainer( getCppuType( (uno::Reference<XDropTargetListener>* )0 ) ); + 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( getCppuType( (uno::Reference<XDropTargetListener>* )0 ) ); + 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( getCppuType( (uno::Reference<XDropTargetListener>* )0 ) ); + + 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( getCppuType( (uno::Reference<XDropTargetListener>* )0 ) ); + 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( getCppuType( (uno::Reference<XDropTargetListener>* )0 ) ); + if( pContainer) + { + OInterfaceIteratorHelper iter( *pContainer); + while( iter.hasMoreElements()) + { + uno::Reference<XDropTargetListener> listener( static_cast<XDropTargetListener*>( iter.next())); + + try { listener->dropActionChanged( dtde); } + catch (RuntimeException&) {} + } + } + } + + + // XServiceInfo + + OUString SAL_CALL DropTarget::getImplementationName() throw (RuntimeException) + { + return dropTarget_getImplementationName(); + } + + + sal_Bool SAL_CALL DropTarget::supportsService( const OUString& ServiceName ) throw (RuntimeException) + { + return ServiceName == "com.sun.star.datatransfer.dnd.OleDropTarget"; + } + + + Sequence< OUString > SAL_CALL DropTarget::getSupportedServiceNames( ) throw (RuntimeException) + { + 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 000000000000..0eea030db18b --- /dev/null +++ b/vcl/osx/DropTarget.hxx @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_DROPTARGET_HXX +#define INCLUDED_VCL_OSX_DROPTARGET_HXX + +#include "DataFlavorMapping.hxx" +#include <cppuhelper/compbase5.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 <boost/utility.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::WeakComponentImplHelper5< com::sun::star::lang::XInitialization, + com::sun::star::datatransfer::dnd::XDropTarget, + com::sun::star::datatransfer::dnd::XDropTargetDragContext, + com::sun::star::datatransfer::dnd::XDropTargetDropContext, + com::sun::star::lang::XServiceInfo >, + private boost::noncopyable +{ +public: + DropTarget(); + virtual ~DropTarget(); + + // Overrides WeakComponentImplHelper::disposing which is called by + // WeakComponentImplHelper::dispose + // Must be called. + virtual void SAL_CALL disposing(); + + // XInitialization + virtual void SAL_CALL initialize( const com::sun::star::uno::Sequence< com::sun::star::uno::Any >& aArguments ) + throw(com::sun::star::uno::Exception); + + // XDropTarget + virtual void SAL_CALL addDropTargetListener( const com::sun::star::uno::Reference< com::sun::star::datatransfer::dnd::XDropTargetListener >& dtl ) + throw(com::sun::star::uno::RuntimeException); + + virtual void SAL_CALL removeDropTargetListener( const com::sun::star::uno::Reference< com::sun::star::datatransfer::dnd::XDropTargetListener >& dtl ) + throw(com::sun::star::uno::RuntimeException); + + // Default is not active + virtual sal_Bool SAL_CALL isActive() throw(com::sun::star::uno::RuntimeException); + virtual void SAL_CALL setActive(sal_Bool isActive) throw(com::sun::star::uno::RuntimeException); + virtual sal_Int8 SAL_CALL getDefaultActions() throw(com::sun::star::uno::RuntimeException); + virtual void SAL_CALL setDefaultActions(sal_Int8 actions) throw(com::sun::star::uno::RuntimeException); + + // XDropTargetDragContext + virtual void SAL_CALL acceptDrag(sal_Int8 dragOperation) throw(com::sun::star::uno::RuntimeException); + virtual void SAL_CALL rejectDrag() throw(com::sun::star::uno::RuntimeException); + + // XDropTargetDragContext + virtual void SAL_CALL acceptDrop(sal_Int8 dropOperation) throw (com::sun::star::uno::RuntimeException); + virtual void SAL_CALL rejectDrop() throw (com::sun::star::uno::RuntimeException); + virtual void SAL_CALL dropComplete(sal_Bool success) throw (com::sun::star::uno::RuntimeException); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() throw (com::sun::star::uno::RuntimeException); + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) throw (com::sun::star::uno::RuntimeException); + virtual com::sun::star::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() throw (com::sun::star::uno::RuntimeException); + + // NSDraggingDestination protocol functions + virtual NSDragOperation draggingEntered(id sender); + virtual NSDragOperation draggingUpdated(id sender); + virtual void draggingExited(id sender); + virtual BOOL prepareForDragOperation(id sender); + virtual BOOL performDragOperation(id sender); + virtual 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 com::sun::star::datatransfer::dnd::DropTargetDropEvent& dte); + void fire_dragEnter(const com::sun::star::datatransfer::dnd::DropTargetDragEnterEvent& dtdee); + void fire_dragExit(const com::sun::star::datatransfer::dnd::DropTargetEvent& dte); + void fire_dragOver(const com::sun::star::datatransfer::dnd::DropTargetDragEvent& dtde); + void fire_dropActionChanged(const com::sun::star::datatransfer::dnd::DropTargetDragEvent& dtde); + +private: + com::sun::star::uno::Reference< com::sun::star::datatransfer::dnd::XDropTargetDragContext > mXCurrentDragContext; + com::sun::star::uno::Reference< com::sun::star::datatransfer::dnd::XDropTargetDropContext > mXCurrentDropContext; + com::sun::star::uno::Reference< com::sun::star::datatransfer::clipboard::XClipboard > mXCurrentDragClipboard; + DataFlavorMapperPtr_t mDataFlavorMapper; + id mView; + AquaSalFrame* mpFrame; + DropTargetHelper* mDropTargetHelper; + bool mbActive; + sal_Int8 mDragSourceSupportedActions; + sal_Int8 mSelectedDropAction; + sal_Int8 mDefaultActions; +}; + +#endif // INCLUDED_VCL_OSX_DROPTARGET_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/HtmlFmtFlt.cxx b/vcl/osx/HtmlFmtFlt.cxx new file mode 100644 index 000000000000..e629f11e982e --- /dev/null +++ b/vcl/osx/HtmlFmtFlt.cxx @@ -0,0 +1,168 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "HtmlFmtFlt.hxx" + +#include <rtl/string.h> + +#include <string> +#include <sstream> +#include <vector> +#include <iomanip> + +#include <boost/assert.hpp> + +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. Staring 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 // private +{ +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(); +} + +} // namespace private + + +// the office always writes the start and end html tag in upper cases and +// without spaces both tags don't allow parameters +const std::string TAG_HTML = std::string("<HTML>"); +const std::string TAG_END_HTML = std::string("</HTML>"); + +// The body tag may have parameters so we need to search for the +// closing '>' manually e.g. <BODY param> #92840# +const std::string TAG_BODY = std::string("<BODY"); +const std::string TAG_END_BODY = std::string("</BODY"); + +Sequence<sal_Int8> SAL_CALL TextHtmlToHTMLFormat(Sequence<sal_Int8>& 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 sal_Char*>(aTextHtml.getConstArray()), + reinterpret_cast<const sal_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* HtmlStartTag = "<html"; + +Sequence<sal_Int8> HTMLFormatToTextHtml(const Sequence<sal_Int8>& aHTMLFormat) +{ + BOOST_ASSERT(isHTMLFormat(aHTMLFormat) && "No HTML Format provided"); + + Sequence<sal_Int8>& nonconstHTMLFormatRef = const_cast< Sequence<sal_Int8>& >(aHTMLFormat); + sal_Char* dataStart = reinterpret_cast<sal_Char*>(nonconstHTMLFormatRef.getArray()); + sal_Char* dataEnd = dataStart + nonconstHTMLFormatRef.getLength() - 1; + const sal_Char* htmlStartTag = strcasestr(dataStart, HtmlStartTag); + + BOOST_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 == NULL) + { + 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 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 sal_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 000000000000..77a92493bad6 --- /dev/null +++ b/vcl/osx/HtmlFmtFlt.hxx @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_HTMLFMTFLT_HXX +#define INCLUDED_VCL_OSX_HTMLFMTFLT_HXX + +#include <com/sun/star/uno/Sequence.hxx> + +/* Transform plain HTML into the format expected by MS Office. + */ +com::sun::star::uno::Sequence<sal_Int8> TextHtmlToHTMLFormat(com::sun::star::uno::Sequence<sal_Int8>& aTextHtml); + +/* Transform the MS Office HTML format into plain HTML. + */ +com::sun::star::uno::Sequence<sal_Int8> HTMLFormatToTextHtml(const com::sun::star::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 com::sun::star::uno::Sequence<sal_Int8>& aHtmlSequence); + +#endif // INCLUDED_VCL_OSX_HTMLFMTFLT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/OSXTransferable.cxx b/vcl/osx/OSXTransferable.cxx new file mode 100644 index 000000000000..7cfffcd4d7c5 --- /dev/null +++ b/vcl/osx/OSXTransferable.cxx @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/types.h> + +#include "OSXTransferable.hxx" + +#include "DataFlavorMapping.hxx" + +using namespace std; +using namespace osl; +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::io; +using namespace com::sun::star::lang; +using namespace com::sun::star::container; + + +namespace // private +{ + bool isValidFlavor( const DataFlavor& aFlavor ) + { + size_t len = aFlavor.MimeType.getLength(); + Type dtype = aFlavor.DataType; + return ((len > 0) && ((dtype == getCppuType((Sequence<sal_Int8>*)0)) || (dtype == getCppuType((OUString*)0)))); + } + +} // namespace private + + +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 ) + throw( UnsupportedFlavorException, IOException, RuntimeException ) +{ + if (!isValidFlavor(aFlavor) || !isDataFlavorSupported(aFlavor)) + { + throw UnsupportedFlavorException("AquaClipboard: Unsupported data flavor", + static_cast<XTransferable*>(this)); + } + + bool bInternal(false); + NSString* sysFormat = + (aFlavor.MimeType.startsWith("image/png")) + ? mDataFlavorMapper->openOfficeImageToSystemFlavor( mPasteboard ) + : mDataFlavorMapper->openOfficeToSystemFlavor(aFlavor, bInternal); + DataProviderPtr_t dp; + + if ([sysFormat caseInsensitiveCompare: NSFilenamesPboardType] == NSOrderedSame) + { + NSArray* sysData = [mPasteboard propertyListForType: sysFormat]; + dp = mDataFlavorMapper->getDataProvider(sysFormat, sysData); + } + else + { + NSData* sysData = [mPasteboard dataForType: sysFormat]; + dp = mDataFlavorMapper->getDataProvider(sysFormat, sysData); + } + + if (dp.get() == NULL) + { + throw UnsupportedFlavorException("AquaClipboard: Unsupported data flavor", + static_cast<XTransferable*>(this)); + } + + return dp->getOOoData(); +} + + +bool OSXTransferable::isUnicodeText(const DataFlavor& flavor) +{ + return (flavor.DataType == getCppuType((OUString*)0)); +} + + +Sequence< DataFlavor > SAL_CALL OSXTransferable::getTransferDataFlavors( ) + throw( RuntimeException ) +{ + return mFlavorList; +} + + +sal_Bool SAL_CALL OSXTransferable::isDataFlavorSupported(const DataFlavor& aFlavor) + throw( RuntimeException ) +{ + for (sal_Int32 i = 0; i < mFlavorList.getLength(); i++) + if (compareDataFlavors(aFlavor, mFlavorList[i])) + return sal_True; + + return sal_False; +} + + +void OSXTransferable::initClipboardItemList() +{ + NSArray* pboardFormats = [mPasteboard types]; + + if (pboardFormats == NULL) + { + throw RuntimeException("AquaClipboard: Cannot get clipboard data", + static_cast<XTransferable*>(this)); + } + + mFlavorList = mDataFlavorMapper->typesArrayToFlavorSequence(pboardFormats); +} + + +/* Compares two DataFlavors. Returns true if both DataFlavor have the same media type + and the number of parameter and all parameter values do match otherwise false + is returned. + */ +bool OSXTransferable::compareDataFlavors(const DataFlavor& lhs, const DataFlavor& rhs ) +{ + try + { + Reference<XMimeContentType> xLhs(mrXMimeCntFactory->createMimeContentType(lhs.MimeType)); + Reference<XMimeContentType> xRhs(mrXMimeCntFactory->createMimeContentType(rhs.MimeType)); + + if (!xLhs->getFullMediaType().equalsIgnoreAsciiCase(xRhs->getFullMediaType()) || + !cmpAllContentTypeParameter(xLhs, xRhs)) + { + return false; + } + } + catch( IllegalArgumentException& ) + { + OSL_FAIL( "Invalid content type detected" ); + return false; + } + + return true; +} + + +bool OSXTransferable::cmpAllContentTypeParameter(const Reference<XMimeContentType> xLhs, + const Reference<XMimeContentType> xRhs) const +{ + 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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/OSXTransferable.hxx b/vcl/osx/OSXTransferable.hxx new file mode 100644 index 000000000000..27885effd03f --- /dev/null +++ b/vcl/osx/OSXTransferable.hxx @@ -0,0 +1,90 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#ifndef _TRANSFERABLE_HXX_ +#define _TRANSFERABLE_HXX_ + +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <cppuhelper/implbase1.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 <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> + + +class OSXTransferable : public ::cppu::WeakImplHelper1<com::sun::star::datatransfer::XTransferable>, + private ::boost::noncopyable +{ +public: + typedef com::sun::star::uno::Sequence< sal_Int8 > ByteSequence_t; + + explicit OSXTransferable(com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XMimeContentTypeFactory> rXMimeCntFactory, + DataFlavorMapperPtr_t pDataFlavorMapper, + NSPasteboard* pasteboard); + + virtual ~OSXTransferable(); + + //------------------------------------------------------------------------ + // XTransferable + //------------------------------------------------------------------------ + + virtual ::com::sun::star::uno::Any SAL_CALL getTransferData( const ::com::sun::star::datatransfer::DataFlavor& aFlavor ) + throw( ::com::sun::star::datatransfer::UnsupportedFlavorException, ::com::sun::star::io::IOException, ::com::sun::star::uno::RuntimeException ); + + virtual ::com::sun::star::uno::Sequence< ::com::sun::star::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) + throw( ::com::sun::star::uno::RuntimeException ); + + virtual sal_Bool SAL_CALL isDataFlavorSupported( const ::com::sun::star::datatransfer::DataFlavor& aFlavor ) + throw( ::com::sun::star::uno::RuntimeException ); + + //------------------------------------------------------------------------ + // Helper functions not part of the XTransferable interface + //------------------------------------------------------------------------ + + void initClipboardItemList(); + + //com::sun::star::uno::Any getClipboardItemData(ClipboardItemPtr_t clipboardItem); + + bool isUnicodeText(const com::sun::star::datatransfer::DataFlavor& flavor); + + bool compareDataFlavors( const com::sun::star::datatransfer::DataFlavor& lhs, + const com::sun::star::datatransfer::DataFlavor& rhs ); + + bool cmpAllContentTypeParameter( const com::sun::star::uno::Reference< com::sun::star::datatransfer::XMimeContentType > xLhs, + const com::sun::star::uno::Reference< com::sun::star::datatransfer::XMimeContentType > xRhs ) const; + +private: + com::sun::star::uno::Sequence< com::sun::star::datatransfer::DataFlavor > mFlavorList; + ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory; + DataFlavorMapperPtr_t mDataFlavorMapper; + NSPasteboard* mPasteboard; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/PictToBmpFlt.cxx b/vcl/osx/PictToBmpFlt.cxx new file mode 100644 index 000000000000..91ed2bc5f177 --- /dev/null +++ b/vcl/osx/PictToBmpFlt.cxx @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * 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 <QuickTime/QuickTime.h> +#include <postmac.h> + +#include <string.h> + +#include "PictToBmpFlt.hxx" + +bool ImageToPNG( com::sun::star::uno::Sequence<sal_Int8>& rImgData, + com::sun::star::uno::Sequence<sal_Int8>& rPngData, + NSBitmapImageFileType eInFormat) +{ + (void) eInFormat; // Really not needed? Weird. + + NSData* pData = [NSData dataWithBytesNoCopy: (void*)rImgData.getConstArray() length: rImgData.getLength() freeWhenDone: 0]; + if( !pData) + return false; + + NSBitmapImageRep* pRep =[NSBitmapImageRep imageRepWithData: pData]; + if( !pRep) + return false; + + NSData* pOut = [pRep representationUsingType: NSPNGFileType properties: nil]; + if( !pOut) + return false; + + const size_t nPngSize = [pOut length]; + rPngData.realloc( nPngSize); + [pOut getBytes: rPngData.getArray() length: nPngSize]; + return (nPngSize > 0); +} + +bool PNGToImage( com::sun::star::uno::Sequence<sal_Int8>& rPngData, + com::sun::star::uno::Sequence<sal_Int8>& rImgData, + NSBitmapImageFileType eOutFormat + ) +{ + NSData* pData = [NSData dataWithBytesNoCopy: const_cast<sal_Int8*>(rPngData.getConstArray()) length: rPngData.getLength() freeWhenDone: 0]; + if( !pData) + return false; + + NSBitmapImageRep* pRep = [NSBitmapImageRep imageRepWithData: pData]; + if( !pRep) + return false; + + NSData* pOut = [pRep representationUsingType: eOutFormat properties: nil]; + 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 000000000000..59f1f2d3fa99 --- /dev/null +++ b/vcl/osx/PictToBmpFlt.hxx @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_PICTTOBMPFLT_HXX +#define INCLUDED_VCL_OSX_PICTTOBMPFLT_HXX + +#include <com/sun/star/uno/Sequence.hxx> + +#include <premac.h> +#include <Cocoa/Cocoa.h> +#include <postmac.h> + +bool ImageToPNG( com::sun::star::uno::Sequence<sal_Int8>& rImgData, + com::sun::star::uno::Sequence<sal_Int8>& rPngData, + NSBitmapImageFileType eInFormat); + +bool PNGToImage( com::sun::star::uno::Sequence<sal_Int8>& rPngData, + com::sun::star::uno::Sequence<sal_Int8>& rImgData, + NSBitmapImageFileType eOutFormat); + +#endif // INCLUDED_VCL_OSX_PICTTOBMPFLT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/README.a11y b/vcl/osx/README.a11y new file mode 100644 index 000000000000..4422713bc4fa --- /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 000000000000..0fda9a32ae21 --- /dev/null +++ b/vcl/osx/a11yactionwrapper.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YACTIONWRAPPER_H +#define INCLUDED_VCL_OSX_A11YACTIONWRAPPER_H + +#include "osx/osxvcltypes.h" +#include "osx/a11ywrapper.h" + +@interface AquaA11yActionWrapper : NSObject +{ +} ++(NSArray *)actionNamesForElement:(AquaA11yWrapper *)wrapper; ++(void)doAction:(NSString *)action ofElement:(AquaA11yWrapper *)wrapper; +@end + +#endif // INCLUDED_VCL_OSX_A11YACTIONWRAPPER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yactionwrapper.mm b/vcl/osx/a11yactionwrapper.mm new file mode 100644 index 000000000000..31dc39527ecd --- /dev/null +++ b/vcl/osx/a11yactionwrapper.mm @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "quartz/utils.h" + +#include "a11yactionwrapper.h" + +// Wrapper for XAccessibleAction + +@implementation AquaA11yActionWrapper : NSObject + ++(NSString *)nativeActionNameFor:(NSString *)actionName { + // TODO: Optimize ? + // Use NSAccessibilityActionDescription + if ( [ actionName isEqualToString: @"click" ] ) { + 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 ] != nil ) { + 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 ] != nil ) { + for ( int cnt = 0; cnt < [ wrapper accessibleAction ] -> getAccessibleActionCount(); cnt++ ) { + if ( [ action isEqualToString: [ AquaA11yActionWrapper nativeActionNameFor: CreateNSString ( [ wrapper accessibleAction ] -> getAccessibleActionDescription ( cnt ) ) ] ] ) { + [ wrapper accessibleAction ] -> doAccessibleAction ( cnt ); + break; + } + } + } +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ycomponentwrapper.h b/vcl/osx/a11ycomponentwrapper.h new file mode 100644 index 000000000000..450f0688155e --- /dev/null +++ b/vcl/osx/a11ycomponentwrapper.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YCOMPONENTWRAPPER_H +#define INCLUDED_VCL_OSX_A11YCOMPONENTWRAPPER_H + +#include "osx/osxvcltypes.h" +#include "osx/a11ywrapper.h" + +@interface AquaA11yComponentWrapper : NSObject +{ +} ++(id)sizeAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)positionAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)descriptionAttributeForElement:(AquaA11yWrapper *)wrapper; ++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames; ++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper; ++(void)setFocusedAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value; +@end + +#endif // INCLUDED_VCL_OSX_A11YCOMPONENTWRAPPER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ycomponentwrapper.mm b/vcl/osx/a11ycomponentwrapper.mm new file mode 100644 index 000000000000..b91029d6aca8 --- /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 ( (float) size.Width, (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 ( (float) location.X, (float) ( screenRect.size.height - size.Height - location.Y ) ); + return [ NSValue valueWithPoint: nsPoint ]; +} + ++(id)descriptionAttributeForElement:(AquaA11yWrapper *)wrapper { + if ( [ wrapper accessibleExtendedComponent ] != nil ) { + 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 = NO; + NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; + if ( [ attribute isEqualToString: NSAccessibilityFocusedAttribute ] + && ! [ [ AquaA11yRoleHelper getNativeRoleFrom: [ wrapper accessibleContext ] ] isEqualToString: NSAccessibilityScrollBarRole ] + && ! [ [ AquaA11yRoleHelper getNativeRoleFrom: [ wrapper accessibleContext ] ] isEqualToString: NSAccessibilityStaticTextRole ] ) { + isSettable = YES; + } + [ 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 = Reference < XAccessibleComponent > ( 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 000000000000..30437a837936 --- /dev/null +++ b/vcl/osx/a11yfactory.mm @@ -0,0 +1,219 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "osx/a11yfactory.h" +#include "osx/a11yfocustracker.hxx" + +#include "a11yfocuslistener.hxx" +#include "a11yrolehelper.h" +#include "a11ywrapperbutton.h" +#include "a11ywrapperstatictext.h" +#include "a11ywrappertextarea.h" +#include "a11ywrappercheckbox.h" +#include "a11ywrappercombobox.h" +#include "a11ywrappergroup.h" +#include "a11ywrapperlist.h" +#include "a11ywrapperradiobutton.h" +#include "a11ywrapperradiogroup.h" +#include "a11ywrapperrow.h" +#include "a11ywrapperscrollarea.h" +#include "a11ywrapperscrollbar.h" +#include "a11ywrappersplitter.h" +#include "a11ywrappertabgroup.h" +#include "a11ywrappertoolbar.h" +#include "a11ytablewrapper.h" + +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +static bool enabled = false; + +@implementation AquaA11yFactory : NSObject + +#pragma mark - +#pragma mark Wrapper Repository + ++(NSMutableDictionary *)allWrapper { + static NSMutableDictionary * mdAllWrapper = nil; + if ( mdAllWrapper == nil ) { + mdAllWrapper = [ [ [ NSMutableDictionary alloc ] init ] retain ]; + // initialize keyboard focus tracker + rtl::Reference< AquaA11yFocusListener > listener( AquaA11yFocusListener::get() ); + AquaA11yFocusTracker::get().setFocusListener(listener.get()); + enabled = true; + } + return mdAllWrapper; +} + ++(NSValue *)keyForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { + return [ NSValue valueWithPointer: rxAccessibleContext.get() ]; +} + ++(NSValue *)keyForAccessibleContextAsRadioGroup: (Reference < XAccessibleContext >) rxAccessibleContext { + return [ NSValue valueWithPointer: ( rxAccessibleContext.get() + 2 ) ]; +} + ++(AquaA11yWrapper *)wrapperForAccessible: (Reference < XAccessible >) rxAccessible { + if ( rxAccessible.is() ) { + Reference< XAccessibleContext > xAccessibleContext = rxAccessible->getAccessibleContext(); + if( xAccessibleContext.is() ) { + return [ AquaA11yFactory wrapperForAccessibleContext: xAccessibleContext ]; + } + } + return nil; +} + ++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { + return [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: YES asRadioGroup: NO ]; +} + ++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate { + return [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: bCreate asRadioGroup: NO ]; +} + ++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate asRadioGroup:(BOOL) asRadioGroup{ + NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ]; + NSValue * nKey = nil; + if ( asRadioGroup ) { + nKey = [ AquaA11yFactory keyForAccessibleContextAsRadioGroup: rxAccessibleContext ]; + } else { + nKey = [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ]; + } + AquaA11yWrapper * aWrapper = (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 an finally all the transients are released. + */ + if ( ! rxAccessibleContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::TRANSIENT ) ) + #endif + { + [ dAllWrapper setObject: aWrapper forKey: nKey ]; + /* fdo#67410: Accessibility notifications are not delivered on NSView subclasses that do not + "reasonably" participate in NSView hierarchy (perhaps the only important point is + that the view is a transitive subview of the NSWindow's content view, but I + did not try to verify that). + + So let the superview-subviews relationship mirror the AXParent-AXChildren relationship. + */ + id parent = [aWrapper accessibilityAttributeValue:NSAccessibilityParentAttribute]; + if (parent) { + if ([parent isKindOfClass:[NSView class]]) { + // SAL_DEBUG("Wrapper INIT: " << [[aWrapper description] UTF8String] << " ==> " << [[parent description] UTF8String]); + NSView *parentView = (NSView *)parent; + [parentView addSubview:aWrapper positioned:NSWindowBelow relativeTo:nil]; + } else if ([parent isKindOfClass:NSClassFromString(@"SalFrameWindow")]) { + NSWindow *window = (NSWindow *)parent; + NSView *salView = [window contentView]; + // SAL_DEBUG("Wrapper INIT SAL: " << [[aWrapper description] UTF8String] << " ==> " << [[salView description] UTF8String]); + [salView addSubview:aWrapper positioned:NSWindowBelow relativeTo:nil]; + } else { + // SAL_DEBUG("Wrapper INIT: !! " << [[aWrapper description] UTF8String] << " !==>! " << [[parent description] UTF8String] << "!!"); + } + } else { + // SAL_DEBUG("Wrapper INIT: " << [[aWrapper description] UTF8String] << " ==> NO PARENT"); + } + } + } + return aWrapper; +} + ++(void)insertIntoWrapperRepository: (NSView *) viewElement forAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { + NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ]; + [ dAllWrapper setObject: viewElement forKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ]; +} + ++(void)removeFromWrapperRepositoryFor: (::com::sun::star::uno::Reference < ::com::sun::star::accessibility::XAccessibleContext >) rxAccessibleContext { + // TODO: when RADIO_BUTTON search for associated RadioGroup-wrapper and delete that as well + AquaA11yWrapper * theWrapper = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: NO ]; + if ( theWrapper != nil ) { + if (![theWrapper isKindOfClass:NSClassFromString(@"SalFrameView")]) { + [theWrapper removeFromSuperview]; + } + [ [ AquaA11yFactory allWrapper ] removeObjectForKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ]; + [ theWrapper release ]; + } +} + ++(void)registerView: (NSView *) theView { + if ( enabled && [ theView isKindOfClass: [ AquaA11yWrapper class ] ] ) { + // insertIntoWrapperRepository gets called from SalFrameView itself to bootstrap the bridge initially + [ (AquaA11yWrapper *) theView accessibleContext ]; + } +} + ++(void)revokeView: (NSView *) theView { + if ( enabled && [ theView isKindOfClass: [ AquaA11yWrapper class ] ] ) { + [ AquaA11yFactory removeFromWrapperRepositoryFor: [ (AquaA11yWrapper *) theView accessibleContext ] ]; + } +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yfocuslistener.cxx b/vcl/osx/a11yfocuslistener.cxx new file mode 100644 index 000000000000..c4e296072868 --- /dev/null +++ b/vcl/osx/a11yfocuslistener.cxx @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <salhelper/refobj.hxx> + +#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 > 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( AquaA11yFocusTracker::get().getFocusedObject() ); + try { + if( xAccessible.is() ) { + Reference< XAccessibleContext > xContext(xAccessible->getAccessibleContext()); + if( xContext.is() ) + m_focusedObject = [ AquaA11yFactory wrapperForAccessibleContext: xContext ]; + } + } catch(const RuntimeException &) { + // intentionally do nothing .. + } + } + + return m_focusedObject; +} + +//------------------------------------------------------------------------------ + +void SAL_CALL +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 .. + } +} + +//------------------------------------------------------------------------------ + +oslInterlockedCount SAL_CALL +AquaA11yFocusListener::acquire() SAL_THROW(()) +{ + return ReferenceObject::acquire(); +} + +//------------------------------------------------------------------------------ + +oslInterlockedCount SAL_CALL +AquaA11yFocusListener::release() SAL_THROW(()) +{ + return ReferenceObject::release(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yfocuslistener.hxx b/vcl/osx/a11yfocuslistener.hxx new file mode 100644 index 000000000000..42d5c358b4cd --- /dev/null +++ b/vcl/osx/a11yfocuslistener.hxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YFOCUSLISTENER_HXX +#define INCLUDED_VCL_OSX_A11YFOCUSLISTENER_HXX + +#include <salhelper/refobj.hxx> + +#include "osx/keyboardfocuslistener.hxx" +#include "osx/osxvcltypes.h" + +class AquaA11yFocusListener : + public KeyboardFocusListener, + public salhelper::ReferenceObject +{ + id m_focusedObject; + + static rtl::Reference< AquaA11yFocusListener > theListener; + + AquaA11yFocusListener(); + virtual ~AquaA11yFocusListener() {}; +public: + + static rtl::Reference< AquaA11yFocusListener > get(); + + id getFocusedUIElement(); + + // KeyboardFocusListener + virtual void SAL_CALL focusedObjectChanged(const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible >& xAccessible); + + // rtl::IReference + virtual oslInterlockedCount SAL_CALL acquire() SAL_THROW(()); + virtual oslInterlockedCount SAL_CALL release() SAL_THROW(()); +}; + +#endif // INCLUDED_VCL_OSX_A11YFOCUSLISTENER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yfocustracker.cxx b/vcl/osx/a11yfocustracker.cxx new file mode 100644 index 000000000000..1017969c879a --- /dev/null +++ b/vcl/osx/a11yfocustracker.cxx @@ -0,0 +1,284 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "vcl/svapp.hxx" +#include "vcl/window.hxx" +#include "vcl/toolbox.hxx" +#include "vcl/menu.hxx" + +#include "osx/a11yfocustracker.hxx" + +#include "documentfocuslistener.hxx" + +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleSelection.hpp> +#include <com/sun/star/accessibility/XAccessibleStateSet.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +//------------------------------------------------------------------------------ + +static inline Window * +getWindow(const ::VclSimpleEvent *pEvent) +{ + return static_cast< const ::VclWindowEvent *> (pEvent)->GetWindow(); +} + + +//------------------------------------------------------------------------------ + +// callback function for Application::addEventListener + +long AquaA11yFocusTracker::WindowEventHandler(AquaA11yFocusTracker *pFocusTracker, ::VclSimpleEvent const *pEvent) +{ + switch (pEvent->GetId()) + { + case VCLEVENT_WINDOW_PAINT: + pFocusTracker-> toolbox_open_floater( getWindow(pEvent) ); + break; + case VCLEVENT_WINDOW_GETFOCUS: + pFocusTracker->window_got_focus( getWindow(pEvent) ); + break; + case VCLEVENT_OBJECT_DYING: + pFocusTracker->m_aDocumentWindowList.erase( getWindow(pEvent) ); + // intentional pass through .. + case VCLEVENT_TOOLBOX_HIGHLIGHTOFF: + pFocusTracker->toolbox_highlight_off( getWindow(pEvent) ); + break; + case VCLEVENT_TOOLBOX_HIGHLIGHT: + pFocusTracker->toolbox_highlight_on( getWindow(pEvent) ); + break; + case VCLEVENT_TABPAGE_ACTIVATE: + pFocusTracker->tabpage_activated( getWindow(pEvent) ); + break; + case VCLEVENT_MENU_HIGHLIGHT: + // 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* > (pEvent) ) + { + pFocusTracker->menu_highlighted( pMenuEvent ); + } + else if( const VclAccessibleEvent* pAccEvent = dynamic_cast < const VclAccessibleEvent* > (pEvent) ) + { + Reference< XAccessible > xAccessible = pAccEvent->GetAccessible(); + if( xAccessible.is() ) + pFocusTracker->setFocusedObject( xAccessible ); + } + break; + default: + break; + }; + + return 0; +} + +//------------------------------------------------------------------------------ + +AquaA11yFocusTracker::AquaA11yFocusTracker() : + m_aWindowEventLink(this, (PSTUB) WindowEventHandler), + m_xDocumentFocusListener(new DocumentFocusListener(*this)) +{ + Application::AddEventListener(m_aWindowEventLink); + window_got_focus(Application::GetFocusWindow()); +} + +//------------------------------------------------------------------------------ + +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() ) + { + sal_Int32 nPos = pToolBox->GetItemPos( pToolBox->GetHighlightItemId() ); + if( nPos != TOOLBOX_ITEM_NOTFOUND ) + setFocusedObject( xContext->getAccessibleChild( nPos ) ); + } + } +} + +//------------------------------------------------------------------------------ + +void AquaA11yFocusTracker::toolbox_open_floater(Window *pWindow) +{ + bool bToolboxFound = false; + bool bFloatingWindowFound = false; + Window * pFloatingWindow = NULL; + while ( pWindow != NULL ) { + if ( pWindow->GetType() == WINDOW_TOOLBOX ) { + bToolboxFound = true; + } else if ( pWindow->GetType() == WINDOW_FLOATINGWINDOW ) { + bFloatingWindowFound = true; + pFloatingWindow = pWindow; + } + pWindow = pWindow->GetParent(); + } + if ( bToolboxFound && bFloatingWindowFound ) { + Reference < XAccessible > rxAccessible = pFloatingWindow -> GetAccessible(); + if ( ! rxAccessible.is() ) { + return; + } + Reference < XAccessibleContext > rxContext = rxAccessible -> getAccessibleContext(); + if ( ! rxContext.is() ) { + return; + } + if ( rxContext -> getAccessibleChildCount() > 0 ) { + Reference < XAccessible > rxAccessibleChild = rxContext -> getAccessibleChild( 0 ); + if ( ! rxAccessibleChild.is() ) { + return; + } + setFocusedObject ( rxAccessibleChild ); + } + } +} + +//------------------------------------------------------------------------------ + +void AquaA11yFocusTracker::toolbox_highlight_on(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(Window *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(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(Window *pWindow) +{ + // The menu bar is handled through VCLEVENT_MENU_HIGHLIGHTED + if( ! pWindow || !pWindow->IsReallyVisible() || pWindow->GetType() == WINDOW_MENUBARWINDOW ) + return; + + // ToolBoxes are handled through VCLEVENT_TOOLBOX_HIGHLIGHT + if( pWindow->GetType() == WINDOW_TOOLBOX ) + return; + + if( pWindow->GetType() == WINDOW_TABCONTROL ) + { + tabpage_activated( pWindow ); + return; + } + + Reference< XAccessible > xAccessible(pWindow->GetAccessible()); + + if( ! xAccessible.is() ) + return; + + Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext(); + + if( ! xContext.is() ) + return; + + Reference< XAccessibleStateSet > xStateSet = xContext->getAccessibleStateSet(); + + if( ! xStateSet.is() ) + return; + +/* the UNO ToolBox wrapper does not (yet?) support XAccessibleSelection, so we + * need to add listeners to the children instead of re-using the tabpage stuff + */ + if( xStateSet->contains(AccessibleStateType::FOCUSED) && (pWindow->GetType() != WINDOW_TREELISTBOX) ) + { + setFocusedObject( xAccessible ); + } + else + { + if( m_aDocumentWindowList.find(pWindow) == m_aDocumentWindowList.end() ) + { + m_aDocumentWindowList.insert(pWindow); + m_xDocumentFocusListener->attachRecursive(xAccessible, xContext, xStateSet); + } +#ifdef ENABLE_TRACING + else + fprintf(stderr, "Window %p already in the list\n", pWindow ); +#endif + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ylistener.cxx b/vcl/osx/a11ylistener.cxx new file mode 100644 index 000000000000..dafabb7133c2 --- /dev/null +++ b/vcl/osx/a11ylistener.cxx @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * 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; + +NSString * getTableNotification( const AccessibleEventObject& aEvent ) +{ + AccessibleTableModelChange aChange; + NSString * notification = nil; + + if( (aEvent.NewValue >>= aChange) && + ( AccessibleTableModelChangeType::INSERT == aChange.Type || AccessibleTableModelChangeType::DELETE == aChange.Type ) && + aChange.FirstRow != aChange.LastRow ) + { + notification = NSAccessibilityRowCountChangedNotification; + } + + return notification; +} + +//------------------------------------------------------------------------------ + +AquaA11yEventListener::AquaA11yEventListener(id wrapperObject, sal_Int16 role) : m_wrapperObject(wrapperObject), m_role(role) +{ + [ m_wrapperObject retain ]; +} + +//------------------------------------------------------------------------------ + +AquaA11yEventListener::~AquaA11yEventListener() +{ + [ m_wrapperObject release ]; +} + +//------------------------------------------------------------------------------ + +void SAL_CALL +AquaA11yEventListener::disposing( const EventObject& ) throw( RuntimeException ) +{ + [ AquaA11yFactory removeFromWrapperRepositoryFor: [ (AquaA11yWrapper *) m_wrapperObject accessibleContext ] ]; +} + +//------------------------------------------------------------------------------ + +void SAL_CALL +AquaA11yEventListener::notifyEvent( const AccessibleEventObject& aEvent ) throw( RuntimeException ) +{ + NSString * notification = nil; + id element = m_wrapperObject; + 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 ) + AquaA11yFocusTracker::get().setFocusedObject( xAccessible ); + } + break; + + case AccessibleEventId::NAME_CHANGED: + notification = NSAccessibilityTitleChangedNotification; + break; + + case AccessibleEventId::CHILD: + // only needed for tooltips (says Apple) + if ( m_role == AccessibleRole::TOOL_TIP ) { + if(aEvent.NewValue.hasValue()) { + notification = NSAccessibilityCreatedNotification; + } else if(aEvent.OldValue.hasValue()) { + notification = NSAccessibilityUIElementDestroyedNotification; + } + } + break; + + case AccessibleEventId::INVALIDATE_ALL_CHILDREN: + // TODO: depricate 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 000000000000..21b2ff5b960a --- /dev/null +++ b/vcl/osx/a11yrolehelper.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 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YROLEHELPER_H +#define INCLUDED_VCL_OSX_A11YROLEHELPER_H + +#include "osx/salinst.h" +#include <com/sun/star/accessibility/XAccessibleContext.hpp> + +@interface AquaA11yRoleHelper : NSObject +{ +} ++(id)getNativeRoleFrom: (::com::sun::star::accessibility::XAccessibleContext *) accessibleContext; ++(id)getNativeSubroleFrom: (sal_Int16) nRole; ++(id)getRoleDescriptionFrom: (NSString *) role with: (NSString *) subRole; +@end + +#endif // INCLUDED_VCL_OSX_A11YROLEHELPER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yrolehelper.mm b/vcl/osx/a11yrolehelper.mm new file mode 100644 index 000000000000..2940a4b2f7aa --- /dev/null +++ b/vcl/osx/a11yrolehelper.mm @@ -0,0 +1,270 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/a11yfactory.h" + +#include "a11yrolehelper.h" + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +@implementation AquaA11yRoleHelper + ++(id)simpleMapNativeRoleFrom: (XAccessibleContext *) accessibleContext { + id nativeRole = nil; + + if (accessibleContext == NULL) + return nativeRole; + + switch( accessibleContext -> getAccessibleRole() ) { +#define MAP(a,b) \ + case a: nativeRole = b; break + + MAP( AccessibleRole::UNKNOWN, NSAccessibilityUnknownRole ); + MAP( AccessibleRole::ALERT, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::COLUMN_HEADER, NSAccessibilityColumnRole ); + MAP( AccessibleRole::CANVAS, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::CHECK_BOX, NSAccessibilityCheckBoxRole ); + MAP( AccessibleRole::CHECK_MENU_ITEM, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::COLOR_CHOOSER, NSAccessibilityColorWellRole ); // FIXME + MAP( AccessibleRole::COMBO_BOX, NSAccessibilityComboBoxRole ); + MAP( AccessibleRole::DATE_EDITOR, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::DESKTOP_ICON, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::DESKTOP_PANE, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::DIRECTORY_PANE, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::DIALOG, NSAccessibilityGroupRole ); + MAP( AccessibleRole::DOCUMENT, NSAccessibilityGroupRole ); + MAP( AccessibleRole::EMBEDDED_OBJECT, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::END_NOTE, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::FILE_CHOOSER, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::FILLER, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::FONT_CHOOSER, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::FOOTER, NSAccessibilityUnknownRole ); // 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, NSAccessibilityUnknownRole ); // FIXME + MAP( AccessibleRole::HEADING, NSAccessibilityUnknownRole ); // 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, NSAccessibilitySplitterRole ); + 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 ); + +#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 = (NSString *) [ AquaA11yRoleHelper simpleMapNativeRoleFrom: rxParentContext.get() ]; + if ( [ roleParent isEqualToString: NSAccessibilityOutlineRole ] ) { + [ nativeRole release ]; + nativeRole = NSAccessibilityRowRole; + } + [ roleParent release ]; + } + } + } else if ( accessibleContext -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) { + Reference < XAccessible > rxAccessible = accessibleContext -> getAccessibleChild(0); + if ( rxAccessible.is() ) { + Reference < XAccessibleContext > rxAccessibleContext = rxAccessible -> getAccessibleContext(); + if ( rxAccessibleContext.is() && rxAccessibleContext -> getAccessibleRole() == AccessibleRole::TEXT ) { + if ( ! rxAccessibleContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::EDITABLE ) ) { + [ nativeRole release ]; + nativeRole = NSAccessibilityPopUpButtonRole; + } + } + } + } + return nativeRole; +} + ++(id)getNativeSubroleFrom: (sal_Int16) nRole { + id nativeSubrole = nil; + switch( nRole ) { +#define MAP(a,b) \ + case a: nativeSubrole = b; break + + MAP( AccessibleRole::UNKNOWN, NSAccessibilityUnknownSubrole ); + MAP( AccessibleRole::ALERT, NSAccessibilitySystemDialogSubrole ); + MAP( AccessibleRole::COLUMN_HEADER, @"" ); + MAP( AccessibleRole::CANVAS, @"" ); + MAP( AccessibleRole::CHECK_BOX, @"" ); + MAP( AccessibleRole::CHECK_MENU_ITEM, @"" ); + MAP( AccessibleRole::COLOR_CHOOSER, @"" ); + MAP( AccessibleRole::COMBO_BOX, @"" ); + MAP( AccessibleRole::DATE_EDITOR, @"" ); + MAP( AccessibleRole::DESKTOP_ICON, @"" ); + MAP( AccessibleRole::DESKTOP_PANE, @"" ); + MAP( AccessibleRole::DIRECTORY_PANE, @"" ); + MAP( AccessibleRole::DIALOG, NSAccessibilityDialogSubrole ); + MAP( AccessibleRole::DOCUMENT, @"" ); + MAP( AccessibleRole::EMBEDDED_OBJECT, @"" ); + MAP( AccessibleRole::END_NOTE, @"" ); + MAP( AccessibleRole::FILE_CHOOSER, @"" ); + MAP( AccessibleRole::FILLER, @"" ); + MAP( AccessibleRole::FONT_CHOOSER, @"" ); + MAP( AccessibleRole::FOOTER, @"" ); + MAP( AccessibleRole::FOOTNOTE, @"" ); + MAP( AccessibleRole::FRAME, @"" ); + MAP( AccessibleRole::GLASS_PANE, @"" ); + MAP( AccessibleRole::GRAPHIC, @"" ); + MAP( AccessibleRole::GROUP_BOX, @"" ); + MAP( AccessibleRole::HEADER, @"" ); + MAP( AccessibleRole::HEADING, @"" ); + MAP( AccessibleRole::HYPER_LINK, NSAccessibilityTextLinkSubrole ); + MAP( AccessibleRole::ICON, @"" ); + MAP( AccessibleRole::INTERNAL_FRAME, @"" ); + MAP( AccessibleRole::LABEL, @"" ); + MAP( AccessibleRole::LAYERED_PANE, @"" ); + MAP( AccessibleRole::LIST, @"" ); + MAP( AccessibleRole::LIST_ITEM, NSAccessibilityOutlineRowSubrole ); + MAP( AccessibleRole::MENU, @"" ); + MAP( AccessibleRole::MENU_BAR, @"" ); + MAP( AccessibleRole::MENU_ITEM, @"" ); + MAP( AccessibleRole::OPTION_PANE, @"" ); + MAP( AccessibleRole::PAGE_TAB, @"" ); + MAP( AccessibleRole::PAGE_TAB_LIST, @"" ); + MAP( AccessibleRole::PANEL, @"" ); + MAP( AccessibleRole::PARAGRAPH, @"" ); + MAP( AccessibleRole::PASSWORD_TEXT, NSAccessibilitySecureTextFieldSubrole ); + MAP( AccessibleRole::POPUP_MENU, @"" ); + MAP( AccessibleRole::PUSH_BUTTON, @"" ); + MAP( AccessibleRole::PROGRESS_BAR, @"" ); + MAP( AccessibleRole::RADIO_BUTTON, @"" ); + MAP( AccessibleRole::RADIO_MENU_ITEM, @"" ); + MAP( AccessibleRole::ROW_HEADER, @"" ); + MAP( AccessibleRole::ROOT_PANE, @"" ); + MAP( AccessibleRole::SCROLL_BAR, @"" ); + MAP( AccessibleRole::SCROLL_PANE, @"" ); + MAP( AccessibleRole::SHAPE, @"" ); + MAP( AccessibleRole::SEPARATOR, @"" ); + MAP( AccessibleRole::SLIDER, @"" ); + MAP( AccessibleRole::SPIN_BOX, @"" ); + MAP( AccessibleRole::SPLIT_PANE, @"" ); + MAP( AccessibleRole::STATUS_BAR, @"" ); + MAP( AccessibleRole::TABLE, @"" ); + MAP( AccessibleRole::TABLE_CELL, @"" ); + MAP( AccessibleRole::TEXT, @"" ); + MAP( AccessibleRole::TEXT_FRAME, @"" ); + MAP( AccessibleRole::TOGGLE_BUTTON, @"" ); + MAP( AccessibleRole::TOOL_BAR, @"" ); + MAP( AccessibleRole::TOOL_TIP, @"" ); + MAP( AccessibleRole::TREE, @"" ); + MAP( AccessibleRole::VIEW_PORT, @"" ); + MAP( AccessibleRole::WINDOW, NSAccessibilityStandardWindowSubrole ); + + MAP( AccessibleRole::BUTTON_DROPDOWN, @"" ); + MAP( AccessibleRole::BUTTON_MENU, @"" ); + MAP( AccessibleRole::CAPTION, @"" ); + MAP( AccessibleRole::CHART, @"" ); + MAP( AccessibleRole::FORM, @"" ); + MAP( AccessibleRole::IMAGE_MAP, @"" ); + MAP( AccessibleRole::NOTE, @"" ); + MAP( AccessibleRole::PAGE, @"" ); + MAP( AccessibleRole::RULER, @"" ); + MAP( AccessibleRole::SECTION, @"" ); + MAP( AccessibleRole::TREE_ITEM, @"" ); + MAP( AccessibleRole::TREE_TABLE, @"" ); + +#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 000000000000..9b254d7df1f6 --- /dev/null +++ b/vcl/osx/a11yselectionwrapper.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YSELECTIONWRAPPER_H +#define INCLUDED_VCL_OSX_A11YSELECTIONWRAPPER_H + +#include "osx/osxvcltypes.h" +#include "osx/a11ywrapper.h" + +@interface AquaA11ySelectionWrapper : NSObject +{ +} ++(id)selectedChildrenAttributeForElement:(AquaA11yWrapper *)wrapper; ++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames; ++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper; ++(void)setSelectedChildrenAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value; +@end + +#endif // INCLUDED_VCL_OSX_A11YSELECTIONWRAPPER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yselectionwrapper.mm b/vcl/osx/a11yselectionwrapper.mm new file mode 100644 index 000000000000..e341aedacfe0 --- /dev/null +++ b/vcl/osx/a11yselectionwrapper.mm @@ -0,0 +1,88 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "osx/a11yfactory.h" + +#include "a11yselectionwrapper.h" + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +@implementation AquaA11ySelectionWrapper : NSObject + ++(id)selectedChildrenAttributeForElement:(AquaA11yWrapper *)wrapper +{ + Reference< XAccessibleSelection > xAccessibleSelection = [ wrapper accessibleSelection ]; + if( xAccessibleSelection.is() ) + { + NSMutableArray * children = [ [ NSMutableArray alloc ] init ]; + try { + sal_Int32 n = xAccessibleSelection -> getSelectedAccessibleChildCount(); + for ( sal_Int32 i=0 ; i < n ; ++i ) { + [ children addObject: [ AquaA11yFactory wrapperForAccessible: xAccessibleSelection -> getSelectedAccessibleChild( i ) ] ]; + } + + return children; + + } catch ( Exception& e) + { + } + } + + 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& e) { + } +} + +@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 000000000000..94b29bd44c78 --- /dev/null +++ b/vcl/osx/a11ytablewrapper.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YTABLEWRAPPER_H +#define INCLUDED_VCL_OSX_A11YTABLEWRAPPER_H + +#include "osx/a11ywrapper.h" + +#define MAXIMUM_ACCESSIBLE_TABLE_CELLS 1000 + +@interface AquaA11yTableWrapper : AquaA11yWrapper +{ +} ++(id)childrenAttributeForElement:(AquaA11yTableWrapper *)wrapper; ++(void)addAttributeNamesTo: (NSMutableArray *)attributeNames object: (AquaA11yWrapper*)pObject; + +-(id)rowsAttribute; +-(id)columnsAttribute; +@end +#endif // INCLUDED_VCL_OSX_A11YTABLEWRAPPER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ytablewrapper.mm b/vcl/osx/a11ytablewrapper.mm new file mode 100644 index 000000000000..f772efcce4b1 --- /dev/null +++ b/vcl/osx/a11ytablewrapper.mm @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/a11yfactory.h" + +#include "a11ytablewrapper.h" + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::uno; + +@implementation AquaA11yTableWrapper : AquaA11yWrapper + ++(id)childrenAttributeForElement:(AquaA11yTableWrapper *)wrapper +{ + XAccessibleTable * accessibleTable = [ wrapper accessibleTable ]; + NSArray* pResult = nil; + if( accessibleTable ) + { + NSMutableArray * cells = [ [ NSMutableArray alloc ] init ]; + try + { + sal_Int32 nRows = accessibleTable->getAccessibleRowCount(); + sal_Int32 nCols = accessibleTable->getAccessibleColumnCount(); + + if( nRows * nCols < MAXIMUM_ACCESSIBLE_TABLE_CELLS ) + { + // make all children visible to the hierarchy + for ( sal_Int32 rowCount = 0; rowCount < nRows; rowCount++ ) + { + for ( sal_Int32 columnCount = 0; columnCount < nCols; columnCount++ ) + { + Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( rowCount, columnCount ); + if ( rAccessibleCell.is() ) + { + id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ]; + [ cells addObject: cell_wrapper ]; + [ cell_wrapper release ]; + } + } + } + } + else + { + XAccessibleComponent * accessibleComponent = [ wrapper accessibleComponent ]; + // find out which cells are actually visible by determining the top-left-cell and the bottom-right-cell + Size tableSize = accessibleComponent -> getSize(); + Point point; + point.X = 0; + point.Y = 0; + Reference < XAccessible > rAccessibleTopLeft = accessibleComponent -> getAccessibleAtPoint ( point ); + point.X = tableSize.Width - 1; + point.Y = tableSize.Height - 1; + Reference < XAccessible > rAccessibleBottomRight = accessibleComponent -> getAccessibleAtPoint ( point ); + if ( rAccessibleTopLeft.is() && rAccessibleBottomRight.is() ) + { + sal_Int32 idxTopLeft = rAccessibleTopLeft -> getAccessibleContext() -> getAccessibleIndexInParent(); + sal_Int32 idxBottomRight = rAccessibleBottomRight -> getAccessibleContext() -> getAccessibleIndexInParent(); + sal_Int32 rowTopLeft = accessibleTable -> getAccessibleRow ( idxTopLeft ); + sal_Int32 columnTopLeft = accessibleTable -> getAccessibleColumn ( idxTopLeft ); + sal_Int32 rowBottomRight = accessibleTable -> getAccessibleRow ( idxBottomRight ); + sal_Int32 columnBottomRight = accessibleTable -> getAccessibleColumn ( idxBottomRight ); + // create an array containing the visible cells + for ( sal_Int32 rowCount = rowTopLeft; rowCount <= rowBottomRight; rowCount++ ) + { + for ( sal_Int32 columnCount = columnTopLeft; columnCount <= columnBottomRight; columnCount++ ) + { + Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( rowCount, columnCount ); + if ( rAccessibleCell.is() ) + { + id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ]; + [ cells addObject: cell_wrapper ]; + [ cell_wrapper release ]; + } + } + } + } + } + pResult = NSAccessibilityUnignoredChildren( cells ); + } + catch (const Exception &e) + { + } + [cells autorelease]; + } + + return pResult; +} + ++(void)addAttributeNamesTo: (NSMutableArray *)attributeNames object: (AquaA11yWrapper*)pObject +{ + XAccessibleTable * accessibleTable = [ pObject accessibleTable ]; + if( accessibleTable ) + { + sal_Int32 nRows = accessibleTable->getAccessibleRowCount(); + sal_Int32 nCols = accessibleTable->getAccessibleColumnCount(); + + + if( nRows*nCols < MAXIMUM_ACCESSIBLE_TABLE_CELLS ) + { + [ attributeNames addObject: NSAccessibilityRowsAttribute ]; + [ attributeNames addObject: NSAccessibilityColumnsAttribute ]; + } + } +} + +-(id)rowsAttribute +{ + NSArray* pResult = nil; + + XAccessibleTable * accessibleTable = [ self accessibleTable ]; + if( accessibleTable ) + { + sal_Int32 nRows = accessibleTable->getAccessibleRowCount(); + sal_Int32 nCols = accessibleTable->getAccessibleColumnCount(); + if( nRows * nCols < MAXIMUM_ACCESSIBLE_TABLE_CELLS ) + { + NSMutableArray * cells = [ [ NSMutableArray alloc ] init ]; + try + { + // find out number of rows + sal_Int32 nRows = accessibleTable->getAccessibleRowCount(); + 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 &e) + { + pResult = nil; + } + [ cells autorelease ]; + } + } + + return pResult; +} + +-(id)columnsAttribute +{ + NSArray* pResult = nil; + + XAccessibleTable * accessibleTable = [ self accessibleTable ]; + + if( accessibleTable ) + { + sal_Int32 nRows = accessibleTable->getAccessibleRowCount(); + sal_Int32 nCols = accessibleTable->getAccessibleColumnCount(); + if( nRows * nCols < MAXIMUM_ACCESSIBLE_TABLE_CELLS ) + { + NSMutableArray * cells = [ [ NSMutableArray alloc ] init ]; + try + { + // find out number of columns + for( sal_Int32 n = 0; n < nCols; n++ ) + { + Reference < XAccessible > rAccessibleCell = accessibleTable -> getAccessibleCellAt ( 0, n ); + if ( rAccessibleCell.is() ) + { + id cell_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rAccessibleCell -> getAccessibleContext() ]; + [ cells addObject: cell_wrapper ]; + [ cell_wrapper release ]; + } + } + pResult = NSAccessibilityUnignoredChildren( cells ); + } + catch (const Exception &e) + { + 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 000000000000..03e3b3a82eef --- /dev/null +++ b/vcl/osx/a11ytextattributeswrapper.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YTEXTATTRIBUTESWRAPPER_H +#define INCLUDED_VCL_OSX_A11YTEXTATTRIBUTESWRAPPER_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yTextAttributesWrapper : NSObject +{ +} ++(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange; +@end +#endif // INCLUDED_VCL_OSX_A11YTEXTATTRIBUTESWRAPPER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ytextattributeswrapper.mm b/vcl/osx/a11ytextattributeswrapper.mm new file mode 100644 index 000000000000..339103740be0 --- /dev/null +++ b/vcl/osx/a11ytextattributeswrapper.mm @@ -0,0 +1,355 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "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/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; +using namespace ::rtl; + +// 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 = NSNoUnderlineStyle; + sal_Int16 value = 0; + property.Value >>= value; + if ( value != ::css_awt::FontUnderline::NONE + && value != ::css_awt::FontUnderline::DONTKNOW) { + underlineStyle = NSSingleUnderlineStyle; + } + 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; + sal_Int16 value = property.Value.get< ::css_awt::FontSlant>(); + if ( value == ::css_awt::FontSlant_ITALIC ) { + italicStyle = NSItalicFontMask; + } + return italicStyle; +} + ++(BOOL)isStrikethrough:(PropertyValue)property { + BOOL strikethrough = NO; + sal_Int16 value = 0; + property.Value >>= value; + if ( value != ::css_awt::FontStrikeout::NONE + && value != ::css_awt::FontStrikeout::DONTKNOW ) { + strikethrough = YES; + } + return strikethrough; +} + ++(BOOL)convertBoolean:(PropertyValue)property { + BOOL myBoolean = NO; + bool value = sal_False; + property.Value >>= value; + if ( value ) { + myBoolean = YES; + } + return myBoolean; +} + ++(NSNumber *)convertShort:(PropertyValue)property { + sal_Int16 value = 0; + property.Value >>= value; + return [ NSNumber numberWithShort: value ]; +} + ++(void)addColor:(SalColor)nSalColor forAttribute:(NSString *)attribute andRange:(NSRange)range toString:(NSMutableAttributedString *)string { + if( nSalColor == COL_TRANSPARENT ) + return; + const RGBAColor aRGBAColor( nSalColor); + CGColorRef aColorRef = CGColorCreate ( CGColorSpaceCreateWithName ( kCGColorSpaceGenericRGB ), aRGBAColor.AsArray() ); + [ string addAttribute: attribute value: (id) aColorRef range: range ]; + CGColorRelease( aColorRef ); +} + ++(void)addFont:(NSFont *)font toString:(NSMutableAttributedString *)string forRange:(NSRange)range { + if ( font != nil ) { + NSDictionary * fontDictionary = [ NSDictionary dictionaryWithObjectsAndKeys: + [ font fontName ], NSAccessibilityFontNameKey, + [ font familyName ], NSAccessibilityFontFamilyKey, + [ font displayName ], NSAccessibilityVisibleNameKey, + [ NSNumber numberWithFloat: [ font pointSize ] ], NSAccessibilityFontSizeKey, + nil + ]; + [ string addAttribute: NSAccessibilityFontTextAttribute + value: fontDictionary + range: range + ]; + } +} + ++(void)applyAttributesFrom:(Sequence < PropertyValue >)attributes toString:(NSMutableAttributedString *)string forRange:(NSRange)range fontDescriptor:(AquaA11yFontDescriptor*)fontDescriptor { + NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; + // constants + static const OUString attrUnderline("CharUnderline"); + static const OUString attrBold("CharWeight"); + static const OUString attrFontname("CharFontName"); + static const OUString attrItalic("CharPosture"); + static const OUString attrHeight("CharHeight"); + static const OUString attrStrikethrough("CharStrikeout"); + static const OUString attrShadow("CharShadowed"); + static const OUString attrUnderlineColor("CharUnderlineColor"); + static const OUString attrUnderlineHasColor("CharUnderlineHasColor"); + static const OUString attrForegroundColor("CharColor"); + static const OUString attrBackgroundColor("CharBackColor"); + static const OUString attrSuperscript("CharEscapement"); + static const OUString attrTextAlignment("ParaAdjust"); + // vars + sal_Int32 underlineColor = 0; + BOOL underlineHasColor = NO; + // add attributes to string + for ( int attrIndex = 0; attrIndex < attributes.getLength(); attrIndex++ ) { + PropertyValue property = attributes [ attrIndex ]; + // TODO: NSAccessibilityMisspelledTextAttribute, NSAccessibilityAttachmentTextAttribute, NSAccessibilityLinkTextAttribute + // NSAccessibilityStrikethroughColorTextAttribute is unsupported by UNP-API + if ( property.Value.hasValue() ) { + if ( property.Name.equals ( attrUnderline ) ) { + int style = [ AquaA11yTextAttributesWrapper convertUnderlineStyle: property ]; + if ( style != NSNoUnderlineStyle ) { + [ string addAttribute: NSAccessibilityUnderlineTextAttribute value: [ NSNumber numberWithInt: style ] range: range ]; + } + } else if ( property.Name.equals ( attrFontname ) ) { + OUString fontname; + property.Value >>= fontname; + [fontDescriptor setName:CreateNSString(fontname)]; + } else if ( property.Name.equals ( attrBold ) ) { + [fontDescriptor setBold:[AquaA11yTextAttributesWrapper convertBoldStyle:property]]; + } else if ( property.Name.equals ( attrItalic ) ) { + [fontDescriptor setItalic:[AquaA11yTextAttributesWrapper convertItalicStyle:property]]; + } else if ( property.Name.equals ( attrHeight ) ) { + float size; + property.Value >>= size; + [fontDescriptor setSize:size]; + } else if ( property.Name.equals ( attrStrikethrough ) ) { + if ( [ AquaA11yTextAttributesWrapper isStrikethrough: property ] ) { + [ string addAttribute: NSAccessibilityStrikethroughTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ]; + } + } else if ( property.Name.equals ( attrShadow ) ) { + if ( [ AquaA11yTextAttributesWrapper convertBoolean: property ] ) { + [ string addAttribute: NSAccessibilityShadowTextAttribute value: [ NSNumber numberWithBool: YES ] range: range ]; + } + } else if ( property.Name.equals ( attrUnderlineColor ) ) { + property.Value >>= underlineColor; + } else if ( property.Name.equals ( attrUnderlineHasColor ) ) { + underlineHasColor = [ AquaA11yTextAttributesWrapper convertBoolean: property ]; + } else if ( property.Name.equals ( attrForegroundColor ) ) { + [ AquaA11yTextAttributesWrapper addColor: property.Value.get<sal_Int32>() forAttribute: NSAccessibilityForegroundColorTextAttribute andRange: range toString: string ]; + } else if ( property.Name.equals ( attrBackgroundColor ) ) { + [ AquaA11yTextAttributesWrapper addColor: property.Value.get<sal_Int32>() forAttribute: NSAccessibilityBackgroundColorTextAttribute andRange: range toString: string ]; + } else if ( property.Name.equals ( attrSuperscript ) ) { + // values < zero mean subscript + // values > zero mean superscript + // this is true for both NSAccessibility-API and UNO-API + NSNumber * number = [ AquaA11yTextAttributesWrapper convertShort: property ]; + if ( [ number shortValue ] != 0 ) { + [ string addAttribute: NSAccessibilitySuperscriptTextAttribute value: number range: range ]; + } + } else if ( property.Name.equals ( attrTextAlignment ) ) { + sal_Int32 alignment; + property.Value >>= alignment; + NSNumber *textAlignment = nil; + switch(alignment) { + case ::com::sun::star::style::ParagraphAdjust_RIGHT : textAlignment = [NSNumber numberWithInteger:NSRightTextAlignment] ; break; + case ::com::sun::star::style::ParagraphAdjust_CENTER: textAlignment = [NSNumber numberWithInteger:NSCenterTextAlignment] ; break; + case ::com::sun::star::style::ParagraphAdjust_BLOCK : textAlignment = [NSNumber numberWithInteger:NSJustifiedTextAlignment]; break; + case ::com::sun::star::style::ParagraphAdjust_LEFT : + default : textAlignment = [NSNumber numberWithInteger:NSLeftTextAlignment] ; break; + } + NSDictionary *paragraphStyle = [NSDictionary dictionaryWithObjectsAndKeys:textAlignment, @"AXTextAlignment", textAlignment, @"AXVisualTextAlignment", nil]; + [string addAttribute:@"AXParagraphStyle" value:paragraphStyle range:range]; + } + } + } + // add underline information + if ( underlineHasColor ) { + [ AquaA11yTextAttributesWrapper addColor: underlineColor forAttribute: NSAccessibilityUnderlineColorTextAttribute andRange: range toString: string ]; + } + // add font information + NSFont * font = [fontDescriptor font]; + [AquaA11yTextAttributesWrapper addFont:font toString:string forRange:range]; + [ pool release ]; +} + ++(void)addMarkup:(XAccessibleTextMarkup*)markup withType:(long)type toString:(NSMutableAttributedString*)string inRange:(NSRange)range { + const long markupCount = markup->getTextMarkupCount(type); + for (long markupIndex = 0; markupIndex < markupCount; ++markupIndex) { + TextSegment markupSegment = markup->getTextMarkup(markupIndex, type); + NSRange markupRange = NSMakeRange(markupSegment.SegmentStart, markupSegment.SegmentEnd - markupSegment.SegmentStart); + markupRange = NSIntersectionRange(range, markupRange); + if (markupRange.length > 0) { + markupRange.location -= range.location; + switch(type) { + case ::com::sun::star::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:(::com::sun::star::text::TextMarkupType::SPELLCHECK) toString:string inRange:range]; +} + ++(NSMutableAttributedString *)createAttributedStringForElement:(AquaA11yWrapper *)wrapper inOrigRange:(id)origRange { + static const Sequence < OUString > emptySequence; + // vars + NSMutableAttributedString * string = nil; + int loc = [ origRange rangeValue ].location; + int len = [ origRange rangeValue ].length; + int endIndex = loc + len; + int currentIndex = loc; + try { + NSString * myString = CreateNSString ( [ wrapper accessibleText ] -> getText() ); // TODO: dirty fix for i87817 + string = [ [ NSMutableAttributedString alloc ] initWithString: CreateNSString ( [ wrapper accessibleText ] -> getTextRange ( loc, loc + len ) ) ]; + if ( [ wrapper accessibleTextAttributes ] != nil && [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 & e ) { + // empty + } catch ( IndexOutOfBoundsException & e ) { + // 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 000000000000..c1830a2072c6 --- /dev/null +++ b/vcl/osx/a11ytextwrapper.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YTEXTWRAPPER_H +#define INCLUDED_VCL_OSX_A11YTEXTWRAPPER_H + +#include "osx/osxvcltypes.h" +#include "osx/a11ywrapper.h" + +@interface AquaA11yTextWrapper : NSObject +{ +} ++(id)valueAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)numberOfCharactersAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)selectedTextAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)selectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)visibleCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)sharedTextUIElementsAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)sharedCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)stringForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range; ++(id)attributedStringForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range; ++(id)rangeForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index; ++(id)rangeForPositionAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)point; ++(id)boundsForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range; ++(id)styleRangeForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index; ++(id)rTFForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range; ++(id)lineForIndexAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)index; ++(id)rangeForLineAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)line; ++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames; ++(void)addParameterizedAttributeNamesTo:(NSMutableArray *)attributeNames; ++(NSArray *)specialAttributeNames; ++(NSArray *)specialParameterizedAttributeNames; ++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper; ++(void)setVisibleCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value; ++(void)setSelectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value; ++(void)setSelectedTextAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value; ++(void)setValueAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value; +@end + +#endif // INCLUDED_VCL_OSX_A11YTEXTWRAPPER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ytextwrapper.mm b/vcl/osx/a11ytextwrapper.mm new file mode 100644 index 000000000000..29d525d8b7bf --- /dev/null +++ b/vcl/osx/a11ytextwrapper.mm @@ -0,0 +1,293 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "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> + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::awt; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::uno; +using namespace ::rtl; + +// 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 ] != nil ) { + NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; + OUString newText = GetOUString ( (NSString *) value ); + NSRange selectedTextRange = [ [ AquaA11yTextWrapper selectedTextRangeAttributeForElement: wrapper ] rangeValue ]; + try { + [ wrapper accessibleEditableText ] -> replaceText ( selectedTextRange.location, selectedTextRange.location + selectedTextRange.length, newText ); + } catch ( const Exception & e ) { + // empty + } + [ pool release ]; + } +} + ++(id)selectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper { + sal_Int32 start = [ wrapper accessibleText ] -> getSelectionStart(); + sal_Int32 end = [ wrapper accessibleText ] -> getSelectionEnd(); + if ( start != end ) { + return [ NSValue valueWithRange: NSMakeRange ( start, end - start ) ]; // true selection + } else { + long caretPos = [ wrapper accessibleText ] -> getCaretPosition(); + if ( caretPos < 0 || caretPos > [ wrapper accessibleText ] -> getCharacterCount() ) { + return nil; + } + return [ NSValue valueWithRange: NSMakeRange ( caretPos, 0 ) ]; // insertion point + } +} + ++(void)setSelectedTextRangeAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value { + NSRange range = [ value rangeValue ]; + try { + [ wrapper accessibleText ] -> setSelection ( range.location, range.location + range.length ); + } catch ( const Exception & e ) { + // 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 +{ + (void)wrapper; + return [NSArray arrayWithObject:wrapper]; +} + ++(id)sharedCharacterRangeAttributeForElement:(AquaA11yWrapper *)wrapper +{ + (void)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 ( (sal_Int32) [ index intValue ] ); + lineNumber = [ NSNumber numberWithInt: line ]; + } catch ( IndexOutOfBoundsException & e ) { + // 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 & e ) { + // 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 & e ) { + // 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 & e ) { + // empty + } catch ( IllegalArgumentException & e ) { + // empty + } + return range; +} + ++(id)rangeForPositionAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)point { + NSValue * value = nil; + sal_Int32 index = [ wrapper accessibleText ] -> getIndexAtPoint ( [ AquaA11yUtil nsPointToVclPoint: point ] ); + 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 ] != nil ) { + // get location on screen (must be added since get CharacterBounds returns values relative to parent) + Point screenPos = [ wrapper accessibleComponent ] -> getLocationOnScreen(); + Point pos ( minx + screenPos.X, miny + screenPos.Y ); + 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 & e ) { + // 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 & e ) { + // empty + } catch ( IllegalArgumentException & e ) { + // empty + } + return range; +} + ++(id)rTFForRangeAttributeForElement:(AquaA11yWrapper *)wrapper forParameter:(id)range { + NSData * rtfData = nil; + NSAttributedString * attrString = (NSAttributedString *) [ AquaA11yTextWrapper attributedStringForRangeAttributeForElement: wrapper forParameter: range ]; + if ( attrString != nil ) { + @try { + rtfData = [ attrString RTFFromRange: [ range rangeValue ] documentAttributes: nil ]; + } @catch ( NSException * e) { + // emtpy + } + } + return rtfData; +} + ++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper { + BOOL isSettable = NO; + if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] + || [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ] + || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ] + || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) { + if ( ! [ [ wrapper accessibilityAttributeValue: NSAccessibilityRoleAttribute ] isEqualToString: NSAccessibilityStaticTextRole ] ) { + isSettable = YES; + } + } + 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 000000000000..5cc5dc4582f9 --- /dev/null +++ b/vcl/osx/a11yutil.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YUTIL_H +#define INCLUDED_VCL_OSX_A11YUTIL_H + +#include <com/sun/star/awt/Point.hpp> + +@interface AquaA11yUtil : NSObject { +} ++(NSValue *)vclPointToNSPoint:(::com::sun::star::awt::Point)vclPoint; ++(::com::sun::star::awt::Point)nsPointToVclPoint:(NSValue *)nsPoint; +@end + +#endif // INCLUDED_VCL_OSX_A11YUTIL_H +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yutil.mm b/vcl/osx/a11yutil.mm new file mode 100644 index 000000000000..68438643ace7 --- /dev/null +++ b/vcl/osx/a11yutil.mm @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/osxvcltypes.h" + +#include "a11yutil.h" + +using namespace ::com::sun::star::awt; + +@implementation AquaA11yUtil : NSObject + +// TODO: should be merged with AquaSalFrame::VCLToCocoa... to a general helper method ++(NSValue *)vclPointToNSPoint:(Point)vclPoint { + // VCL coordinates are in upper-left-notation, Cocoa likes it the Cartesian way (lower-left) + NSRect screenRect = [ [ NSScreen mainScreen ] frame ]; + NSPoint nsPoint = NSMakePoint ( (float) vclPoint.X, (float) ( screenRect.size.height - vclPoint.Y ) ); + return [ NSValue valueWithPoint: nsPoint ]; +} + +// TODO: should be merged with AquaSalFrame::VCLToCocoa... to a general helper method ++(Point)nsPointToVclPoint:(NSValue *)nsPoint { + // VCL coordinates are in upper-left-notation, Cocoa likes it the Cartesian way (lower-left) + NSRect screenRect = [ [ NSScreen mainScreen ] frame ]; + return Point ( static_cast<long>([ nsPoint pointValue ].x), static_cast<long>(screenRect.size.height - [ nsPoint pointValue ].y) ); +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yvaluewrapper.h b/vcl/osx/a11yvaluewrapper.h new file mode 100644 index 000000000000..3ff33d491424 --- /dev/null +++ b/vcl/osx/a11yvaluewrapper.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YVALUEWRAPPER_H +#define INCLUDED_VCL_OSX_A11YVALUEWRAPPER_H + +#include "osx/salinst.h" +#include "osx/osxvcltypes.h" +#include "osx/a11ywrapper.h" + +@interface AquaA11yValueWrapper : NSObject +{ +} ++(id)valueAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)minValueAttributeForElement:(AquaA11yWrapper *)wrapper; ++(id)maxValueAttributeForElement:(AquaA11yWrapper *)wrapper; ++(void)addAttributeNamesTo:(NSMutableArray *)attributeNames; ++(BOOL)isAttributeSettable:(NSString *)attribute forElement:(AquaA11yWrapper *)wrapper; ++(void)setValueAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value; +@end + +#endif // INCLUDED_VCL_OSX_A11YVALUEWRAPPER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yvaluewrapper.mm b/vcl/osx/a11yvaluewrapper.mm new file mode 100644 index 000000000000..539eb9124f67 --- /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 ] != nil ) { + long value = 0; + [ wrapper accessibleValue ] -> getCurrentValue() >>= value; + return [ NSNumber numberWithLong: value ]; + } + return [ NSNumber numberWithLong: 0 ]; +} + ++(id)minValueAttributeForElement:(AquaA11yWrapper *)wrapper { + // TODO: Detect Type from Any + if ( [ wrapper accessibleValue ] != nil ) { + long value = 0; + [ wrapper accessibleValue ] -> getMinimumValue() >>= value; + return [ NSNumber numberWithLong: value ]; + } + return [ NSNumber numberWithLong: 0 ]; +} + ++(id)maxValueAttributeForElement:(AquaA11yWrapper *)wrapper { + // TODO: Detect Type from Any + if ( [ wrapper accessibleValue ] != nil ) { + long value = 0; + [ wrapper accessibleValue ] -> getMaximumValue() >>= value; + return [ NSNumber numberWithLong: value ]; + } + return [ NSNumber numberWithLong: 0 ]; +} + ++(void)setValueAttributeForElement:(AquaA11yWrapper *)wrapper to:(id)value { + // TODO: Detect Type from NSNumber + if ( [ value isKindOfClass: [ NSNumber class ] ] + && [ wrapper accessibleValue ] != nil ) { + NSNumber * number = (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 = NO; + if ( [ wrapper accessibleValue ] != nil + && [ attribute isEqualToString: NSAccessibilityValueAttribute ] + && ! [ wrapper isKindOfClass: [ AquaA11yWrapperStaticText class ] ] ) { + isSettable = YES; + } + 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 000000000000..85ee3ff3ab05 --- /dev/null +++ b/vcl/osx/a11ywrapper.mm @@ -0,0 +1,1150 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "osx/saldata.hxx" + +#include "osx/a11ywrapper.h" +#include "osx/a11ylistener.hxx" +#include "osx/a11yfactory.h" +#include "osx/a11yfocustracker.hxx" + +#include "quartz/utils.h" + +#include "a11yfocuslistener.hxx" +#include "a11yactionwrapper.h" +#include "a11ycomponentwrapper.h" +#include "a11yselectionwrapper.h" +#include "a11ytablewrapper.h" +#include "a11ytextwrapper.h" +#include "a11yvaluewrapper.h" +#include "a11yrolehelper.h" + +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/accessibility/XAccessibleRelationSet.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/lang/DisposedException.hpp> + +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 = NO; + +static std::ostream &operator<<(std::ostream &s, NSObject *obj) { + return s << [[obj description] UTF8String]; +} + +#ifndef _LP64 + +// In 64-bit code NSPoint == CGPoint, and CGPoint already has +// an operator<< in vcl/inc/quartz/util.h + +static std::ostream &operator<<(std::ostream &s, NSPoint point) { + return s << NSStringFromPoint(point); +} + +#endif + +@implementation AquaA11yWrapper : NSView + +#pragma mark - +#pragma mark Init and dealloc + +-(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { + self = [ super init ]; + if ( self != nil ) { + [ self setDefaults: rxAccessibleContext ]; + } + return self; +} + +-(void) setDefaults: (Reference < XAccessibleContext >) rxAccessibleContext { + mpReferenceWrapper = new ReferenceWrapper; + mActsAsRadioGroup = NO; + mpReferenceWrapper -> rAccessibleContext = rxAccessibleContext; + mIsTableCell = NO; + // Querying all supported interfaces + try { + // XAccessibleComponent + mpReferenceWrapper -> rAccessibleComponent = Reference < XAccessibleComponent > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleExtendedComponent + mpReferenceWrapper -> rAccessibleExtendedComponent = Reference < XAccessibleExtendedComponent > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleSelection + mpReferenceWrapper -> rAccessibleSelection = Reference< XAccessibleSelection > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleTable + mpReferenceWrapper -> rAccessibleTable = Reference < XAccessibleTable > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleText + mpReferenceWrapper -> rAccessibleText = Reference < XAccessibleText > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleEditableText + mpReferenceWrapper -> rAccessibleEditableText = Reference < XAccessibleEditableText > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleValue + mpReferenceWrapper -> rAccessibleValue = Reference < XAccessibleValue > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleAction + mpReferenceWrapper -> rAccessibleAction = Reference < XAccessibleAction > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleTextAttributes + mpReferenceWrapper -> rAccessibleTextAttributes = Reference < XAccessibleTextAttributes > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleMultiLineText + mpReferenceWrapper -> rAccessibleMultiLineText = Reference < XAccessibleMultiLineText > ( rxAccessibleContext, UNO_QUERY ); + // XAccessibleTextMarkup + mpReferenceWrapper -> rAccessibleTextMarkup = Reference < XAccessibleTextMarkup > ( 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 an finally all the transients are released. + */ + if ( ! rxAccessibleContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::TRANSIENT ) ) + #endif + { + Reference< XAccessibleEventBroadcaster > xBroadcaster(rxAccessibleContext, UNO_QUERY); + if( xBroadcaster.is() ) { + /* + * We intentionally do not hold a reference to the event listener in the wrapper object, + * but let the listener control the life cycle of the wrapper instead .. + */ + xBroadcaster->addAccessibleEventListener( new AquaA11yEventListener( self, rxAccessibleContext -> getAccessibleRole() ) ); + } + } + // TABLE_CELL + if ( rxAccessibleContext -> getAccessibleRole() == AccessibleRole::TABLE_CELL ) { + mIsTableCell = YES; + } + } catch ( const Exception ) { + } +} + +-(void)dealloc { + if ( mpReferenceWrapper != nil ) { + delete mpReferenceWrapper; + } + [ super dealloc ]; +} + +#pragma mark - +#pragma mark Utility Section + +// generates selectors for attribute name AXAttributeNameHere +// (getter without parameter) attributeNameHereAttribute +// (getter with parameter) attributeNameHereAttributeForParameter: +// (setter) setAttributeNameHereAttributeForElement:to: +-(SEL)selectorForAttribute:(NSString *)attribute asGetter:(BOOL)asGetter withGetterParameter:(BOOL)withGetterParameter { + SEL selector = 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 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 exception ) { + selector = 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 axpressed through the hierarchy. A 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 ]; + return [ super accessibilityAttributeValue: NSAccessibilitySubroleAttribute ]; + } + } +} + +-(id)titleAttribute { + return CreateNSString ( [ self accessibleContext ] -> getAccessibleName() ); +} + +-(id)descriptionAttribute { + if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) { + return [ self titleAttribute ]; + } else if ( [ self accessibleExtendedComponent ] != nil ) { + return [ AquaA11yComponentWrapper descriptionAttributeForElement: self ]; + } else { + return CreateNSString ( [ self accessibleContext ] -> getAccessibleDescription() ); + } +} + +-(id)enabledAttribute { + if ( [ self accessibleContext ] -> getAccessibleStateSet().is() ) { + return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::ENABLED ) ]; + } else { + return nil; + } +} + +-(id)focusedAttribute { + if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) { + id isFocused = nil; + Reference < XAccessible > rxParent = [ self accessibleContext ] -> getAccessibleParent(); + if ( rxParent.is() ) { + Reference < XAccessibleContext > rxContext = rxParent -> getAccessibleContext(); + if ( rxContext.is() && rxContext -> getAccessibleStateSet().is() ) { + isFocused = [ NSNumber numberWithBool: rxContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::FOCUSED ) ]; + } + } + return isFocused; + } else if ( [ self accessibleContext ] -> getAccessibleStateSet().is() ) { + return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::FOCUSED ) ]; + } else { + return nil; + } +} + +-(id)parentAttribute { + if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON && ! mActsAsRadioGroup ) { + Reference < XAccessible > rxAccessible = [ self getFirstRadioButtonInGroup ]; + if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() ) { + Reference < XAccessibleContext > rxAccessibleContext = rxAccessible -> getAccessibleContext(); + id parent_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: YES asRadioGroup: YES ]; + [ parent_wrapper autorelease ]; + return NSAccessibilityUnignoredAncestor( parent_wrapper ); + } + return nil; + } + try { + Reference< XAccessible > xParent( [ self accessibleContext ] -> getAccessibleParent() ); + if ( xParent.is() ) { + Reference< XAccessibleContext > xContext( xParent -> getAccessibleContext() ); + if ( xContext.is() ) { + id parent_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: xContext ]; + [ parent_wrapper autorelease ]; + return NSAccessibilityUnignoredAncestor( parent_wrapper ); + } + } + } catch (const Exception&) { + } + + OSL_ASSERT( 0 ); + return nil; +} + +-(id)childrenAttribute { + if ( mActsAsRadioGroup ) { + NSMutableArray * children = [ [ NSMutableArray alloc ] init ]; + Reference < XAccessibleRelationSet > rxAccessibleRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet(); + AccessibleRelation relationMemberOf = rxAccessibleRelationSet -> getRelationByType ( AccessibleRelationType::MEMBER_OF ); + if ( relationMemberOf.RelationType == AccessibleRelationType::MEMBER_OF && relationMemberOf.TargetSet.hasElements() ) { + for ( int index = 0; index < relationMemberOf.TargetSet.getLength(); index++ ) { + Reference < XAccessible > rMateAccessible = Reference < XAccessible > ( relationMemberOf.TargetSet[index], 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 ] != nil ) + { + AquaA11yTableWrapper* pTable = [self isKindOfClass: [AquaA11yTableWrapper class]] ? (AquaA11yTableWrapper*)self : nil; + return [ AquaA11yTableWrapper childrenAttributeForElement: pTable ]; + } else { + try { + NSMutableArray * children = [ [ NSMutableArray alloc ] init ]; + Reference< XAccessibleContext > xContext( [ self accessibleContext ] ); + + sal_Int32 cnt = xContext -> getAccessibleChildCount(); + for ( sal_Int32 i = 0; i < cnt; i++ ) { + Reference< XAccessible > xChild( xContext -> getAccessibleChild( i ) ); + if( xChild.is() ) { + Reference< XAccessibleContext > xChildContext( xChild -> getAccessibleContext() ); + // the menubar is already accessible (including Apple- and Application-Menu) through NSApplication => omit it here + if ( xChildContext.is() && AccessibleRole::MENU_BAR != xChildContext -> getAccessibleRole() ) { + id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: xChildContext ]; + [ children addObject: wrapper ]; + [ wrapper release ]; + } + } + } + + // if not already acting as RadioGroup now is the time to replace RadioButtons with RadioGroups and remove RadioButtons + if ( ! mActsAsRadioGroup ) { + NSEnumerator * enumerator = [ children objectEnumerator ]; + AquaA11yWrapper * element; + while ( ( element = ( (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 &e) { + // 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 + NSView * theView = [ aWrapper viewElementForParent ]; + return theView; +} + +-(id)topLevelUIElementAttribute { + return [ self windowAttribute ]; +} + +-(id)sizeAttribute { + if ( [ self accessibleComponent ] != nil ) { + return [ AquaA11yComponentWrapper sizeAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)positionAttribute { + if ( [ self accessibleComponent ] != nil ) { + 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 ] != nil ) { + return [ AquaA11yTextWrapper valueAttributeForElement: self ]; + } else if ( [ self accessibleValue ] != nil ) { + return [ AquaA11yValueWrapper valueAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)minValueAttribute { + if ( [ self accessibleValue ] != nil ) { + return [ AquaA11yValueWrapper minValueAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)maxValueAttribute { + if ( [ self accessibleValue ] != nil ) { + return [ AquaA11yValueWrapper maxValueAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)contentsAttribute { + return [ self childrenAttribute ]; +} + +-(id)selectedChildrenAttribute { + return [ AquaA11ySelectionWrapper selectedChildrenAttributeForElement: self ]; +} + +-(id)numberOfCharactersAttribute { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper numberOfCharactersAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)selectedTextAttribute { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper selectedTextAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)selectedTextRangeAttribute { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper selectedTextRangeAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)visibleCharacterRangeAttribute { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper visibleCharacterRangeAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)tabsAttribute { + return self; // TODO ??? +} + +-(id)sharedTextUIElementsAttribute { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper sharedTextUIElementsAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)sharedCharacterRangeAttribute { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper sharedCharacterRangeAttributeForElement: self ]; + } else { + return nil; + } +} + +-(id)expandedAttribute { + return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::EXPANDED ) ]; +} + +-(id)selectedAttribute { + return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::SELECTED ) ]; +} + +-(id)stringForRangeAttributeForParameter:(id)range { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper stringForRangeAttributeForElement: self forParameter: range ]; + } else { + return nil; + } +} + +-(id)attributedStringForRangeAttributeForParameter:(id)range { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper attributedStringForRangeAttributeForElement: self forParameter: range ]; + } else { + return nil; + } +} + +-(id)rangeForIndexAttributeForParameter:(id)index { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper rangeForIndexAttributeForElement: self forParameter: index ]; + } else { + return nil; + } +} + +-(id)rangeForPositionAttributeForParameter:(id)point { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper rangeForPositionAttributeForElement: self forParameter: point ]; + } else { + return nil; + } +} + +-(id)boundsForRangeAttributeForParameter:(id)range { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper boundsForRangeAttributeForElement: self forParameter: range ]; + } else { + return nil; + } +} + +-(id)styleRangeForIndexAttributeForParameter:(id)index { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper styleRangeForIndexAttributeForElement: self forParameter: index ]; + } else { + return nil; + } +} + +-(id)rTFForRangeAttributeForParameter:(id)range { + if ( [ self accessibleText ] != nil ) { + return [ AquaA11yTextWrapper rTFForRangeAttributeForElement: self forParameter: range ]; + } else { + return nil; + } +} + +-(id)orientationAttribute { + NSString * orientation = nil; + Reference < XAccessibleStateSet > stateSet = [ self accessibleContext ] -> getAccessibleStateSet(); + if ( stateSet -> contains ( AccessibleStateType::HORIZONTAL ) ) { + orientation = NSAccessibilityHorizontalOrientationValue; + } else if ( stateSet -> contains ( AccessibleStateType::VERTICAL ) ) { + orientation = NSAccessibilityVerticalOrientationValue; + } + return orientation; +} + +-(id)titleUIElementAttribute { + if ( [ self accessibleContext ] -> getAccessibleRelationSet().is() ) { + NSString * title = [ self titleAttribute ]; + id titleElement = nil; + if ( [ title length ] == 0 ) { + AccessibleRelation relationLabeledBy = [ self accessibleContext ] -> getAccessibleRelationSet() -> getRelationByType ( AccessibleRelationType::LABELED_BY ); + if ( relationLabeledBy.RelationType == AccessibleRelationType::LABELED_BY && relationLabeledBy.TargetSet.hasElements() ) { + Reference < XAccessible > rxAccessible ( relationLabeledBy.TargetSet[0], UNO_QUERY ); + titleElement = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessible -> getAccessibleContext() ]; + } + } + if ( title != nil ) { + [ 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 ] != nil ) { + return [ AquaA11yTextWrapper lineForIndexAttributeForElement: self forParameter: index ]; + } else { + return nil; + } +} + +-(id)rangeForLineAttributeForParameter:(id)line { + if ( [ self accessibleMultiLineText ] != nil ) { + return [ AquaA11yTextWrapper rangeForLineAttributeForElement: self forParameter: line ]; + } else { + return nil; + } +} + +#pragma mark - +#pragma mark Accessibility Protocol + +-(id)accessibilityAttributeValue:(NSString *)attribute { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeValue:" << attribute << "]"); + // #i90575# guard NSAccessibility protocol against unwanted access + if ( isPopupMenuOpen ) { + return nil; + } + + id value = nil; + // if we are no longer in the wrapper repository, we have been disposed + AquaA11yWrapper * theWrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ self accessibleContext ] createIfNotExists: NO ]; + if ( theWrapper != nil || mIsTableCell ) { + try { + SEL methodSelector = [ self selectorForAttribute: attribute asGetter: YES withGetterParameter: NO ]; + if ( [ self respondsToSelector: methodSelector ] ) { + value = [ self performSelector: methodSelector ]; + } + } catch ( const DisposedException & e ) { + mIsTableCell = NO; // just to be sure + [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ]; + return nil; + } catch ( const Exception & e ) { + // empty + } + } + if ( theWrapper != nil ) { + [ theWrapper release ]; // the above called method calls retain on the returned Wrapper + } + return value; +} + +-(BOOL)accessibilityIsIgnored { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityIsIgnored]"); + // #i90575# guard NSAccessibility protocol against unwanted access + if ( isPopupMenuOpen ) { + return NO; + } + BOOL ignored = NO; + 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 = YES; + break; + default: + ignored = ! ( [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::VISIBLE ) ); + break; + } + return ignored; // TODO: to be completed +} + +-(NSArray *)accessibilityAttributeNames { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeNames]"); + // #i90575# guard NSAccessibility protocol against unwanted access + if ( isPopupMenuOpen ) { + return nil; + } + NSString * nativeSubrole = nil; + NSString * title = nil; + NSMutableArray * attributeNames = nil; + sal_Int32 nAccessibleChildren = 0; + try { + // Default Attributes + attributeNames = [ NSMutableArray arrayWithObjects: + NSAccessibilityRoleAttribute, + NSAccessibilityDescriptionAttribute, + NSAccessibilityParentAttribute, + NSAccessibilityWindowAttribute, + NSAccessibilityHelpAttribute, + NSAccessibilityTopLevelUIElementAttribute, + NSAccessibilityRoleDescriptionAttribute, + nil ]; + nativeSubrole = (NSString *) [ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ]; + title = (NSString *) [ self titleAttribute ]; + Reference < XAccessibleRelationSet > rxRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet(); + // Special Attributes depending on attribute values + if ( nativeSubrole != nil && ! [ nativeSubrole isEqualToString: @"" ] ) { + [ attributeNames addObject: NSAccessibilitySubroleAttribute ]; + } + try + { + nAccessibleChildren = [ self accessibleContext ] -> getAccessibleChildCount(); + if ( nAccessibleChildren > 0 ) { + [ attributeNames addObject: NSAccessibilityChildrenAttribute ]; + } + } + catch( DisposedException& ) {} + catch( RuntimeException& ) {} + + if ( title != nil && ! [ 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 ] != nil ) { + [ AquaA11yTextWrapper addAttributeNamesTo: attributeNames ]; + } + if ( [ self accessibleComponent ] != nil ) { + [ AquaA11yComponentWrapper addAttributeNamesTo: attributeNames ]; + } + if ( [ self accessibleSelection ] != nil ) { + [ AquaA11ySelectionWrapper addAttributeNamesTo: attributeNames ]; + } + if ( [ self accessibleValue ] != nil ) { + [ AquaA11yValueWrapper addAttributeNamesTo: attributeNames ]; + } + [ nativeSubrole release ]; + [ title release ]; + return attributeNames; + } catch ( DisposedException & e ) { // Object is no longer available + if ( nativeSubrole != nil ) { + [ nativeSubrole release ]; + } + if ( title != nil ) { + [ title release ]; + } + if ( attributeNames != nil ) { + [ attributeNames release ]; + } + [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ]; + return [ [ NSArray alloc ] init ]; + } +} + +-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeIsSettable:" << attribute << "]"); + BOOL isSettable = NO; + if ( [ self accessibleText ] != nil ) { + isSettable = [ AquaA11yTextWrapper isAttributeSettable: attribute forElement: self ]; + } + if ( ! isSettable && [ self accessibleComponent ] != nil ) { + isSettable = [ AquaA11yComponentWrapper isAttributeSettable: attribute forElement: self ]; + } + if ( ! isSettable && [ self accessibleSelection ] != nil ) { + isSettable = [ AquaA11ySelectionWrapper isAttributeSettable: attribute forElement: self ]; + } + if ( ! isSettable && [ self accessibleValue ] != nil ) { + isSettable = [ AquaA11yValueWrapper isAttributeSettable: attribute forElement: self ]; + } + return isSettable; // TODO: to be completed +} + +-(NSArray *)accessibilityParameterizedAttributeNames { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityParameterizedAttributeNames]"); + NSMutableArray * attributeNames = [ [ NSMutableArray alloc ] init ]; + // Special Attributes depending on interface + if ( [ self accessibleText ] != nil ) { + [ AquaA11yTextWrapper addParameterizedAttributeNamesTo: attributeNames ]; + } + return attributeNames; // TODO: to be completed +} + +-(id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeValue:" << attribute << " forParameter:" << ((NSObject*)parameter) << "]"); + SEL methodSelector = [ self selectorForAttribute: attribute asGetter: YES withGetterParameter: YES ]; + if ( [ self respondsToSelector: methodSelector ] ) { + return [ self performSelector: methodSelector withObject: parameter ]; + } + return nil; // TODO: to be completed +} + +-(BOOL)accessibilitySetOverrideValue:(id)value forAttribute:(NSString *)attribute +{ + SAL_INFO("vcl.a11y", "[" << self << " accessibilitySetOverrideValue:" << ((NSObject*)value) << " forAttribute:" << attribute << "]"); + (void)value; + (void)attribute; + return NO; // TODO +} + +-(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute { + SAL_INFO("vcl.a11y", "[" << self << " accessibilitySetValue:" << ((NSObject*)value) << " forAttribute:" << attribute << "]"); + SEL methodSelector = [ self selectorForAttribute: attribute asGetter: NO withGetterParameter: NO ]; + if ( [ AquaA11yComponentWrapper respondsToSelector: methodSelector ] ) { + [ AquaA11yComponentWrapper performSelector: methodSelector withObject: self withObject: value ]; + } + if ( [ AquaA11yTextWrapper respondsToSelector: methodSelector ] ) { + [ AquaA11yTextWrapper performSelector: methodSelector withObject: self withObject: value ]; + } + if ( [ AquaA11ySelectionWrapper respondsToSelector: methodSelector ] ) { + [ AquaA11ySelectionWrapper performSelector: methodSelector withObject: self withObject: value ]; + } + if ( [ AquaA11yValueWrapper respondsToSelector: methodSelector ] ) { + [ AquaA11yValueWrapper performSelector: methodSelector withObject: self withObject: value ]; + } +} + +-(id)accessibilityFocusedUIElement { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityFocusedUIElement]"); + // #i90575# guard NSAccessibility protocol against unwanted access + if ( isPopupMenuOpen ) { + return nil; + } + + // as this seems to be the first API call on a newly created SalFrameView object, + // make sure self gets registered in the repository .. + [ self accessibleContext ]; + + AquaA11yWrapper * focusedUIElement = AquaA11yFocusListener::get()->getFocusedUIElement(); +// AquaA11yWrapper * ancestor = focusedUIElement; + + // Make sure the focused object is a descendant of self +// do { +// if( self == ancestor ) + return focusedUIElement; + +// ancestor = [ ancestor accessibilityAttributeValue: NSAccessibilityParentAttribute ]; +// } while( nil != ancestor ); + + return self; +} + +-(NSString *)accessibilityActionDescription:(NSString *)action { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityActionDescription:" << action << "]"); + return NSAccessibilityActionDescription(action); +} + +-(AquaA11yWrapper *)actionResponder { + AquaA11yWrapper * wrapper = nil; + // get some information + NSString * role = (NSString *) [ self accessibilityAttributeValue: NSAccessibilityRoleAttribute ]; + id enabledAttr = [ self enabledAttribute ]; + BOOL enabled = [ enabledAttr boolValue ]; + NSView * parent = (NSView *) [ self accessibilityAttributeValue: NSAccessibilityParentAttribute ]; + AquaA11yWrapper * parentAsWrapper = nil; + if ( [ parent isKindOfClass: [ AquaA11yWrapper class ] ] ) { + parentAsWrapper = (AquaA11yWrapper *) parent; + } + NSString * parentRole = (NSString *) [ parent accessibilityAttributeValue: NSAccessibilityRoleAttribute ]; + // if we are a textarea inside a combobox, then the combobox is the action responder + if ( enabled + && [ role isEqualToString: NSAccessibilityTextAreaRole ] + && [ parentRole isEqualToString: NSAccessibilityComboBoxRole ] + && parentAsWrapper != nil ) { + wrapper = parentAsWrapper; + } else if ( enabled && [ self accessibleAction ] != nil ) { + wrapper = self ; + } + [ parentRole release ]; + [ enabledAttr release ]; + [ role release ]; + return wrapper; +} + +-(void)accessibilityPerformAction:(NSString *)action { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityPerformAction:" << action << "]"); + AquaA11yWrapper * actionResponder = [ self actionResponder ]; + if ( actionResponder != nil ) { + [ AquaA11yActionWrapper doAction: action ofElement: actionResponder ]; + } +} + +-(NSArray *)accessibilityActionNames { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityActionNames]"); + NSArray * actionNames = nil; + AquaA11yWrapper * actionResponder = [ self actionResponder ]; + if ( actionResponder != nil ) { + 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 = NO; + NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; + NSValue * position = [ viewElement accessibilityAttributeValue: NSAccessibilityPositionAttribute ]; + NSValue * size = [ viewElement accessibilityAttributeValue: NSAccessibilitySizeAttribute ]; + if ( position != nil && size != nil ) { + 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 = YES; + } + } + [ pool release ]; + return hit; +} + +Reference < XAccessibleContext > hitTestRunner ( com::sun::star::awt::Point point, + Reference < XAccessibleContext > rxAccessibleContext ) { + Reference < XAccessibleContext > hitChild; + Reference < XAccessibleContext > emptyReference; + try { + Reference < XAccessibleComponent > rxAccessibleComponent ( rxAccessibleContext, UNO_QUERY ); + if ( rxAccessibleComponent.is() ) { + com::sun::star::awt::Point location = rxAccessibleComponent -> getLocationOnScreen(); + com::sun::star::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 hirerachy looking doing recursive hit testing. + // apparently necessary as a special treatment for e.g. comboboxes + if ( !hitChild.is() ) { + bool bSafeToIterate = true; + sal_Int32 nCount = rxAccessibleContext -> getAccessibleChildCount(); + + if ( nCount < 0 || nCount > SAL_MAX_UINT16 /* slow enough for anyone */ ) + bSafeToIterate = false; + else { // manages descendants is an horror from the a11y standards guys. + Reference< XAccessibleStateSet > xStateSet; + xStateSet = rxAccessibleContext -> getAccessibleStateSet(); + if (xStateSet.is() && xStateSet -> contains(AccessibleStateType::MANAGES_DESCENDANTS ) ) + bSafeToIterate = false; + } + + if( bSafeToIterate ) { + for ( int i = 0; i < rxAccessibleContext -> getAccessibleChildCount(); i++ ) { + Reference < XAccessible > rxAccessibleChild = rxAccessibleContext -> getAccessibleChild ( i ); + if ( rxAccessibleChild.is() && rxAccessibleChild -> getAccessibleContext().is() && rxAccessibleChild -> getAccessibleContext() -> getAccessibleRole() != AccessibleRole::LIST ) { + Reference < XAccessibleContext > myHitChild = hitTestRunner ( point, rxAccessibleChild -> getAccessibleContext() ); + if ( myHitChild.is() ) { + hitChild = myHitChild; + break; + } + } + } + } + } + } catch ( RuntimeException ) { + return emptyReference; + } + return hitChild; +} + +-(id)accessibilityHitTest:(NSPoint)point { + SAL_INFO("vcl.a11y", "[" << self << " accessibilityHitTest:" << point << "]"); + static id wrapper = nil; + if ( nil != wrapper ) { + [ wrapper release ]; + wrapper = nil; + } + Reference < XAccessibleContext > hitChild; + NSRect screenRect = [ [ NSScreen mainScreen ] frame ]; + com::sun::star::awt::Point hitPoint ( static_cast<long>(point.x) , static_cast<long>(screenRect.size.height - point.y) ); + // check child windows first + NSWindow * window = (NSWindow *) [ self accessibilityAttributeValue: NSAccessibilityWindowAttribute ]; + NSArray * childWindows = [ window childWindows ]; + if ( [ childWindows count ] > 0 ) { + NSWindow * element = nil; + NSEnumerator * enumerator = [ childWindows objectEnumerator ]; + while ( ( element = [ enumerator nextObject ] ) && hitChild == nil ) { + if ( [ element isKindOfClass: [ SalFrameWindow class ] ] && [ self isViewElement: element hitByPoint: point ] ) { + // we have a child window that is hit + Reference < XAccessibleRelationSet > relationSet = [ ( ( SalFrameWindow * ) element ) accessibleContext ] -> getAccessibleRelationSet(); + if ( relationSet.is() && relationSet -> containsRelation ( AccessibleRelationType::SUB_WINDOW_OF )) { + // we have a valid relation to the parent element + AccessibleRelation relation = relationSet -> getRelationByType ( AccessibleRelationType::SUB_WINDOW_OF ); + for ( int i = 0; i < relation.TargetSet.getLength() && !hitChild.is(); i++ ) { + Reference < XAccessible > rxAccessible ( relation.TargetSet [ i ], UNO_QUERY ); + if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() ) { + // hit test for children of parent + hitChild = hitTestRunner ( hitPoint, rxAccessible -> getAccessibleContext() ); + } + } + } + } + } + } + // nothing hit yet, so check ourself + if ( ! hitChild.is() ) { + if ( mpReferenceWrapper == nil ) { + [ self setDefaults: [ self accessibleContext ] ]; + } + hitChild = hitTestRunner ( hitPoint, mpReferenceWrapper -> rAccessibleContext ); + } + if ( hitChild.is() ) { + wrapper = [ AquaA11yFactory wrapperForAccessibleContext: hitChild ]; + } + if ( wrapper != nil ) { + [ wrapper retain ]; // TODO: retain only when transient ? + } + return wrapper; +} + +#pragma mark - +#pragma mark Access Methods + +-(XAccessibleAction *)accessibleAction { + return mpReferenceWrapper -> rAccessibleAction.get(); +} + +-(XAccessibleContext *)accessibleContext { + return mpReferenceWrapper -> rAccessibleContext.get(); +} + +-(XAccessibleComponent *)accessibleComponent { + return mpReferenceWrapper -> rAccessibleComponent.get(); +} + +-(XAccessibleExtendedComponent *)accessibleExtendedComponent { + return mpReferenceWrapper -> rAccessibleExtendedComponent.get(); +} + +-(XAccessibleSelection *)accessibleSelection { + return mpReferenceWrapper -> rAccessibleSelection.get(); +} + +-(XAccessibleTable *)accessibleTable { + return mpReferenceWrapper -> rAccessibleTable.get(); +} + +-(XAccessibleText *)accessibleText { + return mpReferenceWrapper -> rAccessibleText.get(); +} + +-(XAccessibleEditableText *)accessibleEditableText { + return mpReferenceWrapper -> rAccessibleEditableText.get(); +} + +-(XAccessibleValue *)accessibleValue { + return mpReferenceWrapper -> rAccessibleValue.get(); +} + +-(XAccessibleTextAttributes *)accessibleTextAttributes { + return mpReferenceWrapper -> rAccessibleTextAttributes.get(); +} + +-(XAccessibleMultiLineText *)accessibleMultiLineText { + return mpReferenceWrapper -> rAccessibleMultiLineText.get(); +} + +-(XAccessibleTextMarkup *)accessibleTextMarkup { + return mpReferenceWrapper -> rAccessibleTextMarkup.get(); +} + +-(NSView *)viewElementForParent { + return self; +} + +// These four are for AXTextAreas only. They are needed, because bold and italic +// attributes have to be bound to a font on the Mac. Our UNO-API instead handles +// and reports them independently. When they occur we bundle them to a font with +// this information here to create a according NSFont. +-(void)setActsAsRadioGroup:(BOOL)actsAsRadioGroup { + mActsAsRadioGroup = actsAsRadioGroup; +} + +-(BOOL)actsAsRadioGroup { + return mActsAsRadioGroup; +} + ++(void)setPopupMenuOpen:(BOOL)popupMenuOpen { + isPopupMenuOpen = popupMenuOpen; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperbutton.h b/vcl/osx/a11ywrapperbutton.h new file mode 100644 index 000000000000..0bf115f130be --- /dev/null +++ b/vcl/osx/a11ywrapperbutton.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERBUTTON_H +#define INCLUDED_VCL_OSX_A11YWRAPPERBUTTON_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperButton : AquaA11yWrapper +{ +} +-(id)valueAttribute; +-(id)descriptionAttribute; +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERBUTTON_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperbutton.mm b/vcl/osx/a11ywrapperbutton.mm new file mode 100644 index 000000000000..eab7e07d00e3 --- /dev/null +++ b/vcl/osx/a11ywrapperbutton.mm @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" + +#include "a11ywrapperbutton.h" +#include "a11ytextwrapper.h" + +// Wrapper for AXButton role + +@implementation AquaA11yWrapperButton : AquaA11yWrapper + +-(id)valueAttribute { + return [ NSString string ]; // we propagate AXTitle, that's enough +} + +-(id)descriptionAttribute { + return [ NSString string ]; // we propagate AXTitle, that's enough +} + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + if ( [ attributeNames containsObject: NSAccessibilityTitleAttribute ] ) { + [ attributeNames removeObject: NSAccessibilityDescriptionAttribute ]; + } else { + [ attributeNames addObject: NSAccessibilityTitleAttribute ]; + } + // Remove text-specific attributes + [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappercheckbox.h b/vcl/osx/a11ywrappercheckbox.h new file mode 100644 index 000000000000..58cd2986e41e --- /dev/null +++ b/vcl/osx/a11ywrappercheckbox.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERCHECKBOX_H +#define INCLUDED_VCL_OSX_A11YWRAPPERCHECKBOX_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperCheckBox : AquaA11yWrapper +{ +} +-(id)valueAttribute; +-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute; +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERCHECKBOX_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappercheckbox.mm b/vcl/osx/a11ywrappercheckbox.mm new file mode 100644 index 000000000000..e8ea2a00b9d6 --- /dev/null +++ b/vcl/osx/a11ywrappercheckbox.mm @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" + +#include "a11ywrappercheckbox.h" +#include "a11yvaluewrapper.h" +#include "a11ytextwrapper.h" + +// Wrapper for AXCheckbox role + +@implementation AquaA11yWrapperCheckBox : AquaA11yWrapper + +-(id)valueAttribute { + if ( [ self accessibleValue ] != nil ) { + return [ AquaA11yValueWrapper valueAttributeForElement: self ]; + } + return [ NSNumber numberWithInt: 0 ]; +} + +-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { + if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] ) { + return NO; + } + return [ super accessibilityIsAttributeSettable: attribute ]; +} + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Remove text-specific attributes + [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ]; + [ attributeNames addObject: NSAccessibilityValueAttribute ]; + [ attributeNames addObject: NSAccessibilityMinValueAttribute ]; + [ attributeNames addObject: NSAccessibilityMaxValueAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappercombobox.h b/vcl/osx/a11ywrappercombobox.h new file mode 100644 index 000000000000..13058844b746 --- /dev/null +++ b/vcl/osx/a11ywrappercombobox.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERCOMBOBOX_H +#define INCLUDED_VCL_OSX_A11YWRAPPERCOMBOBOX_H + +#include "osx/a11ywrapper.h" +#include <com/sun/star/accessibility/XAccessibleContext.hpp> + +@interface AquaA11yWrapperComboBox : AquaA11yWrapper +{ + AquaA11yWrapper * textArea; +} +-(id)initWithAccessibleContext: (::com::sun::star::uno::Reference < ::com::sun::star::accessibility::XAccessibleContext >) anAccessibleContext; +-(id)valueAttribute; +-(id)numberOfCharactersAttribute; +-(id)selectedTextAttribute; +-(id)selectedTextRangeAttribute; +-(id)visibleCharacterRangeAttribute; +// Accessibility Protocol +-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute; +-(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute; +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERCOMBOBOX_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappercombobox.mm b/vcl/osx/a11ywrappercombobox.mm new file mode 100644 index 000000000000..2467718c79d4 --- /dev/null +++ b/vcl/osx/a11ywrappercombobox.mm @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" + +#include "a11ywrappercombobox.h" +#include "a11yrolehelper.h" + +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +// Wrapper for AXCombobox role + +@implementation AquaA11yWrapperComboBox : AquaA11yWrapper + +#pragma mark - +#pragma mark Specialized Init Method + +-(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { + self = [ super initWithAccessibleContext: rxAccessibleContext ]; + if ( self != nil ) + { + textArea = nil; + } + return self; +} + +#pragma mark - +#pragma mark Private Helper Method + +-(AquaA11yWrapper *)textArea { + // FIXME: May cause problems when stored. Then get dynamically each time (bad performance!) + if ( textArea == nil ) { + NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; + NSArray * elementChildren = [ super childrenAttribute ]; + if ( [ elementChildren count ] > 0 ) { + NSEnumerator * enumerator = [ elementChildren objectEnumerator ]; + id child; + while ( ( child = [ enumerator nextObject ] ) ) { + AquaA11yWrapper * element = ( AquaA11yWrapper * ) child; + if ( [ [ AquaA11yRoleHelper getNativeRoleFrom: [ element accessibleContext ] ] isEqualToString: NSAccessibilityTextAreaRole ] ) { + textArea = element; + break; + } + } + } + [ pool release ]; + } + return textArea; +} + +#pragma mark - +#pragma mark Wrapped Attributes From Contained Text Area + +-(id)valueAttribute { + if ( [ self textArea ] != nil ) { + return [ [ self textArea ] valueAttribute ]; + } + return @""; +} + +-(id)numberOfCharactersAttribute { + if ( [ self textArea ] != nil ) { + return [ [ self textArea ] numberOfCharactersAttribute ]; + } + return [ NSNumber numberWithInt: 0 ]; +} + +-(id)selectedTextAttribute { + if ( [ self textArea ] != nil ) { + return [ [ self textArea ] selectedTextAttribute ]; + } + return @""; +} + +-(id)selectedTextRangeAttribute { + if ( [ self textArea ] != nil ) { + return [ [ self textArea ] selectedTextRangeAttribute ]; + } + return [ NSValue valueWithRange: NSMakeRange ( 0, 0 ) ]; +} + +-(id)visibleCharacterRangeAttribute { + if ( [ self textArea ] != nil ) { + return [ [ self textArea ] visibleCharacterRangeAttribute ]; + } + return [ NSValue valueWithRange: NSMakeRange ( 0, 0 ) ]; +} + +#pragma mark - +#pragma mark Accessibility Protocol + +-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { + if ( [ self textArea ] != nil && ( + [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ] + || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ] + || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) ) { + return [ [ self textArea ] accessibilityIsAttributeSettable: attribute ]; + } + return [ super accessibilityIsAttributeSettable: attribute ]; +} + +-(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute { + if ( [ self textArea ] != nil && ( + [ attribute isEqualToString: NSAccessibilitySelectedTextAttribute ] + || [ attribute isEqualToString: NSAccessibilitySelectedTextRangeAttribute ] + || [ attribute isEqualToString: NSAccessibilityVisibleCharacterRangeAttribute ] ) ) { + return [ [ self textArea ] accessibilitySetValue: value forAttribute: attribute ]; + } + return [ super accessibilitySetValue: value forAttribute: attribute ]; +} + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects: + NSAccessibilityTitleAttribute, + NSAccessibilityChildrenAttribute, + nil ] + ]; + [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects: + NSAccessibilityExpandedAttribute, + NSAccessibilityValueAttribute, + NSAccessibilityNumberOfCharactersAttribute, + NSAccessibilitySelectedTextAttribute, + NSAccessibilitySelectedTextRangeAttribute, + NSAccessibilityVisibleCharacterRangeAttribute, + nil ] + ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappergroup.h b/vcl/osx/a11ywrappergroup.h new file mode 100644 index 000000000000..af2baea63e11 --- /dev/null +++ b/vcl/osx/a11ywrappergroup.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERGROUP_H +#define INCLUDED_VCL_OSX_A11YWRAPPERGROUP_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperGroup : AquaA11yWrapper +{ +} +-(id)titleUIElementAttribute; +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERGROUP_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappergroup.mm b/vcl/osx/a11ywrappergroup.mm new file mode 100644 index 000000000000..3eb864fc7eb8 --- /dev/null +++ b/vcl/osx/a11ywrappergroup.mm @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrappergroup.h" + +// Wrapper for AXGroup role + +@implementation AquaA11yWrapperGroup : AquaA11yWrapper + +-(id)titleUIElementAttribute { + return self; // TODO +} + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects: + NSAccessibilityTitleAttribute, + NSAccessibilityEnabledAttribute, + NSAccessibilitySelectedChildrenAttribute, + nil ] + ]; + [ attributeNames addObject: NSAccessibilityContentsAttribute ]; + [ attributeNames addObject: NSAccessibilityTitleUIElementAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperlist.h b/vcl/osx/a11ywrapperlist.h new file mode 100644 index 000000000000..65f605a173b1 --- /dev/null +++ b/vcl/osx/a11ywrapperlist.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERLIST_H +#define INCLUDED_VCL_OSX_A11YWRAPPERLIST_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperList : AquaA11yWrapper +{ +} +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERLIST_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperlist.mm b/vcl/osx/a11ywrapperlist.mm new file mode 100644 index 000000000000..e10bf727858f --- /dev/null +++ b/vcl/osx/a11ywrapperlist.mm @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrapperlist.h" + +using namespace ::com::sun::star::accessibility; + +// Wrapper for AXList role + +@implementation AquaA11yWrapperList : AquaA11yWrapper + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames addObject: NSAccessibilityOrientationAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperradiobutton.h b/vcl/osx/a11ywrapperradiobutton.h new file mode 100644 index 000000000000..83e3f5fc9e89 --- /dev/null +++ b/vcl/osx/a11ywrapperradiobutton.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERRADIOBUTTON_H +#define INCLUDED_VCL_OSX_A11YWRAPPERRADIOBUTTON_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperRadioButton : AquaA11yWrapper +{ +} +-(id)valueAttribute; +-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute; +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERRADIOBUTTON_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperradiobutton.mm b/vcl/osx/a11ywrapperradiobutton.mm new file mode 100644 index 000000000000..cfd8dbd23871 --- /dev/null +++ b/vcl/osx/a11ywrapperradiobutton.mm @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrapperradiobutton.h" +#include "a11ytextwrapper.h" +#include "a11yvaluewrapper.h" + +// Wrapper for AXRadioButton role + +@implementation AquaA11yWrapperRadioButton : AquaA11yWrapper + +-(id)valueAttribute { + if ( [ self accessibleValue ] != nil ) { + return [ AquaA11yValueWrapper valueAttributeForElement: self ]; + } + return [ NSNumber numberWithInt: 0 ]; +} + +-(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute { + if ( [ attribute isEqualToString: NSAccessibilityValueAttribute ] ) { + return NO; + } + return [ super accessibilityIsAttributeSettable: attribute ]; +} + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ]; + [ attributeNames addObject: NSAccessibilityMinValueAttribute ]; + [ attributeNames addObject: NSAccessibilityMaxValueAttribute ]; + [ attributeNames addObject: NSAccessibilityValueAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperradiogroup.h b/vcl/osx/a11ywrapperradiogroup.h new file mode 100644 index 000000000000..6ae98cbfd583 --- /dev/null +++ b/vcl/osx/a11ywrapperradiogroup.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERRADIOGROUP_H +#define INCLUDED_VCL_OSX_A11YWRAPPERRADIOGROUP_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperRadioGroup : AquaA11yWrapper +{ +} +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERRADIOGROUP_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperradiogroup.mm b/vcl/osx/a11ywrapperradiogroup.mm new file mode 100644 index 000000000000..d66192df67bb --- /dev/null +++ b/vcl/osx/a11ywrapperradiogroup.mm @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrapperradiogroup.h" +#include "a11ytextwrapper.h" + +// Wrapper for AXRadioGroup role + +@implementation AquaA11yWrapperRadioGroup : AquaA11yWrapper + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ]; + [ attributeNames removeObject: NSAccessibilityTitleAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperrow.h b/vcl/osx/a11ywrapperrow.h new file mode 100644 index 000000000000..45cd7477d6ec --- /dev/null +++ b/vcl/osx/a11ywrapperrow.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERROW_H +#define INCLUDED_VCL_OSX_A11YWRAPPERROW_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperRow : AquaA11yWrapper +{ +} +-(id)disclosingAttribute; +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERROW_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperrow.mm b/vcl/osx/a11ywrapperrow.mm new file mode 100644 index 000000000000..8bcef8032cd3 --- /dev/null +++ b/vcl/osx/a11ywrapperrow.mm @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" + +#include "a11ywrapperrow.h" +#include "a11ytextwrapper.h" + +// Wrapper for AXRow role + +@implementation AquaA11yWrapperRow : AquaA11yWrapper + +-(id)disclosingAttribute { + return NULL; // TODO +} + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObjectsInArray: [ AquaA11yTextWrapper specialAttributeNames ] ]; + [ attributeNames removeObject: NSAccessibilityTitleAttribute ]; + [ attributeNames removeObject: NSAccessibilityEnabledAttribute ]; + [ attributeNames removeObject: NSAccessibilityFocusedAttribute ]; + [ attributeNames addObject: NSAccessibilitySelectedAttribute ]; + [ attributeNames addObject: NSAccessibilityDisclosingAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperscrollarea.h b/vcl/osx/a11ywrapperscrollarea.h new file mode 100644 index 000000000000..35a2aa15a028 --- /dev/null +++ b/vcl/osx/a11ywrapperscrollarea.h @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERSCROLLAREA_H +#define INCLUDED_VCL_OSX_A11YWRAPPERSCROLLAREA_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperScrollArea : AquaA11yWrapper +{ +} +-(id)verticalScrollBarAttribute; +-(id)horizontalScrollBarAttribute; +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERSCROLLAREA_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperscrollarea.mm b/vcl/osx/a11ywrapperscrollarea.mm new file mode 100644 index 000000000000..f70f44962ddb --- /dev/null +++ b/vcl/osx/a11ywrapperscrollarea.mm @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" + +#include "a11ywrapperscrollarea.h" +#include "a11ywrapperscrollbar.h" +#include "a11yrolehelper.h" + +// Wrapper for AXScrollArea role + +@implementation AquaA11yWrapperScrollArea : AquaA11yWrapper + +-(id)scrollBarWithOrientation:(NSString *)orientation { + AquaA11yWrapper * theScrollBar = nil; + NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ]; + NSArray * elementChildren = [ self accessibilityAttributeValue: NSAccessibilityChildrenAttribute ]; + if ( [ elementChildren count ] > 0 ) { + NSEnumerator * enumerator = [ elementChildren objectEnumerator ]; + id child; + while ( ( child = [ enumerator nextObject ] ) ) { + AquaA11yWrapper * element = ( AquaA11yWrapper * ) child; + if ( [ element isKindOfClass: [ AquaA11yWrapperScrollBar class ] ] ) { + AquaA11yWrapperScrollBar * scrollBar = (AquaA11yWrapperScrollBar *) element; + if ( [ [ scrollBar orientationAttribute ] isEqualToString: orientation ] ) { + theScrollBar = scrollBar; + break; + } + } + } + } + [ pool release ]; + return theScrollBar; +} + +-(id)verticalScrollBarAttribute { + return [ self scrollBarWithOrientation: NSAccessibilityVerticalOrientationValue ]; +} + +-(id)horizontalScrollBarAttribute { + return [ self scrollBarWithOrientation: NSAccessibilityHorizontalOrientationValue ]; +} + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObject: NSAccessibilityEnabledAttribute ]; + [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects: + NSAccessibilityContentsAttribute, + NSAccessibilityVerticalScrollBarAttribute, + NSAccessibilityHorizontalScrollBarAttribute, + nil ] + ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperscrollbar.h b/vcl/osx/a11ywrapperscrollbar.h new file mode 100644 index 000000000000..a6d87079a31b --- /dev/null +++ b/vcl/osx/a11ywrapperscrollbar.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERSCROLLBAR_H +#define INCLUDED_VCL_OSX_A11YWRAPPERSCROLLBAR_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperScrollBar : AquaA11yWrapper +{ +} +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERSCROLLBAR_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperscrollbar.mm b/vcl/osx/a11ywrapperscrollbar.mm new file mode 100644 index 000000000000..3717cafc883b --- /dev/null +++ b/vcl/osx/a11ywrapperscrollbar.mm @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" + +#include "a11ywrapperscrollbar.h" + +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + +using namespace ::com::sun::star::accessibility; + +// Wrapper for AXScrollBar role + +@implementation AquaA11yWrapperScrollBar : AquaA11yWrapper + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames addObject: NSAccessibilityOrientationAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappersplitter.h b/vcl/osx/a11ywrappersplitter.h new file mode 100644 index 000000000000..18cdb6e0cdca --- /dev/null +++ b/vcl/osx/a11ywrappersplitter.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERSPLITTER_H +#define INCLUDED_VCL_OSX_A11YWRAPPERSPLITTER_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperSplitter : AquaA11yWrapper +{ +} +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERSPLITTER_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappersplitter.mm b/vcl/osx/a11ywrappersplitter.mm new file mode 100644 index 000000000000..66db6dc92f36 --- /dev/null +++ b/vcl/osx/a11ywrappersplitter.mm @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrappersplitter.h" + +using namespace ::com::sun::star::accessibility; + +// Wrapper for AXSplitter role + +@implementation AquaA11yWrapperSplitter : AquaA11yWrapper + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames addObject: NSAccessibilityOrientationAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperstatictext.h b/vcl/osx/a11ywrapperstatictext.h new file mode 100644 index 000000000000..a210d80b7550 --- /dev/null +++ b/vcl/osx/a11ywrapperstatictext.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERSTATICTEXT_H +#define INCLUDED_VCL_OSX_A11YWRAPPERSTATICTEXT_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperStaticText : AquaA11yWrapper +{ +} +-(id)titleAttribute; +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERSTATICTEXT_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrapperstatictext.mm b/vcl/osx/a11ywrapperstatictext.mm new file mode 100644 index 000000000000..ce0d7c425d92 --- /dev/null +++ b/vcl/osx/a11ywrapperstatictext.mm @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrapperstatictext.h" + +// Wrapper for AXStaticText role + +@implementation AquaA11yWrapperStaticText : AquaA11yWrapper + +-(id)titleAttribute { + NSString * title = [ super titleAttribute ]; + if ( [ title isEqualToString: [ super valueAttribute ] ] ) { + return [ NSString string ]; + } + return title; +} + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObject: NSAccessibilityTitleAttribute ]; + [ attributeNames removeObject: NSAccessibilitySharedTextUIElementsAttribute ]; + [ attributeNames removeObject: NSAccessibilitySharedCharacterRangeAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappertabgroup.h b/vcl/osx/a11ywrappertabgroup.h new file mode 100644 index 000000000000..b340d7506995 --- /dev/null +++ b/vcl/osx/a11ywrappertabgroup.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERTABGROUP_H +#define INCLUDED_VCL_OSX_A11YWRAPPERTABGROUP_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperTabGroup : AquaA11yWrapper +{ +} +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERTABGROUP_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappertabgroup.mm b/vcl/osx/a11ywrappertabgroup.mm new file mode 100644 index 000000000000..0174e43093e2 --- /dev/null +++ b/vcl/osx/a11ywrappertabgroup.mm @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrappertabgroup.h" + +// Wrapper for AXTabGroup role + +@implementation AquaA11yWrapperTabGroup : AquaA11yWrapper + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames addObjectsFromArray: [ NSArray arrayWithObjects: + NSAccessibilityContentsAttribute, + NSAccessibilityTabsAttribute, + nil ] + ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappertextarea.h b/vcl/osx/a11ywrappertextarea.h new file mode 100644 index 000000000000..a0cf4f68d6af --- /dev/null +++ b/vcl/osx/a11ywrappertextarea.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERTEXTAREA_H +#define INCLUDED_VCL_OSX_A11YWRAPPERTEXTAREA_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperTextArea : AquaA11yWrapper +{ +} +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERTEXTAREA_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappertextarea.mm b/vcl/osx/a11ywrappertextarea.mm new file mode 100644 index 000000000000..9b4fbf995733 --- /dev/null +++ b/vcl/osx/a11ywrappertextarea.mm @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrappertextarea.h" + +// Wrapper for AXTextArea role + +@implementation AquaA11yWrapperTextArea : AquaA11yWrapper + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObject: NSAccessibilityTitleAttribute ]; + [ attributeNames removeObject: NSAccessibilityEnabledAttribute ]; + [ attributeNames addObject: NSAccessibilityChildrenAttribute ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappertoolbar.h b/vcl/osx/a11ywrappertoolbar.h new file mode 100644 index 000000000000..64bd14250e9e --- /dev/null +++ b/vcl/osx/a11ywrappertoolbar.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_A11YWRAPPERTOOLBAR_H +#define INCLUDED_VCL_OSX_A11YWRAPPERTOOLBAR_H + +#include "osx/a11ywrapper.h" + +@interface AquaA11yWrapperToolbar : AquaA11yWrapper +{ +} +-(NSArray *)accessibilityAttributeNames; +@end + +#endif // INCLUDED_VCL_OSX_A11YWRAPPERTOOLBAR_H + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11ywrappertoolbar.mm b/vcl/osx/a11ywrappertoolbar.mm new file mode 100644 index 000000000000..9636d7882705 --- /dev/null +++ b/vcl/osx/a11ywrappertoolbar.mm @@ -0,0 +1,42 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/salinst.h" +#include "a11ywrappertoolbar.h" + +// Wrapper for AXToolbar role + +@implementation AquaA11yWrapperToolbar : AquaA11yWrapper + +-(NSArray *)accessibilityAttributeNames { + // Default Attributes + NSMutableArray * attributeNames = [ NSMutableArray arrayWithArray: [ super accessibilityAttributeNames ] ]; + // Special Attributes and removing unwanted attributes depending on role + [ attributeNames removeObjectsInArray: [ NSArray arrayWithObjects: + NSAccessibilityTitleAttribute, + NSAccessibilityEnabledAttribute, + nil ] + ]; + return attributeNames; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/clipboard.cxx b/vcl/osx/clipboard.cxx new file mode 100644 index 000000000000..72feefe5bdb6 --- /dev/null +++ b/vcl/osx/clipboard.cxx @@ -0,0 +1,372 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * 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 "comphelper/makesequence.hxx" +#include "comphelper/processfactory.hxx" + +#include <boost/assert.hpp> + +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::datatransfer::clipboard; +using namespace com::sun::star::lang; +using namespace com::sun::star::uno; +using namespace cppu; +using namespace osl; +using namespace std; +using namespace comphelper; + + +@implementation EventListener; + +-(EventListener*)initWithAquaClipboard: (AquaClipboard*) pcb +{ + self = [super init]; + + if (self) + pAquaClipboard = pcb; + + return self; +} + +-(void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString*)type +{ + if( pAquaClipboard ) + pAquaClipboard->provideDataForType(sender, type); +} + +-(void)applicationDidBecomeActive:(NSNotification*)aNotification +{ + if( pAquaClipboard ) + pAquaClipboard->applicationDidBecomeActive(aNotification); +} + +-(void)disposing +{ + pAquaClipboard = NULL; +} + +@end + + +OUString clipboard_getImplementationName() +{ + return OUString("com.sun.star.datatransfer.clipboard.AquaClipboard"); +} + +Sequence<OUString> clipboard_getSupportedServiceNames() +{ + return makeSequence(OUString("com.sun.star.datatransfer.clipboard.SystemClipboard")); +} + + +AquaClipboard::AquaClipboard(NSPasteboard* pasteboard, bool bUseSystemPasteboard) : + WeakComponentImplHelper3<XSystemClipboard, XFlushableClipboard, XServiceInfo>(m_aMutex), + mIsSystemPasteboard(bUseSystemPasteboard) +{ + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + + mrXMimeCntFactory = MimeContentTypeFactory::create(xContext); + + mpDataFlavorMapper = DataFlavorMapperPtr_t(new DataFlavorMapper()); + + if (pasteboard != NULL) + { + mPasteboard = pasteboard; + mIsSystemPasteboard = false; + } + else + { + mPasteboard = bUseSystemPasteboard ? [NSPasteboard generalPasteboard] : + [NSPasteboard pasteboardWithName: NSDragPboard]; + + if (mPasteboard == nil) + { + throw RuntimeException("AquaClipboard: Cannot create Cocoa pasteboard", + static_cast<XClipboardEx*>(this)); + } + } + + [mPasteboard retain]; + + mEventListener = [[EventListener alloc] initWithAquaClipboard: this]; + + if (mEventListener == nil) + { + [mPasteboard release]; + + throw RuntimeException( + OUString("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]; +} + + +Reference<XTransferable> SAL_CALL AquaClipboard::getContents() throw(RuntimeException) +{ + MutexGuard aGuard(m_aMutex); + + // Shortcut: If we are clipboard owner already we don't need + // to drag the data through the system clipboard + if (mXClipboardContent.is()) + { + return mXClipboardContent; + } + + return Reference<XTransferable>(new OSXTransferable(mrXMimeCntFactory, + mpDataFlavorMapper, + mPasteboard)); +} + + +void SAL_CALL AquaClipboard::setContents(const Reference<XTransferable>& xTransferable, + const Reference<XClipboardOwner>& xClipboardOwner) + throw( RuntimeException ) +{ + NSArray* types = xTransferable.is() ? + mpDataFlavorMapper->flavorSequenceToTypesArray(xTransferable->getTransferDataFlavors()) : + [NSArray array]; + + ClearableMutexGuard aGuard(m_aMutex); + + Reference<XClipboardOwner> oldOwner(mXClipboardOwner); + mXClipboardOwner = xClipboardOwner; + + Reference<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() throw( RuntimeException ) +{ + return OUString(); +} + + +sal_Int8 SAL_CALL AquaClipboard::getRenderingCapabilities() throw( RuntimeException ) +{ + return 0; +} + + +void SAL_CALL AquaClipboard::addClipboardListener(const Reference< XClipboardListener >& listener) + throw( RuntimeException ) +{ + MutexGuard aGuard(m_aMutex); + + if (!listener.is()) + throw IllegalArgumentException("empty reference", + static_cast<XClipboardEx*>(this), 1); + + mClipboardListeners.push_back(listener); +} + + +void SAL_CALL AquaClipboard::removeClipboardListener(const Reference< XClipboardListener >& listener) + throw( RuntimeException ) +{ + MutexGuard aGuard(m_aMutex); + + if (!listener.is()) + throw IllegalArgumentException("empty reference", + static_cast<XClipboardEx*>(this), 1); + + mClipboardListeners.remove(listener); +} + + +void AquaClipboard::applicationDidBecomeActive(NSNotification*) +{ + 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 + Reference<XClipboardOwner> oldOwner(mXClipboardOwner); + mXClipboardOwner.clear(); + + Reference<XTransferable> oldContent(mXClipboardContent); + mXClipboardContent.clear(); + + aGuard.clear(); + + if (oldOwner.is()) + { + fireLostClipboardOwnershipEvent(oldOwner, oldContent); + } + + fireClipboardChangedEvent(); + } +} + + +void AquaClipboard::fireClipboardChangedEvent() +{ + ClearableMutexGuard aGuard(m_aMutex); + + list<Reference< XClipboardListener > > listeners(mClipboardListeners); + ClipboardEvent aEvent; + + if (listeners.begin() != listeners.end()) + { + aEvent = ClipboardEvent(static_cast<OWeakObject*>(this), getContents()); + } + + aGuard.clear(); + + while (listeners.begin() != listeners.end()) + { + if (listeners.front().is()) + { + try { listeners.front()->changedContents(aEvent); } + catch (RuntimeException&) { } + } + listeners.pop_front(); + } +} + + +void AquaClipboard::fireLostClipboardOwnershipEvent(Reference<XClipboardOwner> oldOwner, Reference<XTransferable> oldContent) +{ + BOOST_ASSERT(oldOwner.is()); + + try { oldOwner->lostOwnership(static_cast<XClipboardEx*>(this), oldContent); } + catch(RuntimeException&) { } +} + + +void AquaClipboard::provideDataForType(NSPasteboard* sender, NSString* type) +{ + if( mXClipboardContent.is() ) + { + DataProviderPtr_t dp = mpDataFlavorMapper->getDataProvider(type, mXClipboardContent); + NSData* pBoardData = NULL; + + if (dp.get() != NULL) + { + pBoardData = (NSData*)dp->getSystemData(); + [sender setData: pBoardData forType: type]; + } + } +} + + +//------------------------------------------------ +// XFlushableClipboard +//------------------------------------------------ + +void SAL_CALL AquaClipboard::flushClipboard() + throw(RuntimeException) +{ + if (mXClipboardContent.is()) + { + Sequence<DataFlavor> flavorList = mXClipboardContent->getTransferDataFlavors(); + sal_uInt32 nFlavors = flavorList.getLength(); + bool bInternal(false); + + for (sal_uInt32 i = 0; i < nFlavors; i++) + { + NSString* sysType = mpDataFlavorMapper->openOfficeToSystemFlavor(flavorList[i], bInternal); + + if (sysType != NULL) + { + provideDataForType(mPasteboard, sysType); + } + } + mXClipboardContent.clear(); + } +} + + +NSPasteboard* AquaClipboard::getPasteboard() const +{ + return mPasteboard; +} + + +//------------------------------------------------- +// XServiceInfo +//------------------------------------------------- + +OUString SAL_CALL AquaClipboard::getImplementationName() throw( RuntimeException ) +{ + return clipboard_getImplementationName(); +} + + +sal_Bool SAL_CALL AquaClipboard::supportsService( const OUString& /*ServiceName*/ ) throw( RuntimeException ) +{ + return sal_False; +} + + +Sequence< OUString > SAL_CALL AquaClipboard::getSupportedServiceNames() throw( RuntimeException ) +{ + 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 000000000000..1424e34fc4d4 --- /dev/null +++ b/vcl/osx/clipboard.hxx @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_CLIPBOARD_HXX +#define INCLUDED_VCL_OSX_CLIPBOARD_HXX + +#include "DataFlavorMapping.hxx" +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <cppuhelper/compbase3.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 <boost/utility.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; + +// Promiss resolver function +- (void)pasteboard:(NSPasteboard*)sender provideDataForType:(NSString *)type; + +-(void)applicationDidBecomeActive:(NSNotification*)aNotification; + +-(void)disposing; +@end + + +class AquaClipboard : public ::cppu::BaseMutex, + public ::cppu::WeakComponentImplHelper3< com::sun::star::datatransfer::clipboard::XSystemClipboard, + com::sun::star::datatransfer::clipboard::XFlushableClipboard, + com::sun::star::lang::XServiceInfo >, + private ::boost::noncopyable +{ +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 = NULL, + bool bUseSystemClipboard = true); + + ~AquaClipboard(); + + //------------------------------------------------ + // XClipboard + //------------------------------------------------ + + virtual ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable > SAL_CALL getContents() + throw( ::com::sun::star::uno::RuntimeException ); + + virtual void SAL_CALL setContents( const ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable >& xTransferable, + const ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::clipboard::XClipboardOwner >& xClipboardOwner ) + throw( ::com::sun::star::uno::RuntimeException ); + + virtual OUString SAL_CALL getName() + throw( ::com::sun::star::uno::RuntimeException ); + + //------------------------------------------------ + // XClipboardEx + //------------------------------------------------ + + virtual sal_Int8 SAL_CALL getRenderingCapabilities() + throw( ::com::sun::star::uno::RuntimeException ); + + //------------------------------------------------ + // XClipboardNotifier + //------------------------------------------------ + + virtual void SAL_CALL addClipboardListener( const ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::clipboard::XClipboardListener >& listener ) + throw( ::com::sun::star::uno::RuntimeException ); + + virtual void SAL_CALL removeClipboardListener( const ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::clipboard::XClipboardListener >& listener ) + throw( ::com::sun::star::uno::RuntimeException ); + + //------------------------------------------------ + // XFlushableClipboard + //------------------------------------------------ + + virtual void SAL_CALL flushClipboard( ) throw( com::sun::star::uno::RuntimeException ); + + //------------------------------------------------ + // XServiceInfo + //------------------------------------------------ + + virtual OUString SAL_CALL getImplementationName() + throw(::com::sun::star::uno::RuntimeException); + + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) + throw(::com::sun::star::uno::RuntimeException); + + virtual ::com::sun::star::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() + throw(::com::sun::star::uno::RuntimeException); + + /* 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(::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::clipboard::XClipboardOwner> oldOwner, + ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable > oldContent); + + void pasteboardChangedOwner(); + + void provideDataForType(NSPasteboard* sender, NSString* type); + + void applicationDidBecomeActive(NSNotification* aNotification); + +private: + + /* Notify all registered XClipboardListener that the clipboard content + has changed. + */ + void fireClipboardChangedEvent(); + +private: + ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XMimeContentTypeFactory > mrXMimeCntFactory; + ::std::list< ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::clipboard::XClipboardListener > > mClipboardListeners; + ::com::sun::star::uno::Reference< ::com::sun::star::datatransfer::XTransferable > mXClipboardContent; + com::sun::star::uno::Reference< com::sun::star::datatransfer::clipboard::XClipboardOwner > mXClipboardOwner; + DataFlavorMapperPtr_t mpDataFlavorMapper; + bool mIsSystemPasteboard; + NSPasteboard* mPasteboard; + int mPasteboardChangeCount; + EventListener* mEventListener; +}; + +#endif // INCLUDED_VCL_OSX_CLIPBOARD_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/documentfocuslistener.cxx b/vcl/osx/documentfocuslistener.cxx new file mode 100644 index 000000000000..ac65f1e84338 --- /dev/null +++ b/vcl/osx/documentfocuslistener.cxx @@ -0,0 +1,241 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "documentfocuslistener.hxx" + +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> + +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + +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 ) + throw (RuntimeException) +{ + // 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 ) + throw( RuntimeException ) +{ + switch( aEvent.EventId ) + { + case AccessibleEventId::STATE_CHANGED: + try + { + sal_Int16 nState = AccessibleStateType::INVALID; + aEvent.NewValue >>= nState; + + if( AccessibleStateType::FOCUSED == nState ) + m_aFocusTracker.setFocusedObject( getAccessible(aEvent) ); + } + catch(const IndexOutOfBoundsException &) + { + OSL_TRACE("Focused object has invalid index in parent"); + } + 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); + } + + OSL_TRACE( "Invalidate all children called" ); + break; + default: + break; + } +} + +//------------------------------------------------------------------------------ + +Reference< XAccessible > DocumentFocusListener::getAccessible(const EventObject& aEvent ) + throw (IndexOutOfBoundsException, RuntimeException) +{ + Reference< XAccessible > xAccessible(aEvent.Source, UNO_QUERY); + + if( xAccessible.is() ) + return xAccessible; + + Reference< XAccessibleContext > xContext(aEvent.Source, UNO_QUERY); + + if( xContext.is() ) + { + Reference< XAccessible > xParent( xContext->getAccessibleParent() ); + if( xParent.is() ) + { + Reference< XAccessibleContext > xParentContext( xParent->getAccessibleContext() ); + if( xParentContext.is() ) + { + return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() ); + } + } + } + + return Reference< XAccessible >(); +} + +//------------------------------------------------------------------------------ + +void DocumentFocusListener::attachRecursive(const Reference< XAccessible >& xAccessible) + throw (IndexOutOfBoundsException, RuntimeException) +{ + Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext(); + + if( xContext.is() ) + attachRecursive(xAccessible, xContext); +} + +//------------------------------------------------------------------------------ + +void DocumentFocusListener::attachRecursive( + const Reference< XAccessible >& xAccessible, + const Reference< XAccessibleContext >& xContext +) throw (IndexOutOfBoundsException, RuntimeException) +{ + if( xContext.is() ) + { + Reference< XAccessibleStateSet > xStateSet = xContext->getAccessibleStateSet(); + + if( xStateSet.is() ) + attachRecursive(xAccessible, xContext, xStateSet); + } +} + +//------------------------------------------------------------------------------ + +void DocumentFocusListener::attachRecursive( + const Reference< XAccessible >& xAccessible, + const Reference< XAccessibleContext >& xContext, + const Reference< XAccessibleStateSet >& xStateSet +) throw (IndexOutOfBoundsException,RuntimeException) +{ + if( xStateSet->contains(AccessibleStateType::FOCUSED ) ) + m_aFocusTracker.setFocusedObject( xAccessible ); + + Reference< XAccessibleEventBroadcaster > xBroadcaster = + Reference< XAccessibleEventBroadcaster >(xContext, UNO_QUERY); + + // If not already done, add the broadcaster to the list and attach as listener. + if( xBroadcaster.is() && m_aRefList.insert(xBroadcaster).second ) + { + xBroadcaster->addAccessibleEventListener(static_cast< XAccessibleEventListener *>(this)); + + if( ! xStateSet->contains(AccessibleStateType::MANAGES_DESCENDANTS ) ) + { + sal_Int32 n, nmax = xContext->getAccessibleChildCount(); + for( n = 0; n < nmax; n++ ) + { + Reference< XAccessible > xChild( xContext->getAccessibleChild( n ) ); + + if( xChild.is() ) + attachRecursive(xChild); + } + } + } +} + +//------------------------------------------------------------------------------ + +void DocumentFocusListener::detachRecursive(const Reference< XAccessible >& xAccessible) + throw (IndexOutOfBoundsException, RuntimeException) +{ + Reference< XAccessibleContext > xContext = xAccessible->getAccessibleContext(); + + if( xContext.is() ) + detachRecursive(xAccessible, xContext); +} + +//------------------------------------------------------------------------------ + +void DocumentFocusListener::detachRecursive( + const Reference< XAccessible >& xAccessible, + const Reference< XAccessibleContext >& xContext +) throw (IndexOutOfBoundsException, RuntimeException) +{ + Reference< XAccessibleStateSet > xStateSet = xContext->getAccessibleStateSet(); + + if( xStateSet.is() ) + detachRecursive(xAccessible, xContext, xStateSet); +} + +//------------------------------------------------------------------------------ + +void DocumentFocusListener::detachRecursive( + const Reference< XAccessible >&, + const Reference< XAccessibleContext >& xContext, + const Reference< XAccessibleStateSet >& xStateSet +) throw (IndexOutOfBoundsException, RuntimeException) +{ + Reference< XAccessibleEventBroadcaster > xBroadcaster = + Reference< XAccessibleEventBroadcaster >(xContext, UNO_QUERY); + + if( xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster) ) + { + xBroadcaster->removeAccessibleEventListener(static_cast< XAccessibleEventListener *>(this)); + + if( ! xStateSet->contains(AccessibleStateType::MANAGES_DESCENDANTS ) ) + { + sal_Int32 n, nmax = xContext->getAccessibleChildCount(); + for( n = 0; n < nmax; n++ ) + { + Reference< XAccessible > xChild( xContext->getAccessibleChild( n ) ); + + if( xChild.is() ) + detachRecursive(xChild); + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/documentfocuslistener.hxx b/vcl/osx/documentfocuslistener.hxx new file mode 100644 index 000000000000..1a604a677df5 --- /dev/null +++ b/vcl/osx/documentfocuslistener.hxx @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_DOCUMENTFOCUSLISTENER_HXX +#define INCLUDED_VCL_OSX_DOCUMENTFOCUSLISTENER_HXX + +#include <com/sun/star/accessibility/XAccessibleEventListener.hpp> + +#include <cppuhelper/implbase1.hxx> + +#include "osx/a11yfocustracker.hxx" + +#include <set> + +// ------------------------- +// - DocumentFocusListener - +// ------------------------- + +class DocumentFocusListener : + public ::cppu::WeakImplHelper1< ::com::sun::star::accessibility::XAccessibleEventListener > +{ + +public: + + DocumentFocusListener(AquaA11yFocusTracker& rTracker); + + void attachRecursive( + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible >& xAccessible + ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + void attachRecursive( + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible >& xAccessible, + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >& xContext + ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + void attachRecursive( + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible >& xAccessible, + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >& xContext, + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleStateSet >& xStateSet + ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + void detachRecursive( + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible >& xAccessible + ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + void detachRecursive( + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible >& xAccessible, + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >& xContext + ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + void detachRecursive( + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible >& xAccessible, + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >& xContext, + const ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleStateSet >& xStateSet + ) throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + static ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessible > getAccessible(const ::com::sun::star::lang::EventObject& aEvent ) + throw (::com::sun::star::lang::IndexOutOfBoundsException, ::com::sun::star::uno::RuntimeException); + + // XEventListener + virtual void SAL_CALL disposing( const ::com::sun::star::lang::EventObject& Source ) + throw (::com::sun::star::uno::RuntimeException); + + // XAccessibleEventListener + virtual void SAL_CALL notifyEvent( const ::com::sun::star::accessibility::AccessibleEventObject& aEvent ) + throw( ::com::sun::star::uno::RuntimeException ); + +private: + std::set< ::com::sun::star::uno::Reference< ::com::sun::star::uno::XInterface > > m_aRefList; + + AquaA11yFocusTracker& m_aFocusTracker; +}; + +#endif // INCLUDED_VCL_OSX_DOCUMENTFOCUSLISTENER_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/printaccessoryview.mm b/vcl/osx/printaccessoryview.mm new file mode 100644 index 000000000000..517dcc09975f --- /dev/null +++ b/vcl/osx/printaccessoryview.mm @@ -0,0 +1,1381 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "sal/config.h" + +#include "tools/resary.hxx" + +#include "vcl/print.hxx" +#include "vcl/image.hxx" +#include "vcl/virdev.hxx" +#include "vcl/svapp.hxx" +#include "vcl/unohelp.hxx" + +#include "osx/printview.h" +#include "osx/salinst.h" +#include "quartz/utils.h" + +#include "svdata.hxx" +#include "svids.hrc" + +#include "com/sun/star/i18n/XBreakIterator.hpp" +#include "com/sun/star/i18n/WordType.hpp" + +#include <map> + +using namespace vcl; +using namespace com::sun::star; +using namespace com::sun::star::beans; +using namespace com::sun::star::uno; + +/* Note: the accesory view as implemented here is already deprecated in Leopard. Unfortunately + as long as our baseline is Tiger we cannot gain the advantages over multiple accessory views + as well havs having accessory views AND a preview (as long as you are linked vs. 10.4 libraries + the preview insists on not being present. This is unfortunate. +*/ + +class ControllerProperties; + +@interface ControlTarget : NSObject +{ + ControllerProperties* mpController; +} +-(id)initWithControllerMap: (ControllerProperties*)pController; +-(void)triggered:(id)pSender; +-(void)triggeredNumeric:(id)pSender; +-(void)triggeredPreview:(id)pSender; +-(void)dealloc; +@end + + +class ControllerProperties +{ + vcl::PrinterController* mpController; + std::map< int, rtl::OUString > maTagToPropertyName; + std::map< int, sal_Int32 > maTagToValueInt; + std::map< NSView*, NSView* > maViewPairMap; + std::vector< NSObject* > maViews; + int mnNextTag; + sal_Int32 mnLastPageCount; + PrintAccessoryViewState* mpState; + NSPrintOperation* mpOp; + NSView* mpAccessoryView; + NSTabView* mpTabView; + NSBox* mpPreviewBox; + NSImageView* mpPreview; + NSTextField* mpPageEdit; + NSStepper* mpStepper; + NSTextView* mpPagesLabel; + ResStringArray maLocalizedStrings; + + public: + ControllerProperties( vcl::PrinterController* i_pController, + NSPrintOperation* i_pOp, + NSView* i_pAccessoryView, + NSTabView* i_pTabView, + PrintAccessoryViewState* i_pState ) + : mpController( i_pController ), + mnNextTag( 0 ), + mnLastPageCount( i_pController->getFilteredPageCount() ), + mpState( i_pState ), + mpOp( i_pOp ), + mpAccessoryView( i_pAccessoryView ), + mpTabView( i_pTabView ), + mpPreviewBox( nil ), + mpPreview( nil ), + mpPageEdit( nil ), + mpStepper( nil ), + mpPagesLabel( nil ), + maLocalizedStrings( VclResId( SV_PRINT_NATIVE_STRINGS ) ) + { + mpState->bNeedRestart = false; + DBG_ASSERT( maLocalizedStrings.Count() >= 5, "resources not found !" ); + } + + rtl::OUString getMoreString() + { + return maLocalizedStrings.Count() >= 4 + ? OUString( maLocalizedStrings.GetString( 3 ) ) + : OUString( "More" ); + } + + rtl::OUString getPrintSelectionString() + { + return maLocalizedStrings.Count() >= 5 + ? OUString( maLocalizedStrings.GetString( 4 ) ) + : OUString( "Print selection only" ); + } + + void updatePrintJob() + { + // TODO: refresh page count etc from mpController + + // page range may have changed depending on options + sal_Int32 nPages = mpController->getFilteredPageCount(); + #if OSL_DEBUG_LEVEL > 1 + if( nPages != mnLastPageCount ) + fprintf( stderr, "trouble: number of pages changed from %ld to %ld !\n", mnLastPageCount, nPages ); + #endif + mpState->bNeedRestart = (nPages != mnLastPageCount); + NSTabViewItem* pItem = [mpTabView selectedTabViewItem]; + if( pItem ) + mpState->nLastPage = [mpTabView indexOfTabViewItem: pItem]; + else + mpState->nLastPage = 0; + mnLastPageCount = nPages; + if( mpState->bNeedRestart ) + { + // Warning: bad hack ahead + // Apple does not give us a chance of changing the page count, + // and they don't let us cancel the dialog either + // hack: send a cancel message to the window displaying our views. + // this is ugly. + NSWindow* pNSWindow = [NSApp modalWindow]; + if( pNSWindow ) + [pNSWindow cancelOperation: nil]; + [[mpOp printInfo] setJobDisposition: NSPrintCancelJob]; + } + else + { + sal_Int32 nPage = [mpStepper intValue]; + updatePreviewImage( nPage-1 ); + } + } + + int addNameTag( const rtl::OUString& i_rPropertyName ) + { + int nNewTag = mnNextTag++; + maTagToPropertyName[ nNewTag ] = i_rPropertyName; + return nNewTag; + } + + int addNameAndValueTag( const rtl::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, rtl::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() ) + { + PropertyValue* pVal = mpController->getValue( name_it->second ); + if( pVal ) + { + pVal->Value <<= value_it->second; + updatePrintJob(); + } + } + } + + void changePropertyWithIntValue( int i_nTag, sal_Int64 i_nValue ) + { + std::map< int, rtl::OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag ); + if( name_it != maTagToPropertyName.end() ) + { + PropertyValue* pVal = mpController->getValue( name_it->second ); + if( pVal ) + { + pVal->Value <<= i_nValue; + updatePrintJob(); + } + } + } + + void changePropertyWithBoolValue( int i_nTag, sal_Bool i_bValue ) + { + std::map< int, rtl::OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag ); + if( name_it != maTagToPropertyName.end() ) + { + PropertyValue* pVal = mpController->getValue( name_it->second ); + if( pVal ) + { + // ugly + if( name_it->second.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("PrintContent")) ) + pVal->Value <<= i_bValue ? sal_Int32(2) : sal_Int32(0); + else + pVal->Value <<= i_bValue; + updatePrintJob(); + } + } + } + + void changePropertyWithStringValue( int i_nTag, const rtl::OUString& i_rValue ) + { + std::map< int, rtl::OUString >::const_iterator name_it = maTagToPropertyName.find( i_nTag ); + if( name_it != maTagToPropertyName.end() ) + { + PropertyValue* pVal = mpController->getValue( name_it->second ); + if( pVal ) + { + pVal->Value <<= i_rValue; + updatePrintJob(); + } + } + } + + 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 = (NSControl*)pObj; + else if( [pObj isKindOfClass: [NSCell class]] ) + pCell = (NSCell*)pObj; + + int nTag = pCtrl ? [pCtrl tag] : + pCell ? [pCell tag] : + -1; + + std::map< int, rtl::OUString >::const_iterator name_it = maTagToPropertyName.find( nTag ); + if( name_it != maTagToPropertyName.end() && ! name_it->second.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("PrintContent")) ) + { + BOOL bEnabled = mpController->isUIOptionEnabled( name_it->second ) ? YES : NO; + if( pCtrl ) + { + [pCtrl setEnabled: bEnabled]; + NSView* pOther = getPair( pCtrl ); + if( pOther && [pOther isKindOfClass: [NSControl class]] ) + [(NSControl*)pOther setEnabled: bEnabled]; + } + else if( pCell ) + [pCell setEnabled: bEnabled]; + + } + } + } + + void updatePreviewImage( sal_Int32 i_nPage ) + { + sal_Int32 nPages = mpController->getFilteredPageCount(); + NSRect aViewFrame = [mpPreview frame]; + Size aPixelSize( static_cast<long>(aViewFrame.size.width), + static_cast<long>(aViewFrame.size.height) ); + if( i_nPage >= 0 && nPages > i_nPage ) + { + GDIMetaFile aMtf; + PrinterController::PageSize aPageSize( mpController->getFilteredPageFile( i_nPage, aMtf, false ) ); + VirtualDevice aDev; + if( mpController->getPrinter()->GetPrinterOptions().IsConvertToGreyscales() ) + aDev.SetDrawMode( aDev.GetDrawMode() | ( DRAWMODE_GRAYLINE | DRAWMODE_GRAYFILL | DRAWMODE_GRAYTEXT | + DRAWMODE_GRAYBITMAP | DRAWMODE_GRAYGRADIENT ) ); + // see salprn.cxx, currently we pretend to be a 720dpi device on printers + aDev.SetReferenceDevice( 720, 720 ); + aDev.EnableOutput( TRUE ); + Size aLogicSize( aDev.PixelToLogic( aPixelSize, MapMode( MAP_100TH_MM ) ) ); + double fScaleX = double(aLogicSize.Width())/double(aPageSize.aSize.Width()); + double fScaleY = double(aLogicSize.Height())/double(aPageSize.aSize.Height()); + double fScale = (fScaleX < fScaleY) ? fScaleX : fScaleY; + // #i104784# if we render the page too small then rounding issues result in + // layout artifacts looking really bad. So scale the page unto a device that is not + // full page size but not too small either. This also results in much better visual + // quality of the preview, e.g. when its height approaches the number of text lines + if( fScale < 0.1 ) + fScale = 0.1; + aMtf.WindStart(); + aMtf.Scale( fScale, fScale ); + aMtf.WindStart(); + aLogicSize.Width() = long(double(aPageSize.aSize.Width()) * fScale); + aLogicSize.Height() = long(double(aPageSize.aSize.Height()) * fScale); + aPixelSize = aDev.LogicToPixel( aLogicSize, MapMode( MAP_100TH_MM ) ); + aDev.SetOutputSizePixel( aPixelSize ); + aMtf.WindStart(); + aDev.SetMapMode( MapMode( MAP_100TH_MM ) ); + aMtf.Play( &aDev, Point( 0, 0 ), aLogicSize ); + aDev.EnableMapMode( FALSE ); + Image aImage( aDev.GetBitmap( Point( 0, 0 ), aPixelSize ) ); + NSImage* pImage = CreateNSImage( aImage ); + [mpPreview setImage: [pImage autorelease]]; + } + else + [mpPreview setImage: nil]; + } + + void setupPreview( ControlTarget* i_pCtrlTarget ) + { + if( maLocalizedStrings.Count() < 3 ) + return; + + // get the preview control + NSRect aPreviewFrame = [mpAccessoryView frame]; + aPreviewFrame.origin.x = 0; + aPreviewFrame.origin.y = 5; + aPreviewFrame.size.width = 190; + aPreviewFrame.size.height -= 7; + + // create a box to put the preview controls in + mpPreviewBox = [[NSBox alloc] initWithFrame: aPreviewFrame]; + [mpPreviewBox setTitle: [CreateNSString( maLocalizedStrings.GetString( 0 ) ) autorelease]]; + [mpAccessoryView addSubview: [mpPreviewBox autorelease]]; + + // now create the image view of the preview + NSSize aMargins = [mpPreviewBox contentViewMargins]; + aPreviewFrame.origin.x = 0; + aPreviewFrame.origin.y = 34; + aPreviewFrame.size.width -= 2*(aMargins.width+1); + aPreviewFrame.size.height -= 61; + mpPreview = [[NSImageView alloc] initWithFrame: aPreviewFrame]; + [mpPreview setImageScaling: NSScaleProportionally]; + [mpPreview setImageAlignment: NSImageAlignCenter]; + [mpPreview setImageFrameStyle: NSImageFrameNone]; + [mpPreviewBox addSubview: [mpPreview autorelease]]; + + // add a label + sal_Int32 nPages = mpController->getFilteredPageCount(); + rtl::OUStringBuffer aBuf( 16 ); + aBuf.appendAscii( "/ " ); + aBuf.append( rtl::OUString::number( nPages ) ); + + NSString* pText = CreateNSString( aBuf.makeStringAndClear() ); + NSRect aTextRect = { { 100, 5 }, { 100, 22 } }; + mpPagesLabel = [[NSTextView alloc] initWithFrame: aTextRect]; + [mpPagesLabel setFont: [NSFont controlContentFontOfSize: 0]]; + [mpPagesLabel setEditable: NO]; + [mpPagesLabel setSelectable: NO]; + [mpPagesLabel setDrawsBackground: NO]; + [mpPagesLabel setString: [pText autorelease]]; + [mpPagesLabel setToolTip: [CreateNSString( maLocalizedStrings.GetString( 2 ) ) autorelease]]; + [mpPreviewBox addSubview: [mpPagesLabel autorelease]]; + + NSRect aFieldRect = { { 45, 5 }, { 35, 25 } }; + mpPageEdit = [[NSTextField alloc] initWithFrame: aFieldRect]; + [mpPageEdit setEditable: YES]; + [mpPageEdit setSelectable: YES]; + [mpPageEdit setDrawsBackground: YES]; + [mpPageEdit setToolTip: [CreateNSString( maLocalizedStrings.GetString( 1 ) ) autorelease]]; + [mpPreviewBox addSubview: [mpPageEdit autorelease]]; + + // add a stepper control + NSRect aStepFrame = { { 85, 5 }, { 15, 25 } }; + mpStepper = [[NSStepper alloc] initWithFrame: aStepFrame]; + [mpStepper setIncrement: 1]; + [mpStepper setValueWraps: NO]; + [mpPreviewBox addSubview: [mpStepper autorelease]]; + + // constrain the text field to decimal numbers + NSNumberFormatter* pFormatter = [[NSNumberFormatter alloc] init]; + [pFormatter setFormatterBehavior: NSNumberFormatterBehavior10_4]; + [pFormatter setMinimum: [[NSNumber numberWithInt: 1] autorelease]]; + [pFormatter setMaximum: [[NSNumber numberWithInt: nPages] autorelease]]; + [pFormatter setNumberStyle: NSNumberFormatterDecimalStyle]; + [pFormatter setAllowsFloats: NO]; + [pFormatter setMaximumFractionDigits: 0]; + [mpPageEdit setFormatter: pFormatter]; + [mpStepper setMinValue: 1]; + [mpStepper setMaxValue: nPages]; + + [mpPageEdit setIntValue: 1]; + [mpStepper setIntValue: 1]; + + // connect target and action + [mpStepper setTarget: i_pCtrlTarget]; + [mpStepper setAction: @selector(triggeredPreview:)]; + [mpPageEdit setTarget: i_pCtrlTarget]; + [mpPageEdit setAction: @selector(triggeredPreview:)]; + + // set first preview image + updatePreviewImage( 0 ); + } + + void changePreview( NSObject* i_pSender ) + { + if( [i_pSender isMemberOfClass: [NSTextField class]] ) + { + NSTextField* pField = (NSTextField*)i_pSender; + if( pField == mpPageEdit ) // sanity check + { + sal_Int32 nPage = [pField intValue]; + [mpStepper setIntValue: nPage]; + updatePreviewImage( nPage-1 ); + } + } + else if( [i_pSender isMemberOfClass: [NSStepper class]] ) + { + NSStepper* pStepper = (NSStepper*)i_pSender; + if( pStepper == mpStepper ) // sanity check + { + sal_Int32 nPage = [pStepper intValue]; + [mpPageEdit setIntValue: nPage]; + updatePreviewImage( nPage-1 ); + } + } + } +}; + +static void filterAccelerator( rtl::OUString& io_rText ) +{ + rtl::OUStringBuffer aBuf( io_rText.getLength() ); + for( sal_Int32 nIndex = 0; nIndex != -1; ) + aBuf.append( io_rText.getToken( 0, '~', nIndex ) ); + io_rText = 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 = (NSPopUpButton*)pSender; + NSMenuItem* pSelected = [pBtn selectedItem]; + if( pSelected ) + { + int nTag = [pSelected tag]; + mpController->changePropertyWithIntValue( nTag ); + } + } + else if( [pSender isMemberOfClass: [NSButton class]] ) + { + NSButton* pBtn = (NSButton*)pSender; + int nTag = [pBtn tag]; + mpController->changePropertyWithBoolValue( nTag, [pBtn state] == NSOnState ); + } + else if( [pSender isMemberOfClass: [NSMatrix class]] ) + { + NSObject* pObj = [(NSMatrix*)pSender selectedCell]; + if( [pObj isMemberOfClass: [NSButtonCell class]] ) + { + NSButtonCell* pCell = (NSButtonCell*)pObj; + int nTag = [pCell tag]; + mpController->changePropertyWithIntValue( nTag ); + } + } + else if( [pSender isMemberOfClass: [NSTextField class]] ) + { + NSTextField* pField = (NSTextField*)pSender; + int nTag = [pField tag]; + rtl::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 = (NSTextField*)pSender; + int nTag = [pField tag]; + sal_Int64 nValue = [pField intValue]; + + NSView* pOther = mpController->getPair( pField ); + if( pOther ) + [(NSControl*)pOther setIntValue: nValue]; + + mpController->changePropertyWithIntValue( nTag, nValue ); + } + else if( [pSender isMemberOfClass: [NSStepper class]] ) + { + NSStepper* pStep = (NSStepper*)pSender; + int nTag = [pStep tag]; + sal_Int64 nValue = [pStep intValue]; + + NSView* pOther = mpController->getPair( pStep ); + if( pOther ) + [(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)triggeredPreview:(id)pSender +{ + mpController->changePreview( pSender ); +} +-(void)dealloc +{ + delete mpController; + [super dealloc]; +} +@end + +struct ColumnItem +{ + NSControl* pControl; + long nOffset; + NSControl* pSubControl; + + ColumnItem( NSControl* i_pControl = nil, long i_nOffset = 0, NSControl* i_pSub = nil ) + : pControl( i_pControl ) + , nOffset( i_nOffset ) + , pSubControl( i_pSub ) + {} + + long getWidth() const + { + long nWidth = 0; + if( pControl ) + { + NSRect aCtrlRect = [pControl frame]; + nWidth = aCtrlRect.size.width; + nWidth += nOffset; + if( pSubControl ) + { + NSRect aSubRect = [pSubControl frame]; + nWidth += aSubRect.size.width; + nWidth += aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width); + } + } + return nWidth; + } +}; + +static void adjustViewAndChildren( NSView* pNSView, NSSize& rMaxSize, + std::vector< ColumnItem >& rLeftColumn, + std::vector< ColumnItem >& rRightColumn + ) +{ + // balance columns + + // first get overall column widths + long nLeftWidth = 0; + long nRightWidth = 0; + for( size_t i = 0; i < rLeftColumn.size(); i++ ) + { + long nW = rLeftColumn[i].getWidth(); + if( nW > nLeftWidth ) + nLeftWidth = nW; + } + for( size_t i = 0; i < rRightColumn.size(); i++ ) + { + long nW = rRightColumn[i].getWidth(); + if( nW > nRightWidth ) + nRightWidth = nW; + } + + // right align left column + for( size_t i = 0; i < rLeftColumn.size(); i++ ) + { + if( rLeftColumn[i].pControl ) + { + NSRect aCtrlRect = [rLeftColumn[i].pControl frame]; + long nX = nLeftWidth - aCtrlRect.size.width; + if( rLeftColumn[i].pSubControl ) + { + NSRect aSubRect = [rLeftColumn[i].pSubControl frame]; + nX -= aSubRect.size.width + (aSubRect.origin.x - (aCtrlRect.origin.x + aCtrlRect.size.width)); + aSubRect.origin.x = nLeftWidth - aSubRect.size.width; + [rLeftColumn[i].pSubControl setFrame: aSubRect]; + } + aCtrlRect.origin.x = nX; + [rLeftColumn[i].pControl setFrame: aCtrlRect]; + } + } + + // left align right column + for( size_t i = 0; i < rRightColumn.size(); i++ ) + { + if( rRightColumn[i].pControl ) + { + NSRect aCtrlRect = [rRightColumn[i].pControl frame]; + long nX = nLeftWidth + 3; + if( rRightColumn[i].pSubControl ) + { + NSRect aSubRect = [rRightColumn[i].pSubControl frame]; + aSubRect.origin.x = nX + aSubRect.origin.x - aCtrlRect.origin.x; + [rRightColumn[i].pSubControl setFrame: aSubRect]; + } + aCtrlRect.origin.x = nX; + [rRightColumn[i].pControl setFrame: aCtrlRect]; + } + } + + NSArray* pSubViews = [pNSView subviews]; + unsigned int nViews = [pSubViews count]; + NSRect aUnion = { { 0, 0 }, { 0, 0 } }; + + // 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 = (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 rtl::OUString& i_rText ) +{ + NSString* pText = CreateNSString( i_rText ); + NSRect aTextRect = { { 0, 0 }, {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 rtl::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, + sal_True ); + nRet = aBoundary.endPos; + } + return nRet; +} + +static void linebreakCell( NSCell* pBtn, const rtl::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 ) + { + rtl::OUStringBuffer aBuf( i_rText ); + aBuf[nIndex] = '\n'; + pText = CreateNSString( aBuf.makeStringAndClear() ); + [pBtn setTitle: pText]; + [pText release]; + } + } +} + +static void addSubgroup( NSView* pCurParent, long& rCurY, const rtl::OUString& rText ) +{ + NSControl* pTextView = createLabel( rText ); + [pCurParent addSubview: [pTextView autorelease]]; + NSRect aTextRect = [pTextView frame]; + // move to nCurY + aTextRect.origin.y = rCurY - aTextRect.size.height; + [pTextView setFrame: aTextRect]; + + NSRect aSepRect = { { aTextRect.size.width + 1, aTextRect.origin.y }, { 100, 6 } }; + NSBox* pBox = [[NSBox alloc] initWithFrame: aSepRect]; + [pBox setBoxType: NSBoxSeparator]; + [pCurParent addSubview: [pBox autorelease]]; + + // update nCurY + rCurY = aTextRect.origin.y - 5; +} + +static void addBool( NSView* pCurParent, long& rCurX, long& rCurY, long nAttachOffset, + const rtl::OUString& rText, sal_Bool bEnabled, + const rtl::OUString& rProperty, sal_Bool bValue, + std::vector<ColumnItem >& rRightColumn, + ControllerProperties* pControllerProperties, + ControlTarget* pCtrlTarget + ) +{ + NSRect aCheckRect = { { static_cast<CGFloat>(rCurX + nAttachOffset), 0 }, { 0, 15 } }; + NSButton* pBtn = [[NSButton alloc] initWithFrame: aCheckRect]; + [pBtn setButtonType: NSSwitchButton]; + [pBtn setState: bValue ? NSOnState : NSOffState]; + if( ! bEnabled ) + [pBtn setEnabled: NO]; + linebreakCell( [pBtn cell], rText ); + [pBtn sizeToFit]; + + rRightColumn.push_back( ColumnItem( pBtn ) ); + + // connect target + [pBtn setTarget: pCtrlTarget]; + [pBtn setAction: @selector(triggered:)]; + int nTag = pControllerProperties->addNameTag( rProperty ); + pControllerProperties->addObservedControl( pBtn ); + [pBtn setTag: nTag]; + + aCheckRect = [pBtn frame]; + // #i115837# add a murphy factor; it can apparently occasionally happen + // that sizeToFit does not a perfect job and that the button linebreaks again + // if - and only if - there is already a '\n' contained in the text and the width + // is minimally of + aCheckRect.size.width += 1; + + // move to rCurY + aCheckRect.origin.y = rCurY - aCheckRect.size.height; + [pBtn setFrame: aCheckRect]; + + [pCurParent addSubview: [pBtn autorelease]]; + + // update rCurY + rCurY = aCheckRect.origin.y - 5; +} + +static void addRadio( NSView* pCurParent, long& rCurX, long& rCurY, long nAttachOffset, + const rtl::OUString& rText, + const rtl::OUString& rProperty, Sequence< rtl::OUString > rChoices, sal_Int32 nSelectValue, + std::vector<ColumnItem >& rLeftColumn, + std::vector<ColumnItem >& rRightColumn, + ControllerProperties* pControllerProperties, + ControlTarget* pCtrlTarget + ) +{ + sal_Int32 nOff = 0; + if( rText.getLength() ) + { + // add a label + NSControl* pTextView = createLabel( rText ); + NSRect aTextRect = [pTextView frame]; + aTextRect.origin.x = rCurX + nAttachOffset; + [pCurParent addSubview: [pTextView autorelease]]; + + rLeftColumn.push_back( ColumnItem( pTextView ) ); + + // move to nCurY + aTextRect.origin.y = rCurY - aTextRect.size.height; + [pTextView setFrame: aTextRect]; + + // update nCurY + rCurY = aTextRect.origin.y - 5; + + // indent the radio group relative to the text + // nOff = 20; + } + + // setup radio matrix + NSButtonCell* pProto = [[NSButtonCell alloc] init]; + + NSRect aRadioRect = { { static_cast<CGFloat>(rCurX + nOff), 0 }, { static_cast<CGFloat>(280 - rCurX), static_cast<CGFloat>(5*rChoices.getLength()) } }; + [pProto setTitle: @"RadioButtonGroup"]; + [pProto setButtonType: NSRadioButton]; + NSMatrix* pMatrix = [[NSMatrix alloc] initWithFrame: aRadioRect + mode: NSRadioModeMatrix + prototype: (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]; + filterAccelerator( rChoices[m] ); + linebreakCell( pCell, rChoices[m] ); + // connect target and action + [pCell setTarget: pCtrlTarget]; + [pCell setAction: @selector(triggered:)]; + int nTag = pControllerProperties->addNameAndValueTag( rProperty, m ); + pControllerProperties->addObservedControl( pCell ); + [pCell setTag: nTag]; + // set current selection + if( nSelectValue == m ) + [pMatrix selectCellAtRow: m column: 0]; + } + [pMatrix sizeToFit]; + aRadioRect = [pMatrix frame]; + + // move it down, so it comes to the correct position + aRadioRect.origin.y = rCurY - aRadioRect.size.height; + [pMatrix setFrame: aRadioRect]; + [pCurParent addSubview: [pMatrix autorelease]]; + + rRightColumn.push_back( ColumnItem( pMatrix ) ); + + // update nCurY + rCurY = aRadioRect.origin.y - 5; + + [pProto release]; +} + +static void addList( NSView* pCurParent, long& rCurX, long& rCurY, long /*nAttachOffset*/, + const rtl::OUString& rText, + const rtl::OUString& rProperty, const Sequence< rtl::OUString > rChoices, sal_Int32 nSelectValue, + std::vector<ColumnItem >& rLeftColumn, + std::vector<ColumnItem >& rRightColumn, + ControllerProperties* pControllerProperties, + ControlTarget* pCtrlTarget + ) +{ + // don't indent attached lists, looks bad in the existing cases + NSControl* pTextView = createLabel( rText ); + [pCurParent addSubview: [pTextView autorelease]]; + rLeftColumn.push_back( ColumnItem( pTextView ) ); + NSRect aTextRect = [pTextView frame]; + aTextRect.origin.x = rCurX /* + nAttachOffset*/; + + // don't indent attached lists, looks bad in the existing cases + NSRect aBtnRect = { { rCurX /*+ nAttachOffset*/ + aTextRect.size.width, 0 }, { 0, 15 } }; + NSPopUpButton* pBtn = [[NSPopUpButton alloc] initWithFrame: aBtnRect pullsDown: NO]; + + // iterate options + for( sal_Int32 m = 0; m < rChoices.getLength(); m++ ) + { + NSString* pItemText = CreateNSString( rChoices[m] ); + [pBtn addItemWithTitle: pItemText]; + NSMenuItem* pItem = [pBtn itemWithTitle: pItemText]; + int nTag = pControllerProperties->addNameAndValueTag( rProperty, m ); + [pItem setTag: nTag]; + [pItemText release]; + } + + [pBtn selectItemAtIndex: nSelectValue]; + + // add the button to observed controls for enabled state changes + // also add a tag just for this purpose + pControllerProperties->addObservedControl( pBtn ); + [pBtn setTag: pControllerProperties->addNameTag( rProperty )]; + + [pBtn sizeToFit]; + [pCurParent addSubview: [pBtn autorelease]]; + + rRightColumn.push_back( ColumnItem( pBtn ) ); + + // connect target and action + [pBtn setTarget: pCtrlTarget]; + [pBtn setAction: @selector(triggered:)]; + + // move to nCurY + aBtnRect = [pBtn frame]; + aBtnRect.origin.y = rCurY - aBtnRect.size.height; + [pBtn setFrame: aBtnRect]; + + // align label + aTextRect.origin.y = aBtnRect.origin.y + (aBtnRect.size.height - aTextRect.size.height)/2; + [pTextView setFrame: aTextRect]; + + // update rCurY + rCurY = aBtnRect.origin.y - 5; +} + +static void addEdit( NSView* pCurParent, long& rCurX, long& rCurY, long nAttachOffset, + const rtl::OUString rCtrlType, + const rtl::OUString& rText, + const rtl::OUString& rProperty, const PropertyValue* pValue, + sal_Int64 nMinValue, sal_Int64 nMaxValue, + std::vector<ColumnItem >& rLeftColumn, + std::vector<ColumnItem >& rRightColumn, + ControllerProperties* pControllerProperties, + ControlTarget* pCtrlTarget + ) +{ + sal_Int32 nOff = 0; + if( rText.getLength() ) + { + // add a label + NSControl* pTextView = createLabel( rText ); + [pCurParent addSubview: [pTextView autorelease]]; + + rLeftColumn.push_back( ColumnItem( pTextView ) ); + + // move to nCurY + NSRect aTextRect = [pTextView frame]; + aTextRect.origin.x = rCurX + nAttachOffset; + aTextRect.origin.y = rCurY - aTextRect.size.height; + [pTextView setFrame: aTextRect]; + + // update nCurY + rCurY = aTextRect.origin.y - 5; + + // and set the offset for the real edit field + nOff = aTextRect.size.width + 5; + } + + NSRect aFieldRect = { { static_cast<CGFloat>(rCurX + nOff + nAttachOffset), 0 }, { 100, 25 } }; + NSTextField* pFieldView = [[NSTextField alloc] initWithFrame: aFieldRect]; + [pFieldView setEditable: YES]; + [pFieldView setSelectable: YES]; + [pFieldView setDrawsBackground: YES]; + [pFieldView sizeToFit]; // FIXME: this does nothing + [pCurParent addSubview: [pFieldView autorelease]]; + + rRightColumn.push_back( ColumnItem( pFieldView ) ); + + // add the field to observed controls for enabled state changes + // also add a tag just for this purpose + pControllerProperties->addObservedControl( pFieldView ); + int nTag = pControllerProperties->addNameTag( rProperty ); + [pFieldView setTag: nTag]; + // pControllerProperties->addNamedView( pFieldView, aPropertyName ); + + // move to nCurY + aFieldRect.origin.y = rCurY - aFieldRect.size.height; + [pFieldView setFrame: aFieldRect]; + + if( rCtrlType.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "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() ) + { + rtl::OUString aValue; + pValue->Value >>= aValue; + if( aValue.getLength() ) + { + NSString* pText = CreateNSString( aValue ); + [pFieldView setStringValue: pText]; + [pText release]; + } + } + } + + // update nCurY + rCurY = aFieldRect.origin.y - 5; +} + +// In 10.5 and later: +// 'setAccessoryView:' is deprecated + +// Make deprecation warnings just warnings in a -Werror compilation. + +#if HAVE_GCC_PRAGMA_DIAGNOSTIC_MODIFY +// #pragma GCC diagnostic push +#pragma GCC diagnostic warning "-Wdeprecated-declarations" +#endif + +@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; + + NSView* pCurParent = 0; + long nCurY = 0; + long nCurX = 0; + NSRect aViewFrame = { { 0, 0 }, {600, 400 } }; + NSRect aTabViewFrame = { { 190, 0 }, {410, 400 } }; + NSSize aMaxTabSize = { 0, 0 }; + NSView* pAccessoryView = [[NSView alloc] initWithFrame: aViewFrame]; + NSTabView* pTabView = [[NSTabView alloc] initWithFrame: aTabViewFrame]; + [pAccessoryView addSubview: [pTabView autorelease]]; + + sal_Bool bIgnoreSubgroup = sal_False; + + ControllerProperties* pControllerProperties = new ControllerProperties( pController, pOp, pAccessoryView, pTabView, pState ); + 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( int i = 0; i < rOptions.getLength(); i++ ) + { + Sequence< beans::PropertyValue > aOptProp; + rOptions[i].Value >>= aOptProp; + + rtl::OUString aCtrlType; + rtl::OUString aPropertyName; + Sequence< rtl::OUString > aChoices; + Sequence< sal_Bool > aChoicesDisabled; + sal_Int32 aSelectionChecked = 0; + for( int n = 0; n < aOptProp.getLength(); n++ ) + { + const beans::PropertyValue& rEntry( aOptProp[ n ] ); + if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("ControlType")) ) + { + rEntry.Value >>= aCtrlType; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Choices")) ) + { + rEntry.Value >>= aChoices; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("ChoicesDisabled")) ) + { + rEntry.Value >>= aChoicesDisabled; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Property")) ) + { + PropertyValue aVal; + rEntry.Value >>= aVal; + aPropertyName = aVal.Name; + if( aPropertyName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("PrintContent")) ) + aVal.Value >>= aSelectionChecked; + } + } + if( aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Radio")) && + aPropertyName.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("PrintContent")) && + aChoices.getLength() > 2 ) + { + bAddSelectionCheckBox = true; + bSelectionBoxEnabled = aChoicesDisabled.getLength() < 2 || ! aChoicesDisabled[2]; + bSelectionBoxChecked = (aSelectionChecked==2); + break; + } + } + + for( int i = 0; i < rOptions.getLength(); i++ ) + { + Sequence< beans::PropertyValue > aOptProp; + rOptions[i].Value >>= aOptProp; + + // extract ui element + bool bEnabled = true; + rtl::OUString aCtrlType; + rtl::OUString aText; + rtl::OUString aPropertyName; + rtl::OUString aGroupHint; + Sequence< rtl::OUString > aChoices; + sal_Int64 nMinValue = 0, nMaxValue = 0; + long nAttachOffset = 0; + sal_Bool bIgnore = sal_False; + + for( int n = 0; n < aOptProp.getLength(); n++ ) + { + const beans::PropertyValue& rEntry( aOptProp[ n ] ); + if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Text")) ) + { + rEntry.Value >>= aText; + filterAccelerator( aText ); + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("ControlType")) ) + { + rEntry.Value >>= aCtrlType; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Choices")) ) + { + rEntry.Value >>= aChoices; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Property")) ) + { + PropertyValue aVal; + rEntry.Value >>= aVal; + aPropertyName = aVal.Name; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Enabled")) ) + { + sal_Bool bValue = sal_True; + rEntry.Value >>= bValue; + bEnabled = bValue; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("MinValue")) ) + { + rEntry.Value >>= nMinValue; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("MaxValue")) ) + { + rEntry.Value >>= nMaxValue; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("AttachToDependency")) ) + { + nAttachOffset = 20; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("InternalUIOnly")) ) + { + rEntry.Value >>= bIgnore; + } + else if( rEntry.Name.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("GroupingHint")) ) + { + rEntry.Value >>= aGroupHint; + } + } + + if( aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Group")) || + aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Subgroup")) || + aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Radio")) || + aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("List")) || + aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Edit")) || + aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Range")) || + aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Bool")) ) + { + // since our build target is MacOSX 10.4 we can have only one accessory view + // so we have a single accessory view that is tabbed for grouping + if( aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Group")) + || ! pCurParent + || ( aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Subgroup")) && nCurY < -250 && ! bIgnore ) + ) + { + rtl::OUString aGroupTitle( aText ); + if( aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Subgroup")) ) + aGroupTitle = pControllerProperties->getMoreString(); + // set size of current parent + if( pCurParent ) + adjustViewAndChildren( pCurParent, aMaxTabSize, aLeftColumn, aRightColumn ); + + // new tab item + if( ! aText.getLength() ) + aText = OUString( "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]; + + // reset indent + nCurX = 20; + // reset Y + nCurY = 0; + // clear columns + aLeftColumn.clear(); + aRightColumn.clear(); + + if( bAddSelectionCheckBox ) + { + addBool( pCurParent, nCurX, nCurY, 0, + pControllerProperties->getPrintSelectionString(), bSelectionBoxEnabled, + OUString( "PrintContent" ), bSelectionBoxChecked, + aRightColumn, pControllerProperties, pCtrlTarget ); + bAddSelectionCheckBox = false; + } + } + + if( aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Subgroup")) && pCurParent ) + { + bIgnoreSubgroup = bIgnore; + if( bIgnore ) + continue; + + addSubgroup( pCurParent, nCurY, aText ); + } + else if( bIgnoreSubgroup || bIgnore ) + { + continue; + } + else if( aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Bool")) && pCurParent ) + { + sal_Bool bVal = sal_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.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("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.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("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.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("Edit")) || aCtrlType.equalsAsciiL(RTL_CONSTASCII_STRINGPARAM("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 ); + + // leave some space for the preview + if( aMaxTabSize.height < 200 ) + aMaxTabSize.height = 200; + + // 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]; + + pControllerProperties->setupPreview( pCtrlTarget ); + + // set the accessory view + [pOp setAccessoryView: [pAccessoryView autorelease]]; + + // set the current selecte tab item + if( pState->nLastPage >= 0 && pState->nLastPage < [pTabView numberOfTabViewItems] ) + [pTabView selectTabViewItemAtIndex: pState->nLastPage]; + + return pCtrlTarget; +} + +// #pragma GCC diagnostic pop + +@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 000000000000..d2f4ac006d71 --- /dev/null +++ b/vcl/osx/printview.mm @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "vcl/print.hxx" + +#include "osx/printview.h" +#include "osx/salprn.h" + +@implementation AquaPrintView +-(id)initWithController: (vcl::PrinterController*)pController withInfoPrinter: (AquaSalInfoPrinter*)pInfoPrinter +{ + NSRect aRect = { { 0, 0 }, [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 = (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; + NSPoint aPoint = { 0, 0 }; + return aPoint; +} + +-(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 = (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 000000000000..b9b4b09f6b0d --- /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 000000000000..856429aee5bd --- /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 000000000000..d39d10119c0c --- /dev/null +++ b/vcl/osx/res/MainMenu.nib/keyedobjects.nib diff --git a/vcl/osx/res/cursors/airbrush.png b/vcl/osx/res/cursors/airbrush.png Binary files differnew file mode 100644 index 000000000000..7ec780c4f9f9 --- /dev/null +++ b/vcl/osx/res/cursors/airbrush.png diff --git a/vcl/osx/res/cursors/ase.png b/vcl/osx/res/cursors/ase.png Binary files differnew file mode 100644 index 000000000000..a3a30e0bcdce --- /dev/null +++ b/vcl/osx/res/cursors/ase.png diff --git a/vcl/osx/res/cursors/asn.png b/vcl/osx/res/cursors/asn.png Binary files differnew file mode 100644 index 000000000000..7a140b1ec926 --- /dev/null +++ b/vcl/osx/res/cursors/asn.png diff --git a/vcl/osx/res/cursors/asne.png b/vcl/osx/res/cursors/asne.png Binary files differnew file mode 100644 index 000000000000..311506aeb349 --- /dev/null +++ b/vcl/osx/res/cursors/asne.png diff --git a/vcl/osx/res/cursors/asns.png b/vcl/osx/res/cursors/asns.png Binary files differnew file mode 100644 index 000000000000..1c8950eb28bc --- /dev/null +++ b/vcl/osx/res/cursors/asns.png diff --git a/vcl/osx/res/cursors/asnswe.png b/vcl/osx/res/cursors/asnswe.png Binary files differnew file mode 100644 index 000000000000..aae5246fbbc0 --- /dev/null +++ b/vcl/osx/res/cursors/asnswe.png diff --git a/vcl/osx/res/cursors/asnw.png b/vcl/osx/res/cursors/asnw.png Binary files differnew file mode 100644 index 000000000000..9fd0036df077 --- /dev/null +++ b/vcl/osx/res/cursors/asnw.png diff --git a/vcl/osx/res/cursors/ass.png b/vcl/osx/res/cursors/ass.png Binary files differnew file mode 100644 index 000000000000..bee09e736ad1 --- /dev/null +++ b/vcl/osx/res/cursors/ass.png diff --git a/vcl/osx/res/cursors/asse.png b/vcl/osx/res/cursors/asse.png Binary files differnew file mode 100644 index 000000000000..d7883211d44f --- /dev/null +++ b/vcl/osx/res/cursors/asse.png diff --git a/vcl/osx/res/cursors/assw.png b/vcl/osx/res/cursors/assw.png Binary files differnew file mode 100644 index 000000000000..0b0a496a52ec --- /dev/null +++ b/vcl/osx/res/cursors/assw.png diff --git a/vcl/osx/res/cursors/asw.png b/vcl/osx/res/cursors/asw.png Binary files differnew file mode 100644 index 000000000000..5a4b9519e075 --- /dev/null +++ b/vcl/osx/res/cursors/asw.png diff --git a/vcl/osx/res/cursors/aswe.png b/vcl/osx/res/cursors/aswe.png Binary files differnew file mode 100644 index 000000000000..b9c5afaac043 --- /dev/null +++ b/vcl/osx/res/cursors/aswe.png diff --git a/vcl/osx/res/cursors/chain.png b/vcl/osx/res/cursors/chain.png Binary files differnew file mode 100644 index 000000000000..dbf069924d73 --- /dev/null +++ b/vcl/osx/res/cursors/chain.png diff --git a/vcl/osx/res/cursors/chainnot.png b/vcl/osx/res/cursors/chainnot.png Binary files differnew file mode 100644 index 000000000000..547703edf12c --- /dev/null +++ b/vcl/osx/res/cursors/chainnot.png diff --git a/vcl/osx/res/cursors/chart.png b/vcl/osx/res/cursors/chart.png Binary files differnew file mode 100644 index 000000000000..de5514006e1f --- /dev/null +++ b/vcl/osx/res/cursors/chart.png diff --git a/vcl/osx/res/cursors/copydata.png b/vcl/osx/res/cursors/copydata.png Binary files differnew file mode 100644 index 000000000000..b6202fd9144f --- /dev/null +++ b/vcl/osx/res/cursors/copydata.png diff --git a/vcl/osx/res/cursors/copydlnk.png b/vcl/osx/res/cursors/copydlnk.png Binary files differnew file mode 100644 index 000000000000..fab24c9f8f7c --- /dev/null +++ b/vcl/osx/res/cursors/copydlnk.png diff --git a/vcl/osx/res/cursors/copyf.png b/vcl/osx/res/cursors/copyf.png Binary files differnew file mode 100644 index 000000000000..70546d0c0c22 --- /dev/null +++ b/vcl/osx/res/cursors/copyf.png diff --git a/vcl/osx/res/cursors/copyf2.png b/vcl/osx/res/cursors/copyf2.png Binary files differnew file mode 100644 index 000000000000..b6f76051f10f --- /dev/null +++ b/vcl/osx/res/cursors/copyf2.png diff --git a/vcl/osx/res/cursors/copyflnk.png b/vcl/osx/res/cursors/copyflnk.png Binary files differnew file mode 100644 index 000000000000..23561e484e36 --- /dev/null +++ b/vcl/osx/res/cursors/copyflnk.png diff --git a/vcl/osx/res/cursors/crook.png b/vcl/osx/res/cursors/crook.png Binary files differnew file mode 100644 index 000000000000..4378f8df8351 --- /dev/null +++ b/vcl/osx/res/cursors/crook.png diff --git a/vcl/osx/res/cursors/crop.png b/vcl/osx/res/cursors/crop.png Binary files differnew file mode 100644 index 000000000000..92a778ada31a --- /dev/null +++ b/vcl/osx/res/cursors/crop.png diff --git a/vcl/osx/res/cursors/darc.png b/vcl/osx/res/cursors/darc.png Binary files differnew file mode 100644 index 000000000000..9772a1c6b85a --- /dev/null +++ b/vcl/osx/res/cursors/darc.png diff --git a/vcl/osx/res/cursors/dbezier.png b/vcl/osx/res/cursors/dbezier.png Binary files differnew file mode 100644 index 000000000000..988498137e9a --- /dev/null +++ b/vcl/osx/res/cursors/dbezier.png diff --git a/vcl/osx/res/cursors/dcapt.png b/vcl/osx/res/cursors/dcapt.png Binary files differnew file mode 100644 index 000000000000..d1ef82818735 --- /dev/null +++ b/vcl/osx/res/cursors/dcapt.png diff --git a/vcl/osx/res/cursors/dcirccut.png b/vcl/osx/res/cursors/dcirccut.png Binary files differnew file mode 100644 index 000000000000..cb4ed0e85ecd --- /dev/null +++ b/vcl/osx/res/cursors/dcirccut.png diff --git a/vcl/osx/res/cursors/dconnect.png b/vcl/osx/res/cursors/dconnect.png Binary files differnew file mode 100644 index 000000000000..e4a43bdbe021 --- /dev/null +++ b/vcl/osx/res/cursors/dconnect.png diff --git a/vcl/osx/res/cursors/dellipse.png b/vcl/osx/res/cursors/dellipse.png Binary files differnew file mode 100644 index 000000000000..319c4574c7c1 --- /dev/null +++ b/vcl/osx/res/cursors/dellipse.png diff --git a/vcl/osx/res/cursors/detectiv.png b/vcl/osx/res/cursors/detectiv.png Binary files differnew file mode 100644 index 000000000000..abe93f263d4d --- /dev/null +++ b/vcl/osx/res/cursors/detectiv.png diff --git a/vcl/osx/res/cursors/dfree.png b/vcl/osx/res/cursors/dfree.png Binary files differnew file mode 100644 index 000000000000..2de92942adde --- /dev/null +++ b/vcl/osx/res/cursors/dfree.png diff --git a/vcl/osx/res/cursors/dline.png b/vcl/osx/res/cursors/dline.png Binary files differnew file mode 100644 index 000000000000..6afb670ef8a8 --- /dev/null +++ b/vcl/osx/res/cursors/dline.png diff --git a/vcl/osx/res/cursors/dpie.png b/vcl/osx/res/cursors/dpie.png Binary files differnew file mode 100644 index 000000000000..44a9474846b9 --- /dev/null +++ b/vcl/osx/res/cursors/dpie.png diff --git a/vcl/osx/res/cursors/dpolygon.png b/vcl/osx/res/cursors/dpolygon.png Binary files differnew file mode 100644 index 000000000000..847e6ad9bea5 --- /dev/null +++ b/vcl/osx/res/cursors/dpolygon.png diff --git a/vcl/osx/res/cursors/drect.png b/vcl/osx/res/cursors/drect.png Binary files differnew file mode 100644 index 000000000000..ff3dcbba07b4 --- /dev/null +++ b/vcl/osx/res/cursors/drect.png diff --git a/vcl/osx/res/cursors/dtext.png b/vcl/osx/res/cursors/dtext.png Binary files differnew file mode 100644 index 000000000000..ee375f0e47a0 --- /dev/null +++ b/vcl/osx/res/cursors/dtext.png diff --git a/vcl/osx/res/cursors/fill.png b/vcl/osx/res/cursors/fill.png Binary files differnew file mode 100644 index 000000000000..220641b9beb4 --- /dev/null +++ b/vcl/osx/res/cursors/fill.png diff --git a/vcl/osx/res/cursors/help.png b/vcl/osx/res/cursors/help.png Binary files differnew file mode 100644 index 000000000000..e29c19eccd22 --- /dev/null +++ b/vcl/osx/res/cursors/help.png diff --git a/vcl/osx/res/cursors/hourglass.png b/vcl/osx/res/cursors/hourglass.png Binary files differnew file mode 100644 index 000000000000..07bb5af73e6e --- /dev/null +++ b/vcl/osx/res/cursors/hourglass.png diff --git a/vcl/osx/res/cursors/hshear.png b/vcl/osx/res/cursors/hshear.png Binary files differnew file mode 100644 index 000000000000..b45beded2d93 --- /dev/null +++ b/vcl/osx/res/cursors/hshear.png diff --git a/vcl/osx/res/cursors/linkdata.png b/vcl/osx/res/cursors/linkdata.png Binary files differnew file mode 100644 index 000000000000..6432db0155b6 --- /dev/null +++ b/vcl/osx/res/cursors/linkdata.png diff --git a/vcl/osx/res/cursors/linkf.png b/vcl/osx/res/cursors/linkf.png Binary files differnew file mode 100644 index 000000000000..e17107fec9ff --- /dev/null +++ b/vcl/osx/res/cursors/linkf.png diff --git a/vcl/osx/res/cursors/magnify.png b/vcl/osx/res/cursors/magnify.png Binary files differnew file mode 100644 index 000000000000..4e73146b91e4 --- /dev/null +++ b/vcl/osx/res/cursors/magnify.png diff --git a/vcl/osx/res/cursors/mirror.png b/vcl/osx/res/cursors/mirror.png Binary files differnew file mode 100644 index 000000000000..8fac93f0b6df --- /dev/null +++ b/vcl/osx/res/cursors/mirror.png diff --git a/vcl/osx/res/cursors/movebw.png b/vcl/osx/res/cursors/movebw.png Binary files differnew file mode 100644 index 000000000000..63bf76ad3942 --- /dev/null +++ b/vcl/osx/res/cursors/movebw.png diff --git a/vcl/osx/res/cursors/movedata.png b/vcl/osx/res/cursors/movedata.png Binary files differnew file mode 100644 index 000000000000..60ece8a53e59 --- /dev/null +++ b/vcl/osx/res/cursors/movedata.png diff --git a/vcl/osx/res/cursors/movedlnk.png b/vcl/osx/res/cursors/movedlnk.png Binary files differnew file mode 100644 index 000000000000..6951cd718d97 --- /dev/null +++ b/vcl/osx/res/cursors/movedlnk.png diff --git a/vcl/osx/res/cursors/movef.png b/vcl/osx/res/cursors/movef.png Binary files differnew file mode 100644 index 000000000000..97a01c88fa4d --- /dev/null +++ b/vcl/osx/res/cursors/movef.png diff --git a/vcl/osx/res/cursors/movef2.png b/vcl/osx/res/cursors/movef2.png Binary files differnew file mode 100644 index 000000000000..2cdddb410aae --- /dev/null +++ b/vcl/osx/res/cursors/movef2.png diff --git a/vcl/osx/res/cursors/moveflnk.png b/vcl/osx/res/cursors/moveflnk.png Binary files differnew file mode 100644 index 000000000000..53301ba58c50 --- /dev/null +++ b/vcl/osx/res/cursors/moveflnk.png diff --git a/vcl/osx/res/cursors/movept.png b/vcl/osx/res/cursors/movept.png Binary files differnew file mode 100644 index 000000000000..41945deb1916 --- /dev/null +++ b/vcl/osx/res/cursors/movept.png diff --git a/vcl/osx/res/cursors/neswsize.png b/vcl/osx/res/cursors/neswsize.png Binary files differnew file mode 100644 index 000000000000..91b89b5803ec --- /dev/null +++ b/vcl/osx/res/cursors/neswsize.png diff --git a/vcl/osx/res/cursors/notallow.png b/vcl/osx/res/cursors/notallow.png Binary files differnew file mode 100644 index 000000000000..df770a495194 --- /dev/null +++ b/vcl/osx/res/cursors/notallow.png diff --git a/vcl/osx/res/cursors/nullptr.png b/vcl/osx/res/cursors/nullptr.png Binary files differnew file mode 100644 index 000000000000..489636595bec --- /dev/null +++ b/vcl/osx/res/cursors/nullptr.png diff --git a/vcl/osx/res/cursors/nwsesize.png b/vcl/osx/res/cursors/nwsesize.png Binary files differnew file mode 100644 index 000000000000..fc6a33288ef2 --- /dev/null +++ b/vcl/osx/res/cursors/nwsesize.png diff --git a/vcl/osx/res/cursors/pen.png b/vcl/osx/res/cursors/pen.png Binary files differnew file mode 100644 index 000000000000..81b583086778 --- /dev/null +++ b/vcl/osx/res/cursors/pen.png diff --git a/vcl/osx/res/cursors/pivotcol.png b/vcl/osx/res/cursors/pivotcol.png Binary files differnew file mode 100644 index 000000000000..1c38b915b886 --- /dev/null +++ b/vcl/osx/res/cursors/pivotcol.png diff --git a/vcl/osx/res/cursors/pivotdel.png b/vcl/osx/res/cursors/pivotdel.png Binary files differnew file mode 100644 index 000000000000..fbd663ee36c1 --- /dev/null +++ b/vcl/osx/res/cursors/pivotdel.png diff --git a/vcl/osx/res/cursors/pivotfld.png b/vcl/osx/res/cursors/pivotfld.png Binary files differnew file mode 100644 index 000000000000..04375de1efe6 --- /dev/null +++ b/vcl/osx/res/cursors/pivotfld.png diff --git a/vcl/osx/res/cursors/pivotrow.png b/vcl/osx/res/cursors/pivotrow.png Binary files differnew file mode 100644 index 000000000000..18ef0e8e59ba --- /dev/null +++ b/vcl/osx/res/cursors/pivotrow.png diff --git a/vcl/osx/res/cursors/pntbrsh.png b/vcl/osx/res/cursors/pntbrsh.png Binary files differnew file mode 100644 index 000000000000..ec8d799f66c2 --- /dev/null +++ b/vcl/osx/res/cursors/pntbrsh.png diff --git a/vcl/osx/res/cursors/rotate.png b/vcl/osx/res/cursors/rotate.png Binary files differnew file mode 100644 index 000000000000..a8137e077e56 --- /dev/null +++ b/vcl/osx/res/cursors/rotate.png diff --git a/vcl/osx/res/cursors/tblsele.png b/vcl/osx/res/cursors/tblsele.png Binary files differnew file mode 100644 index 000000000000..a2da05e009d1 --- /dev/null +++ b/vcl/osx/res/cursors/tblsele.png diff --git a/vcl/osx/res/cursors/tblsels.png b/vcl/osx/res/cursors/tblsels.png Binary files differnew file mode 100644 index 000000000000..ba20589c794f --- /dev/null +++ b/vcl/osx/res/cursors/tblsels.png diff --git a/vcl/osx/res/cursors/tblselse.png b/vcl/osx/res/cursors/tblselse.png Binary files differnew file mode 100644 index 000000000000..4ee7f62b304b --- /dev/null +++ b/vcl/osx/res/cursors/tblselse.png diff --git a/vcl/osx/res/cursors/tblselsw.png b/vcl/osx/res/cursors/tblselsw.png Binary files differnew file mode 100644 index 000000000000..11d7cb34d64d --- /dev/null +++ b/vcl/osx/res/cursors/tblselsw.png diff --git a/vcl/osx/res/cursors/tblselw.png b/vcl/osx/res/cursors/tblselw.png Binary files differnew file mode 100644 index 000000000000..62813a975855 --- /dev/null +++ b/vcl/osx/res/cursors/tblselw.png diff --git a/vcl/osx/res/cursors/timemove.png b/vcl/osx/res/cursors/timemove.png Binary files differnew file mode 100644 index 000000000000..3dae038a2011 --- /dev/null +++ b/vcl/osx/res/cursors/timemove.png diff --git a/vcl/osx/res/cursors/timesize.png b/vcl/osx/res/cursors/timesize.png Binary files differnew file mode 100644 index 000000000000..22178c9b8d0e --- /dev/null +++ b/vcl/osx/res/cursors/timesize.png diff --git a/vcl/osx/res/cursors/vshear.png b/vcl/osx/res/cursors/vshear.png Binary files differnew file mode 100644 index 000000000000..b01cb6c935e1 --- /dev/null +++ b/vcl/osx/res/cursors/vshear.png diff --git a/vcl/osx/res/cursors/vtext.png b/vcl/osx/res/cursors/vtext.png Binary files differnew file mode 100644 index 000000000000..2d6c847c9fc7 --- /dev/null +++ b/vcl/osx/res/cursors/vtext.png diff --git a/vcl/osx/saldata.cxx b/vcl/osx/saldata.cxx new file mode 100644 index 000000000000..55a5e6493782 --- /dev/null +++ b/vcl/osx/saldata.cxx @@ -0,0 +1,265 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include "osx/saldata.hxx" +#include "osx/salnsmenu.h" +#include "osx/salinst.h" + +#import "apple_remote/RemoteMainController.h" + +oslThreadKey SalData::s_aAutoReleaseKey = 0; + +static void SAL_CALL releasePool( void* pPool ) +{ + if( pPool ) + [(NSAutoreleasePool*)pPool release]; +} + +SalData::SalData() +: + mpTimerProc( NULL ), + mpFirstInstance( NULL ), + mpFirstObject( NULL ), + mpFirstVD( NULL ), + mpFirstPrinter( NULL ), + mpFontList( NULL ), + mpStatusItem( nil ), + mxRGBSpace( CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB) ), + mxGraySpace( CGColorSpaceCreateWithName(kCGColorSpaceGenericGray) ), + mxP50Space( NULL ), + mxP50Pattern( NULL ), + maCursors( POINTER_COUNT, INVALID_CURSOR_PTR ), + mbIsScrollbarDoubleMax( false ), +#if !HAVE_FEATURE_MACOSX_SANDBOX + mpMainController( NULL ), +#endif + mpDockIconClickHandler( nil ), + mnDPIX( 0 ), + mnDPIY( 0 ) +{ + if( s_aAutoReleaseKey == 0 ) + s_aAutoReleaseKey = osl_createThreadKey( releasePool ); +} + +SalData::~SalData() +{ + CGPatternRelease( mxP50Pattern ); + CGColorSpaceRelease( mxP50Space ); + CGColorSpaceRelease( mxRGBSpace ); + CGColorSpaceRelease( mxGraySpace ); + for( unsigned int i = 0; i < maCursors.size(); i++ ) + { + NSCursor* pCurs = maCursors[i]; + if( pCurs && pCurs != INVALID_CURSOR_PTR ) + [pCurs release]; + } + if( s_aAutoReleaseKey ) + { + // release the last pool + NSAutoreleasePool* pPool = nil; + pPool = reinterpret_cast<NSAutoreleasePool*>( osl_getThreadKeyData( s_aAutoReleaseKey ) ); + if( pPool ) + { + osl_setThreadKeyData( s_aAutoReleaseKey, NULL ); + [pPool release]; + } + + osl_destroyThreadKey( s_aAutoReleaseKey ); + s_aAutoReleaseKey = 0; + } +#if !HAVE_FEATURE_MACOSX_SANDBOX + if ( mpMainController ) + [mpMainController release]; +#endif +} + +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" ); + } +} + +struct curs_ent +{ + const char* pBaseName; + const NSPoint aHotSpot; +} +const aCursorTab[ POINTER_COUNT ] = +{ +{ NULL, { 0, 0 } }, //POINTER_ARROW +{ "nullptr", { 16, 16 } }, //POINTER_NULL +{ "hourglass", { 15, 15 } }, //POINTER_WAIT +{ NULL, { 0, 0 } }, //POINTER_TEXT +{ "help", { 0, 0 } }, //POINTER_HELP +{ NULL, { 0, 0 } }, //POINTER_CROSS +{ NULL, { 0, 0 } }, //POINTER_MOVE +{ NULL, { 0, 0 } }, //POINTER_NSIZE +{ NULL, { 0, 0 } }, //POINTER_SSIZE +{ NULL, { 0, 0 } }, //POINTER_WSIZE +{ NULL, { 0, 0 } }, //POINTER_ESIZE +{ "nwsesize", { 15, 15 } }, //POINTER_NWSIZE +{ "neswsize", { 15, 15 } }, //POINTER_NESIZE +{ "neswsize", { 15, 15 } }, //POINTER_SWSIZE +{ "nwsesize", { 15, 15 } }, //POINTER_SESIZE +{ NULL, { 0, 0 } }, //POINTER_WINDOW_NSIZE +{ NULL, { 0, 0 } }, //POINTER_WINDOW_SSIZE +{ NULL, { 0, 0 } }, //POINTER_WINDOW_WSIZE +{ NULL, { 0, 0 } }, //POINTER_WINDOW_ESIZE +{ "nwsesize", { 15, 15 } }, //POINTER_WINDOW_NWSIZE +{ "neswsize", { 15, 15 } }, //POINTER_WINDOW_NESIZE +{ "neswsize", { 15, 15 } }, //POINTER_WINDOW_SWSIZE +{ "nwsesize", { 15, 15 } }, //POINTER_WINDOW_SESIZE +{ NULL, { 0, 0 } }, //POINTER_HSPLIT +{ NULL, { 0, 0 } }, //POINTER_VSPLIT +{ NULL, { 0, 0 } }, //POINTER_HSIZEBAR +{ NULL, { 0, 0 } }, //POINTER_VSIZEBAR +{ NULL, { 0, 0 } }, //POINTER_HAND +{ NULL, { 0, 0 } }, //POINTER_REFHAND +{ "pen", { 3, 27 } }, //POINTER_PEN +{ "magnify", { 12, 13 } }, //POINTER_MAGNIFY +{ "fill", { 10, 22 } }, //POINTER_FILL +{ "rotate", { 15, 15 } }, //POINTER_ROTATE +{ "hshear", { 15, 15 } }, //POINTER_HSHEAR +{ "vshear", { 15, 15 } }, //POINTER_VSHEAR +{ "mirror", { 14, 12 } }, //POINTER_MIRROR +{ "crook", { 15, 14 } }, //POINTER_CROOK +{ "crop", { 9, 9 } }, //POINTER_CROP +{ "movept", { 0, 0 } }, //POINTER_MOVEPOINT +{ "movebw", { 0, 0 } }, //POINTER_MOVEBEZIERWEIGHT +{ "movedata", { 0, 0 } }, //POINTER_MOVEDATA +{ "copydata", { 0, 0 } }, //POINTER_COPYDATA +{ "linkdata", { 0, 0 } }, //POINTER_LINKDATA +{ "movedlnk", { 0, 0 } }, //POINTER_MOVEDATALINK +{ "copydlnk", { 0, 0 } }, //POINTER_COPYDATALINK +{ "movef", { 8, 8 } }, //POINTER_MOVEFILE +{ "copyf", { 8, 8 } }, //POINTER_COPYFILE +{ "linkf", { 8, 8 } }, //POINTER_LINKFILE +{ "moveflnk", { 8, 8 } }, //POINTER_MOVEFILELINK +{ "copyflnk", { 8, 8 } }, //POINTER_COPYFILELINK +{ "movef2", { 7, 8 } }, //POINTER_MOVEFILES +{ "copyf2", { 7, 8 } }, //POINTER_COPYFILES +{ "notallow", { 15, 15 } }, //POINTER_NOTALLOWED +{ "dline", { 8, 8 } }, //POINTER_DRAW_LINE +{ "drect", { 8, 8 } }, //POINTER_DRAW_RECT +{ "dpolygon", { 8, 8 } }, //POINTER_DRAW_POLYGON +{ "dbezier", { 8, 8 } }, //POINTER_DRAW_BEZIER +{ "darc", { 8, 8 } }, //POINTER_DRAW_ARC +{ "dpie", { 8, 8 } }, //POINTER_DRAW_PIE +{ "dcirccut", { 8, 8 } }, //POINTER_DRAW_CIRCLECUT +{ "dellipse", { 8, 8 } }, //POINTER_DRAW_ELLIPSE +{ "dfree", { 8, 8 } }, //POINTER_DRAW_FREEHAND +{ "dconnect", { 8, 8 } }, //POINTER_DRAW_CONNECT +{ "dtext", { 8, 8 } }, //POINTER_DRAW_TEXT +{ "dcapt", { 8, 8 } }, //POINTER_DRAW_CAPTION +{ "chart", { 15, 16 } }, //POINTER_CHART +{ "detectiv", { 12, 13 } }, //POINTER_DETECTIVE +{ "pivotcol", { 7, 5 } }, //POINTER_PIVOT_COL +{ "pivotrow", { 8, 7 } }, //POINTER_PIVOT_ROW +{ "pivotfld", { 8, 7 } }, //POINTER_PIVOT_FIELD +{ "chain", { 0, 2 } }, //POINTER_CHAIN +{ "chainnot", { 2, 2 } }, //POINTER_CHAIN_NOTALLOWED +{ "timemove", { 16, 16 } }, //POINTER_TIMEEVENT_MOVE +{ "timesize", { 16, 17 } }, //POINTER_TIMEEVENT_SIZE +{ "asn", { 16, 12 } }, //POINTER_AUTOSCROLL_N +{ "ass", { 15, 19 } }, //POINTER_AUTOSCROLL_S +{ "asw", { 12, 15 } }, //POINTER_AUTOSCROLL_W +{ "ase", { 19, 16 } }, //POINTER_AUTOSCROLL_E +{ "asnw", { 10, 10 } }, //POINTER_AUTOSCROLL_NW +{ "asne", { 21, 10 } }, //POINTER_AUTOSCROLL_NE +{ "assw", { 21, 21 } }, //POINTER_AUTOSCROLL_SW +{ "asse", { 21, 21 } }, //POINTER_AUTOSCROLL_SE +{ "asns", { 15, 15 } }, //POINTER_AUTOSCROLL_NS +{ "aswe", { 15, 15 } }, //POINTER_AUTOSCROLL_WE +{ "asnswe", { 15, 15 } }, //POINTER_AUTOSCROLL_NSWE +{ "airbrush", { 5, 22 } }, //POINTER_AIRBRUSH +{ "vtext", { 15, 15 } }, //POINTER_TEXT_VERTICAL +{ "pivotdel", { 18, 15 } }, //POINTER_PIVOT_DELETE +{ "tblsels", { 15, 30 } }, //POINTER_TAB_SELECT_S +{ "tblsele", { 30, 16 } }, //POINTER_TAB_SELECT_E +{ "tblselse", { 30, 30 } }, //POINTER_TAB_SELECT_SE +{ "tblselw", { 1, 16 } }, //POINTER_TAB_SELECT_W +{ "tblselsw", { 1, 30 } }, //POINTER_TAB_SELECT_SW +{ "pntbrsh", { 9, 16 } } //POINTER_PAINTBRUSH +}; + +NSCursor* SalData::getCursor( PointerStyle i_eStyle ) +{ + if( i_eStyle >= POINTER_COUNT ) + return nil; + + NSCursor* pCurs = maCursors[ i_eStyle ]; + if( pCurs == INVALID_CURSOR_PTR ) + { + pCurs = nil; + if( aCursorTab[ i_eStyle ].pBaseName ) + { + NSPoint aHotSpot = aCursorTab[ i_eStyle ].aHotSpot; + CFStringRef pCursorName = + CFStringCreateWithCStringNoCopy( + kCFAllocatorDefault, + aCursorTab[ i_eStyle ].pBaseName, + kCFStringEncodingASCII, + kCFAllocatorNull ); + CFBundleRef hMain = CFBundleGetMainBundle(); + CFURLRef hURL = CFBundleCopyResourceURL( hMain, pCursorName, CFSTR("png"), CFSTR("cursors") ); + if( hURL ) + { + pCurs = [[NSCursor alloc] initWithImage: [[NSImage alloc] initWithContentsOfURL: (NSURL*)hURL] hotSpot: aHotSpot]; + CFRelease( hURL ); + } + CFRelease( pCursorName ); + } + 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]; + [pData->mpStatusItem setView: pView ]; + [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 000000000000..cf1fe45b97e3 --- /dev/null +++ b/vcl/osx/salframe.cxx @@ -0,0 +1,1769 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <string> + +#include "rtl/ustrbuf.hxx" + +#include "osl/file.h" + +#include "vcl/svapp.hxx" +#include "vcl/window.hxx" +#include "vcl/syswin.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 "quartz/utils.h" + +#include "salwtype.hxx" + +#include "premac.h" +#include <objc/objc-runtime.h> +// needed for theming +// FIXME: move theming code to salnativewidgets.cxx +#include <Carbon/Carbon.h> +#include "postmac.h" + +using namespace std; + +// ======================================================================= + +AquaSalFrame* AquaSalFrame::s_pCaptureFrame = NULL; + +// ======================================================================= + +AquaSalFrame::AquaSalFrame( SalFrame* pParent, sal_uLong salFrameStyle ) : + mpNSWindow(nil), + mpNSView(nil), + mpDockMenuEntry(nil), + mpGraphics(NULL), + mpParent(NULL), + 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( NULL ), + mnExtStyle( 0 ), + mePointerStyle( POINTER_ARROW ), + mnTrackingRectTag( 0 ), + mrClippingPath( 0 ), + mnICOptions( 0 ) +{ + maSysData.nSize = sizeof( SystemEnvData ); + + mpParent = dynamic_cast<AquaSalFrame*>(pParent); + + initWindowAndView(); + + SalData* pSalData = GetSalData(); + pSalData->maFrames.push_front( this ); + pSalData->maFrameCheck.insert( this ); +} + +// ----------------------------------------------------------------------- + +AquaSalFrame::~AquaSalFrame() +{ + // 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 + ResetClipRegion(); + + [SalFrameView unsetMouseFrame: this]; + + SalData* pSalData = GetSalData(); + pSalData->maFrames.remove( this ); + pSalData->maFrameCheck.erase( this ); + pSalData->maPresentationFrames.remove( this ); + + DBG_ASSERT( this != s_pCaptureFrame, "capture frame destroyed" ); + if( this == s_pCaptureFrame ) + s_pCaptureFrame = NULL; + + delete mpGraphics; + + if( mpDockMenuEntry ) + // life cycle comment: the menu has ownership of the item, so no release + [AquaSalInstance::GetDynamicDockMenu() removeItem: mpDockMenuEntry]; + if ( mpNSView ) { + [AquaA11yFactory revokeView: mpNSView]; + [mpNSView release]; + } + if ( mpNSWindow ) + [mpNSWindow release]; +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::initWindowAndView() +{ + // initialize mirroring parameters + // FIXME: screens changing + NSScreen* pNSScreen = [mpNSWindow screen]; + if( pNSScreen == nil ) + pNSScreen = [NSScreen mainScreen]; + maScreenRect = [pNSScreen frame]; + + // calculate some default geometry + NSRect aVisibleRect = [pNSScreen visibleFrame]; + CocoaToVCL( aVisibleRect ); + + maGeometry.nX = static_cast<int>(aVisibleRect.origin.x + aVisibleRect.size.width / 10); + maGeometry.nY = static_cast<int>(aVisibleRect.origin.y + aVisibleRect.size.height / 10); + maGeometry.nWidth = static_cast<unsigned int>(aVisibleRect.size.width * 0.8); + maGeometry.nHeight = static_cast<unsigned int>(aVisibleRect.size.height * 0.8); + + // calculate style mask + if( (mnStyle & SAL_FRAME_STYLE_FLOAT) || + (mnStyle & SAL_FRAME_STYLE_OWNERDRAWDECORATION) ) + mnStyleMask = NSBorderlessWindowMask; + else if( mnStyle & SAL_FRAME_STYLE_DEFAULT ) + { + mnStyleMask = NSTitledWindowMask | + NSMiniaturizableWindowMask | + NSResizableWindowMask | + NSClosableWindowMask; + // make default window "maximized" + maGeometry.nX = static_cast<int>(aVisibleRect.origin.x); + maGeometry.nY = static_cast<int>(aVisibleRect.origin.y); + maGeometry.nWidth = static_cast<int>(aVisibleRect.size.width); + maGeometry.nHeight = static_cast<int>(aVisibleRect.size.height); + mbPositioned = mbSized = true; + } + else + { + if( (mnStyle & SAL_FRAME_STYLE_MOVEABLE) ) + { + mnStyleMask |= NSTitledWindowMask; + if( mpParent == NULL ) + mnStyleMask |= NSMiniaturizableWindowMask; + } + if( (mnStyle & SAL_FRAME_STYLE_SIZEABLE) ) + mnStyleMask |= NSResizableWindowMask; + if( (mnStyle & SAL_FRAME_STYLE_CLOSEABLE) ) + mnStyleMask |= NSClosableWindowMask; + // documentation says anything other than NSBorderlessWindowMask (=0) + // should also include NSTitledWindowMask; + if( mnStyleMask != 0 ) + mnStyleMask |= NSTitledWindowMask; + } + + // #i91990# support GUI-less (daemon) execution + @try + { + mpNSWindow = [[SalFrameWindow alloc] initWithSalFrame: this]; + mpNSView = [[SalFrameView alloc] initWithSalFrame: this]; + } + @catch ( id exception ) + { + return; + } + + if( (mnStyle & SAL_FRAME_STYLE_TOOLTIP) ) + [mpNSWindow setIgnoresMouseEvents: YES]; + else + [mpNSWindow setAcceptsMouseMovedEvents: YES]; + [mpNSWindow setHasShadow: YES]; + + // WTF? With the 10.6 SDK and gcc 4.2.1, we get: class 'NSWindow' + // does not implement the 'NSWindowDelegate' protocol. Anyway, + // having the window object be its own delegate object is + // apparently what the code does on purpose, see discussion in + // https://issues.apache.org/ooo/show_bug.cgi?id=91990 + + // So to silence the warning when compiling with -Werror, instead of: + // [mpNSWindow setDelegate: mpNSWindow]; + // do this: + objc_msgSend(mpNSWindow, @selector(setDelegate:), mpNSWindow); + + if( [mpNSWindow respondsToSelector: @selector(setRestorable:)]) + { + objc_msgSend(mpNSWindow, @selector(setRestorable:), NO); + } + NSRect aRect = { { 0,0 }, { static_cast<CGFloat>(maGeometry.nWidth), static_cast<CGFloat>(maGeometry.nHeight) } }; + mnTrackingRectTag = [mpNSView addTrackingRect: aRect owner: mpNSView userData: nil assumeInside: NO]; + + maSysData.mpNSView = mpNSView; + + UpdateFrameGeometry(); + + [mpNSWindow setContentView: mpNSView]; +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::CocoaToVCL( NSRect& io_rRect, bool bRelativeToScreen ) +{ + if( bRelativeToScreen ) + io_rRect.origin.y = maScreenRect.size.height - (io_rRect.origin.y+io_rRect.size.height); + else + io_rRect.origin.y = maGeometry.nHeight - (io_rRect.origin.y+io_rRect.size.height); +} + +void AquaSalFrame::VCLToCocoa( NSRect& io_rRect, bool bRelativeToScreen ) +{ + if( bRelativeToScreen ) + io_rRect.origin.y = maScreenRect.size.height - (io_rRect.origin.y+io_rRect.size.height); + else + io_rRect.origin.y = maGeometry.nHeight - (io_rRect.origin.y+io_rRect.size.height); +} + +void AquaSalFrame::CocoaToVCL( NSPoint& io_rPoint, bool bRelativeToScreen ) +{ + if( bRelativeToScreen ) + io_rPoint.y = maScreenRect.size.height - io_rPoint.y; + else + io_rPoint.y = maGeometry.nHeight - io_rPoint.y; +} + +void AquaSalFrame::VCLToCocoa( NSPoint& io_rPoint, bool bRelativeToScreen ) +{ + if( bRelativeToScreen ) + io_rPoint.y = maScreenRect.size.height - io_rPoint.y; + else + io_rPoint.y = maGeometry.nHeight - io_rPoint.y; +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::screenParametersChanged() +{ + UpdateFrameGeometry(); + + if( mpGraphics ) + mpGraphics->updateResolution(); + CallCallback( SALEVENT_DISPLAYCHANGED, 0 ); +} + +// ----------------------------------------------------------------------- + +SalGraphics* AquaSalFrame::GetGraphics() +{ + if ( mbGraphics ) + return NULL; + + if ( !mpGraphics ) + { + mpGraphics = new AquaSalGraphics; + mpGraphics->SetWindowGraphics( this ); + } + + mbGraphics = TRUE; + return mpGraphics; +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::ReleaseGraphics( SalGraphics *pGraphics ) +{ + (void)pGraphics; + DBG_ASSERT( pGraphics == mpGraphics, "graphics released on wrong frame" ); + mbGraphics = FALSE; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalFrame::PostEvent( void *pData ) +{ + GetSalData()->mpFirstInstance->PostUserEvent( this, SALEVENT_USEREVENT, pData ); + return TRUE; +} + +// ----------------------------------------------------------------------- +void AquaSalFrame::SetTitle(const OUString& rTitle) +{ + if ( !mpNSWindow ) + return; + + // #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 sal_uLong nAppWindowStyle = (SAL_FRAME_STYLE_CLOSEABLE | SAL_FRAME_STYLE_MOVEABLE); + if( mpParent == NULL && + (mnStyle & nAppWindowStyle) == nAppWindowStyle ) + { + if( mpDockMenuEntry == NULL ) + { + NSMenu* pDock = AquaSalInstance::GetDynamicDockMenu(); + 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 ) +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + if( i_rDocURL.startsWith( "file:" ) ) + { + OUString aSysPath; + osl_getSystemPathFromFileURL( i_rDocURL.pData, &aSysPath.pData ); + NSString* pStr = CreateNSString( aSysPath ); + if( pStr ) + { + [pStr autorelease]; + [mpNSWindow setRepresentedFilename: pStr]; + } + } +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::initShow() +{ + mbInitShow = false; + if( ! mbPositioned && ! mbFullScreen ) + { + Rectangle aScreenRect; + GetWorkArea( aScreenRect ); + if( mpParent ) // center relative to parent + { + // center on parent + long nNewX = mpParent->maGeometry.nX + ((long)mpParent->maGeometry.nWidth - (long)maGeometry.nWidth)/2; + if( nNewX < aScreenRect.Left() ) + nNewX = aScreenRect.Left(); + if( long(nNewX + maGeometry.nWidth) > aScreenRect.Right() ) + nNewX = aScreenRect.Right() - maGeometry.nWidth-1; + long nNewY = mpParent->maGeometry.nY + ((long)mpParent->maGeometry.nHeight - (long)maGeometry.nHeight)/2; + if( nNewY < aScreenRect.Top() ) + nNewY = aScreenRect.Top(); + if( nNewY > aScreenRect.Bottom() ) + nNewY = aScreenRect.Bottom() - maGeometry.nHeight-1; + SetPosSize( nNewX - mpParent->maGeometry.nX, + nNewY - mpParent->maGeometry.nY, + 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ); + } + else if( ! (mnStyle & SAL_FRAME_STYLE_SIZEABLE) ) + { + // center on screen + long nNewX = (aScreenRect.GetWidth() - maGeometry.nWidth)/2; + long nNewY = (aScreenRect.GetHeight() - maGeometry.nHeight)/2; + SetPosSize( nNewX, nNewY, 0, 0, SAL_FRAME_POSSIZE_X | SAL_FRAME_POSSIZE_Y ); + } + } + + // make sure the view is present in the wrapper list before any children receive focus + [AquaA11yFactory registerView: mpNSView]; +} + +void AquaSalFrame::SendPaintEvent( const Rectangle* pRect ) +{ + SalPaintEvent aPaintEvt( 0, 0, maGeometry.nWidth, maGeometry.nHeight, true ); + if( pRect ) + { + aPaintEvt.mnBoundX = pRect->Left(); + aPaintEvt.mnBoundY = pRect->Top(); + aPaintEvt.mnBoundWidth = pRect->GetWidth(); + aPaintEvt.mnBoundHeight = pRect->GetHeight(); + } + + CallCallback(SALEVENT_PAINT, &aPaintEvt); +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::Show(sal_Bool bVisible, sal_Bool bNoActivate) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + mbShown = bVisible; + if(bVisible) + { + if( mbInitShow ) + initShow(); + + CallCallback(SALEVENT_RESIZE, 0); + // 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 & (SAL_FRAME_STYLE_OWNERDRAWDECORATION | SAL_FRAME_STYLE_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::Enable( sal_Bool ) +{ +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::SetMinClientSize( long nWidth, long nHeight ) +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + mnMinWidth = nWidth; + mnMinHeight = nHeight; + + if( mpNSWindow ) + { + // Always add the decoration as the dimension concerns only + // the content rectangle + nWidth += maGeometry.nLeftDecoration + maGeometry.nRightDecoration; + nHeight += maGeometry.nTopDecoration + maGeometry.nBottomDecoration; + + NSSize aSize = { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) }; + + // Size of full window (content+structure) although we only + // have the client size in arguments + [mpNSWindow setMinSize: aSize]; + } +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::SetMaxClientSize( long nWidth, long nHeight ) +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + mnMaxWidth = nWidth; + mnMaxHeight = nHeight; + + if( mpNSWindow ) + { + // Always add the decoration as the dimension concerns only + // the content rectangle + nWidth += maGeometry.nLeftDecoration + maGeometry.nRightDecoration; + nHeight += maGeometry.nTopDecoration + maGeometry.nBottomDecoration; + + // Carbon windows can't have a size greater than 32767x32767 + if (nWidth>32767) nWidth=32767; + if (nHeight>32767) nHeight=32767; + + NSSize aSize = { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) }; + + // Size of full window (content+structure) although we only + // have the client size in arguments + [mpNSWindow setMaxSize: aSize]; + } +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::SetClientSize( long nWidth, long nHeight ) +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + if( mpNSWindow ) + { + NSSize aSize = { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) }; + + [mpNSWindow setContentSize: aSize]; + UpdateFrameGeometry(); + if( mbShown ) + // trigger filling our backbuffer + SendPaintEvent(); + } +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::GetClientSize( long& rWidth, long& rHeight ) +{ + if( mbShown || mbInitShow ) + { + rWidth = maGeometry.nWidth; + rHeight = maGeometry.nHeight; + } + else + { + rWidth = 0; + rHeight = 0; + } +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::SetWindowState( const SalFrameState* pState ) +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + if ( mpNSWindow ) + { + // set normal state + NSRect aStateRect = [mpNSWindow frame]; + aStateRect = [NSWindow contentRectForFrameRect: aStateRect styleMask: mnStyleMask]; + CocoaToVCL( aStateRect ); + if( pState->mnMask & WINDOWSTATE_MASK_X ) + aStateRect.origin.x = float(pState->mnX); + if( pState->mnMask & WINDOWSTATE_MASK_Y ) + aStateRect.origin.y = float(pState->mnY); + if( pState->mnMask & WINDOWSTATE_MASK_WIDTH ) + aStateRect.size.width = float(pState->mnWidth); + if( pState->mnMask & WINDOWSTATE_MASK_HEIGHT ) + aStateRect.size.height = float(pState->mnHeight); + VCLToCocoa( aStateRect ); + aStateRect = [NSWindow frameRectForContentRect: aStateRect styleMask: mnStyleMask]; + + [mpNSWindow setFrame: aStateRect display: NO]; + if( pState->mnState == WINDOWSTATE_STATE_MINIMIZED ) + [mpNSWindow miniaturize: NSApp]; + else if( [mpNSWindow isMiniaturized] ) + [mpNSWindow deminiaturize: NSApp]; + + + /* ZOOMED is not really maximized (actually it toggles between a user set size and + the program specified one), but comes closest since the default behavior is + "maximized" if the user did not intervene + */ + if( pState->mnState == WINDOWSTATE_STATE_MAXIMIZED ) + { + if(! [mpNSWindow isZoomed]) + [mpNSWindow zoom: NSApp]; + } + else + { + if( [mpNSWindow isZoomed] ) + [mpNSWindow zoom: NSApp]; + } + } + + // get new geometry + UpdateFrameGeometry(); + + sal_uInt16 nEvent = 0; + if( pState->mnMask & (WINDOWSTATE_MASK_X | WINDOWSTATE_MASK_Y) ) + { + mbPositioned = true; + nEvent = SALEVENT_MOVE; + } + + if( pState->mnMask & (WINDOWSTATE_MASK_WIDTH | WINDOWSTATE_MASK_HEIGHT) ) + { + mbSized = true; + nEvent = (nEvent == SALEVENT_MOVE) ? SALEVENT_MOVERESIZE : SALEVENT_RESIZE; + } + // send event that we were moved/sized + if( nEvent ) + CallCallback( nEvent, NULL ); + + if( mbShown && mpNSWindow ) + { + // trigger filling our backbuffer + SendPaintEvent(); + + // tell the system the views need to be updated + [mpNSWindow display]; + } +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalFrame::GetWindowState( SalFrameState* pState ) +{ + if ( !mpNSWindow ) + return FALSE; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + pState->mnMask = WINDOWSTATE_MASK_X | + WINDOWSTATE_MASK_Y | + WINDOWSTATE_MASK_WIDTH | + WINDOWSTATE_MASK_HEIGHT | + WINDOWSTATE_MASK_STATE; + + NSRect aStateRect = [mpNSWindow frame]; + aStateRect = [NSWindow contentRectForFrameRect: aStateRect styleMask: mnStyleMask]; + CocoaToVCL( aStateRect ); + pState->mnX = long(aStateRect.origin.x); + pState->mnY = long(aStateRect.origin.y); + pState->mnWidth = long(aStateRect.size.width); + pState->mnHeight = long(aStateRect.size.height); + + if( [mpNSWindow isMiniaturized] ) + pState->mnState = WINDOWSTATE_STATE_MINIMIZED; + else if( ! [mpNSWindow isZoomed] ) + pState->mnState = WINDOWSTATE_STATE_NORMAL; + else + pState->mnState = WINDOWSTATE_STATE_MAXIMIZED; + + return TRUE; +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::SetScreenNumber(unsigned int nScreen) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + NSArray* pScreens = [NSScreen screens]; + Rectangle aRet; + 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( sal_Bool bFullScreen, sal_Int32 nDisplay ) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + SAL_INFO("vcl.macosx", OSL_THIS_FUNC << ": mbFullScreen=" << mbFullScreen << ", bFullScreen=" << bFullScreen); + + if( mbFullScreen == bFullScreen ) + return; + + 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 = { { 0, 0 }, { 0, 0 } }; + // get correct screen + NSScreen* pScreen = nil; + NSArray* pScreens = [NSScreen screens]; + if( pScreens ) + { + if( nDisplay >= 0 && (unsigned int)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]; + } + + UpdateFrameGeometry(); + + if( mbShown ) + CallCallback( SALEVENT_MOVERESIZE, NULL ); + } + else + { + { + [mpNSWindow setFrame: maFullScreenRect display: mbShown ? YES : NO]; + } + UpdateFrameGeometry(); + + if( mbShown ) + CallCallback( SALEVENT_MOVERESIZE, NULL ); + + // show the dock and the menubar + [NSMenu setMenuBarVisible:YES]; + } + if( mbShown ) + // trigger filling our backbuffer + SendPaintEvent(); +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::StartPresentation( sal_Bool bStart ) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + 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( sal_Bool ) +{ +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::ToTop(sal_uInt16 nFlags) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + if( ! (nFlags & SAL_FRAME_TOTOP_RESTOREWHENMIN) ) + { + if( ! [mpNSWindow isVisible] || [mpNSWindow isMiniaturized] ) + return; + } + if( nFlags & SAL_FRAME_TOTOP_GRABFOCUS ) + [mpNSWindow makeKeyAndOrderFront: NSApp]; + else + [mpNSWindow orderFront: NSApp]; +} + +// ----------------------------------------------------------------------- + +NSCursor* AquaSalFrame::getCurrentCursor() const +{ + NSCursor* pCursor = nil; + switch( mePointerStyle ) + { + case POINTER_TEXT: pCursor = [NSCursor IBeamCursor]; break; + case POINTER_CROSS: pCursor = [NSCursor crosshairCursor]; break; + case POINTER_HAND: + case POINTER_MOVE: pCursor = [NSCursor openHandCursor]; break; + case POINTER_NSIZE: pCursor = [NSCursor resizeUpCursor]; break; + case POINTER_SSIZE: pCursor = [NSCursor resizeDownCursor]; break; + case POINTER_ESIZE: pCursor = [NSCursor resizeRightCursor]; break; + case POINTER_WSIZE: pCursor = [NSCursor resizeLeftCursor]; break; + case POINTER_ARROW: pCursor = [NSCursor arrowCursor]; break; + case POINTER_VSPLIT: + case POINTER_VSIZEBAR: + case POINTER_WINDOW_NSIZE: + case POINTER_WINDOW_SSIZE: + pCursor = [NSCursor resizeUpDownCursor]; break; + case POINTER_HSPLIT: + case POINTER_HSIZEBAR: + case POINTER_WINDOW_ESIZE: + case POINTER_WINDOW_WSIZE: + pCursor = [NSCursor resizeLeftRightCursor]; break; + case POINTER_REFHAND: pCursor = [NSCursor pointingHandCursor]; break; + case POINTER_NULL: [NSCursor hide]; break; + + default: + pCursor = GetSalData()->getCursor( mePointerStyle ); + if( pCursor == nil ) + { + OSL_FAIL( "unmapped cursor" ); + pCursor = [NSCursor arrowCursor]; + } + break; + } + return pCursor; +} + +void AquaSalFrame::SetPointer( PointerStyle ePointerStyle ) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + if( ePointerStyle >= POINTER_COUNT || ePointerStyle == mePointerStyle ) + return; + mePointerStyle = ePointerStyle; + + [mpNSWindow invalidateCursorRectsForView: mpNSView]; +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::SetPointerPos( long nX, long nY ) +{ + // FIXME: use Cocoa functions + + // FIXME: multiscreen support + CGPoint aPoint = { static_cast<CGFloat>(nX + maGeometry.nX), static_cast<CGFloat>(nY + maGeometry.nY) }; + CGDirectDisplayID mainDisplayID = CGMainDisplayID(); + CGDisplayMoveCursorToPoint( mainDisplayID, aPoint ); +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::Flush( void ) +{ + if( !(mbGraphics && mpGraphics && mpNSView && mbShown) ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + + [mpNSView setNeedsDisplay: YES]; + + // outside of the application's event loop (e.g. IntroWindow) + // nothing would trigger paint event handling + // => fall back to synchronous painting + if( ImplGetSVData()->maAppData.mnDispatchLevel <= 0 ) + { + [mpNSView display]; + } +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::Flush( const Rectangle& rRect ) +{ + if( !(mbGraphics && mpGraphics && mpNSView && mbShown) ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + NSRect aNSRect = { { static_cast<CGFloat>(rRect.Left()), static_cast<CGFloat>(rRect.Top()) }, { static_cast<CGFloat>(rRect.GetWidth()), static_cast<CGFloat>(rRect.GetHeight()) } }; + VCLToCocoa( aNSRect, false ); + [mpNSView setNeedsDisplayInRect: aNSRect]; + + // outside of the application's event loop (e.g. IntroWindow) + // nothing would trigger paint event handling + // => fall back to synchronous painting + if( ImplGetSVData()->maAppData.mnDispatchLevel <= 0 ) + { + [mpNSView display]; + } +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::Sync() +{ + if( mbGraphics && mpGraphics && mpNSView && mbShown ) + { + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + [mpNSView setNeedsDisplay: YES]; + [mpNSView display]; + } +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::SetInputContext( SalInputContext* pContext ) +{ + if (!pContext) + { + mnICOptions = 0; + return; + } + + mnICOptions = pContext->mnOptions; + + if(!(pContext->mnOptions & SAL_INPUTCONTEXT_TEXT)) + return; +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::EndExtTextInput( sal_uInt16 ) +{ +} + +// ----------------------------------------------------------------------- + +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++ ) + { + OUStringBuffer aKey( 3 ); + aKey.append( 'F' ); + aKey.append( sal_Int32( i - KEY_F1 + 1 ) ); + aKeyMap[ i ] = aKey.makeStringAndClear(); + } + + aKeyMap[ KEY_DOWN ] = OUString( sal_Unicode( 0x21e3 ) ); + aKeyMap[ KEY_UP ] = OUString( sal_Unicode( 0x21e1 ) ); + aKeyMap[ KEY_LEFT ] = OUString( sal_Unicode( 0x21e0 ) ); + aKeyMap[ KEY_RIGHT ] = OUString( sal_Unicode( 0x21e2 ) ); + aKeyMap[ KEY_HOME ] = OUString( sal_Unicode( 0x2196 ) ); + aKeyMap[ KEY_END ] = OUString( sal_Unicode( 0x2198 ) ); + aKeyMap[ KEY_PAGEUP ] = OUString( sal_Unicode( 0x21de ) ); + aKeyMap[ KEY_PAGEDOWN ] = OUString( sal_Unicode( 0x21df ) ); + aKeyMap[ KEY_RETURN ] = OUString( sal_Unicode( 0x21a9 ) ); + aKeyMap[ KEY_ESCAPE ] = OUString( "esc" ); + aKeyMap[ KEY_TAB ] = OUString( sal_Unicode( 0x21e5 ) ); + aKeyMap[ KEY_BACKSPACE ]= OUString( sal_Unicode( 0x232b ) ); + aKeyMap[ KEY_SPACE ] = OUString( sal_Unicode( 0x2423 ) ); + aKeyMap[ KEY_DELETE ] = OUString( sal_Unicode( 0x2326 ) ); + aKeyMap[ KEY_ADD ] = OUString( '+' ); + aKeyMap[ KEY_SUBTRACT ] = OUString( '-' ); + aKeyMap[ KEY_DIVIDE ] = OUString( '/' ); + aKeyMap[ KEY_MULTIPLY ] = OUString( '*' ); + aKeyMap[ KEY_POINT ] = OUString( '.' ); + aKeyMap[ KEY_COMMA ] = OUString( ',' ); + aKeyMap[ KEY_LESS ] = OUString( '<' ); + aKeyMap[ KEY_GREATER ] = OUString( '>' ); + aKeyMap[ KEY_EQUAL ] = OUString( '=' ); + aKeyMap[ KEY_OPEN ] = OUString( sal_Unicode( 0x23cf ) ); + + /* 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_TILDE ] = OUString( sal_Unicode( ) ); + aKeyMap[ KEY_QUOTELEFT ]= OUString( sal_Unicode( ) ); + */ + + } + + OUStringBuffer aResult( 16 ); + + sal_uInt16 nUnmodifiedCode = (nKeyCode & KEY_CODE); + std::map< sal_uInt16, OUString >::const_iterator it = aKeyMap.find( nUnmodifiedCode ); + if( it != aKeyMap.end() ) + { + if( (nKeyCode & KEY_SHIFT) != 0 ) + aResult.append( sal_Unicode( 0x21e7 ) ); + if( (nKeyCode & KEY_MOD1) != 0 ) + aResult.append( sal_Unicode( 0x2318 ) ); + // we do not really handle Alt (see below) + // we map it to MOD3, whichis actually Command + if( (nKeyCode & (KEY_MOD2|KEY_MOD3)) != 0 ) + aResult.append( sal_Unicode( 0x2303 ) ); + + 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 = ((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 = ((CFBooleanRef)CFPreferencesCopyAppValue( jumpScroll, kCFPreferencesCurrentApplication )); + if( jumpStr ) + { + if( CFGetTypeID( jumpStr ) == CFBooleanGetTypeID() ) + rSettings.SetPrimaryButtonWarpsSlider(jumpStr == kCFBooleanTrue); + CFRelease( jumpStr ); + } + CFRelease( jumpScroll ); + } +} + +static Color getColor( NSColor* pSysColor, const Color& rDefault, NSWindow* pWin ) +{ + Color aRet( rDefault ); + if( pSysColor ) + { + // transform to RGB + NSColor* pRBGColor = [pSysColor colorUsingColorSpaceName: NSDeviceRGBColorSpace device: [pWin deviceDescription]]; + if( pRBGColor ) + { + CGFloat r = 0, g = 0, b = 0, a = 0; + [pRBGColor getRed: &r green: &g blue: &b alpha: &a]; + aRet = Color( int(r*255.999), int(g*255.999), int(b*255.999) ); + /* + do not release here; leads to duplicate free in yield + it seems the converted color comes out autoreleased, although this + is not documented + [pRBGColor release]; + */ + } + } + return aRet; +} + +static Font getFont( NSFont* pFont, long nDPIY, const Font& rDefault ) +{ + Font aResult( rDefault ); + if( pFont ) + { + aResult.SetName( GetOUString( [pFont familyName] ) ); + aResult.SetHeight( static_cast<int>(([pFont pointSize] * 72.0 / (float)nDPIY)+0.5) ); + aResult.SetItalic( ([pFont italicAngle] != 0.0) ? ITALIC_NORMAL : ITALIC_NONE ); + // FIMXE: bold ? + } + + return aResult; +} + +void AquaSalFrame::getResolution( sal_Int32& o_rDPIX, sal_Int32& o_rDPIY ) +{ + if( ! mpGraphics ) + { + GetGraphics(); + ReleaseGraphics( mpGraphics ); + } + mpGraphics->GetResolution( o_rDPIX, o_rDPIY ); +} + +// on OSX-Aqua the style settings are independent of the frame, so it does +// not really belong here. Since the connection to the Appearance_Manager +// is currently done in salnativewidgets.cxx this would be a good place. +// On the other hand VCL's platform independent code currently only asks +// SalFrames for system settings anyway, so moving the code somewhere else +// doesn't make the anything cleaner for now +void AquaSalFrame::UpdateSettings( AllSettings& rSettings ) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + [mpNSView lockFocus]; + + StyleSettings aStyleSettings = rSettings.GetStyleSettings(); + + // Background Color + Color aBackgroundColor = Color( 0xEC, 0xEC, 0xEC ); + aStyleSettings.Set3DColors( aBackgroundColor ); + aStyleSettings.SetFaceColor( aBackgroundColor ); + Color aInactiveTabColor( aBackgroundColor ); + aInactiveTabColor.DecreaseLuminance( 32 ); + aStyleSettings.SetInactiveTabColor( aInactiveTabColor ); + + aStyleSettings.SetDialogColor( aBackgroundColor ); + aStyleSettings.SetLightBorderColor( aBackgroundColor ); + Color aShadowColor( aStyleSettings.GetShadowColor() ); + aShadowColor.IncreaseLuminance( 32 ); + aStyleSettings.SetShadowColor( aShadowColor ); + + // get the system font settings + Font aAppFont = aStyleSettings.GetAppFont(); + sal_Int32 nDPIX = 72, nDPIY = 72; + getResolution( nDPIX, nDPIY ); + aAppFont = getFont( [NSFont systemFontOfSize: 0], nDPIY, aAppFont ); + + aStyleSettings.SetToolbarIconSize( STYLE_TOOLBAR_ICONSIZE_LARGE ); + + // TODO: better mapping of OS X<->LibreOffice font settings + aStyleSettings.SetAppFont( aAppFont ); + aStyleSettings.SetHelpFont( aAppFont ); + aStyleSettings.SetPushButtonFont( aAppFont ); + + Font aTitleFont( getFont( [NSFont titleBarFontOfSize: 0], nDPIY, aAppFont ) ); + aStyleSettings.SetTitleFont( aTitleFont ); + aStyleSettings.SetFloatTitleFont( aTitleFont ); + + Font aMenuFont( getFont( [NSFont menuFontOfSize: 0], nDPIY, aAppFont ) ); + aStyleSettings.SetMenuFont( aMenuFont ); + + aStyleSettings.SetToolFont( aAppFont ); + + Font aLabelFont( getFont( [NSFont labelFontOfSize: 0], nDPIY, aAppFont ) ); + aStyleSettings.SetLabelFont( aLabelFont ); + aStyleSettings.SetInfoFont( aLabelFont ); + aStyleSettings.SetRadioCheckFont( aLabelFont ); + aStyleSettings.SetFieldFont( aLabelFont ); + aStyleSettings.SetGroupFont( aLabelFont ); + aStyleSettings.SetIconFont( aLabelFont ); + + Color aHighlightColor( getColor( [NSColor selectedTextBackgroundColor], + aStyleSettings.GetHighlightColor(), mpNSWindow ) ); + aStyleSettings.SetHighlightColor( aHighlightColor ); + Color aHighlightTextColor( getColor( [NSColor selectedTextColor], + aStyleSettings.GetHighlightTextColor(), mpNSWindow ) ); + aStyleSettings.SetHighlightTextColor( aHighlightTextColor ); + + Color aMenuHighlightColor( getColor( [NSColor selectedMenuItemColor], + aStyleSettings.GetMenuHighlightColor(), mpNSWindow ) ); + aStyleSettings.SetMenuHighlightColor( aMenuHighlightColor ); + Color aMenuHighlightTextColor( getColor( [NSColor selectedMenuItemTextColor], + aStyleSettings.GetMenuHighlightTextColor(), mpNSWindow ) ); + aStyleSettings.SetMenuHighlightTextColor( aMenuHighlightTextColor ); + + aStyleSettings.SetMenuColor( aBackgroundColor ); + Color aMenuTextColor( getColor( [NSColor textColor], + aStyleSettings.GetMenuTextColor(), mpNSWindow ) ); + aStyleSettings.SetMenuTextColor( aMenuTextColor ); + aStyleSettings.SetMenuBarTextColor( aMenuTextColor ); + aStyleSettings.SetMenuBarRolloverTextColor( aMenuTextColor ); + + aStyleSettings.SetCursorBlinkTime( 500 ); + + // no mnemonics on OS X + aStyleSettings.SetOptions( aStyleSettings.GetOptions() | STYLE_OPTION_NOMNEMONICS ); + + getAppleScrollBarVariant(aStyleSettings); + + // set scrollbar size + aStyleSettings.SetScrollBarSize( static_cast<long int>([NSScroller scrollerWidth]) ); + + // images in menus false for MacOSX + aStyleSettings.SetPreferredUseImagesInMenus( false ); + aStyleSettings.SetHideDisabledMenuItems( sal_True ); + aStyleSettings.SetAcceleratorsInContextMenus( sal_False ); + + rSettings.SetStyleSettings( aStyleSettings ); + + [mpNSView unlockFocus]; +} + +// ----------------------------------------------------------------------- + +const SystemEnvData* AquaSalFrame::GetSystemData() const +{ + return &maSysData; +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::Beep() +{ + NSBeep(); +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::SetPosSize(long nX, long nY, long nWidth, long nHeight, sal_uInt16 nFlags) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + sal_uInt16 nEvent = 0; + + if( [mpNSWindow isMiniaturized] ) + [mpNSWindow deminiaturize: NSApp]; // expand the window + + 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; + } + + NSRect aFrameRect = [mpNSWindow frame]; + NSRect aContentRect = [NSWindow contentRectForFrameRect: aFrameRect styleMask: mnStyleMask]; + + // position is always relative to parent frame + NSRect aParentContentRect; + + if( mpParent ) + { + if( Application::GetSettings().GetLayoutRTL() ) + { + if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 ) + nX = mpParent->maGeometry.nWidth - nWidth-1 - nX; + else + nX = mpParent->maGeometry.nWidth - static_cast<long int>( aContentRect.size.width-1) - nX; + } + NSRect aParentFrameRect = [mpParent->mpNSWindow frame]; + aParentContentRect = [NSWindow contentRectForFrameRect: aParentFrameRect styleMask: mpParent->mnStyleMask]; + } + else + aParentContentRect = maScreenRect; // use screen if no parent + + CocoaToVCL( aContentRect ); + CocoaToVCL( aParentContentRect ); + + bool bPaint = false; + if( (nFlags & (SAL_FRAME_POSSIZE_WIDTH | SAL_FRAME_POSSIZE_HEIGHT)) != 0 ) + { + if( nWidth != aContentRect.size.width || nHeight != aContentRect.size.height ) + bPaint = true; + } + + // use old window pos if no new pos requested + if( (nFlags & SAL_FRAME_POSSIZE_X) != 0 ) + aContentRect.origin.x = nX + aParentContentRect.origin.x; + if( (nFlags & SAL_FRAME_POSSIZE_Y) != 0) + aContentRect.origin.y = nY + aParentContentRect.origin.y; + + // use old size if no new size requested + if( (nFlags & SAL_FRAME_POSSIZE_WIDTH) != 0 ) + aContentRect.size.width = nWidth; + if( (nFlags & SAL_FRAME_POSSIZE_HEIGHT) != 0) + aContentRect.size.height = nHeight; + + VCLToCocoa( aContentRect ); + + // do not display yet, we need to update our backbuffer + { + [mpNSWindow setFrame: [NSWindow frameRectForContentRect: aContentRect styleMask: mnStyleMask] display: NO]; + } + + UpdateFrameGeometry(); + + if (nEvent) + CallCallback(nEvent, NULL); + + if( mbShown && bPaint ) + { + // trigger filling our backbuffer + SendPaintEvent(); + + // now inform the system that the views need to be drawn + [mpNSWindow display]; + } +} + +void AquaSalFrame::GetWorkArea( Rectangle& rRect ) +{ + if ( !mpNSWindow ) + return; + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + NSScreen* pScreen = [mpNSWindow screen]; + if( pScreen == nil ) + pScreen = [NSScreen mainScreen]; + NSRect aRect = [pScreen visibleFrame]; + CocoaToVCL( aRect ); + rRect.Left() = static_cast<long>(aRect.origin.x); + rRect.Top() = static_cast<long>(aRect.origin.y); + rRect.Right() = static_cast<long>(aRect.origin.x + aRect.size.width - 1); + rRect.Bottom() = static_cast<long>(aRect.origin.y + aRect.size.height - 1); +} + +SalPointerState AquaSalFrame::GetPointerState() +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + SalPointerState state; + state.mnState = 0; + + // get position + NSPoint aPt = [mpNSWindow mouseLocationOutsideOfEventStream]; + CocoaToVCL( aPt, false ); + state.maPos = Point(static_cast<long>(aPt.x), static_cast<long>(aPt.y)); + + NSEvent* pCur = [NSApp currentEvent]; + bool bMouseEvent = false; + if( pCur ) + { + bMouseEvent = true; + switch( [pCur type] ) + { + case NSLeftMouseDown: state.mnState |= MOUSE_LEFT; break; + case NSLeftMouseUp: break; + case NSRightMouseDown: state.mnState |= MOUSE_RIGHT; break; + case NSRightMouseUp: break; + case NSOtherMouseDown: state.mnState |= ([pCur buttonNumber] == 2) ? MOUSE_MIDDLE : 0; break; + case NSOtherMouseUp: break; + case NSMouseMoved: break; + case NSLeftMouseDragged: state.mnState |= MOUSE_LEFT; break; + case NSRightMouseDragged: state.mnState |= MOUSE_RIGHT; break; + case NSOtherMouseDragged: state.mnState |= ([pCur buttonNumber] == 2) ? MOUSE_MIDDLE : 0; break; + break; + default: + bMouseEvent = false; + break; + } + } + if( bMouseEvent ) + { + unsigned int nMask = (unsigned int)[pCur modifierFlags]; + if( (nMask & NSShiftKeyMask) != 0 ) + state.mnState |= KEY_SHIFT; + if( (nMask & NSControlKeyMask) != 0 ) + state.mnState |= KEY_MOD3; + if( (nMask & NSAlternateKeyMask) != 0 ) + state.mnState |= KEY_MOD2; + if( (nMask & NSCommandKeyMask) != 0 ) + state.mnState |= KEY_MOD1; + + } + 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_NSApllication::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; +} + +SalFrame::SalIndicatorState AquaSalFrame::GetIndicatorState() +{ + SalIndicatorState aState; + aState.mnState = 0; + return aState; +} + +void AquaSalFrame::SimulateKeyPress( sal_uInt16 /*nKeyCode*/ ) +{ +} + +bool AquaSalFrame::SetPluginParent( SystemParentData* ) +{ + // plugin parent may be killed unexpectedly by + // plugging process; + + //TODO: implement + return sal_False; +} + +sal_Bool AquaSalFrame::MapUnicodeToKeyCode( sal_Unicode , LanguageType , KeyCode& ) +{ + // not supported yet + return FALSE; +} + +LanguageType AquaSalFrame::GetInputLanguage() +{ + //TODO: implement + return LANGUAGE_DONTKNOW; +} + +void AquaSalFrame::DrawMenuBar() +{ +} + +void AquaSalFrame::SetMenu( SalMenu* pSalMenu ) +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + AquaSalMenu* pMenu = static_cast<AquaSalMenu*>(pSalMenu); + DBG_ASSERT( ! pMenu || pMenu->mbMenuBar, "setting non menubar on frame" ); + mpMenu = pMenu; + if( mpMenu ) + mpMenu->setMainMenu(); +} + +void AquaSalFrame::SetExtendedFrameStyle( SalExtStyle nStyle ) +{ + if ( mpNSWindow ) + { + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + 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 + Show( FALSE ); + mpParent = (AquaSalFrame*)pNewParent; + // insert to correct parent and paint + Show( bShown ); +} + +void AquaSalFrame::UpdateFrameGeometry() +{ + if ( !mpNSWindow ) + { + return; + } + + // 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 ) + { + maScreenRect = [pScreen frame]; + NSArray* pScreens = [NSScreen screens]; + if( pScreens ) + maGeometry.nDisplayScreenNumber = [pScreens indexOfObject: pScreen]; + } + + NSRect aFrameRect = [mpNSWindow frame]; + NSRect aContentRect = [NSWindow contentRectForFrameRect: aFrameRect styleMask: mnStyleMask]; + + // release old track rect + [mpNSView removeTrackingRect: mnTrackingRectTag]; + // install the new track rect + NSRect aTrackRect = { { 0, 0 }, aContentRect.size }; + mnTrackingRectTag = [mpNSView addTrackingRect: aTrackRect owner: mpNSView userData: nil assumeInside: NO]; + + // convert to vcl convention + CocoaToVCL( aFrameRect ); + CocoaToVCL( aContentRect ); + + maGeometry.nX = static_cast<int>(aContentRect.origin.x); + maGeometry.nY = static_cast<int>(aContentRect.origin.y); + + maGeometry.nLeftDecoration = static_cast<unsigned int>(aContentRect.origin.x - aFrameRect.origin.x); + maGeometry.nRightDecoration = static_cast<unsigned int>((aFrameRect.origin.x + aFrameRect.size.width) - + (aContentRect.origin.x + aContentRect.size.width)); + + maGeometry.nTopDecoration = static_cast<unsigned int>(aContentRect.origin.y - aFrameRect.origin.y); + maGeometry.nBottomDecoration = static_cast<unsigned int>((aFrameRect.origin.y + aFrameRect.size.height) - + (aContentRect.origin.y + aContentRect.size.height)); + + maGeometry.nWidth = static_cast<unsigned int>(aContentRect.size.width); + maGeometry.nHeight = static_cast<unsigned int>(aContentRect.size.height); +} + +// ----------------------------------------------------------------------- + +void AquaSalFrame::CaptureMouse( sal_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 = NULL; +} + +void AquaSalFrame::ResetClipRegion() +{ + if ( !mpNSWindow ) + { + return; + } + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + // release old path and indicate no clipping + CGPathRelease( mrClippingPath ); + mrClippingPath = NULL; + + if( mpNSView && mbShown ) + [mpNSView setNeedsDisplay: YES]; + if( mpNSWindow ) + { + [mpNSWindow setOpaque: YES]; + [mpNSWindow invalidateShadow]; + } +} + +void AquaSalFrame::BeginSetClipRegion( sal_uLong nRects ) +{ + if ( !mpNSWindow ) + { + return; + } + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + // release old path + if( mrClippingPath ) + { + CGPathRelease( mrClippingPath ); + mrClippingPath = NULL; + } + + if( maClippingRects.size() > SAL_CLIPRECT_COUNT && nRects < maClippingRects.size() ) + { + std::vector<CGRect> aEmptyVec; + maClippingRects.swap( aEmptyVec ); + } + maClippingRects.clear(); + maClippingRects.reserve( nRects ); +} + +void AquaSalFrame::UnionClipRegion( long nX, long nY, long nWidth, long nHeight ) +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + if( nWidth && nHeight ) + { + NSRect aRect = { { static_cast<CGFloat>(nX), static_cast<CGFloat>(nY) }, { static_cast<CGFloat>(nWidth), static_cast<CGFloat>(nHeight) } }; + VCLToCocoa( aRect, false ); + maClippingRects.push_back( CGRectMake(aRect.origin.x, aRect.origin.y, aRect.size.width, aRect.size.height) ); + } +} + +void AquaSalFrame::EndSetClipRegion() +{ + if ( !mpNSWindow ) + { + return; + } + + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + if( ! maClippingRects.empty() ) + { + mrClippingPath = CGPathCreateMutable(); + CGPathAddRects( mrClippingPath, NULL, &maClippingRects[0], maClippingRects.size() ); + } + if( mpNSView && mbShown ) + [mpNSView setNeedsDisplay: YES]; + if( mpNSWindow ) + { + [mpNSWindow setOpaque: (mrClippingPath != NULL) ? 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 000000000000..81ae1cdce8c3 --- /dev/null +++ b/vcl/osx/salframeview.mm @@ -0,0 +1,1805 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/alloca.h> +#include <sal/macros.h> + +#include "tools/helpers.hxx" +#include "vcl/window.hxx" +#include "vcl/svapp.hxx" + +#include "osx/salinst.h" +#include "quartz/salgdi.h" +#include "osx/salframe.h" +#include "osx/salframeview.h" +#include "osx/a11yfactory.h" +#include "quartz/utils.h" + +#define WHEEL_EVENT_FACTOR 1.5 + +// for allowing fullscreen support on deployment targets < OSX 10.7 +#if !defined(MAC_OS_X_VERSION_10_7) + #define NSWindowCollectionBehaviorFullScreenPrimary (1 << 7) + #define NSWindowCollectionBehaviorFullScreenAuxiliary (1 << 8) +// #define NSFullScreenWindowMask (1 << 14) +#endif + + +static sal_uInt16 ImplGetModifierMask( unsigned int nMask ) +{ + sal_uInt16 nRet = 0; + if( (nMask & NSShiftKeyMask) != 0 ) + nRet |= KEY_SHIFT; + if( (nMask & NSControlKeyMask) != 0 ) + nRet |= KEY_MOD3; + if( (nMask & NSAlternateKeyMask) != 0 ) + nRet |= KEY_MOD2; + if( (nMask & NSCommandKeyMask) != 0 ) + nRet |= KEY_MOD1; + return nRet; +} + +static sal_uInt16 ImplMapCharCode( sal_Unicode aCode ) +{ + static sal_uInt16 aKeyCodeMap[ 128 ] = + { + 0, 0, 0, 0, 0, 0, 0, 0, + KEY_BACKSPACE, KEY_TAB, KEY_RETURN, 0, 0, KEY_RETURN, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, KEY_TAB, 0, KEY_ESCAPE, 0, 0, 0, 0, + KEY_SPACE, 0, 0, 0, 0, 0, 0, 0, + 0, 0, KEY_MULTIPLY, KEY_ADD, KEY_COMMA, KEY_SUBTRACT, KEY_POINT, KEY_DIVIDE, + KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, + KEY_8, KEY_9, 0, 0, KEY_LESS, KEY_EQUAL, KEY_GREATER, 0, + 0, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, + KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, + KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, + KEY_X, KEY_Y, KEY_Z, 0, 0, 0, 0, 0, + KEY_QUOTELEFT, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, + KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, + KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, + KEY_X, KEY_Y, KEY_Z, 0, 0, 0, KEY_TILDE, KEY_BACKSPACE + }; + + // Note: the mapping 0x7f should by rights be KEY_DELETE + // however if you press "backspace" 0x7f is reported + // whereas for "delete" 0xf728 gets reported + + // Note: the mapping of 0x19 to KEY_TAB is because for unknown reasons + // tab alone is reported as 0x09 (as expected) but shift-tab is + // reported as 0x19 (end of medium) + + static sal_uInt16 aFunctionKeyCodeMap[ 128 ] = + { + KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_F1, KEY_F2, KEY_F3, KEY_F4, + KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, + KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, + KEY_F21, KEY_F22, KEY_F23, KEY_F24, KEY_F25, KEY_F26, 0, 0, + 0, 0, 0, 0, 0, 0, 0, KEY_INSERT, + KEY_DELETE, KEY_HOME, 0, KEY_END, KEY_PAGEUP, KEY_PAGEDOWN, 0, 0, + 0, 0, 0, 0, 0, KEY_MENU, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, KEY_UNDO, KEY_REPEAT, KEY_FIND, KEY_HELP, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 + }; + + sal_uInt16 nKeyCode = 0; + if( aCode < SAL_N_ELEMENTS( aKeyCodeMap) ) + nKeyCode = aKeyCodeMap[ aCode ]; + else if( aCode >= 0xf700 && aCode < 0xf780 ) + nKeyCode = aFunctionKeyCodeMap[ aCode - 0xf700 ]; + return nKeyCode; +} + +static sal_uInt16 ImplMapKeyCode(sal_uInt16 nKeyCode) +{ + /* + http://stackoverflow.com/questions/2080312/where-can-i-find-a-list-of-key-codes-for-use-with-cocoas-nsevent-class/2080324#2080324 + /System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h + */ + + static sal_uInt16 aKeyCodeMap[ 0x80 ] = + { + KEY_A, KEY_S, KEY_D, KEY_F, KEY_H, KEY_G, KEY_Z, KEY_X, + KEY_C, KEY_V, 0, KEY_B, KEY_Q, KEY_W, KEY_E, KEY_R, + KEY_Y, KEY_T, KEY_1, KEY_2, KEY_3, KEY_4, KEY_6, KEY_5, + KEY_EQUAL, KEY_9, KEY_7, KEY_SUBTRACT, KEY_8, KEY_0, KEY_BRACKETRIGHT, KEY_0, + KEY_U, KEY_BRACKETLEFT, KEY_I, KEY_P, KEY_RETURN, KEY_L, KEY_J, KEY_QUOTELEFT, + KEY_K, KEY_SEMICOLON, 0, KEY_COMMA, KEY_DIVIDE, KEY_N, KEY_M, KEY_POINT, + KEY_TAB, KEY_SPACE, KEY_TILDE, 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 = NULL; +// store the last pressed button for enter/exit events +// which lack that information +static sal_uInt16 s_nLastButton = 0; + +// combinations of keys we need to handle ourselves +static const struct ExceptionalKey +{ + const sal_uInt16 nKeyCode; + const unsigned int nModifierMask; +} aExceptionalKeys[] = +{ + { KEY_D, NSControlKeyMask | NSShiftKeyMask | NSAlternateKeyMask }, + { KEY_D, NSCommandKeyMask | NSShiftKeyMask | NSAlternateKeyMask } +}; + +static AquaSalFrame* getMouseContainerFrame() +{ + NSInteger nWindows = 0; + NSCountWindows( &nWindows ); + NSInteger* pWindows = (NSInteger*)alloca( nWindows * sizeof(NSInteger) ); + // note: NSWindowList is supposed to be in z-order front to back + NSWindowList( nWindows, pWindows ); + AquaSalFrame* pDispatchFrame = NULL; + for(int i = 0; i < nWindows && ! pDispatchFrame; i++ ) + { + NSWindow* pWin = [NSApp windowWithWindowNumber: pWindows[i]]; + if( pWin && [pWin isMemberOfClass: [SalFrameWindow class]] && [(SalFrameWindow*)pWin containsMouse] ) + pDispatchFrame = [(SalFrameWindow*)pWin getSalFrame]; + } + return pDispatchFrame; +} + +@implementation SalFrameWindow +-(id)initWithSalFrame: (AquaSalFrame*)pFrame +{ + mDraggingDestinationHandler = nil; + mpFrame = pFrame; + NSRect aRect = { { static_cast<CGFloat>(pFrame->maGeometry.nX), static_cast<CGFloat>(pFrame->maGeometry.nY) }, + { static_cast<CGFloat>(pFrame->maGeometry.nWidth), static_cast<CGFloat>(pFrame->maGeometry.nHeight) } }; + pFrame->VCLToCocoa( aRect ); + NSWindow* pNSWindow = [super initWithContentRect: aRect styleMask: mpFrame->getStyleMask() backing: NSBackingStoreBuffered defer: NO ]; + [pNSWindow useOptimizedDrawing: YES]; // OSX recommendation when there are no overlapping subviews within the receiver + + // enable OSX>=10.7 fullscreen options if available and useful + bool bAllowFullScreen = (0 == (mpFrame->mnStyle & (SAL_FRAME_STYLE_DIALOG | SAL_FRAME_STYLE_TOOLTIP | SAL_FRAME_STYLE_SYSTEMCHILD | SAL_FRAME_STYLE_FLOAT | SAL_FRAME_STYLE_TOOLWINDOW | SAL_FRAME_STYLE_INTRO))); + bAllowFullScreen &= (0 == (~mpFrame->mnStyle & (SAL_FRAME_STYLE_SIZEABLE))); + bAllowFullScreen &= (mpFrame->mpParent == NULL); + const SEL setCollectionBehavior = @selector(setCollectionBehavior:); + if( bAllowFullScreen && [pNSWindow respondsToSelector: setCollectionBehavior]) + { + const int bMode= (bAllowFullScreen ? NSWindowCollectionBehaviorFullScreenPrimary : NSWindowCollectionBehaviorFullScreenAuxiliary); + [pNSWindow performSelector:setCollectionBehavior withObject:(id)(intptr_t)bMode]; + } + + // disable OSX>=10.7 window restoration until we support it directly + const SEL setRestorable = @selector(setRestorable:); + if( [pNSWindow respondsToSelector: setRestorable]) { + [pNSWindow performSelector:setRestorable withObject:(id)NO]; + } + + return (SalFrameWindow *) pNSWindow; +} + +-(AquaSalFrame*)getSalFrame +{ + return mpFrame; +} + +-(void)displayIfNeeded +{ + if( GetSalData() && GetSalData()->mpFirstInstance ) + { + comphelper::SolarMutex* pMutex = GetSalData()->mpFirstInstance->GetYieldMutex(); + if( pMutex ) + { + pMutex->acquire(); + [super displayIfNeeded]; + pMutex->release(); + } + } +} + +-(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 & + ( SAL_FRAME_STYLE_FLOAT | + SAL_FRAME_STYLE_TOOLTIP | + SAL_FRAME_STYLE_INTRO + )) == 0 ) + return YES; + if( (mpFrame->mnStyle & SAL_FRAME_STYLE_OWNERDRAWDECORATION) != 0 ) + return YES; + if( mpFrame->mbFullScreen ) + return YES; + if( (mpFrame->mnStyle & SAL_FRAME_STYLE_FLOAT_FOCUSABLE) ) + return YES; + return [super canBecomeKeyWindow]; +} + +-(void)windowDidBecomeKey: (NSNotification*)pNotification +{ + (void)pNotification; + YIELD_GUARD; + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + static const sal_uLong nGuessDocument = SAL_FRAME_STYLE_MOVEABLE| + SAL_FRAME_STYLE_SIZEABLE| + SAL_FRAME_STYLE_CLOSEABLE; + + if( mpFrame->mpMenu ) + mpFrame->mpMenu->setMainMenu(); + else if( ! mpFrame->mpParent && + ( (mpFrame->mnStyle & nGuessDocument) == nGuessDocument || // set default menu for e.g. help + mpFrame->mbFullScreen ) ) // ser default menu for e.g. presentation + { + AquaSalMenu::setDefaultMenu(); + } + #if 0 + // FIXME: we should disable menus while in modal mode + // however from down here there is currently no reliable way to + // find out when to do this + if( (mpFrame->mpParent && mpFrame->mpParent->GetWindow()->IsInModalMode()) ) + AquaSalMenu::enableMainMenu( false ); + #endif + mpFrame->CallCallback( SALEVENT_GETFOCUS, 0 ); + mpFrame->SendPaintEvent(); // repaint controls as active + } +} + +-(void)windowDidResignKey: (NSNotification*)pNotification +{ + (void)pNotification; + YIELD_GUARD; + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->CallCallback(SALEVENT_LOSEFOCUS, 0); + mpFrame->SendPaintEvent(); // repaint controls as inactive + } +} + +-(void)windowDidChangeScreen: (NSNotification*)pNotification +{ + (void)pNotification; + YIELD_GUARD; + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + mpFrame->screenParametersChanged(); +} + +-(void)windowDidMove: (NSNotification*)pNotification +{ + (void)pNotification; + YIELD_GUARD; + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->UpdateFrameGeometry(); + mpFrame->CallCallback( SALEVENT_MOVE, 0 ); + } +} + +-(void)windowDidResize: (NSNotification*)pNotification +{ + (void)pNotification; + YIELD_GUARD; + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->UpdateFrameGeometry(); + mpFrame->CallCallback( SALEVENT_RESIZE, 0 ); + mpFrame->SendPaintEvent(); + } +} + +-(void)windowDidMiniaturize: (NSNotification*)pNotification +{ + (void)pNotification; + YIELD_GUARD; + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->mbShown = false; + mpFrame->UpdateFrameGeometry(); + mpFrame->CallCallback( SALEVENT_RESIZE, 0 ); + } +} + +-(void)windowDidDeminiaturize: (NSNotification*)pNotification +{ + (void)pNotification; + YIELD_GUARD; + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->mbShown = true; + mpFrame->UpdateFrameGeometry(); + mpFrame->CallCallback( SALEVENT_RESIZE, 0 ); + } +} + +-(BOOL)windowShouldClose: (NSNotification*)pNotification +{ + (void)pNotification; + YIELD_GUARD; + + BOOL bRet = YES; + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + // #i84461# end possible input + mpFrame->CallCallback( SALEVENT_ENDEXTTEXTINPUT, 0 ); + if( AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->CallCallback( SALEVENT_CLOSE, 0 ); + bRet = NO; // application will close the window or not, AppKit shouldn't + } + } + + return bRet; +} + +-(void)windowDidEnterFullScreen: (NSNotification*)pNotification +{ + YIELD_GUARD; + + if( !mpFrame || !AquaSalFrame::isAlive( mpFrame)) + return; + mpFrame->mbFullScreen = true; + (void)pNotification; +} + +-(void)windowDidExitFullScreen: (NSNotification*)pNotification +{ + YIELD_GUARD; + + if( !mpFrame || !AquaSalFrame::isAlive( mpFrame)) + return; + mpFrame->mbFullScreen = false; + (void)pNotification; +} + +-(void)dockMenuItemTriggered: (id)sender +{ + (void)sender; + YIELD_GUARD; + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + mpFrame->ToTop( SAL_FRAME_TOTOP_RESTOREWHENMIN | SAL_FRAME_TOTOP_GRABFOCUS ); +} + +-(::com::sun::star::uno::Reference < ::com::sun::star::accessibility::XAccessibleContext >)accessibleContext +{ + return mpFrame -> GetWindow() -> GetAccessible() -> getAccessibleContext(); +} + +-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender +{ + return [mDraggingDestinationHandler draggingEntered: sender]; +} + +-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender +{ + return [mDraggingDestinationHandler draggingUpdated: sender]; +} + +-(void)draggingExited:(id <NSDraggingInfo>)sender +{ + [mDraggingDestinationHandler draggingExited: sender]; +} + +-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender +{ + return [mDraggingDestinationHandler prepareForDragOperation: sender]; +} + +-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender +{ + return [mDraggingDestinationHandler performDragOperation: sender]; +} + +-(void)concludeDragOperation:(id <NSDraggingInfo>)sender +{ + [mDraggingDestinationHandler concludeDragOperation: sender]; +} + +-(void)registerDraggingDestinationHandler:(id)theHandler +{ + mDraggingDestinationHandler = theHandler; +} + +-(void)unregisterDraggingDestinationHandler:(id)theHandler +{ + (void)theHandler; + mDraggingDestinationHandler = nil; +} + +@end + +@implementation SalFrameView ++(void)unsetMouseFrame: (AquaSalFrame*)pFrame +{ + if( pFrame == s_pMouseFrame ) + s_pMouseFrame = NULL; +} + +-(id)initWithSalFrame: (AquaSalFrame*)pFrame +{ + if ((self = [super initWithFrame: [NSWindow contentRectForFrameRect: [pFrame->getNSWindow() frame] styleMask: pFrame->mnStyleMask]]) != nil) + { + mDraggingDestinationHandler = nil; + mpFrame = pFrame; + mMarkedRange = NSMakeRange(NSNotFound, 0); + mSelectedRange = NSMakeRange(NSNotFound, 0); + mpReferenceWrapper = nil; + mpMouseEventListener = nil; + mpLastSuperEvent = nil; + } + + mfLastMagnifyTime = 0.0; + return self; +} + +-(AquaSalFrame*)getSalFrame +{ + return mpFrame; +} + +-(void)resetCursorRects +{ + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + // FIXME: does this leak the returned NSCursor of getCurrentCursor ? + NSRect aRect = { { 0, 0 }, { static_cast<CGFloat>(mpFrame->maGeometry.nWidth), static_cast<CGFloat>(mpFrame->maGeometry.nHeight) } }; + [self addCursorRect: aRect cursor: mpFrame->getCurrentCursor()]; + } +} + +-(BOOL)acceptsFirstResponder +{ + return YES; +} + +-(BOOL)acceptsFirstMouse: (NSEvent*)pEvent +{ + (void)pEvent; + return YES; +} + +-(BOOL)isOpaque +{ + if( !mpFrame) + return YES; + if( !AquaSalFrame::isAlive( mpFrame)) + return YES; + if( !mpFrame->getClipPath()) + return YES; + return NO; +} + +// helper class similar to a osl::Guard< comphelper::SolarMutex > for the +// SalYieldMutex; the difference is that it only does tryToAcquire instead of +// acquire so dreaded deadlocks like #i93512# are prevented +class TryGuard +{ +public: + TryGuard() { mbGuarded = ImplSalYieldMutexTryToAcquire(); } + ~TryGuard() { if( mbGuarded ) ImplSalYieldMutexRelease(); } + bool IsGuarded() { return mbGuarded; } +private: + bool mbGuarded; +}; + +-(void)drawRect: (NSRect)aRect +{ + // HOTFIX: #i93512# prevent deadlocks if any other thread already has the SalYieldMutex + TryGuard aTryGuard; + if( !aTryGuard.IsGuarded() ) + { + // NOTE: the mpFrame access below is not guarded yet! + // TODO: mpFrame et al need to be guarded by an independent mutex + AquaSalGraphics* pGraphics = (mpFrame && AquaSalFrame::isAlive(mpFrame)) ? mpFrame->mpGraphics : NULL; + if( pGraphics ) + { + // we did not get the mutex so we cannot draw now => request to redraw later + // convert the NSRect to a CGRect for Refreshrect() + const CGRect aCGRect = {{aRect.origin.x,aRect.origin.y},{aRect.size.width,aRect.size.height}}; + pGraphics->RefreshRect( aCGRect ); + } + return; + } + + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + { + if( mpFrame->mpGraphics ) + { + mpFrame->mpGraphics->UpdateWindow( aRect ); + if( mpFrame->getClipPath() ) + [mpFrame->getNSWindow() invalidateShadow]; + } + } +} + +-(void)sendMouseEventToFrame: (NSEvent*)pEvent button:(sal_uInt16)nButton eventtype:(sal_uInt16)nEvent +{ + YIELD_GUARD; + + 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_uLong>( [pEvent timestamp] * 1000.0 ); + pDispatchFrame->mnLastModifierFlags = [pEvent modifierFlags]; + + NSPoint aPt = [NSEvent mouseLocation]; + pDispatchFrame->CocoaToVCL( aPt ); + + sal_uInt16 nModMask = ImplGetModifierMask( [pEvent modifierFlags] ); + // #i82284# emulate ctrl left + if( nModMask == KEY_MOD3 && nButton == MOUSE_LEFT ) + { + nModMask = 0; + nButton = MOUSE_RIGHT; + } + + SalMouseEvent aEvent; + aEvent.mnTime = pDispatchFrame->mnLastEventTime; + aEvent.mnX = static_cast<long>(aPt.x) - pDispatchFrame->maGeometry.nX; + aEvent.mnY = static_cast<long>(aPt.y) - pDispatchFrame->maGeometry.nY; + aEvent.mnButton = nButton; + aEvent.mnCode = aEvent.mnButton | nModMask; + + // --- RTL --- (mirror mouse pos) + if( Application::GetSettings().GetLayoutRTL() ) + aEvent.mnX = pDispatchFrame->maGeometry.nWidth-1-aEvent.mnX; + + pDispatchFrame->CallCallback( nEvent, &aEvent ); + } +} + +-(void)mouseDown: (NSEvent*)pEvent +{ + if ( mpMouseEventListener != nil && + [mpMouseEventListener respondsToSelector: @selector(mouseDown:)]) + { + [mpMouseEventListener mouseDown: [pEvent copyWithZone: NULL]]; + } + + 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: NULL]]; + } + 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 = NULL; + + // #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 +{ + YIELD_GUARD; + + // TODO: ?? -(float)magnification; + if( AquaSalFrame::isAlive( mpFrame ) ) + { + const NSTimeInterval fMagnifyTime = [pEvent timestamp]; + mpFrame->mnLastEventTime = static_cast<sal_uLong>( 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 deltaZ]; + + mfLastMagnifyTime = [pEvent timestamp]; + // TODO: change to 0.1 when COMMAND_WHEEL_ZOOM handlers allow finer zooming control + static const float fMagnifyFactor = 0.25; + static const float fMinMagnifyStep = 15.0 / fMagnifyFactor; + if( fabs(mfMagnifyDeltaSum) <= fMinMagnifyStep ) + return; + + // adapt NSEvent-sensitivity to application expectations + // TODO: rather make COMMAND_WHEEL_ZOOM handlers smarter + const float fDeltaZ = mfMagnifyDeltaSum * fMagnifyFactor; + int nDeltaZ = FRound( fDeltaZ ); + if( !nDeltaZ ) + { + // handle new series immediately + if( !bNewSeries ) + return; + nDeltaZ = (fDeltaZ >= 0.0) ? +1 : -1; + } + // eventually give credit for delta sum + mfMagnifyDeltaSum -= nDeltaZ / fMagnifyFactor; + + NSPoint aPt = [NSEvent mouseLocation]; + mpFrame->CocoaToVCL( aPt ); + + SalWheelMouseEvent aEvent; + aEvent.mnTime = mpFrame->mnLastEventTime; + aEvent.mnX = static_cast<long>(aPt.x) - mpFrame->maGeometry.nX; + aEvent.mnY = static_cast<long>(aPt.y) - mpFrame->maGeometry.nY; + aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags ); + aEvent.mnCode |= KEY_MOD1; // we want zooming, no scrolling + aEvent.mbDeltaIsPixel = TRUE; + + // --- RTL --- (mirror mouse pos) + if( Application::GetSettings().GetLayoutRTL() ) + aEvent.mnX = mpFrame->maGeometry.nWidth-1-aEvent.mnX; + + aEvent.mnDelta = nDeltaZ; + aEvent.mnNotchDelta = (nDeltaZ >= 0) ? +1 : -1; + if( aEvent.mnDelta == 0 ) + aEvent.mnDelta = aEvent.mnNotchDelta; + aEvent.mbHorz = FALSE; + aEvent.mnScrollLines = nDeltaZ; + if( aEvent.mnScrollLines == 0 ) + aEvent.mnScrollLines = 1; + 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 +{ + YIELD_GUARD; + + if( AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->mnLastEventTime = static_cast<sal_uLong>( [pEvent timestamp] * 1000.0 ); + mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; + + // merge pending scroll wheel events + float dX = 0.0; + float dY = 0.0; + for(;;) + { + dX += [pEvent deltaX]; + dY += [pEvent deltaY]; + NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSScrollWheelMask + untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ]; + if( !pNextEvent ) + break; + pEvent = pNextEvent; + } + + NSPoint aPt = [NSEvent mouseLocation]; + mpFrame->CocoaToVCL( aPt ); + + SalWheelMouseEvent aEvent; + aEvent.mnTime = mpFrame->mnLastEventTime; + aEvent.mnX = static_cast<long>(aPt.x) - mpFrame->maGeometry.nX; + aEvent.mnY = static_cast<long>(aPt.y) - mpFrame->maGeometry.nY; + aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags ); + aEvent.mbDeltaIsPixel = TRUE; + + // --- RTL --- (mirror mouse pos) + if( Application::GetSettings().GetLayoutRTL() ) + aEvent.mnX = mpFrame->maGeometry.nWidth-1-aEvent.mnX; + + if( dX != 0.0 ) + { + aEvent.mnDelta = static_cast<long>(floor(dX)); + aEvent.mnNotchDelta = dX < 0 ? -1 : 1; + if( aEvent.mnDelta == 0 ) + aEvent.mnDelta = aEvent.mnNotchDelta; + aEvent.mbHorz = TRUE; + aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL; + mpFrame->CallCallback( SALEVENT_WHEELMOUSE, &aEvent ); + } + if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame )) + { + aEvent.mnDelta = static_cast<long>(floor(dY)); + aEvent.mnNotchDelta = dY < 0 ? -1 : 1; + if( aEvent.mnDelta == 0 ) + aEvent.mnDelta = aEvent.mnNotchDelta; + aEvent.mbHorz = FALSE; + aEvent.mnScrollLines = SAL_WHEELMOUSE_EVENT_PAGESCROLL; + mpFrame->CallCallback( SALEVENT_WHEELMOUSE, &aEvent ); + } + } +} + +-(void)scrollWheel: (NSEvent*)pEvent +{ + YIELD_GUARD; + + if( AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->mnLastEventTime = static_cast<sal_uLong>( [pEvent timestamp] * 1000.0 ); + mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; + + // merge pending scroll wheel events + float dX = 0.0; + float dY = 0.0; + for(;;) + { + dX += [pEvent deltaX]; + dY += [pEvent deltaY]; + NSEvent* pNextEvent = [NSApp nextEventMatchingMask: NSScrollWheelMask + untilDate: nil inMode: NSDefaultRunLoopMode dequeue: YES ]; + if( !pNextEvent ) + break; + pEvent = pNextEvent; + } + + NSPoint aPt = [NSEvent mouseLocation]; + mpFrame->CocoaToVCL( aPt ); + + SalWheelMouseEvent aEvent; + aEvent.mnTime = mpFrame->mnLastEventTime; + aEvent.mnX = static_cast<long>(aPt.x) - mpFrame->maGeometry.nX; + aEvent.mnY = static_cast<long>(aPt.y) - mpFrame->maGeometry.nY; + aEvent.mnCode = ImplGetModifierMask( mpFrame->mnLastModifierFlags ); + aEvent.mbDeltaIsPixel = FALSE; + + // --- RTL --- (mirror mouse pos) + if( Application::GetSettings().GetLayoutRTL() ) + aEvent.mnX = mpFrame->maGeometry.nWidth-1-aEvent.mnX; + + if( dX != 0.0 ) + { + aEvent.mnDelta = static_cast<long>(floor(dX)); + aEvent.mnNotchDelta = dX < 0 ? -1 : 1; + if( aEvent.mnDelta == 0 ) + aEvent.mnDelta = aEvent.mnNotchDelta; + aEvent.mbHorz = TRUE; + aEvent.mnScrollLines = dX > 0 ? dX/WHEEL_EVENT_FACTOR : -dX/WHEEL_EVENT_FACTOR; + if( aEvent.mnScrollLines == 0 ) + aEvent.mnScrollLines = 1; + + mpFrame->CallCallback( SALEVENT_WHEELMOUSE, &aEvent ); + } + if( dY != 0.0 && AquaSalFrame::isAlive( mpFrame ) ) + { + aEvent.mnDelta = static_cast<long>(floor(dY)); + aEvent.mnNotchDelta = dY < 0 ? -1 : 1; + if( aEvent.mnDelta == 0 ) + aEvent.mnDelta = aEvent.mnNotchDelta; + aEvent.mbHorz = FALSE; + aEvent.mnScrollLines = dY > 0 ? dY/WHEEL_EVENT_FACTOR : -dY/WHEEL_EVENT_FACTOR; + if( aEvent.mnScrollLines < 1 ) + aEvent.mnScrollLines = 1; + + mpFrame->CallCallback( SALEVENT_WHEELMOUSE, &aEvent ); + } + } +} + + +-(void)keyDown: (NSEvent*)pEvent +{ + YIELD_GUARD; + + if( AquaSalFrame::isAlive( mpFrame ) ) + { + mpLastEvent = pEvent; + mbInKeyInput = true; + mbNeedSpecialKeyHandle = false; + mbKeyHandled = false; + + mpFrame->mnLastEventTime = static_cast<sal_uLong>( [pEvent timestamp] * 1000.0 ); + mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; + + if( ! [self handleKeyDownException: pEvent] ) + { + NSArray* pArray = [NSArray arrayWithObject: pEvent]; + [self interpretKeyEvents: pArray]; + } + + mbInKeyInput = false; + } +} + +-(BOOL)handleKeyDownException:(NSEvent*)pEvent +{ + // check for a very special set of modified characters + NSString* pUnmodifiedString = [pEvent charactersIgnoringModifiers]; + + if( pUnmodifiedString && [pUnmodifiedString length] == 1 ) + { + /* #i103102# key events with command and alternate don't make it through + interpretKeyEvents (why ?). Try to dispatch them here first, + if not successful continue normally + */ + if( (mpFrame->mnLastModifierFlags & (NSAlternateKeyMask | NSCommandKeyMask)) + == (NSAlternateKeyMask | NSCommandKeyMask) ) + { + if( [self sendSingleCharacter: mpLastEvent] ) + return YES; + } + unichar keyChar = [pUnmodifiedString characterAtIndex: 0]; + sal_uInt16 nKeyCode = ImplMapCharCode( keyChar ); + + // Caution: should the table grow to more than 5 or 6 entries, + // we must consider moving it to a kind of hash map + const unsigned int nExceptions = SAL_N_ELEMENTS( aExceptionalKeys ); + for( unsigned int i = 0; i < nExceptions; i++ ) + { + if( nKeyCode == aExceptionalKeys[i].nKeyCode && + (mpFrame->mnLastModifierFlags & aExceptionalKeys[i].nModifierMask) + == aExceptionalKeys[i].nModifierMask ) + { + [self sendKeyInputAndReleaseToFrame: nKeyCode character: 0]; + + return YES; + } + } + } + return NO; +} + +-(void)flagsChanged: (NSEvent*)pEvent +{ + YIELD_GUARD; + + if( AquaSalFrame::isAlive( mpFrame ) ) + { + mpFrame->mnLastEventTime = static_cast<sal_uLong>( [pEvent timestamp] * 1000.0 ); + mpFrame->mnLastModifierFlags = [pEvent modifierFlags]; + } +} + +-(void)insertText:(id)aString +{ + YIELD_GUARD; + + if( AquaSalFrame::isAlive( mpFrame ) ) + { + NSString* pInsert = nil; + if( [aString isMemberOfClass: [NSAttributedString class]] ) + pInsert = [aString string]; + else + pInsert = aString; + + int nLen = 0; + if( pInsert && ( nLen = [pInsert length] ) > 0 ) + { + OUString aInsertString( GetOUString( pInsert ) ); + // aCharCode initializer is safe since aInsertString will at least contain '\0' + sal_Unicode aCharCode = *aInsertString.getStr(); + + if( nLen == 1 && + aCharCode < 0x80 && + aCharCode > 0x1f && + ! [self hasMarkedText ] + ) + { + sal_uInt16 nKeyCode = ImplMapCharCode( aCharCode ); + unsigned int nLastModifiers = mpFrame->mnLastModifierFlags; + + // #i99567# + // find out the unmodified key code + + // sanity check + if( mpLastEvent && ( [mpLastEvent type] == NSKeyDown || [mpLastEvent type] == NSKeyUp ) ) + { + // get unmodified string + NSString* pUnmodifiedString = [mpLastEvent charactersIgnoringModifiers]; + if( pUnmodifiedString && [pUnmodifiedString length] == 1 ) + { + // map the unmodified key code + unichar keyChar = [pUnmodifiedString characterAtIndex: 0]; + nKeyCode = ImplMapCharCode( keyChar ); + } + nLastModifiers = [mpLastEvent modifierFlags]; + + } + // #i99567# + // applications and vcl's edit fields ignore key events with ALT + // however we're at a place where we know text should be inserted + // so it seems we need to strip the Alt modifier here + if( (nLastModifiers & (NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask)) + == NSAlternateKeyMask ) + { + nLastModifiers = 0; + } + [self sendKeyInputAndReleaseToFrame: nKeyCode character: aCharCode modifiers: nLastModifiers]; + } + else + { + SalExtTextInputEvent aEvent; + aEvent.mnTime = mpFrame->mnLastEventTime; + aEvent.maText = aInsertString; + aEvent.mpTextAttr = NULL; + aEvent.mnCursorPos = aInsertString.getLength(); + aEvent.mnDeltaStart = 0; + aEvent.mnCursorFlags = 0; + aEvent.mbOnlyCursor = FALSE; + mpFrame->CallCallback( SALEVENT_EXTTEXTINPUT, &aEvent ); + if( AquaSalFrame::isAlive( mpFrame ) ) + mpFrame->CallCallback( SALEVENT_ENDEXTTEXTINPUT, 0 ); + } + } + else + { + SalExtTextInputEvent aEvent; + aEvent.mnTime = mpFrame->mnLastEventTime; + aEvent.maText = OUString(); + aEvent.mpTextAttr = NULL; + aEvent.mnCursorPos = 0; + aEvent.mnDeltaStart = 0; + aEvent.mnCursorFlags = 0; + aEvent.mbOnlyCursor = FALSE; + mpFrame->CallCallback( SALEVENT_EXTTEXTINPUT, &aEvent ); + if( AquaSalFrame::isAlive( mpFrame ) ) + mpFrame->CallCallback( SALEVENT_ENDEXTTEXTINPUT, 0 ); + + } + mbKeyHandled = true; + [self unmarkText]; + } +} + +-(void)insertTab: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: KEY_TAB character: '\t' modifiers: 0]; +} + +-(void)insertBacktab: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: (KEY_TAB | KEY_SHIFT) character: '\t' modifiers: 0]; +} + +-(void)moveLeft: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: 0]; +} + +-(void)moveLeftAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: KEY_LEFT character: 0 modifiers: NSShiftKeyMask]; +} + +-(void)moveBackwardAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::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: NSShiftKeyMask]; +} + +-(void)moveForwardAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_FORWARD character: 0 modifiers: 0]; +} + +-(void)moveWordLeft: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_WORD_BACKWARD character: 0 modifiers: 0]; +} + +-(void)moveWordBackward: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_WORD_BACKWARD character: 0 modifiers: 0]; +} + +-(void)moveWordBackwardAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_WORD_BACKWARD character: 0 modifiers: 0]; +} + +-(void)moveWordLeftAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_WORD_BACKWARD character: 0 modifiers: 0]; +} + +-(void)moveWordRight: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_WORD_FORWARD character: 0 modifiers: 0]; +} + +-(void)moveWordForward: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_WORD_FORWARD character: 0 modifiers: 0]; +} + +-(void)moveWordForwardAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_WORD_FORWARD character: 0 modifiers: 0]; +} + +-(void)moveWordRightAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_WORD_FORWARD character: 0 modifiers: 0]; +} + +-(void)moveToEndOfLine: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_TO_END_OF_LINE character: 0 modifiers: 0]; +} + +-(void)moveToRightEndOfLine: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_TO_END_OF_LINE character: 0 modifiers: 0]; +} + +-(void)moveToEndOfLineAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_END_OF_LINE character: 0 modifiers: 0]; +} + +-(void)moveToRightEndOfLineAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_END_OF_LINE character: 0 modifiers: 0]; +} + +-(void)moveToBeginningOfLine: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; +} + +-(void)moveToLeftEndOfLine: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; +} + +-(void)moveToBeginningOfLineAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; +} + +-(void)moveToLeftEndOfLineAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; +} + +-(void)moveToEndOfParagraph: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)moveToEndOfParagraphAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)moveParagraphForward: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)moveParagraphForwardAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)moveToBeginningOfParagraph: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)moveParagraphBackward: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)moveToBeginningOfParagraphAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)moveParagraphBackwardAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)moveToEndOfDocument: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::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: com::sun::star::awt::Key::MOVE_TO_END_OF_DOCUMENT character: 0 modifiers: 0]; +} + +-(void)moveToEndOfDocumentAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_TO_END_OF_DOCUMENT character: 0 modifiers: 0]; +} + +-(void)moveToBeginningOfDocument: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::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: com::sun::star::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT character: 0 modifiers: 0]; +} + +-(void)moveToBeginningOfDocumentAndModifySelection: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::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: com::sun::star::awt::Key::DELETE_WORD_BACKWARD character: 0 modifiers: 0]; +} + +-(void)deleteWordForward: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::DELETE_WORD_FORWARD character: 0 modifiers: 0]; +} + +-(void)deleteToBeginningOfLine: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::DELETE_TO_BEGIN_OF_LINE character: 0 modifiers: 0]; +} + +-(void)deleteToEndOfLine: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::DELETE_TO_END_OF_LINE character: 0 modifiers: 0]; +} + +-(void)deleteToBeginningOfParagraph: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)deleteToEndOfParagraph: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::DELETE_TO_END_OF_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)insertLineBreak: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::INSERT_LINEBREAK character: 0 modifiers: 0]; +} + +-(void)insertParagraphSeparator: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::INSERT_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)selectWord: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_WORD character: 0 modifiers: 0]; +} + +-(void)selectLine: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_LINE character: 0 modifiers: 0]; +} + +-(void)selectParagraph: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::awt::Key::SELECT_PARAGRAPH character: 0 modifiers: 0]; +} + +-(void)selectAll: (id)aSender +{ + (void)aSender; + [self sendKeyInputAndReleaseToFrame: com::sun::star::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 +{ + YIELD_GUARD; + + long nRet = 0; + if( AquaSalFrame::isAlive( mpFrame ) ) + { + SalKeyEvent aEvent; + aEvent.mnTime = mpFrame->mnLastEventTime; + 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 ? true : false; + if( AquaSalFrame::isAlive( mpFrame ) ) + mpFrame->CallCallback( SALEVENT_KEYUP, &aEvent ); + } + return nRet ? YES : NO; +} + + +-(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 unicodes 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 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 = YES; + + return bHasMarkedText; +} + +- (NSRange)markedRange +{ + // FIXME: + // #i106901# + // if we come here outside of mbInKeyInput, this is likely to be because + // of the keyboard viewer. For unknown reasons having no marked range + // in this case causes a crash. So we say we have a marked range anyway + // This is a hack, since it is not understood what a) causes that crash + // and b) why we should have a marked range at this point. + if( ! mbInKeyInput ) + return NSMakeRange( 0, 0 ); + + return [self hasMarkedText] ? mMarkedRange : NSMakeRange( NSNotFound, 0 ); +} + +- (NSRange)selectedRange +{ + return mSelectedRange; +} + +- (void)setMarkedText:(id)aString selectedRange:(NSRange)selRange +{ + if( ![aString isKindOfClass:[NSAttributedString class]] ) + aString = [[[NSAttributedString alloc] initWithString:aString] autorelease]; + NSRange rangeToReplace = [self hasMarkedText] ? [self markedRange] : [self selectedRange]; + if( rangeToReplace.location == NSNotFound ) + { + mMarkedRange = NSMakeRange( selRange.location, [aString length] ); + mSelectedRange = NSMakeRange( selRange.location, selRange.length ); + } + else + { + mMarkedRange = NSMakeRange( rangeToReplace.location, [aString length] ); + mSelectedRange = NSMakeRange( rangeToReplace.location + selRange.location, selRange.length ); + } + + int len = [aString length]; + SalExtTextInputEvent aInputEvent; + aInputEvent.mnTime = mpFrame->mnLastEventTime; + aInputEvent.mnDeltaStart = 0; + aInputEvent.mbOnlyCursor = FALSE; + if( len > 0 ) { + NSString *pString = [aString string]; + OUString aInsertString( GetOUString( pString ) ); + std::vector<sal_uInt16> aInputFlags = std::vector<sal_uInt16>( std::max( 1, len ), 0 ); + for ( int i = 0; i < len; i++ ) + { + unsigned int nUnderlineValue; + NSRange effectiveRange; + + effectiveRange = NSMakeRange(i, 1); + nUnderlineValue = [[aString attribute:NSUnderlineStyleAttributeName atIndex:i effectiveRange:&effectiveRange] unsignedIntValue]; + + switch (nUnderlineValue & 0xff) { + case NSUnderlineStyleSingle: + aInputFlags[i] = EXTTEXTINPUT_ATTR_UNDERLINE; + break; + case NSUnderlineStyleThick: + aInputFlags[i] = EXTTEXTINPUT_ATTR_UNDERLINE | EXTTEXTINPUT_ATTR_HIGHLIGHT; + break; + case NSUnderlineStyleDouble: + aInputFlags[i] = EXTTEXTINPUT_ATTR_BOLDUNDERLINE; + break; + default: + aInputFlags[i] = EXTTEXTINPUT_ATTR_HIGHLIGHT; + break; + } + } + + aInputEvent.maText = aInsertString; + aInputEvent.mnCursorPos = selRange.location; + aInputEvent.mpTextAttr = &aInputFlags[0]; + mpFrame->CallCallback( SALEVENT_EXTTEXTINPUT, (void *)&aInputEvent ); + } else { + aInputEvent.maText = OUString(); + aInputEvent.mnCursorPos = 0; + aInputEvent.mnCursorFlags = 0; + aInputEvent.mpTextAttr = 0; + mpFrame->CallCallback( SALEVENT_EXTTEXTINPUT, (void *)&aInputEvent ); + mpFrame->CallCallback( SALEVENT_ENDEXTTEXTINPUT, 0 ); + } + mbKeyHandled= true; +} + +- (void)unmarkText +{ + mSelectedRange = mMarkedRange = NSMakeRange(NSNotFound, 0); +} + +- (NSAttributedString *)attributedSubstringFromRange:(NSRange)theRange +{ + (void)theRange; + // FIXME + return nil; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint +{ + (void)thePoint; + // FIXME + return 0; +} + +- (NSInteger)conversationIdentifier +{ + return (long)self; +} + +- (void)doCommandBySelector:(SEL)aSelector +{ + if( AquaSalFrame::isAlive( mpFrame ) ) + { + #if OSL_DEBUG_LEVEL > 1 + // fprintf( stderr, "SalFrameView: doCommandBySelector %s\n", (char*)aSelector ); + #endif + if( (mpFrame->mnICOptions & SAL_INPUTCONTEXT_TEXT) != 0 && + aSelector != NULL && [self respondsToSelector: aSelector] ) + { + [self performSelector: aSelector]; + } + else + { + [self sendSingleCharacter:mpLastEvent]; + } + } + + mbKeyHandled = true; +} + +-(void)clearLastEvent +{ + mpLastEvent = nil; +} + +- (NSRect)firstRectForCharacterRange:(NSRange)theRange +{ + (void)theRange; + SalExtTextInputPosEvent aPosEvent; + mpFrame->CallCallback( SALEVENT_EXTTEXTINPUTPOS, (void *)&aPosEvent ); + + NSRect rect; + + rect.origin.x = aPosEvent.mnX + mpFrame->maGeometry.nX; + rect.origin.y = aPosEvent.mnY + mpFrame->maGeometry.nY + 4; // add some space for underlines + rect.size.width = aPosEvent.mnWidth; + rect.size.height = aPosEvent.mnHeight; + + mpFrame->VCLToCocoa( rect ); + return rect; +} + +-(id)parentAttribute { + return mpFrame->getNSWindow(); +} + +-(::com::sun::star::accessibility::XAccessibleContext *)accessibleContext +{ + if ( mpReferenceWrapper == nil ) { + // some frames never become visible .. + Window *pWindow = mpFrame -> GetWindow(); + if ( ! pWindow ) + return nil; + + mpReferenceWrapper = new ReferenceWrapper; + mpReferenceWrapper -> rAccessibleContext = pWindow -> /*GetAccessibleChildWindow( 0 ) ->*/ GetAccessible() -> getAccessibleContext(); + [ AquaA11yFactory insertIntoWrapperRepository: self forAccessibleContext: mpReferenceWrapper -> rAccessibleContext ]; + } + return [ super accessibleContext ]; +} + +-(NSView*)viewElementForParent +{ + return mpFrame->getNSView(); +} + +-(void)registerMouseEventListener: (id)theListener +{ + mpMouseEventListener = theListener; +} + +-(void)unregisterMouseEventListener: (id)theListener +{ + (void)theListener; + mpMouseEventListener = nil; +} + +-(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender +{ + return [mDraggingDestinationHandler draggingEntered: sender]; +} + +-(NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender +{ + return [mDraggingDestinationHandler draggingUpdated: sender]; +} + +-(void)draggingExited:(id <NSDraggingInfo>)sender +{ + [mDraggingDestinationHandler draggingExited: sender]; +} + +-(BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender +{ + return [mDraggingDestinationHandler prepareForDragOperation: sender]; +} + +-(BOOL)performDragOperation:(id <NSDraggingInfo>)sender +{ + return [mDraggingDestinationHandler performDragOperation: sender]; +} + +-(void)concludeDragOperation:(id <NSDraggingInfo>)sender +{ + [mDraggingDestinationHandler concludeDragOperation: sender]; +} + +-(void)registerDraggingDestinationHandler:(id)theHandler +{ + mDraggingDestinationHandler = theHandler; +} + +-(void)unregisterDraggingDestinationHandler:(id)theHandler +{ + (void)theHandler; + mDraggingDestinationHandler = nil; +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/salinst.cxx b/vcl/osx/salinst.cxx new file mode 100644 index 000000000000..9aab3a066c24 --- /dev/null +++ b/vcl/osx/salinst.cxx @@ -0,0 +1,1207 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * 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 <stdio.h> + +#include <tools/solarmutex.hxx> + +#include "osl/process.h" + +#include "rtl/ustrbuf.hxx" + +#include "vcl/svapp.hxx" +#include "vcl/window.hxx" +#include "vcl/timer.hxx" +#include "vcl/solarmutex.hxx" + +#include "osx/saldata.hxx" +#include "osx/salinst.h" +#include "osx/salframe.h" +#include "osx/salobj.h" +#include "osx/salsys.h" +#include "osx/salvd.h" +#include "quartz/salbmp.h" +#include "quartz/utils.h" +#include "osx/salprn.h" +#include "osx/saltimer.h" +#include "osx/vclnsapp.h" + +#include "print.h" +#include "impbmp.hxx" +#include "salimestatus.hxx" + +#include <comphelper/processfactory.hxx> + +#include <com/sun/star/uri/XExternalUriReferenceTranslator.hpp> +#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" + +using namespace std; +using namespace ::com::sun::star; + +extern sal_Bool ImplSVMain(); + +static int* gpnInit = 0; +static NSMenu* pDockMenu = nil; +static bool bNoSVMain = true; +static bool bLeftMain = false; +// ----------------------------------------------------------------------- + +class AquaDelayedSettingsChanged : public Timer +{ + bool mbInvalidate; + public: + AquaDelayedSettingsChanged( bool bInvalidate ) : + mbInvalidate( bInvalidate ) + { + } + + virtual void Timeout() + { + SalData* pSalData = GetSalData(); + if( ! pSalData->maFrames.empty() ) + pSalData->maFrames.front()->CallCallback( SALEVENT_SETTINGSCHANGED, NULL ); + + if( mbInvalidate ) + { + for( std::list< AquaSalFrame* >::iterator it = pSalData->maFrames.begin(); + it != pSalData->maFrames.end(); ++it ) + { + if( (*it)->mbShown ) + (*it)->SendPaintEvent( NULL ); + } + } + Stop(); + delete this; + } +}; + +void AquaSalInstance::delayedSettingsChanged( bool bInvalidate ) +{ + osl::Guard< comphelper::SolarMutex > aGuard( *mpSalYieldMutex ); + AquaDelayedSettingsChanged* pTimer = new AquaDelayedSettingsChanged( bInvalidate ); + pTimer->SetTimeout( 50 ); + pTimer->Start(); +} + + +// the AppEventList must be available before any SalData/SalInst/etc. objects are ready +AquaSalInstance::AppEventList 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; +} + + +// initialize the cocoa VCL_NSApplication object +// returns an NSAutoreleasePool that must be released when the event loop begins +static void initNSApp() +{ + // 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]; + + [[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()->mpMainController = [[MainController 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 +} + +sal_Bool ImplSVMainHook( int * pnInit ) +{ + unlink([[NSString stringWithFormat:@"%@/Library/Saved Application State/%s.savedState/restorecount.plist", NSHomeDirectory(), MACOSX_BUNDLE_IDENTIFIER] UTF8String]); + + gpnInit = pnInit; + + bNoSVMain = false; + initNSApp(); + + NSPoint aPt = { 0, 0 }; + NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined + location: aPt + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: AquaSalInstance::AppExecuteSVMain + data1: 0 + data2: 0 ]; + if( pEvent ) + { + [NSApp postEvent: pEvent atStart: NO]; + + 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(), NULL }; + NSApplicationMain( 1, pArgv ); +#endif + } + else + { + OSL_FAIL( "NSApplication initialization could not be done" ); + } + + return TRUE; // indicate that ImplSVMainHook is implemented +} + +// ======================================================================= + +void SalAbort( const OUString& rErrorText, bool bDumpCore ) +{ + if( rErrorText.isEmpty() ) + fprintf( stderr, "Application Error " ); + else + fprintf( stderr, "%s ", + OUStringToOString( rErrorText, osl_getThreadTextEncoding() ).getStr() ); + if( bDumpCore ) + abort(); + else + _exit(1); +} + +// ----------------------------------------------------------------------- + +void InitSalData() +{ + SalData *pSalData = new SalData; + SetSalData( pSalData ); +} + +// ----------------------------------------------------------------------- + +const OUString& SalGetDesktopEnvironment() +{ + static OUString aDesktopEnvironment( "MacOSX" ); + return aDesktopEnvironment; +} + +// ----------------------------------------------------------------------- + +void DeInitSalData() +{ + SalData *pSalData = GetSalData(); + if( pSalData->mpStatusItem ) + { + [pSalData->mpStatusItem release]; + pSalData->mpStatusItem = nil; + } + delete pSalData; + SetSalData( NULL ); +} + +// ----------------------------------------------------------------------- + +extern "C" { +#include <crt_externs.h> +} + +// ----------------------------------------------------------------------- + +void InitSalMain() +{ +} + +// ======================================================================= + +SalYieldMutex::SalYieldMutex() +{ + mnCount = 0; + mnThreadId = 0; +} + +void SalYieldMutex::acquire() +{ + SolarMutexObject::acquire(); + mnThreadId = osl::Thread::getCurrentIdentifier(); + mnCount++; +} + +void SalYieldMutex::release() +{ + if ( mnThreadId == osl::Thread::getCurrentIdentifier() ) + { + if ( mnCount == 1 ) + mnThreadId = 0; + mnCount--; + } + SolarMutexObject::release(); +} + +bool SalYieldMutex::tryToAcquire() +{ + if ( SolarMutexObject::tryToAcquire() ) + { + mnThreadId = osl::Thread::getCurrentIdentifier(); + mnCount++; + return true; + } + else + return false; +} + +// ----------------------------------------------------------------------- + +// some convenience functions regarding the yield mutex, aka solar mutex + +sal_Bool ImplSalYieldMutexTryToAcquire() +{ + AquaSalInstance* pInst = (AquaSalInstance*) GetSalData()->mpFirstInstance; + if ( pInst ) + return pInst->mpSalYieldMutex->tryToAcquire(); + else + return FALSE; +} + +void ImplSalYieldMutexAcquire() +{ + AquaSalInstance* pInst = (AquaSalInstance*) GetSalData()->mpFirstInstance; + if ( pInst ) + pInst->mpSalYieldMutex->acquire(); +} + +void ImplSalYieldMutexRelease() +{ + AquaSalInstance* pInst = (AquaSalInstance*) GetSalData()->mpFirstInstance; + if ( pInst ) + pInst->mpSalYieldMutex->release(); +} + +// ======================================================================= + +SalInstance* CreateSalInstance() +{ + // this is the case for not using SVMain + // not so good + if( bNoSVMain ) + initNSApp(); + + SalData* pSalData = GetSalData(); + DBG_ASSERT( pSalData->mpFirstInstance == NULL, "more than one instance created" ); + AquaSalInstance* pInst = new AquaSalInstance; + + // init instance (only one instance in this version !!!) + pSalData->mpFirstInstance = 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.mbProgressNeedsErase = true; + ImplGetSVData()->maNWFData.mbCheckBoxNeedsErase = true; + ImplGetSVData()->maNWFData.mnStatusBarLowerRightOffset = 10; + ImplGetSVData()->maGDIData.mbNoXORClipping = true; + ImplGetSVData()->maWinData.mbNoSaveBackground = true; + + return pInst; +} + +// ----------------------------------------------------------------------- + +void DestroySalInstance( SalInstance* pInst ) +{ + delete pInst; +} + +// ----------------------------------------------------------------------- + +AquaSalInstance::AquaSalInstance() +{ + mpSalYieldMutex = new SalYieldMutex; + mpSalYieldMutex->acquire(); + ::tools::SolarMutex::SetSolarMutex( mpSalYieldMutex ); + maMainThread = osl::Thread::getCurrentIdentifier(); + mbWaitingYield = false; + maUserEventListMutex = osl_createMutex(); + mnActivePrintJobs = 0; + maWaitingYieldCond = osl_createCondition(); +} + +// ----------------------------------------------------------------------- + +AquaSalInstance::~AquaSalInstance() +{ + ::tools::SolarMutex::SetSolarMutex( 0 ); + mpSalYieldMutex->release(); + delete mpSalYieldMutex; + osl_destroyMutex( maUserEventListMutex ); + osl_destroyCondition( maWaitingYieldCond ); +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::wakeupYield() +{ + // wakeup :Yield + if( mbWaitingYield ) + { + SalData::ensureThreadAutoreleasePool(); + NSPoint aPt = { 0, 0 }; + NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined + location: aPt + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: nil + subtype: AquaSalInstance::YieldWakeupEvent + data1: 0 + data2: 0 ]; + if( pEvent ) + [NSApp postEvent: pEvent atStart: NO]; + } +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::PostUserEvent( AquaSalFrame* pFrame, sal_uInt16 nType, void* pData ) +{ + osl_acquireMutex( maUserEventListMutex ); + maUserEvents.push_back( SalUserEvent( pFrame, pData, nType ) ); + osl_releaseMutex( maUserEventListMutex ); + + // notify main loop that an event has arrived + wakeupYield(); +} + +// ----------------------------------------------------------------------- + +comphelper::SolarMutex* AquaSalInstance::GetYieldMutex() +{ + return mpSalYieldMutex; +} + +// ----------------------------------------------------------------------- + +sal_uLong AquaSalInstance::ReleaseYieldMutex() +{ + SalYieldMutex* pYieldMutex = mpSalYieldMutex; + if ( pYieldMutex->GetThreadId() == + osl::Thread::getCurrentIdentifier() ) + { + sal_uLong nCount = pYieldMutex->GetAcquireCount(); + sal_uLong n = nCount; + while ( n ) + { + pYieldMutex->release(); + n--; + } + + return nCount; + } + else + return 0; +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::AcquireYieldMutex( sal_uLong nCount ) +{ + SalYieldMutex* pYieldMutex = mpSalYieldMutex; + while ( nCount ) + { + pYieldMutex->acquire(); + nCount--; + } +} + +// ----------------------------------------------------------------------- + +bool AquaSalInstance::CheckYieldMutex() +{ + bool bRet = true; + + SalYieldMutex* pYieldMutex = mpSalYieldMutex; + if ( pYieldMutex->GetThreadId() != osl::Thread::getCurrentIdentifier()) + { + bRet = false; + } + + return bRet; +} + +// ----------------------------------------------------------------------- + +bool AquaSalInstance::isNSAppThread() const +{ + return osl::Thread::getCurrentIdentifier() == maMainThread; +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::handleAppDefinedEvent( NSEvent* pEvent ) +{ + switch( [pEvent subtype] ) + { + case AppStartTimerEvent: + AquaSalTimer::handleStartTimerEvent( pEvent ); + break; + case AppEndLoopEvent: + [NSApp stop: NSApp]; + break; + case AppExecuteSVMain: + { + int nResult = ImplSVMain(); + if( gpnInit ) + *gpnInit = nResult; + [NSApp stop: NSApp]; + bLeftMain = true; + if( pDockMenu ) + { + [pDockMenu release]; + pDockMenu = nil; + } + } + break; + case AppleRemoteEvent: + { + sal_Int16 nCommand = 0; + SalData* pSalData = GetSalData(); + bool bIsFullScreenMode = false; + + std::list<AquaSalFrame*>::iterator it = pSalData->maFrames.begin(); + while( it != pSalData->maFrames.end() ) + { + if ( (*it) && ((*it)->mbFullScreen == true) ) + bIsFullScreenMode = true; + ++it; + } + + switch ([pEvent data1]) + { + case kRemoteButtonPlay: + nCommand = ( bIsFullScreenMode == true ) ? MEDIA_COMMAND_PLAY_PAUSE : MEDIA_COMMAND_PLAY; + break; + + // kept for experimentation purpose (scheduled for future implementation) + // case kRemoteButtonMenu: nCommand = MEDIA_COMMAND_MENU; break; + + case kRemoteButtonPlus: nCommand = MEDIA_COMMAND_VOLUME_UP; break; + + case kRemoteButtonMinus: nCommand = MEDIA_COMMAND_VOLUME_DOWN; break; + + case kRemoteButtonRight: nCommand = MEDIA_COMMAND_NEXTTRACK; break; + + case kRemoteButtonRight_Hold: nCommand = MEDIA_COMMAND_NEXTTRACK_HOLD; break; + + case kRemoteButtonLeft: nCommand = MEDIA_COMMAND_PREVIOUSTRACK; break; + + case kRemoteButtonLeft_Hold: nCommand = MEDIA_COMMAND_REWIND; break; + + case kRemoteButtonPlay_Hold: nCommand = MEDIA_COMMAND_PLAY_HOLD; break; + + case kRemoteButtonMenu_Hold: nCommand = MEDIA_COMMAND_STOP; break; + + // FIXME : not detected + case kRemoteButtonPlus_Hold: + case kRemoteButtonMinus_Hold: + break; + + default: + break; + } + AquaSalFrame* pFrame = pSalData->maFrames.front(); + Window * pWindow = pFrame->GetWindow() ? pSalData->maFrames.front()->GetWindow() : NULL; + + if( pWindow ) + { + const Point aPoint; + CommandEvent aCEvt( aPoint, COMMAND_MEDIA, FALSE, &nCommand ); + NotifyEvent aNCmdEvt( EVENT_COMMAND, pWindow, &aCEvt ); + + if ( !ImplCallPreNotify( aNCmdEvt ) ) + pWindow->Command( aCEvt ); + } + + } + break; + + case YieldWakeupEvent: + // do nothing, fall out of Yield + break; + + default: + OSL_FAIL( "unhandled NSApplicationDefined event" ); + break; + }; +} + +// ----------------------------------------------------------------------- + +class ReleasePoolHolder +{ + NSAutoreleasePool* mpPool; + public: + ReleasePoolHolder() : mpPool( [[NSAutoreleasePool alloc] init] ) {} + ~ReleasePoolHolder() { [mpPool release]; } +}; + +void AquaSalInstance::Yield( 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; + + // Release all locks so that we don't deadlock when we pull pending + // events from the event queue + bool bDispatchUser = true; + while( bDispatchUser ) + { + sal_uLong nCount = ReleaseYieldMutex(); + + // get one user event + osl_acquireMutex( maUserEventListMutex ); + SalUserEvent aEvent( NULL, NULL, 0 ); + if( ! maUserEvents.empty() ) + { + aEvent = maUserEvents.front(); + maUserEvents.pop_front(); + } + else + bDispatchUser = false; + osl_releaseMutex( maUserEventListMutex ); + + AcquireYieldMutex( nCount ); + + // dispatch it + if( aEvent.mpFrame && AquaSalFrame::isAlive( aEvent.mpFrame ) ) + { + aEvent.mpFrame->CallCallback( aEvent.mnType, aEvent.mpData ); + osl_setCondition( maWaitingYieldCond ); + // return if only one event is asked for + if( ! bHandleAllCurrentEvents ) + return; + } + } + + // handle cocoa event queue + // cocoa events mye be only handled in the thread the NSApp was created + if( isNSAppThread() && mnActivePrintJobs == 0 ) + { + // we need to be woken up by a cocoa-event + // if a user event should be posted by the event handling below + bool bOldWaitingYield = mbWaitingYield; + mbWaitingYield = bWait; + + // handle available events + NSEvent* pEvent = nil; + bool bHadEvent = false; + do + { + sal_uLong nCount = ReleaseYieldMutex(); + + pEvent = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: nil + inMode: NSDefaultRunLoopMode dequeue: YES]; + if( pEvent ) + { + [NSApp sendEvent: pEvent]; + bHadEvent = true; + } + [NSApp updateWindows]; + + AcquireYieldMutex( nCount ); + } while( bHandleAllCurrentEvents && pEvent ); + + // if we had no event yet, wait for one if requested + if( bWait && ! bHadEvent ) + { + sal_uLong nCount = ReleaseYieldMutex(); + + NSDate* pDt = AquaSalTimer::pRunningTimer ? [AquaSalTimer::pRunningTimer fireDate] : [NSDate distantFuture]; + pEvent = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: pDt + inMode: NSDefaultRunLoopMode dequeue: YES]; + if( pEvent ) + [NSApp sendEvent: pEvent]; + [NSApp updateWindows]; + + AcquireYieldMutex( nCount ); + + // #i86581# + // FIXME: sometimes the NSTimer will never fire. Firing it by hand then + // fixes the problem even seems to set the correct next firing date + // Why oh why ? + if( ! pEvent && AquaSalTimer::pRunningTimer ) + { + // this cause crashes on MacOSX 10.4 + // [AquaSalTimer::pRunningTimer fire]; + ImplGetSVData()->mpSalTimer->CallCallback(); + } + } + + mbWaitingYield = bOldWaitingYield; + + // collect update rectangles + const std::list< AquaSalFrame* > rFrames( GetSalData()->maFrames ); + for( std::list< AquaSalFrame* >::const_iterator it = rFrames.begin(); it != rFrames.end(); ++it ) + { + if( (*it)->mbShown && ! (*it)->maInvalidRect.IsEmpty() ) + { + (*it)->Flush( (*it)->maInvalidRect ); + (*it)->maInvalidRect.SetEmpty(); + } + } + osl_setCondition( maWaitingYieldCond ); + } + else if( bWait ) + { + // #i103162# + // wait until any thread (most likely the main thread) + // has dispatched an event, cop out at 200 ms + osl_resetCondition( maWaitingYieldCond ); + TimeValue aVal = { 0, 200000000 }; + sal_uLong nCount = ReleaseYieldMutex(); + osl_waitCondition( maWaitingYieldCond, &aVal ); + AcquireYieldMutex( nCount ); + } + + // 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; + } + } +} + +// ----------------------------------------------------------------------- + +bool AquaSalInstance::AnyInput( sal_uInt16 nType ) +{ + if( nType & VCL_INPUT_APPEVENT ) + { + if( ! aAppEventList.empty() ) + return true; + if( nType == VCL_INPUT_APPEVENT ) + return false; + } + + if( nType & VCL_INPUT_TIMER ) + { + if( AquaSalTimer::pRunningTimer ) + { + NSDate* pDt = [AquaSalTimer::pRunningTimer fireDate]; + if( pDt && [pDt timeIntervalSinceNow] < 0 ) + { + return true; + } + } + } + + unsigned/*NSUInteger*/ nEventMask = 0; + if( nType & VCL_INPUT_MOUSE) + nEventMask |= + NSLeftMouseDownMask | NSRightMouseDownMask | NSOtherMouseDownMask | + NSLeftMouseUpMask | NSRightMouseUpMask | NSOtherMouseUpMask | + NSLeftMouseDraggedMask | NSRightMouseDraggedMask | NSOtherMouseDraggedMask | + NSScrollWheelMask | + // NSMouseMovedMask | + NSMouseEnteredMask | NSMouseExitedMask; + if( nType & VCL_INPUT_KEYBOARD) + nEventMask |= NSKeyDownMask | NSKeyUpMask | NSFlagsChangedMask; + if( nType & VCL_INPUT_OTHER) + nEventMask |= NSTabletPoint; + // TODO: VCL_INPUT_PAINT / more VCL_INPUT_OTHER + if( !nType) + return false; + + NSEvent* pEvent = [NSApp nextEventMatchingMask: nEventMask untilDate: nil + inMode: NSDefaultRunLoopMode dequeue: NO]; + return (pEvent != NULL); +} + +// ----------------------------------------------------------------------- + +SalFrame* AquaSalInstance::CreateChildFrame( SystemParentData*, sal_uLong /*nSalFrameStyle*/ ) +{ + return NULL; +} + +// ----------------------------------------------------------------------- + +SalFrame* AquaSalInstance::CreateFrame( SalFrame* pParent, sal_uLong nSalFrameStyle ) +{ + SalData::ensureThreadAutoreleasePool(); + + SalFrame* pFrame = new AquaSalFrame( pParent, nSalFrameStyle ); + return pFrame; +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::DestroyFrame( SalFrame* pFrame ) +{ + delete pFrame; +} + +// ----------------------------------------------------------------------- + +SalObject* AquaSalInstance::CreateObject( SalFrame* pParent, SystemWindowData* /* pWindowData */, sal_Bool /* bShow */ ) +{ + // SystemWindowData is meaningless on Mac OS X + AquaSalObject *pObject = NULL; + + if ( pParent ) + pObject = new AquaSalObject( static_cast<AquaSalFrame*>(pParent) ); + + return pObject; +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::DestroyObject( SalObject* pObject ) +{ + delete ( pObject ); +} + +// ----------------------------------------------------------------------- + +SalPrinter* AquaSalInstance::CreatePrinter( SalInfoPrinter* pInfoPrinter ) +{ + return new AquaSalPrinter( dynamic_cast<AquaSalInfoPrinter*>(pInfoPrinter) ); +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::DestroyPrinter( SalPrinter* pPrinter ) +{ + delete pPrinter; +} + +// ----------------------------------------------------------------------- + +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; + DBG_ASSERT( nTypeCount == nNameCount, "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 ) + { + SalPrinterQueueInfo* pInfo = new SalPrinterQueueInfo; + pInfo->maPrinterName = GetOUString( pName ); + if( pType ) + pInfo->maDriver = GetOUString( pType ); + pInfo->mnStatus = 0; + pInfo->mnJobs = 0; + pInfo->mpSysData = NULL; + + pList->Add( pInfo ); + } + } +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::GetPrinterQueueState( SalPrinterQueueInfo* ) +{ +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::DeletePrinterQueueInfo( SalPrinterQueueInfo* pInfo ) +{ + delete pInfo; +} + +// ----------------------------------------------------------------------- + +OUString AquaSalInstance::GetDefaultPrinter() +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + if( maDefaultPrinter.isEmpty() ) + { + NSPrintInfo* pPI = [NSPrintInfo sharedPrintInfo]; + DBG_ASSERT( pPI, "no print info" ); + if( pPI ) + { + NSPrinter* pPr = [pPI printer]; + DBG_ASSERT( pPr, "no printer in default info" ); + if( pPr ) + { + NSString* pDefName = [pPr name]; + DBG_ASSERT( pDefName, "printer has no name" ); + maDefaultPrinter = GetOUString( pDefName ); + } + } + } + return maDefaultPrinter; +} + +// ----------------------------------------------------------------------- + +SalInfoPrinter* AquaSalInstance::CreateInfoPrinter( SalPrinterQueueInfo* pQueueInfo, + ImplJobSetup* pSetupData ) +{ + // #i113170# may not be the main thread if called from UNO API + SalData::ensureThreadAutoreleasePool(); + + SalInfoPrinter* pNewInfoPrinter = NULL; + 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; +} + +// ----------------------------------------------------------------------- + +SalSystem* AquaSalInstance::CreateSystem() +{ + return new AquaSalSystem(); +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::DestroySystem( SalSystem* pSystem ) +{ + delete pSystem; +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::SetEventCallback( void*, bool(*)(void*,void*,int) ) +{ +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::SetErrorEventCallback( void*, bool(*)(void*,void*,int) ) +{ +} + +// ----------------------------------------------------------------------- + +void* AquaSalInstance::GetConnectionIdentifier( ConnectionIdentifierType& rReturnedType, int& rReturnedBytes ) +{ + rReturnedBytes = 1; + rReturnedType = AsciiCString; + return (void*)""; +} + +// 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(); +} + +// ----------------------------------------------------------------------- + +SalBitmap* AquaSalInstance::CreateSalBitmap() +{ + return new QuartzSalBitmap(); +} + +// ----------------------------------------------------------------------- + +SalSession* AquaSalInstance::CreateSalSession() +{ + return NULL; +} + +// ----------------------------------------------------------------------- + +class MacImeStatus : public SalI18NImeStatus +{ +public: + MacImeStatus() {} + virtual ~MacImeStatus() {} + + // asks whether there is a status window available + // to toggle into menubar + virtual bool canToggle() { return false; } + virtual void toggle() {} +}; + +// ----------------------------------------------------------------------- + +SalI18NImeStatus* AquaSalInstance::CreateI18NImeStatus() +{ + return new MacImeStatus(); +} + +// YieldMutexReleaser +YieldMutexReleaser::YieldMutexReleaser() : mnCount( 0 ) +{ + SalData* pSalData = GetSalData(); + if( ! pSalData->mpFirstInstance->isNSAppThread() ) + { + SalData::ensureThreadAutoreleasePool(); + mnCount = pSalData->mpFirstInstance->ReleaseYieldMutex(); + } +} + +YieldMutexReleaser::~YieldMutexReleaser() +{ + if( mnCount != 0 ) + GetSalData()->mpFirstInstance->AcquireYieldMutex( mnCount ); +} + +CGImageRef CreateCGImage( const Image& rImage ) +{ + BitmapEx aBmpEx( rImage.GetBitmapEx() ); + Bitmap aBmp( aBmpEx.GetBitmap() ); + + if( ! aBmp || ! aBmp.ImplGetImpBitmap() ) + return NULL; + + // simple case, no transparency + QuartzSalBitmap* pSalBmp = static_cast<QuartzSalBitmap*>(aBmp.ImplGetImpBitmap()->ImplGetSalBitmap()); + + if( ! pSalBmp ) + return NULL; + + CGImageRef xImage = NULL; + if( ! (aBmpEx.IsAlpha() || aBmpEx.IsTransparent() ) ) + xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight ); + else if( aBmpEx.IsAlpha() ) + { + AlphaMask aAlphaMask( aBmpEx.GetAlpha() ); + Bitmap aMask( aAlphaMask.GetBitmap() ); + QuartzSalBitmap* pMaskBmp = static_cast<QuartzSalBitmap*>(aMask.ImplGetImpBitmap()->ImplGetSalBitmap()); + if( pMaskBmp ) + xImage = pSalBmp->CreateWithMask( *pMaskBmp, 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight ); + else + xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight ); + } + else if( aBmpEx.GetTransparentType() == TRANSPARENT_BITMAP ) + { + Bitmap aMask( aBmpEx.GetMask() ); + QuartzSalBitmap* pMaskBmp = static_cast<QuartzSalBitmap*>(aMask.ImplGetImpBitmap()->ImplGetSalBitmap()); + if( pMaskBmp ) + xImage = pSalBmp->CreateWithMask( *pMaskBmp, 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight ); + else + xImage = pSalBmp->CreateCroppedImage( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight ); + } + else if( aBmpEx.GetTransparentType() == TRANSPARENT_COLOR ) + { + Color aTransColor( aBmpEx.GetTransparentColor() ); + SalColor nTransColor = MAKE_SALCOLOR( aTransColor.GetRed(), aTransColor.GetGreen(), aTransColor.GetBlue() ); + xImage = pSalBmp->CreateColorMask( 0, 0, pSalBmp->mnWidth, pSalBmp->mnHeight, nTransColor ); + } + + return xImage; +} + +NSImage* CreateNSImage( const Image& rImage ) +{ + CGImageRef xImage = CreateCGImage( rImage ); + + if( ! xImage ) + return nil; + + Size aSize( rImage.GetSizePixel() ); + NSImage* pImage = [[NSImage alloc] initWithSize: NSMakeSize( aSize.Width(), aSize.Height() )]; + if( pImage ) + { + [pImage setFlipped: YES]; + [pImage lockFocus]; + + NSGraphicsContext* pContext = [NSGraphicsContext currentContext]; + CGContextRef rCGContext = reinterpret_cast<CGContextRef>([pContext graphicsPort]); + + 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; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/salmenu.cxx b/vcl/osx/salmenu.cxx new file mode 100644 index 000000000000..db3dd2ef845c --- /dev/null +++ b/vcl/osx/salmenu.cxx @@ -0,0 +1,963 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <comphelper/string.hxx> + +#include "rtl/ustrbuf.hxx" + +#include "vcl/cmdevt.hxx" +#include "vcl/floatwin.hxx" +#include "vcl/window.hxx" +#include "vcl/svapp.hxx" + +#include "osx/saldata.hxx" +#include "osx/salinst.h" +#include "osx/salmenu.h" +#include "osx/salnsmenu.h" +#include "osx/salframe.h" +#include "osx/a11ywrapper.h" +#include "quartz/utils.h" + +#include "svids.hrc" +#include "window.h" + +#include <objc/objc-runtime.h> + +const AquaSalMenu* AquaSalMenu::pCurrentMenuBar = NULL; + +@interface MainMenuSelector : NSObject +{ +} +-(void)showDialog: (int)nDialog; +-(void)showPreferences: (id)sender; +-(void)showAbout: (id)sender; +@end + +@implementation MainMenuSelector +-(void)showDialog: (int)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 == SHOWDIALOG_ID_ABOUT ) + aDialog = "ABOUT"; + else if( nDialog == SHOWDIALOG_ID_PREFERENCES ) + aDialog = "PREFERENCES"; + const ApplicationEvent* pAppEvent = new ApplicationEvent( + ApplicationEvent::TYPE_SHOWDIALOG, aDialog); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + } +} + +-(void)showPreferences: (id) sender +{ + (void)sender; + YIELD_GUARD; + + [self showDialog: SHOWDIALOG_ID_PREFERENCES]; +} +-(void)showAbout: (id) sender +{ + (void)sender; + YIELD_GUARD; + + [self showDialog: SHOWDIALOG_ID_ABOUT]; +} +@end + + +// FIXME: currently this is leaked +static MainMenuSelector* pMainMenuSelector = nil; + +static void initAppMenu() +{ + static bool bOnce = true; + if( bOnce ) + { + bOnce = false; + + ResMgr* pMgr = ImplGetResMgr(); + if( pMgr ) + { + // get the main menu + NSMenu* pMainMenu = [NSApp mainMenu]; + if( pMainMenu != nil ) + { + // create the action selector + pMainMenuSelector = [[MainMenuSelector alloc] init]; + + // get the proper submenu + NSMenu* pAppMenu = [[pMainMenu itemAtIndex: 0] submenu]; + if( pAppMenu ) + { + // insert about entry + OUString aAbout( ResId( SV_STDTEXT_ABOUT, *pMgr ) ); + NSString* pString = CreateNSString( aAbout ); + NSMenuItem* pNewItem = [pAppMenu insertItemWithTitle: pString + action: @selector(showAbout:) + keyEquivalent: @"" + atIndex: 0]; + if (pString) + [pString release]; + if( pNewItem ) + { + [pNewItem setTarget: pMainMenuSelector]; + [pAppMenu insertItem: [NSMenuItem separatorItem] atIndex: 1]; + } + + // insert preferences entry + OUString aPref( ResId( SV_STDTEXT_PREFERENCES, *pMgr ) ); + pString = CreateNSString( aPref ); + pNewItem = [pAppMenu insertItemWithTitle: pString + action: @selector(showPreferences:) + keyEquivalent: @"," + atIndex: 2]; + if (pString) + [pString release]; + if( pNewItem ) + { + [pNewItem setKeyEquivalentModifierMask: NSCommandKeyMask]; + [pNewItem setTarget: pMainMenuSelector]; + [pAppMenu insertItem: [NSMenuItem separatorItem] atIndex: 3]; + } + + // WARNING: ultra ugly code ahead + + // rename standard entries + // rename "Services" + pNewItem = [pAppMenu itemAtIndex: 4]; + if( pNewItem ) + { + pString = CreateNSString( OUString( ResId( SV_MENU_MAC_SERVICES, *pMgr ) ) ); + [pNewItem setTitle: pString]; + if( pString ) + [pString release]; + } + + // rename "Hide NewApplication" + pNewItem = [pAppMenu itemAtIndex: 6]; + if( pNewItem ) + { + pString = CreateNSString( OUString( ResId( SV_MENU_MAC_HIDEAPP, *pMgr ) ) ); + [pNewItem setTitle: pString]; + if( pString ) + [pString release]; + } + + // rename "Hide Others" + pNewItem = [pAppMenu itemAtIndex: 7]; + if( pNewItem ) + { + pString = CreateNSString( OUString( ResId( SV_MENU_MAC_HIDEALL, *pMgr ) ) ); + [pNewItem setTitle: pString]; + if( pString ) + [pString release]; + } + + // rename "Show all" + pNewItem = [pAppMenu itemAtIndex: 8]; + if( pNewItem ) + { + pString = CreateNSString( OUString( ResId( SV_MENU_MAC_SHOWALL, *pMgr ) ) ); + [pNewItem setTitle: pString]; + if( pString ) + [pString release]; + } + + // rename "Quit NewApplication" + pNewItem = [pAppMenu itemAtIndex: 10]; + if( pNewItem ) + { + pString = CreateNSString( OUString( ResId( SV_MENU_MAC_QUITAPP, *pMgr ) ) ); + [pNewItem setTitle: pString]; + if( pString ) + [pString release]; + } + } + } + } + } +} + +// ======================================================================= + +SalMenu* AquaSalInstance::CreateMenu( sal_Bool bMenuBar, Menu* pVCLMenu ) +{ + initAppMenu(); + + AquaSalMenu *pAquaSalMenu = new AquaSalMenu( bMenuBar ); + pAquaSalMenu->mpVCLMenu = pVCLMenu; + + return pAquaSalMenu; +} + +void AquaSalInstance::DestroyMenu( SalMenu* pSalMenu ) +{ + delete pSalMenu; +} + +SalMenuItem* AquaSalInstance::CreateMenuItem( const SalItemParams* pItemData ) +{ + if( !pItemData ) + return NULL; + + AquaSalMenuItem *pSalMenuItem = new AquaSalMenuItem( pItemData ); + + return pSalMenuItem; +} + +void AquaSalInstance::DestroyMenuItem( SalMenuItem* pSalMenuItem ) +{ + delete pSalMenuItem; +} + + +// ======================================================================= + + +/* + * AquaSalMenu + */ + +AquaSalMenu::AquaSalMenu( bool bMenuBar ) : + mbMenuBar( bMenuBar ), + mpMenu( nil ), + mpVCLMenu( NULL ), + mpFrame( NULL ), + mpParentSalMenu( NULL ) +{ + if( ! mbMenuBar ) + { + mpMenu = [[SalNSMenu alloc] initWithMenu: this]; + // With the 10.6 SDK and gcc 4.2.1, we get: class 'NSMenu' + // does not implement the 'NSMenuDelegate' protocol. Anyway, + // having the menu object be its own delegate object is + // apparently what the code does on purpose. + + // So to silcense the warning, instead of: + // [mpMenu setDelegate: mpMenu]; + // do this: + objc_msgSend(mpMenu, @selector(setDelegate:), 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 = NULL; + + // 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: NULL]; + /* #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]; + } + } +} + +sal_Int32 removeUnusedItemsRunner(NSMenu * pMenu) +{ + NSArray * elements = [pMenu itemArray]; + NSEnumerator * it = [elements objectEnumerator]; + id elem; + NSMenuItem * lastDisplayedMenuItem = nil; + sal_Int32 drawnItems = 0; + bool firstEnabledItemIsNoSeparator = false; + while((elem=[it nextObject]) != nil) { + NSMenuItem * item = static_cast<NSMenuItem *>(elem); + if( (![item isEnabled] && ![item isSeparatorItem]) || ([item isSeparatorItem] && (lastDisplayedMenuItem != nil && [lastDisplayedMenuItem isSeparatorItem])) ) { + [[item menu]removeItem:item]; + } else { + if( ! firstEnabledItemIsNoSeparator && [item isSeparatorItem] ) { + [[item menu]removeItem:item]; + } else { + firstEnabledItemIsNoSeparator = true; + lastDisplayedMenuItem = item; + drawnItems++; + if( [item hasSubmenu] ) { + removeUnusedItemsRunner( [item submenu] ); + } + } + } + } + if( lastDisplayedMenuItem != nil && [lastDisplayedMenuItem isSeparatorItem]) { + [[lastDisplayedMenuItem menu]removeItem:lastDisplayedMenuItem]; + } + return drawnItems; +} + +bool AquaSalMenu::ShowNativePopupMenu(FloatingWindow * pWin, const Rectangle& rRect, sal_uLong nFlags) +{ + // do not use native popup menu when AQUA_NATIVE_MENUS is set to sal_False + if( ! VisibleMenuBar() ) { + return false; + } + + // set offsets for positioning + const float offset = 9.0; + + // get the pointers + AquaSalFrame * pParentAquaSalFrame = (AquaSalFrame *) pWin->ImplGetWindowImpl()->mpRealParent->ImplGetFrame(); + NSWindow* pParentNSWindow = pParentAquaSalFrame->mpNSWindow; + NSView* pParentNSView = [pParentNSWindow contentView]; + NSView* pPopupNSView = ((AquaSalFrame *) pWin->ImplGetWindow()->ImplGetFrame())->mpNSView; + NSRect popupFrame = [pPopupNSView frame]; + + // since we manipulate the menu below (removing entries) + // let's rather make a copy here and work with that + NSMenu* pCopyMenu = [mpMenu copy]; + + // filter disabled elements + removeUnusedItemsRunner( pCopyMenu ); + + // 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( pWin->ImplCalcPos( pWin, rRect, nFlags, nArrangeIndex ) ); + displayPopupFrame.origin.x = pWin->ImplGetFrame()->maGeometry.nX - pParentAquaSalFrame->maGeometry.nX + offset; + displayPopupFrame.origin.y = pWin->ImplGetFrame()->maGeometry.nY - pParentAquaSalFrame->maGeometry.nY + offset; + pParentAquaSalFrame->VCLToCocoa(displayPopupFrame, false); + + // #i111992# if this menu was opened due to a key event, prevent dispatching that yet again + if( [pParentNSView respondsToSelector: @selector(clearLastEvent)] ) + [pParentNSView performSelector:@selector(clearLastEvent)]; + + // open popup menu + NSPopUpButtonCell * pPopUpButtonCell = [[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO]; + [pPopUpButtonCell setMenu: pCopyMenu]; + [pPopUpButtonCell selectItem:nil]; + [AquaA11yWrapper setPopupMenuOpen: YES]; + [pPopUpButtonCell performClickWithFrame:displayPopupFrame inView:pParentNSView]; + [pPopUpButtonCell release]; + [AquaA11yWrapper setPopupMenuOpen: NO]; + + // clean up the copy + [pCopyMenu release]; + 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 : NULL; +} + +void AquaSalMenu::unsetMainMenu() +{ + pCurrentMenuBar = NULL; + + // remove items from main menu + NSMenu* pMenu = [NSApp mainMenu]; + for( int nItems = [pMenu numberOfItems]; nItems > 1; nItems-- ) + [pMenu removeItemAtIndex: 1]; +} + +void AquaSalMenu::setMainMenu() +{ + DBG_ASSERT( mbMenuBar, "setMainMenu on non menubar" ); + if( mbMenuBar ) + { + if( pCurrentMenuBar != this ) + { + unsetMainMenu(); + // insert our items + for( unsigned int 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 == NULL ) + 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 == NULL ) + setDefaultMenu(); + + return; + } + } +} + +sal_Bool AquaSalMenu::VisibleMenuBar() +{ + // Enable/disable experimental native menus code? + // + // To disable native menus, set the environment variable AQUA_NATIVE_MENUS to FALSE + + static const char *pExperimental = getenv ("AQUA_NATIVE_MENUS"); + + if ( pExperimental && !strcasecmp(pExperimental, "FALSE") ) + return sal_False; + + // End of experimental code enable/disable part + + return sal_True; +} + +void AquaSalMenu::SetFrame( const SalFrame *pFrame ) +{ + mpFrame = static_cast<const AquaSalFrame*>(pFrame); +} + +void AquaSalMenu::InsertItem( SalMenuItem* pSalMenuItem, unsigned nPos ) +{ + AquaSalMenuItem *pAquaSalMenuItem = static_cast<AquaSalMenuItem*>(pSalMenuItem); + + pAquaSalMenuItem->mpParentMenu = this; + DBG_ASSERT( pAquaSalMenuItem->mpVCLMenu == NULL || + pAquaSalMenuItem->mpVCLMenu == mpVCLMenu || + mpVCLMenu == NULL, + "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 = NULL; + 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 = NULL; + + 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 == NULL ) + { + 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 = NULL; + } + pAquaSalMenuItem->mpSubMenu = NULL; + [pAquaSalMenuItem->mpMenuItem setSubmenu: nil]; + } +} + +void AquaSalMenu::CheckItem( unsigned nPos, sal_Bool bCheck ) +{ + if( nPos < maItems.size() ) + { + NSMenuItem* pItem = maItems[nPos]->mpMenuItem; + [pItem setState: bCheck ? NSOnState : NSOffState]; + } +} + +void AquaSalMenu::EnableItem( unsigned nPos, sal_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 = (AquaSalMenuItem *) i_pSalMenuItem; + + // Delete mnemonics + OUString aText( comphelper::string::remove(i_rText, '~') ); + + /* #i90015# until there is a correct solution + strip out any appended (.*) in menubar entries + */ + if( mbMenuBar ) + { + sal_Int32 nPos = aText.lastIndexOf( '(' ); + if( nPos != -1 ) + { + sal_Int32 nPos2 = aText.indexOf( ')' ); + if( nPos2 != -1 ) + aText = aText.replaceAt( nPos, nPos2-nPos+1, "" ); + } + } + + 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 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; + } + } + } + else // not even a code ? nonsense -> ignore + return; + + DBG_ASSERT( nCommandKey, "unmapped accelerator key" ); + + nModifier=rKeyCode.GetAllModifier(); + + // should always use the command key + int nItemModifier = 0; + + if (nModifier & KEY_SHIFT) + { + nItemModifier |= NSShiftKeyMask; // actually useful only for function keys + if( nKeyCode >= KEY_A && nKeyCode <= KEY_Z ) + nCommandKey = nKeyCode - KEY_A + 'A'; + } + + if (nModifier & KEY_MOD1) + nItemModifier |= NSCommandKeyMask; + + if(nModifier & KEY_MOD2) + nItemModifier |= NSAlternateKeyMask; + + if(nModifier & KEY_MOD3) + nItemModifier |= NSControlKeyMask; + + AquaSalMenuItem *pAquaSalMenuItem = (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 NULL; +} + +void AquaSalMenu::statusLayout() +{ + if( GetSalData()->mpStatusItem ) + { + NSView* pNSView = [GetSalData()->mpStatusItem view]; + if( [pNSView isMemberOfClass: [OOStatusItemView class]] ) // well of course it is + [(OOStatusItemView*)pNSView layout]; + else + OSL_FAIL( "someone stole our status view" ); + } +} + +void AquaSalMenu::releaseButtonEntry( 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; + } +} + +bool AquaSalMenu::AddMenuBarButton( const SalMenuButtonItem& i_rNewItem ) +{ + if( ! mbMenuBar || ! VisibleMenuBar() ) + 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[0]) ); + } + + if( pCurrentMenuBar == this ) + statusLayout(); +} + +Rectangle AquaSalMenu::GetMenuBarButtonRectPixel( sal_uInt16 i_nItemId, SalFrame* i_pReferenceFrame ) +{ + if( ! i_pReferenceFrame || ! AquaSalFrame::isAlive( static_cast<AquaSalFrame*>(i_pReferenceFrame) ) ) + return Rectangle(); + + MenuBarButtonEntry* pEntry = findButtonItem( i_nItemId ); + + if( ! pEntry ) + return Rectangle(); + + NSStatusItem* pItem = SalData::getStatusItem(); + if( ! pItem ) + return Rectangle(); + + NSView* pNSView = [pItem view]; + if( ! pNSView ) + return Rectangle(); + NSWindow* pNSWin = [pNSView window]; + if( ! pNSWin ) + return Rectangle(); + + NSRect aRect = [pNSWin frame]; + aRect.origin = [pNSWin convertBaseToScreen: NSMakePoint( 0, 0 )]; + + // make coordinates relative to reference frame + static_cast<AquaSalFrame*>(i_pReferenceFrame)->CocoaToVCL( aRect.origin ); + aRect.origin.x -= i_pReferenceFrame->maGeometry.nX; + aRect.origin.y -= i_pReferenceFrame->maGeometry.nY + aRect.size.height; + + return Rectangle( Point(static_cast<long int>(aRect.origin.x), + static_cast<long int>(aRect.origin.y) + ), + Size( static_cast<long int>(aRect.size.width), + static_cast<long int>(aRect.size.height) + ) + ); +} + +// ======================================================================= + +/* + * SalMenuItem + */ + +AquaSalMenuItem::AquaSalMenuItem( const SalItemParams* pItemData ) : + mnId( pItemData->nId ), + mpVCLMenu( pItemData->pMenu ), + mpParentMenu( NULL ), + mpSubMenu( NULL ), + mpMenuItem( nil ) +{ + // Delete mnemonics + OUString aText( comphelper::string::remove(pItemData->aText, '~') ); + + if (pItemData->eType == MENUITEM_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]; + NSString* pString = CreateNSString( aText ); + if (pString) + { + [mpMenuItem setTitle: pString]; + [pString release]; + } + // anything but a separator should set a menu to dispatch to + DBG_ASSERT( mpVCLMenu, "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 000000000000..ec6ca1d9c72f --- /dev/null +++ b/vcl/osx/salnativewidgets.cxx @@ -0,0 +1,1462 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "vcl/salnativewidgets.hxx" +#include "vcl/decoview.hxx" +#include "vcl/svapp.hxx" +#include "vcl/timer.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" + +#ifndef NSAppKitVersionNumber10_7 +#define NSAppKitVersionNumber10_7 1138 +#endif + +class AquaBlinker : public Timer +{ + AquaSalFrame* mpFrame; + Rectangle maInvalidateRect; + + AquaBlinker( AquaSalFrame* pFrame, const Rectangle& rRect ) + : mpFrame( pFrame ), maInvalidateRect( rRect ) + { + mpFrame->maBlinkers.push_back( this ); + } + + public: + + static void Blink( AquaSalFrame*, const Rectangle&, int nTimeout = 80 ); + + virtual void Timeout() + { + Stop(); + if( AquaSalFrame::isAlive( mpFrame ) && mpFrame->mbShown ) + { + mpFrame->maBlinkers.remove( this ); + mpFrame->SendPaintEvent( &maInvalidateRect ); + } + delete this; + } +}; + +void AquaBlinker::Blink( AquaSalFrame* pFrame, const Rectangle& rRect, int nTimeout ) +{ + // prevent repeated paints from triggering themselves all the time + for( std::list< AquaBlinker* >::const_iterator it = pFrame->maBlinkers.begin(); + it != pFrame->maBlinkers.end(); ++it ) + { + if( (*it)->maInvalidateRect == rRect ) + return; + } + AquaBlinker* pNew = new AquaBlinker( pFrame, rRect ); + pNew->SetTimeout( nTimeout ); + pNew->Start(); +} + +// Helper returns an HIRect + +static HIRect ImplGetHIRectFromRectangle(Rectangle aRect) +{ + HIRect aHIRect; + aHIRect.origin.x = static_cast<float>(aRect.Left()); + aHIRect.origin.y = static_cast<float>(aRect.Top()); + aHIRect.size.width = static_cast<float>(aRect.GetWidth()); + aHIRect.size.height = static_cast<float>(aRect.GetHeight()); + return aHIRect; +} + +static ThemeButtonValue ImplGetButtonValue( ButtonValue aButtonValue ) +{ + switch( aButtonValue ) + { + case BUTTONVALUE_ON: + return kThemeButtonOn; + break; + + case BUTTONVALUE_OFF: + return kThemeButtonOff; + break; + + case BUTTONVALUE_MIXED: + case BUTTONVALUE_DONTKNOW: + default: + return kThemeButtonMixed; + break; + } +} + +static bool AquaGetScrollRect( /* TODO: int nScreen, */ ControlPart nPart, + const Rectangle& rControlRect, Rectangle& rResultRect ) +{ + bool bRetVal = true; + rResultRect = rControlRect; + + switch( nPart ) + { + case PART_BUTTON_UP: + if (NSAppKitVersionNumber < NSAppKitVersionNumber10_7) + { + if( GetSalData()->mbIsScrollbarDoubleMax ) + rResultRect.Top() = rControlRect.Bottom() - 2*BUTTON_HEIGHT; + rResultRect.Bottom() = rResultRect.Top() + BUTTON_HEIGHT; + } + else + { + rResultRect.Bottom() = rResultRect.Top(); + } + break; + + case PART_BUTTON_DOWN: + if (NSAppKitVersionNumber < NSAppKitVersionNumber10_7) + { + rResultRect.Top() = rControlRect.Bottom() - BUTTON_HEIGHT; + } + else + { + rResultRect.Top() = rResultRect.Bottom(); + } + break; + + case PART_BUTTON_LEFT: + if (NSAppKitVersionNumber < NSAppKitVersionNumber10_7) + { + if( GetSalData()->mbIsScrollbarDoubleMax ) + rResultRect.Left() = rControlRect.Right() - 2*BUTTON_WIDTH; + rResultRect.Right() = rResultRect.Left() + BUTTON_WIDTH; + } + else + { + rResultRect.Right() = rResultRect.Left(); + } + break; + + case PART_BUTTON_RIGHT: + if (NSAppKitVersionNumber < NSAppKitVersionNumber10_7) + { + rResultRect.Left() = rControlRect.Right() - BUTTON_WIDTH; + } + else + { + rResultRect.Left() = rResultRect.Right(); + } + break; + + case PART_TRACK_HORZ_AREA: + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) + break; + rResultRect.Right() -= BUTTON_WIDTH + 1; + if( GetSalData()->mbIsScrollbarDoubleMax ) + rResultRect.Right() -= BUTTON_WIDTH; + else + rResultRect.Left() += BUTTON_WIDTH + 1; + break; + + case PART_TRACK_VERT_AREA: + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) + break; + rResultRect.Bottom() -= BUTTON_HEIGHT + 1; + if( GetSalData()->mbIsScrollbarDoubleMax ) + rResultRect.Bottom() -= BUTTON_HEIGHT; + else + rResultRect.Top() += BUTTON_HEIGHT + 1; + break; + case PART_THUMB_HORZ: + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) + break; + if( GetSalData()->mbIsScrollbarDoubleMax ) + { + rResultRect.Left() += 8; + rResultRect.Right() += 6; + } + else + { + rResultRect.Left() += 4; + rResultRect.Right() += 4; + } + break; + case PART_THUMB_VERT: + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) + break; + if( GetSalData()->mbIsScrollbarDoubleMax ) + { + rResultRect.Top() += 8; + rResultRect.Bottom() += 8; + } + else + { + rResultRect.Top() += 4; + rResultRect.Bottom() += 4; + } + break; + case PART_TRACK_HORZ_LEFT: + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) + break; + if( GetSalData()->mbIsScrollbarDoubleMax ) + rResultRect.Right() += 8; + else + rResultRect.Right() += 4; + break; + case PART_TRACK_HORZ_RIGHT: + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) + break; + if( GetSalData()->mbIsScrollbarDoubleMax ) + rResultRect.Left() += 6; + else + rResultRect.Left() += 4; + break; + case PART_TRACK_VERT_UPPER: + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) + break; + if( GetSalData()->mbIsScrollbarDoubleMax ) + rResultRect.Bottom() += 8; + else + rResultRect.Bottom() += 4; + break; + case PART_TRACK_VERT_LOWER: + if (NSAppKitVersionNumber >= NSAppKitVersionNumber10_7) + break; + if( GetSalData()->mbIsScrollbarDoubleMax ) + rResultRect.Top() += 8; + else + rResultRect.Top() += 4; + break; + default: + bRetVal = false; + } + + return bRetVal; +} + +/* + * IsNativeControlSupported() + * -------------------------- + * Returns sal_True if the platform supports native + * drawing of the control defined by nPart. + * + */ +sal_Bool AquaSalGraphics::IsNativeControlSupported( ControlType nType, ControlPart nPart ) +{ + bool bOk = sal_False; + + // Native controls are now defaults + // If you want to disable experimental native controls code, + // just set the environment variable SAL_NO_NWF to something + // and vcl controls will be used as default again. + + switch( nType ) + { + case CTRL_PUSHBUTTON: + case CTRL_RADIOBUTTON: + case CTRL_CHECKBOX: + case CTRL_LISTNODE: + if( nPart == PART_ENTIRE_CONTROL ) + return true; + break; + + case CTRL_SCROLLBAR: + if( nPart == PART_DRAW_BACKGROUND_HORZ || + nPart == PART_DRAW_BACKGROUND_VERT || + nPart == PART_ENTIRE_CONTROL || + nPart == HAS_THREE_BUTTONS ) + return true; + break; + + case CTRL_SLIDER: + if( nPart == PART_TRACK_HORZ_AREA || nPart == PART_TRACK_VERT_AREA ) + return true; + break; + + case CTRL_EDITBOX: + if( nPart == PART_ENTIRE_CONTROL || + nPart == HAS_BACKGROUND_TEXTURE ) + return true; + break; + + case CTRL_MULTILINE_EDITBOX: + if( nPart == PART_ENTIRE_CONTROL || + nPart == HAS_BACKGROUND_TEXTURE ) + return true; + break; + + case CTRL_SPINBOX: + if( nPart == PART_ENTIRE_CONTROL || + nPart == PART_ALL_BUTTONS || + nPart == HAS_BACKGROUND_TEXTURE ) + return true; + break; + + case CTRL_SPINBUTTONS: + return false; + break; + + case CTRL_COMBOBOX: + if( nPart == PART_ENTIRE_CONTROL || + nPart == HAS_BACKGROUND_TEXTURE ) + return true; + break; + + case CTRL_LISTBOX: + if( nPart == PART_ENTIRE_CONTROL || + nPart == PART_WINDOW || + nPart == HAS_BACKGROUND_TEXTURE || + nPart == PART_SUB_EDIT + ) + return true; + break; + + case CTRL_TAB_ITEM: + case CTRL_TAB_PANE: + case CTRL_TAB_BODY: // see vcl/source/window/tabpage.cxx + if( nPart == PART_ENTIRE_CONTROL || + nPart == PART_TABS_DRAW_RTL || + nPart == HAS_BACKGROUND_TEXTURE ) + return true; + break; + + // when PART_BUTTON is used, toolbar icons are not highlighted when mouse rolls over. + // More Aqua compliant + case CTRL_TOOLBAR: + if( nPart == PART_ENTIRE_CONTROL || + nPart == PART_DRAW_BACKGROUND_HORZ || + nPart == PART_DRAW_BACKGROUND_VERT) + return true; + break; + + case CTRL_WINDOW_BACKGROUND: + if ( nPart == PART_BACKGROUND_WINDOW || + nPart == PART_BACKGROUND_DIALOG ) + return true; + break; + + case CTRL_MENUBAR: + if( nPart == PART_ENTIRE_CONTROL ) + return true; + break; + + case CTRL_TOOLTIP: // ** TO DO + break; + + case CTRL_MENU_POPUP: + if( nPart == PART_ENTIRE_CONTROL || + nPart == PART_MENU_ITEM || + nPart == PART_MENU_ITEM_CHECK_MARK || + nPart == PART_MENU_ITEM_RADIO_MARK) + return true; + break; + case CTRL_PROGRESS: + case CTRL_INTROPROGRESS: + if( nPart == PART_ENTIRE_CONTROL ) + return true; + break; + case CTRL_FRAME: + if( nPart == PART_BORDER ) + return true; + break; + case CTRL_LISTNET: + if( nPart == PART_ENTIRE_CONTROL ) + return true; + break; + } + + return bOk; +} + +/* + * HitTestNativeControl() + * + * If the return value is sal_True, bIsInside contains information whether + * aPos was or was not inside the native widget specified by the + * nType/nPart combination. + */ +sal_Bool AquaSalGraphics::hitTestNativeControl( ControlType nType, ControlPart nPart, const Rectangle& rControlRegion, + const Point& rPos, sal_Bool& rIsInside ) +{ + if ( nType == CTRL_SCROLLBAR ) + { + Rectangle aRect; + bool bValid = AquaGetScrollRect( /* TODO: m_nScreen */ nPart, rControlRegion, aRect ); + rIsInside = bValid ? aRect.IsInside( rPos ) : sal_False; + if( NSAppKitVersionNumber < NSAppKitVersionNumber10_7 && + GetSalData()->mbIsScrollbarDoubleMax ) + { + // in double max mode the actual trough is a little smaller than the track + // there is some visual filler that is not sensitive + if( bValid && rIsInside ) + { + if( nPart == PART_TRACK_HORZ_AREA ) + { + // the left 4 pixels are not hit sensitive + if( rPos.X() - aRect.Left() < 4 ) + rIsInside = sal_False; + } + else if( nPart == PART_TRACK_VERT_AREA ) + { + // the top 4 pixels are not hit sensitive + if( rPos.Y() - aRect.Top() < 4 ) + rIsInside = sal_False; + } + } + } + return bValid; + } // CTRL_SCROLLBAR + + return sal_False; +} + +/* + kThemeStateInactive = 0, + kThemeStateActive = 1, + kThemeStatePressed = 2, + kThemeStateRollover = 6, + kThemeStateUnavailable = 7, + kThemeStateUnavailableInactive = 8 + kThemeStatePressedUp = 2, + kThemeStatePressedDown = 3 + +#define CTRL_STATE_ENABLED 0x0001 +#define CTRL_STATE_FOCUSED 0x0002 +#define CTRL_STATE_PRESSED 0x0004 +#define CTRL_STATE_ROLLOVER 0x0008 +#define CTRL_STATE_HIDDEN 0x0010 +#define CTRL_STATE_DEFAULT 0x0020 +#define CTRL_STATE_SELECTED 0x0040 +#define CTRL_CACHING_ALLOWED 0x8000 // set when the control is completely visible (i.e. not clipped) +*/ +UInt32 AquaSalGraphics::getState( ControlState nState ) +{ + const bool bDrawActive = mpFrame ? ([mpFrame->getNSWindow() isKeyWindow] ? true : false) : true; + if( (nState & CTRL_STATE_ENABLED) == 0 || ! bDrawActive ) + { + if( (nState & CTRL_STATE_HIDDEN) == 0 ) + return kThemeStateInactive; + else + return kThemeStateUnavailableInactive; + } + + if( (nState & CTRL_STATE_HIDDEN) != 0 ) + return kThemeStateUnavailable; + + if( (nState & CTRL_STATE_PRESSED) != 0 ) + return kThemeStatePressed; + + return kThemeStateActive; +} + +UInt32 AquaSalGraphics::getTrackState( ControlState nState ) +{ + const bool bDrawActive = mpFrame ? ([mpFrame->getNSWindow() isKeyWindow] ? true : false) : true; + if( (nState & CTRL_STATE_ENABLED) == 0 || ! bDrawActive ) + return kThemeTrackInactive; + + return kThemeTrackActive; +} + +/* + * DrawNativeControl() + * + * Draws the requested control described by nPart/nState. + * + * rControlRegion: The bounding region of the complete control in VCL frame coordinates. + * aValue: An optional value (tristate/numerical/string) + * aCaption: A caption or title string (like button text etc) + */ +sal_Bool AquaSalGraphics::drawNativeControl(ControlType nType, + ControlPart nPart, + const Rectangle& rControlRegion, + ControlState nState, + const ImplControlValue& aValue, + const OUString& ) +{ + sal_Bool bOK = sal_False; + + if( ! CheckContext() ) + return false; + + CGContextSaveGState( mrContext ); + + Rectangle buttonRect = rControlRegion; + HIRect rc = ImplGetHIRectFromRectangle(buttonRect); + + switch( nType ) + { + + case CTRL_COMBOBOX: + if ( nPart == HAS_BACKGROUND_TEXTURE || + nPart == PART_ENTIRE_CONTROL ) + { + HIThemeButtonDrawInfo aComboInfo; + aComboInfo.version = 0; + aComboInfo.kind = kThemeComboBox; + aComboInfo.state = getState( nState ); + aComboInfo.value = kThemeButtonOn; + aComboInfo.adornment = kThemeAdornmentNone; + + if( (nState & CTRL_STATE_FOCUSED) != 0 ) + aComboInfo.adornment |= kThemeAdornmentFocus; + + HIThemeDrawButton(&rc, &aComboInfo, mrContext, kHIThemeOrientationNormal,&rc); + bOK = true; + } + break; + + case CTRL_TOOLBAR: + { + HIThemeMenuItemDrawInfo aMenuItemDrawInfo; + aMenuItemDrawInfo.version = 0; + aMenuItemDrawInfo.state = kThemeMenuActive; + aMenuItemDrawInfo.itemType = kThemeMenuItemHierBackground; + HIThemeDrawMenuItem(&rc,&rc,&aMenuItemDrawInfo,mrContext,kHIThemeOrientationNormal,NULL); + bOK = true; + } + break; + + case CTRL_WINDOW_BACKGROUND: + { + HIThemeBackgroundDrawInfo aThemeBackgroundInfo; + aThemeBackgroundInfo.version = 0; + aThemeBackgroundInfo.state = getState( nState ); + aThemeBackgroundInfo.kind = kThemeBrushDialogBackgroundActive; + // FIXME: without this magical offset there is a 2 pixel black border on the right and bottom + rc.size.width += 2; + rc.size.height += 2; + + HIThemeApplyBackground( &rc, &aThemeBackgroundInfo, mrContext, kHIThemeOrientationNormal); + CGContextFillRect( mrContext, rc ); + bOK = true; + } + break; + + case CTRL_MENUBAR: + case CTRL_MENU_POPUP: + { + if ((nPart == PART_ENTIRE_CONTROL) || (nPart == PART_MENU_ITEM )|| (nPart == HAS_BACKGROUND_TEXTURE )) + { + // 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; + // the Aqua grey theme when the item is selected is drawn here. + aMenuItemDrawInfo.itemType = kThemeMenuItemPlain; + + if ((nPart == PART_MENU_ITEM ) && (nState & CTRL_STATE_SELECTED)) + { + // the 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,mrContext,kHIThemeOrientationNormal); + + // repaints the item either blue (selected) and/or Aqua grey (active only) + HIThemeDrawMenuItem(&rc,&rc,&aMenuItemDrawInfo,mrContext,kHIThemeOrientationNormal,&rc); + + bOK = true; + } + else if(( nPart == PART_MENU_ITEM_CHECK_MARK )||( nPart == PART_MENU_ITEM_RADIO_MARK )) { + if( nState & CTRL_STATE_PRESSED ) {//checked, else it is not displayed (see vcl/source/window/menu.cxx) + HIThemeTextInfo aTextInfo; + aTextInfo.version = 0; + aTextInfo.state = ((nState & CTRL_STATE_ENABLED)==0) ? kThemeStateInactive: kThemeStateActive; + aTextInfo.fontID = kThemeMenuItemMarkFont; + aTextInfo.horizontalFlushness=kHIThemeTextHorizontalFlushCenter; + aTextInfo.verticalFlushness=kHIThemeTextVerticalFlushTop; + aTextInfo.options=kHIThemeTextBoxOptionNone; + aTextInfo.truncationPosition=kHIThemeTextTruncationNone; + //aTextInfo.truncationMaxLines unused because of kHIThemeTextTruncationNone + + if( nState & CTRL_STATE_SELECTED) aTextInfo.state = kThemeStatePressed; //item highlighted + + UniChar mark=( nPart == PART_MENU_ITEM_CHECK_MARK ) ? kCheckUnicode: kBulletUnicode;//0x2713; + CFStringRef cfString = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault, &mark, 1, kCFAllocatorNull); + HIThemeDrawTextBox(cfString, &rc, &aTextInfo, mrContext, kHIThemeOrientationNormal); + if (cfString) + CFRelease(cfString); + + bOK = true; + } + } + } + break; + + case CTRL_PUSHBUTTON: + { + // [ FIXME] : instead of use a value, vcl can retrieve corect values on the fly (to be implemented) + const int PB_Mini_Height = 15; + const int PB_Norm_Height = 21; + + HIThemeButtonDrawInfo aPushInfo; + aPushInfo.version = 0; + + // no animation + aPushInfo.animation.time.start = 0; + aPushInfo.animation.time.current = 0; + PushButtonValue* pPBVal = aValue.getType() == CTRL_PUSHBUTTON ? (PushButtonValue*)&aValue : NULL; + int nPaintHeight = static_cast<int>(rc.size.height); + + if( pPBVal && pPBVal->mbBevelButton ) + { + aPushInfo.kind = kThemeRoundedBevelButton; + } + else if( rc.size.height <= PB_Norm_Height ) + { + aPushInfo.kind = kThemePushButtonMini; + nPaintHeight = PB_Mini_Height; + } + else if( pPBVal->mbSingleLine || rc.size.height < (PB_Norm_Height + PB_Norm_Height/2) ) + { + aPushInfo.kind = kThemePushButtonNormal; + nPaintHeight = PB_Norm_Height; + + // avoid clipping when focused + rc.origin.x += FOCUS_RING_WIDTH/2; + rc.size.width -= FOCUS_RING_WIDTH; + + if( (nState & CTRL_STATE_DEFAULT) != 0 ) + { + AquaBlinker::Blink( mpFrame, buttonRect ); + // show correct animation phase + aPushInfo.animation.time.current = CFAbsoluteTimeGetCurrent(); + } + } + else + aPushInfo.kind = kThemeBevelButton; + + // translate the origin for controls with fixed paint height + // so content ends up somewhere sensible + int delta_y = static_cast<int>(rc.size.height) - nPaintHeight; + rc.origin.y += delta_y/2; + + aPushInfo.state = getState( nState ); + aPushInfo.value = ImplGetButtonValue( aValue.getTristateVal() ); + + aPushInfo.adornment = (( nState & CTRL_STATE_DEFAULT ) != 0) ? + kThemeAdornmentDefault : + kThemeAdornmentNone; + if( (nState & CTRL_STATE_FOCUSED) != 0 ) + aPushInfo.adornment |= kThemeAdornmentFocus; + + HIThemeDrawButton( &rc, &aPushInfo, mrContext, kHIThemeOrientationNormal, NULL ); + bOK = true; + } + break; + + case CTRL_RADIOBUTTON: + case CTRL_CHECKBOX: + { + HIThemeButtonDrawInfo aInfo; + aInfo.version = 0; + switch( nType ) + { + case CTRL_RADIOBUTTON: if(rc.size.width >= BUTTON_HEIGHT) aInfo.kind = kThemeRadioButton; + else aInfo.kind = kThemeSmallRadioButton; + break; + case CTRL_CHECKBOX: if(rc.size.width >= BUTTON_HEIGHT) aInfo.kind = kThemeCheckBox; + else aInfo.kind = kThemeSmallCheckBox; + break; + } + + aInfo.state = getState( nState ); + + ButtonValue aButtonValue = aValue.getTristateVal(); + aInfo.value = ImplGetButtonValue( aButtonValue ); + + aInfo.adornment = (( nState & CTRL_STATE_DEFAULT ) != 0) ? + kThemeAdornmentDefault : + kThemeAdornmentNone; + if( (nState & CTRL_STATE_FOCUSED) != 0 ) + aInfo.adornment |= kThemeAdornmentFocus; + HIThemeDrawButton( &rc, &aInfo, mrContext, kHIThemeOrientationNormal, NULL ); + bOK = true; + } + break; + + case CTRL_LISTNODE: + { + ButtonValue aButtonValue = aValue.getTristateVal(); + + if( Application::GetSettings().GetLayoutRTL() && aButtonValue == BUTTONVALUE_OFF ) + { + // FIXME: a value of kThemeDisclosureLeft + // should draw a theme compliant left disclosure triangle + // sadly this does not seem to work, so we'll draw a left + // grey equilateral triangle here ourselves. + // Perhaps some other HIThemeButtonDrawInfo setting would do the trick ? + + CGContextSetShouldAntialias( mrContext, true ); + CGFloat aGrey[] = { 0.45, 0.45, 0.45, 1.0 }; + CGContextSetFillColor( mrContext, aGrey ); + CGContextBeginPath( mrContext ); + float x = rc.origin.x + rc.size.width; + float y = rc.origin.y; + CGContextMoveToPoint( mrContext, x, y ); + y += rc.size.height; + CGContextAddLineToPoint( mrContext, x, y ); + x -= rc.size.height * 0.866; // cos( 30 degree ) is approx. 0.866 + y -= rc.size.height/2; + CGContextAddLineToPoint( mrContext, x, y ); + CGContextDrawPath( mrContext, kCGPathEOFill ); + } + else + { + HIThemeButtonDrawInfo aInfo; + aInfo.version = 0; + aInfo.kind = kThemeDisclosureTriangle; + aInfo.value = kThemeDisclosureRight; + aInfo.state = getState( nState ); + + aInfo.adornment = kThemeAdornmentNone; + + switch( aButtonValue ) { + case BUTTONVALUE_ON: aInfo.value = kThemeDisclosureDown;//expanded + break; + case BUTTONVALUE_OFF: + // FIXME: this should have drawn a theme compliant disclosure triangle + // (see above) + if( Application::GetSettings().GetLayoutRTL() ) + { + aInfo.value = kThemeDisclosureLeft;//collapsed, RTL + } + break; + case BUTTONVALUE_DONTKNOW: //what to do? + default: + break; + } + + HIThemeDrawButton( &rc, &aInfo, mrContext, kHIThemeOrientationNormal, NULL ); + } + bOK = true; + } + break; + + case CTRL_PROGRESS: + case CTRL_INTROPROGRESS: + { + long nProgressWidth = aValue.getNumericVal(); + HIThemeTrackDrawInfo aTrackInfo; + aTrackInfo.version = 0; + aTrackInfo.kind = (rc.size.height > 10) ? kThemeProgressBarLarge : kThemeProgressBarMedium; + aTrackInfo.bounds = rc; + aTrackInfo.min = 0; + aTrackInfo.max = static_cast<SInt32>(rc.size.width); + aTrackInfo.value = nProgressWidth; + aTrackInfo.reserved = 0; + aTrackInfo.bounds.origin.y -= 2; // FIXME: magic for shadow + aTrackInfo.bounds.size.width -= 2; // FIXME: magic for shadow + aTrackInfo.attributes = kThemeTrackHorizontal; + if( Application::GetSettings().GetLayoutRTL() ) + aTrackInfo.attributes |= kThemeTrackRightToLeft; + aTrackInfo.enableState = getTrackState( nState ); + // the intro bitmap never gets key anyway; we want to draw that enabled + if( nType == CTRL_INTROPROGRESS ) + aTrackInfo.enableState = kThemeTrackActive; + aTrackInfo.filler1 = 0; + aTrackInfo.trackInfo.progress.phase = static_cast<UInt8>(CFAbsoluteTimeGetCurrent()*10.0); + + HIThemeDrawTrack( &aTrackInfo, NULL, mrContext, kHIThemeOrientationNormal ); + bOK = true; + } + break; + + case CTRL_SLIDER: + { + SliderValue* pSLVal = (SliderValue*)&aValue; + + HIThemeTrackDrawInfo aTrackDraw; + aTrackDraw.kind = kThemeSliderMedium; + if( nPart == PART_TRACK_HORZ_AREA || nPart == PART_TRACK_VERT_AREA ) + { + aTrackDraw.bounds = rc; + aTrackDraw.min = pSLVal->mnMin; + aTrackDraw.max = pSLVal->mnMax; + aTrackDraw.value = pSLVal->mnCur; + aTrackDraw.reserved = 0; + aTrackDraw.attributes = kThemeTrackShowThumb; + if( nPart == PART_TRACK_HORZ_AREA ) + aTrackDraw.attributes |= kThemeTrackHorizontal; + aTrackDraw.enableState = (nState & CTRL_STATE_ENABLED) + ? kThemeTrackActive : kThemeTrackInactive; + + SliderTrackInfo aSlideInfo; + aSlideInfo.thumbDir = kThemeThumbUpward; + aSlideInfo.pressState = 0; + aTrackDraw.trackInfo.slider = aSlideInfo; + + HIThemeDrawTrack( &aTrackDraw, NULL, mrContext, kHIThemeOrientationNormal ); + bOK = true; + } + } + break; + + case CTRL_SCROLLBAR: + { + const ScrollbarValue* pScrollbarVal = (aValue.getType() == CTRL_SCROLLBAR) ? static_cast<const ScrollbarValue*>(&aValue) : NULL; + + if( nPart == PART_DRAW_BACKGROUND_VERT || + nPart == PART_DRAW_BACKGROUND_HORZ ) + { + HIThemeTrackDrawInfo aTrackDraw; + aTrackDraw.kind = kThemeMediumScrollBar; + // FIXME: the scrollbar length must be adjusted + if (nPart == PART_DRAW_BACKGROUND_VERT) + rc.size.height += 2; + else + rc.size.width += 2; + + aTrackDraw.bounds = rc; + aTrackDraw.min = pScrollbarVal->mnMin; + aTrackDraw.max = pScrollbarVal->mnMax - pScrollbarVal->mnVisibleSize; + aTrackDraw.value = pScrollbarVal->mnCur; + aTrackDraw.reserved = 0; + aTrackDraw.attributes = kThemeTrackShowThumb; + if( nPart == PART_DRAW_BACKGROUND_HORZ ) + aTrackDraw.attributes |= kThemeTrackHorizontal; + aTrackDraw.enableState = getTrackState( nState ); + + ScrollBarTrackInfo aScrollInfo; + aScrollInfo.viewsize = pScrollbarVal->mnVisibleSize; + aScrollInfo.pressState = 0; + + if ( pScrollbarVal->mnButton1State & CTRL_STATE_ENABLED ) + { + if ( pScrollbarVal->mnButton1State & CTRL_STATE_PRESSED ) + aScrollInfo.pressState = kThemeTopOutsideArrowPressed; + } + + if ( pScrollbarVal->mnButton2State & CTRL_STATE_ENABLED ) + { + if ( pScrollbarVal->mnButton2State & CTRL_STATE_PRESSED ) + aScrollInfo.pressState = kThemeBottomOutsideArrowPressed; + } + + if ( pScrollbarVal->mnThumbState & CTRL_STATE_ENABLED ) + { + if ( pScrollbarVal->mnThumbState & CTRL_STATE_PRESSED ) + aScrollInfo.pressState = kThemeThumbPressed; + } + + aTrackDraw.trackInfo.scrollbar = aScrollInfo; + + HIThemeDrawTrack( &aTrackDraw, NULL, mrContext, kHIThemeOrientationNormal ); + bOK = true; + } + } + break; + + case CTRL_TAB_PANE: + { + HIThemeTabPaneDrawInfo aTabPaneDrawInfo; + aTabPaneDrawInfo.version = 1; + aTabPaneDrawInfo.state = kThemeStateActive; + aTabPaneDrawInfo.direction=kThemeTabNorth; + aTabPaneDrawInfo.size=kHIThemeTabSizeNormal; + aTabPaneDrawInfo.kind=kHIThemeTabKindNormal; + + //the border is outside the rect rc for Carbon + //but for VCL it should be inside + rc.origin.x+=1; + rc.origin.y-=TAB_HEIGHT_NORMAL/2; + rc.size.height+=TAB_HEIGHT_NORMAL/2; + rc.size.width-=2; + + HIThemeDrawTabPane(&rc, &aTabPaneDrawInfo, mrContext, kHIThemeOrientationNormal); + + bOK = true; + } + break; + + case CTRL_TAB_ITEM: + { + HIThemeTabDrawInfo aTabItemDrawInfo; + aTabItemDrawInfo.version=1; + aTabItemDrawInfo.style=kThemeTabNonFront; + aTabItemDrawInfo.direction=kThemeTabNorth; + aTabItemDrawInfo.size=kHIThemeTabSizeNormal; + aTabItemDrawInfo.adornment=kHIThemeTabAdornmentTrailingSeparator; + //State + if(nState & CTRL_STATE_SELECTED) { + aTabItemDrawInfo.style=kThemeTabFront; + } + if(nState & CTRL_STATE_FOCUSED) { + aTabItemDrawInfo.adornment|=kHIThemeTabAdornmentFocus; + } + + //first, last or middle tab + aTabItemDrawInfo.position=kHIThemeTabPositionMiddle; + + TabitemValue* pTabValue = (TabitemValue *) &aValue; + unsigned int nAlignment = pTabValue->mnAlignment; + //TABITEM_LEFTALIGNED (and TABITEM_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 TABITEM_FIRST_IN_GROUP (and TABITEM_LAST_IN_GROUP) because when the + //line width is different from window width, there may not be TABITEM_RIGHTALIGNED + if( ( (nAlignment & TABITEM_LEFTALIGNED)&&(nAlignment & TABITEM_RIGHTALIGNED) ) || + ( (nAlignment & TABITEM_FIRST_IN_GROUP)&&(nAlignment & TABITEM_LAST_IN_GROUP) ) + ) //tab alone + aTabItemDrawInfo.position=kHIThemeTabPositionOnly; + else if((nAlignment & TABITEM_LEFTALIGNED)||(nAlignment & TABITEM_FIRST_IN_GROUP)) + aTabItemDrawInfo.position=kHIThemeTabPositionFirst; + else if((nAlignment & TABITEM_RIGHTALIGNED)||(nAlignment & TABITEM_LAST_IN_GROUP)) + aTabItemDrawInfo.position=kHIThemeTabPositionLast; + + //support for RTL + //see issue 79748 + if( Application::GetSettings().GetLayoutRTL() ) { + if( aTabItemDrawInfo.position == kHIThemeTabPositionFirst ) + aTabItemDrawInfo.position = kHIThemeTabPositionLast; + else if( aTabItemDrawInfo.position == kHIThemeTabPositionLast ) + aTabItemDrawInfo.position = kHIThemeTabPositionFirst; + } + + rc.size.width+=2;//because VCL has 2 empty pixels between 2 tabs + rc.origin.x-=1; + + HIThemeDrawTab(&rc, &aTabItemDrawInfo, mrContext, kHIThemeOrientationNormal, &rc ); + + bOK=true; + } + break; + + case CTRL_LISTBOX: + switch( nPart) + { + case PART_ENTIRE_CONTROL: + case PART_BUTTON_DOWN: + { + HIThemeButtonDrawInfo aListInfo; + aListInfo.version = 0; + aListInfo.kind = kThemePopupButton; + aListInfo.state = getState( nState );//kThemeStateInactive -> greyed + aListInfo.value = kThemeButtonOn; + + aListInfo.adornment = kThemeAdornmentDefault; + if( (nState & CTRL_STATE_FOCUSED) != 0 ) + aListInfo.adornment |= kThemeAdornmentFocus; + + HIThemeDrawButton(&rc, &aListInfo, mrContext, kHIThemeOrientationNormal,&rc); + bOK = true; + break; + } + case PART_WINDOW: + { + HIThemeFrameDrawInfo aTextDrawInfo; + aTextDrawInfo.version=0; + aTextDrawInfo.kind=kHIThemeFrameTextFieldSquare; + aTextDrawInfo.state=getState( nState ); + aTextDrawInfo.isFocused=false; + + rc.size.width+=1;//else there's a white space because an OS X theme has no 3D border + rc.size.height+=1; + HIThemeDrawFrame(&rc, &aTextDrawInfo, mrContext, kHIThemeOrientationNormal); + + if(nState & CTRL_STATE_FOCUSED) HIThemeDrawFocusRect(&rc, true, mrContext, kHIThemeOrientationNormal); + + bOK=true; + break; + } + } + break; + + case CTRL_EDITBOX: + case CTRL_MULTILINE_EDITBOX: + { + HIThemeFrameDrawInfo aTextDrawInfo; + aTextDrawInfo.version=0; + aTextDrawInfo.kind=kHIThemeFrameTextFieldSquare; + aTextDrawInfo.state=getState( nState ); + aTextDrawInfo.isFocused=false; + + rc.size.width += 1; // else there may be a white space because an OS X theme has no 3D border + // change rc so that the frame will encompass only the content region + // see counterpart in GetNativeControlRegion + rc.size.width += 2; + rc.size.height += 2; + + //CGContextSetFillColorWithColor + CGContextFillRect (mrContext, CGRectMake(rc.origin.x, rc.origin.y, rc.size.width, rc.size.height)); + //fill a white background, because drawFrame only draws the border + + HIThemeDrawFrame(&rc, &aTextDrawInfo, mrContext, kHIThemeOrientationNormal); + + if(nState & CTRL_STATE_FOCUSED) HIThemeDrawFocusRect(&rc, true, mrContext, kHIThemeOrientationNormal); + + bOK=true; + } + break; + + case CTRL_SPINBOX: + { + if(nPart == PART_ENTIRE_CONTROL) + { + //text field: + HIThemeFrameDrawInfo aTextDrawInfo; + aTextDrawInfo.version=0; + aTextDrawInfo.kind=kHIThemeFrameTextFieldSquare; + aTextDrawInfo.state=getState( nState ); + aTextDrawInfo.isFocused=false; + + //rc.size.width contains the full size of the spinbox ie textfield + button + //so we remove the button width and the space between the button and the textfield + rc.size.width -= SPIN_BUTTON_SPACE + SPIN_BUTTON_WIDTH + 2*FOCUS_RING_WIDTH; + rc.origin.x += FOCUS_RING_WIDTH; + rc.origin.y += FOCUS_RING_WIDTH; + + //CGContextSetFillColorWithColor + CGContextFillRect (mrContext, CGRectMake(rc.origin.x, rc.origin.y, rc.size.width, rc.size.height)); + //fill a white background, because drawFrame only draws the border + + HIThemeDrawFrame(&rc, &aTextDrawInfo, mrContext, kHIThemeOrientationNormal); + + if(nState & CTRL_STATE_FOCUSED) HIThemeDrawFocusRect(&rc, true, mrContext, kHIThemeOrientationNormal); + + //buttons: + const SpinbuttonValue* pSpinButtonVal = (aValue.getType() == CTRL_SPINBUTTONS) ? static_cast<const SpinbuttonValue*>(&aValue) : NULL; + ControlState nUpperState = CTRL_STATE_ENABLED;//state of the upper button + ControlState nLowerState = CTRL_STATE_ENABLED;//and of the lower button + if(pSpinButtonVal) {//pSpinButtonVal is sometimes null + nUpperState = (ControlState) pSpinButtonVal->mnUpperState; + nLowerState = (ControlState) pSpinButtonVal->mnLowerState; + + HIThemeButtonDrawInfo aSpinInfo; + aSpinInfo.kind = kThemeIncDecButton; + aSpinInfo.state = kThemeStateActive; + if(nUpperState & CTRL_STATE_PRESSED) + aSpinInfo.state = kThemeStatePressedUp; + else if(nLowerState & CTRL_STATE_PRESSED) + aSpinInfo.state = kThemeStatePressedDown; + else if((nUpperState & ~CTRL_STATE_ENABLED)||(nLowerState & ~CTRL_STATE_ENABLED)) + aSpinInfo.state = kThemeStateInactive; + else if((nUpperState & CTRL_STATE_ROLLOVER)||(nLowerState & CTRL_STATE_ROLLOVER)) + aSpinInfo.state = kThemeStateRollover; + + Rectangle aSpinRect( pSpinButtonVal->maUpperRect ); + aSpinRect.Union( pSpinButtonVal->maLowerRect ); + HIRect buttonRc = ImplGetHIRectFromRectangle(aSpinRect); + + // FIXME: without this fuzz factor there is some unwanted clipping + if( Application::GetSettings().GetLayoutRTL() ) + buttonRc.origin.x -= FOCUS_RING_WIDTH - CLIP_FUZZ; + else + buttonRc.origin.x += FOCUS_RING_WIDTH + CLIP_FUZZ; + + switch( aValue.getTristateVal() ) + { + case BUTTONVALUE_ON: aSpinInfo.value = kThemeButtonOn; + break; + case BUTTONVALUE_OFF: aSpinInfo.value = kThemeButtonOff; + break; + case BUTTONVALUE_MIXED: + case BUTTONVALUE_DONTKNOW: + default: aSpinInfo.value = kThemeButtonMixed; + break; + } + + aSpinInfo.adornment = ( ((nUpperState & CTRL_STATE_DEFAULT) != 0 ) || + ((nLowerState & CTRL_STATE_DEFAULT) != 0 )) ? + kThemeAdornmentDefault : + kThemeAdornmentNone; + if( ((nUpperState & CTRL_STATE_FOCUSED) != 0 ) || ((nLowerState & CTRL_STATE_FOCUSED) != 0 )) + aSpinInfo.adornment |= kThemeAdornmentFocus; + + HIThemeDrawButton( &buttonRc, &aSpinInfo, mrContext, kHIThemeOrientationNormal, NULL ); + } + + bOK=true; + } + + } + break; + + case CTRL_FRAME: + { + sal_uInt16 nStyle = aValue.getNumericVal(); + if( nPart == PART_BORDER ) { + if(!( nStyle & FRAME_DRAW_MENU ) && !(nStyle & FRAME_DRAW_WINDOWBORDER) ) + { + // #i84756# 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 + if( rc.origin.y + rc.size.height >= mpFrame->maGeometry.nHeight-3 ) + { + CGMutablePathRef rPath = CGPathCreateMutable(); + CGPathAddRect( rPath, NULL, CGRectMake( 0, 0, mpFrame->maGeometry.nWidth-1, mpFrame->maGeometry.nHeight-1 ) ); + + CGContextBeginPath( mrContext ); + CGContextAddPath( mrContext, rPath ); + CGContextClip( mrContext ); + CGPathRelease( rPath ); + } + + HIThemeFrameDrawInfo aTextDrawInfo; + aTextDrawInfo.version=0; + aTextDrawInfo.kind=kHIThemeFrameListBox; + aTextDrawInfo.state=kThemeStateActive; + aTextDrawInfo.isFocused=false; + + HIThemeDrawFrame(&rc, &aTextDrawInfo, mrContext, kHIThemeOrientationNormal); + + bOK=true; + } + } + } + break; + + case CTRL_LISTNET: + { + //do nothing as there isn't net for listviews on macos + bOK=true; + } + break; + + } + + CGContextRestoreGState( mrContext ); + + /* #i90291# 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 + (the background is always drawn as a whole since we don't know anything + about its possible contents) + */ + if( nType == CTRL_WINDOW_BACKGROUND ) + { + CGRect aRect = { { 0, 0 }, { 0, 0 } }; + if( mxClipPath ) + aRect = CGPathGetBoundingBox( mxClipPath ); + if( aRect.size.width != 0 && aRect.size.height != 0 ) + buttonRect.Intersection( Rectangle( Point( static_cast<long int>(aRect.origin.x), + static_cast<long int>(aRect.origin.y) ), + Size( static_cast<long int>(aRect.size.width), + static_cast<long int>(aRect.size.height) ) ) ); + } + + RefreshRect( buttonRect.Left(), buttonRect.Top(), buttonRect.GetWidth(), buttonRect.GetHeight() ); + + return bOK; +} + +/* + * GetNativeControlRegion() + * + * If the return value is sal_True, rNativeBoundingRegion + * contains the true bounding region covered by the control + * including any adornment, while rNativeContentRegion contains the area + * within the control that can be safely drawn into without drawing over + * the borders of the control. + * + * rControlRegion: The bounding region of the control in VCL frame coordinates. + * aValue: An optional value (tristate/numerical/string) + * aCaption: A caption or title string (like button text etc) + */ +sal_Bool AquaSalGraphics::getNativeControlRegion( ControlType nType, ControlPart nPart, const Rectangle& rControlRegion, ControlState /*nState*/, + const ImplControlValue& aValue, const OUString&, + Rectangle &rNativeBoundingRegion, Rectangle &rNativeContentRegion ) + +{ + sal_Bool toReturn = sal_False; + + Rectangle aCtrlBoundRect( rControlRegion ); + short x = aCtrlBoundRect.Left(); + short y = aCtrlBoundRect.Top(); + short w, h; + + sal_uInt8 nBorderCleanup = 0; + + switch (nType) + { + case CTRL_SLIDER: + { + if( nPart == PART_THUMB_HORZ ) + { + w = 19; // taken from HIG + h = aCtrlBoundRect.GetHeight(); + rNativeBoundingRegion = rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h ) ); + toReturn = true; + } + else if( nPart == PART_THUMB_VERT ) + { + w = aCtrlBoundRect.GetWidth(); + h = 18; // taken from HIG + rNativeBoundingRegion = rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h ) ); + toReturn = true; + } + } + break; + + case CTRL_SCROLLBAR: + { + Rectangle aRect; + if( AquaGetScrollRect( /* m_nScreen */ nPart, aCtrlBoundRect, aRect ) ) + { + toReturn = sal_True; + rNativeBoundingRegion = aRect; + rNativeContentRegion = aRect; + } + } + break; + + case CTRL_PUSHBUTTON: + case CTRL_RADIOBUTTON: + case CTRL_CHECKBOX: + { + if ( nType == CTRL_PUSHBUTTON ) + { + w = aCtrlBoundRect.GetWidth(); + h = aCtrlBoundRect.GetHeight(); + } + else + { + // checkbox and radio borders need cleanup after unchecking them + nBorderCleanup = 4; + + // TEXT_SEPARATOR to respect Aqua HIG + w = BUTTON_WIDTH + TEXT_SEPARATOR; + h = BUTTON_HEIGHT; + + } + + rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h + nBorderCleanup) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h ) ); + + toReturn = sal_True; + } + break; + case CTRL_PROGRESS: + { + Rectangle aRect( aCtrlBoundRect ); + if( aRect.GetHeight() < 16 ) + aRect.Bottom() = aRect.Top() + 9; // values taken from HIG for medium progress + else + aRect.Bottom() = aRect.Top() + 15; // values taken from HIG for large progress + rNativeBoundingRegion = aRect; + rNativeContentRegion = aRect; + toReturn = sal_True; + } + break; + + case CTRL_INTROPROGRESS: + { + Rectangle aRect( aCtrlBoundRect ); + aRect.Bottom() = aRect.Top() + INTRO_PROGRESS_HEIGHT; // values taken from HIG for medium progress + rNativeBoundingRegion = aRect; + rNativeContentRegion = aRect; + toReturn = sal_True; + } + break; + + case CTRL_TAB_ITEM: + + w = aCtrlBoundRect.GetWidth() + 2*TAB_TEXT_OFFSET - 2*VCL_TAB_TEXT_OFFSET; + +#ifdef OLD_TAB_STYLE + h = TAB_HEIGHT_NORMAL; +#else + h = TAB_HEIGHT_NORMAL+2; +#endif + rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h ) ); + + toReturn = sal_True; + + break; + + case CTRL_EDITBOX: + { + w = aCtrlBoundRect.GetWidth(); + if( w < 3+2*FOCUS_RING_WIDTH ) + w = 3+2*FOCUS_RING_WIDTH; + h = TEXT_EDIT_HEIGHT_NORMAL+2*FOCUS_RING_WIDTH; + if( h < aCtrlBoundRect.GetHeight() ) + h = aCtrlBoundRect.GetHeight(); + + rNativeContentRegion = Rectangle( Point( x+FOCUS_RING_WIDTH, y+FOCUS_RING_WIDTH ), Size( w-2*(FOCUS_RING_WIDTH+1), h-2*(FOCUS_RING_WIDTH+1) ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h ) ); + + toReturn = sal_True; + } + break; + case CTRL_LISTBOX: + case CTRL_COMBOBOX: + { + if( nPart == PART_ENTIRE_CONTROL ) + { + w = aCtrlBoundRect.GetWidth(); + h = COMBOBOX_HEIGHT_NORMAL;//listboxes and comboxes have the same height + + rNativeContentRegion = Rectangle( Point( x+FOCUS_RING_WIDTH, y+FOCUS_RING_WIDTH ), Size( w-2*FOCUS_RING_WIDTH, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h+2*FOCUS_RING_WIDTH ) ); + + toReturn = sal_True; + } + else if( nPart == PART_BUTTON_DOWN ) + { + w = aCtrlBoundRect.GetWidth(); + if( w < 3+2*FOCUS_RING_WIDTH ) + w = 3+2*FOCUS_RING_WIDTH; + h = COMBOBOX_HEIGHT_NORMAL;//listboxes and comboxes have the same height + + x += w-DROPDOWN_BUTTON_WIDTH - FOCUS_RING_WIDTH; + y += FOCUS_RING_WIDTH; + w = DROPDOWN_BUTTON_WIDTH; + + rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w+FOCUS_RING_WIDTH, h+2*FOCUS_RING_WIDTH ) ); + + toReturn = true; + } + else if( nPart == PART_SUB_EDIT ) + { + w = aCtrlBoundRect.GetWidth(); + h = COMBOBOX_HEIGHT_NORMAL;//listboxes and comboxes have the same height + + x += FOCUS_RING_WIDTH; + x += 3; // add an offset for rounded borders + y += 2; // don't draw into upper border + y += FOCUS_RING_WIDTH; + w -= 3 + DROPDOWN_BUTTON_WIDTH + 2*FOCUS_RING_WIDTH; + if( nType == CTRL_LISTBOX ) + w -= 9; // HIG specifies 9 units distance between dropdown button area and content + h -= 4; // don't draw into lower border + + rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w+FOCUS_RING_WIDTH, h+2*FOCUS_RING_WIDTH ) ); + + toReturn = true; + } + } + break; + case CTRL_SPINBOX: + if( nPart == PART_ENTIRE_CONTROL ) { + w = aCtrlBoundRect.GetWidth(); + if( w < 3+2*FOCUS_RING_WIDTH+SPIN_BUTTON_SPACE+SPIN_BUTTON_WIDTH ) + w = 3+2*FOCUS_RING_WIDTH+SPIN_BUTTON_SPACE+SPIN_BUTTON_WIDTH; + h = TEXT_EDIT_HEIGHT_NORMAL; + + rNativeContentRegion = Rectangle( Point( x+FOCUS_RING_WIDTH, y ), Size( w-2*FOCUS_RING_WIDTH, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h+2*FOCUS_RING_WIDTH ) ); + + toReturn = sal_True; + } + else if( nPart == PART_SUB_EDIT ) { + w = aCtrlBoundRect.GetWidth() - SPIN_BUTTON_SPACE - SPIN_BUTTON_WIDTH; + h = TEXT_EDIT_HEIGHT_NORMAL; + x += 4; // add an offset for rounded borders + y += 2; // don't draw into upper border + w -= 8; // offset for left and right rounded border + h -= 4; // don't draw into upper or ower border + + rNativeContentRegion = Rectangle( Point( x + FOCUS_RING_WIDTH, y + FOCUS_RING_WIDTH ), Size( w - 2* FOCUS_RING_WIDTH, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h+2*FOCUS_RING_WIDTH ) ); + + toReturn = sal_True; + } + else if( nPart == PART_BUTTON_UP ) { + //aCtrlBoundRect.GetWidth() contains the width of the full control + //ie the width of the textfield + button + //x is the position of the left corner of the full control + x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - SPIN_BUTTON_SPACE - CLIP_FUZZ; + y += FOCUS_RING_WIDTH - CLIP_FUZZ; + w = SPIN_BUTTON_WIDTH + 2*CLIP_FUZZ; + h = SPIN_UPPER_BUTTON_HEIGHT + 2*CLIP_FUZZ; + + rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h ) ); + + toReturn = sal_True; + } + else if( nPart == PART_BUTTON_DOWN ) { + x += aCtrlBoundRect.GetWidth() - SPIN_BUTTON_WIDTH - SPIN_BUTTON_SPACE - CLIP_FUZZ; + y += SPIN_UPPER_BUTTON_HEIGHT + FOCUS_RING_WIDTH - CLIP_FUZZ; + w = SPIN_BUTTON_WIDTH + 2*CLIP_FUZZ; + h = SPIN_LOWER_BUTTON_HEIGHT + 2*CLIP_FUZZ; + + rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h ) ); + + toReturn = sal_True; + } + break; + case CTRL_FRAME: + { + sal_uInt16 nStyle = aValue.getNumericVal(); + if( ( nPart == PART_BORDER ) && + !( nStyle & (FRAME_DRAW_MENU | FRAME_DRAW_WINDOWBORDER | FRAME_DRAW_BORDERWINDOWBORDER) ) ) + { + Rectangle aRect(aCtrlBoundRect); + if( nStyle & FRAME_DRAW_DOUBLEIN ) + { + aRect.Left() += 1; + aRect.Top() += 1; + //rRect.Right() -= 1; + //rRect.Bottom() -= 1; + } + else + { + aRect.Left() += 1; + aRect.Top() += 1; + aRect.Right() -= 1; + aRect.Bottom() -= 1; + } + + rNativeContentRegion = aRect; + rNativeBoundingRegion = aRect; + + toReturn = sal_True; + } + } + break; + + case CTRL_MENUBAR: + case CTRL_MENU_POPUP: + { + if(( nPart == PART_MENU_ITEM_CHECK_MARK )||( nPart == PART_MENU_ITEM_RADIO_MARK )) { + + w=10; + h=10;//dimensions of the mark (10px font) + + rNativeContentRegion = Rectangle( Point( x, y ), Size( w, h ) ); + rNativeBoundingRegion = Rectangle( Point( x, y ), Size( w, h ) ); + + toReturn = sal_True; + } + } + 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 000000000000..796b05ca7ae4 --- /dev/null +++ b/vcl/osx/salnsmenu.mm @@ -0,0 +1,207 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * 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/salframe.h" +#include "osx/salmenu.h" +#include "osx/salnsmenu.h" + +#include "vcl/window.hxx" + +@implementation SalNSMenu +-(id)initWithMenu: (AquaSalMenu*)pMenu +{ + mpMenu = pMenu; + return [super initWithTitle: [NSString string]]; +} + +-(void)menuNeedsUpdate: (NSMenu*)pMenu +{ + (void)pMenu; + YIELD_GUARD; + + 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" ); + } + } +} + +-(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; + YIELD_GUARD; + + const AquaSalFrame* pFrame = mpMenuItem->mpParentMenu ? mpMenuItem->mpParentMenu->getFrame() : NULL; + 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); + 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]; + [SalData::getStatusItem() drawStatusBarBackgroundInRect: aRect withHighlight: NO]; + 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 ) + { + NSRect aFromRect = { { 0, 0 }, + { static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Width()), + static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().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: NSCompositeSourceOver 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 ) + { + NSRect aFromRect = { { 0, 0 }, + { static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().Width()), + static_cast<CGFloat>(rButtons[i].maButton.maImage.GetSizePixel().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 = { { static_cast<CGFloat>(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: NULL]; + 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 000000000000..bad2f4e51ff7 --- /dev/null +++ b/vcl/osx/salnstimer.mm @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osx/saltimer.h" +#include "osx/salnstimer.h" +#include "osx/salinst.h" +#include "osx/saldata.hxx" + +#include "svdata.hxx" + +@implementation TimerCallbackCaller +-(void)timerElapsed:(NSTimer*)pTimer +{ + (void)pTimer; + ImplSVData* pSVData = ImplGetSVData(); + if( AquaSalTimer::bDispatchTimer ) + { + if( pSVData->mpSalTimer ) + { + YIELD_GUARD; + pSVData->mpSalTimer->CallCallback(); + + // NSTimer does not end nextEventMatchingMask of NSApplication + // so we need to wakeup a waiting Yield to inform it something happened + GetSalData()->mpFirstInstance->wakeupYield(); + } + } +} +@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 000000000000..16a56b5261ca --- /dev/null +++ b/vcl/osx/salobj.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 <string.h> + +#include "osx/saldata.hxx" +#include "osx/salobj.h" +#include "osx/salframe.h" + +// ======================================================================= + +AquaSalObject::AquaSalObject( AquaSalFrame* pFrame ) : + mpFrame( pFrame ), + mnClipX( -1 ), + mnClipY( -1 ), + mnClipWidth( -1 ), + mnClipHeight( -1 ), + mbClip( false ), + mnX( 0 ), + mnY( 0 ), + mnWidth( 20 ), + mnHeight( 20 ) +{ + maSysData.nSize = sizeof( maSysData ); + maSysData.mpNSView = NULL; + + NSRect aInitFrame = { { 0, 0 }, { 20, 20 } }; + mpClipView = [[NSClipView alloc] initWithFrame: aInitFrame ]; + if( mpClipView ) + { + [mpFrame->getNSView() addSubview: mpClipView]; + [mpClipView setHidden: YES]; + } + maSysData.mpNSView = [[NSView alloc] initWithFrame: aInitFrame]; + if( maSysData.mpNSView ) + { + if( mpClipView ) + [mpClipView setDocumentView: maSysData.mpNSView]; + } +} + +// ----------------------------------------------------------------------- + +AquaSalObject::~AquaSalObject() +{ + if( maSysData.mpNSView ) + { + NSView *pView = maSysData.mpNSView; + [pView removeFromSuperview]; + [pView release]; + } + if( mpClipView ) + { + [mpClipView removeFromSuperview]; + [mpClipView release]; + } +} + +/* + 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(); +} + +// ----------------------------------------------------------------------- + +sal_uInt16 AquaSalObject::GetClipRegionType() +{ + return SAL_OBJECT_CLIP_INCLUDERECTS; +} + +// ----------------------------------------------------------------------- + +void AquaSalObject::BeginSetClipRegion( sal_uLong ) +{ + mbClip = false; +} + +// ----------------------------------------------------------------------- + +void AquaSalObject::UnionClipRegion( long nX, long nY, long nWidth, long nHeight ) +{ + if( mbClip ) + { + if( nX < mnClipX ) + { + mnClipWidth += mnClipX - nX; + mnClipX = nX; + } + if( nX + nWidth > mnClipX + mnClipWidth ) + mnClipWidth = nX + nWidth - mnClipX; + if( nY < mnClipY ) + { + mnClipHeight += mnClipY - nY; + mnClipY = nY; + } + if( nY + nHeight > mnClipY + mnClipHeight ) + mnClipHeight = nY + nHeight - mnClipY; + } + else + { + mnClipX = nX; + mnClipY = nY; + mnClipWidth = nWidth; + mnClipHeight = nHeight; + mbClip = true; + } +} + +// ----------------------------------------------------------------------- + +void AquaSalObject::EndSetClipRegion() +{ + setClippedPosSize(); +} + +// ----------------------------------------------------------------------- + +void AquaSalObject::SetPosSize( long nX, long nY, long nWidth, long nHeight ) +{ + mnX = nX; + mnY = nY; + mnWidth = nWidth; + mnHeight = nHeight; + setClippedPosSize(); +} + +// ----------------------------------------------------------------------- + +void AquaSalObject::setClippedPosSize() +{ + NSRect aViewRect = { { 0, 0 }, { static_cast<CGFloat>(mnWidth), static_cast<CGFloat>(mnHeight) } }; + if( maSysData.mpNSView ) + { + NSView* pNSView = maSysData.mpNSView; + [pNSView setFrame: aViewRect]; + } + + NSRect aClipViewRect = { { static_cast<CGFloat>(mnX), static_cast<CGFloat>(mnY) }, { static_cast<CGFloat>(mnWidth), static_cast<CGFloat>(mnHeight) } }; + NSPoint aClipPt = { 0, 0 }; + 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( sal_Bool bVisible ) +{ + if( mpClipView ) + [mpClipView setHidden: (bVisible ? NO : YES)]; +} + +// ----------------------------------------------------------------------- + +const SystemEnvData* AquaSalObject::GetSystemData() const +{ + return &maSysData; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/salprn.cxx b/vcl/osx/salprn.cxx new file mode 100644 index 000000000000..082fa1cdc199 --- /dev/null +++ b/vcl/osx/salprn.cxx @@ -0,0 +1,755 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <boost/bind.hpp> + +#include "officecfg/Office/Common.hxx" + +#include "vcl/print.hxx" +#include <sal/macros.h> + +#include "osx/salinst.h" +#include "osx/salprn.h" +#include "osx/printview.h" +#include "quartz/salgdi.h" +#include "osx/saldata.hxx" +#include "quartz/utils.h" + +#include "jobset.h" +#include "salptype.hxx" + +#include "com/sun/star/beans/PropertyValue.hpp" +#include "com/sun/star/awt/Size.hpp" +#include "com/sun/star/uno/Sequence.hxx" + +#include <algorithm> + +using namespace vcl; +using namespace com::sun::star; +using namespace com::sun::star::beans; + + +// ======================================================================= + +AquaSalInfoPrinter::AquaSalInfoPrinter( const SalPrinterQueueInfo& i_rQueue ) : + mpGraphics( 0 ), + 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] == NSLandscapeOrientation) ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT; +#if MACOSX_SDK_VERSION >= 1090 + [mpPrintInfo setOrientation: NSPaperOrientationPortrait]; +#else + [mpPrintInfo setOrientation: NSPortraitOrientation]; +#endif + } + + mpGraphics = new AquaSalGraphics(); + + const int nWidth = 100, nHeight = 100; + maContextMemory.reset( reinterpret_cast<sal_uInt8*>( rtl_allocateMemory( nWidth * 4 * nHeight ) ), + boost::bind( rtl_freeMemory, _1 ) ); + + if( maContextMemory ) + { + mrContext = CGBitmapContextCreate( maContextMemory.get(), nWidth, nHeight, 8, nWidth * 4, GetSalData()->mxRGBSpace, kCGImageAlphaNoneSkipFirst ); + if( mrContext ) + SetupPrinterGraphics( mrContext ); + } +} + +// ----------------------------------------------------------------------- + +AquaSalInfoPrinter::~AquaSalInfoPrinter() +{ + delete mpGraphics; + if( mpPrintInfo ) + [mpPrintInfo release]; + if( mrContext ) + CFRelease( mrContext ); +} + +// ----------------------------------------------------------------------- + +void AquaSalInfoPrinter::SetupPrinterGraphics( CGContextRef i_rContext ) const +{ + if( mpGraphics ) + { + if( mpPrintInfo ) + { + // FIXME: get printer resolution + long nDPIX = 720, nDPIY = 720; + NSSize aPaperSize = [mpPrintInfo paperSize]; + + NSRect aImageRect = [mpPrintInfo imageablePageBounds]; + if( mePageOrientation == ORIENTATION_PORTRAIT ) + { + // move mirrored CTM back into paper + double dX = 0, dY = aPaperSize.height; + // move CTM to reflect imageable area + dX += aImageRect.origin.x; + dY -= aPaperSize.height - aImageRect.size.height - aImageRect.origin.y; + CGContextTranslateCTM( i_rContext, dX + mnStartPageOffsetX, dY - mnStartPageOffsetY ); + // scale to be top/down and reflect our "virtual" DPI + CGContextScaleCTM( i_rContext, 72.0/double(nDPIX), -(72.0/double(nDPIY)) ); + } + else + { + // move CTM to reflect imageable area + double dX = aImageRect.origin.x, dY = aPaperSize.height - aImageRect.size.height - aImageRect.origin.y; + CGContextTranslateCTM( i_rContext, -dX, -dY ); + // turn by 90 degree + CGContextRotateCTM( i_rContext, M_PI/2 ); + // move turned CTM back into paper + dX = aPaperSize.height; + dY = -aPaperSize.width; + CGContextTranslateCTM( i_rContext, dX + mnStartPageOffsetY, dY - mnStartPageOffsetX ); + // scale to be top/down and reflect our "virtual" DPI + CGContextScaleCTM( i_rContext, -(72.0/double(nDPIY)), (72.0/double(nDPIX)) ); + } + mpGraphics->SetPrinterGraphics( i_rContext, nDPIX, nDPIY, 1.0 ); + } + else + OSL_FAIL( "no print info in SetupPrinterGraphics" ); + } +} + +// ----------------------------------------------------------------------- + +SalGraphics* AquaSalInfoPrinter::GetGraphics() +{ + SalGraphics* pGraphics = mbGraphics ? NULL : mpGraphics; + mbGraphics = true; + return pGraphics; +} + +// ----------------------------------------------------------------------- + +void AquaSalInfoPrinter::ReleaseGraphics( SalGraphics* ) +{ + mbGraphics = false; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalInfoPrinter::Setup( SalFrame*, ImplJobSetup* ) +{ + return sal_False; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalInfoPrinter::SetPrinterData( ImplJobSetup* io_pSetupData ) +{ + // FIXME: implement driver data + if( io_pSetupData && io_pSetupData->mpDriverData ) + return SetData( ~0, io_pSetupData ); + + + sal_Bool bSuccess = sal_True; + + // set system type + io_pSetupData->mnSystem = 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->mePaperFormat = aInfo.getPaper(); + if( io_pSetupData->mePaperFormat == PAPER_USER ) + { + io_pSetupData->mnPaperWidth = PtTo10Mu( width ); + io_pSetupData->mnPaperHeight = PtTo10Mu( height ); + } + else + { + io_pSetupData->mnPaperWidth = 0; + io_pSetupData->mnPaperHeight = 0; + } + + // set orientation + io_pSetupData->meOrientation = mePageOrientation; + + io_pSetupData->mnPaperBin = 0; + io_pSetupData->mpDriverData = reinterpret_cast<sal_uInt8*>(rtl_allocateMemory( 4 )); + io_pSetupData->mnDriverDataLen = 4; + } + else + bSuccess = sal_False; + + + return bSuccess; +} + +// ----------------------------------------------------------------------- + +void AquaSalInfoPrinter::setPaperSize( long i_nWidth, long i_nHeight, Orientation i_eSetOrientation ) +{ + + Orientation ePaperOrientation = ORIENTATION_PORTRAIT; + const PaperInfo* pPaper = matchPaper( i_nWidth, i_nHeight, ePaperOrientation ); + + if( pPaper ) + { + NSString* pPaperName = [CreateNSString( OStringToOUString(PaperInfo::toPSName(pPaper->getPaper()), RTL_TEXTENCODING_ASCII_US) ) autorelease]; + [mpPrintInfo setPaperName: pPaperName]; + } + else if( i_nWidth > 0 && i_nHeight > 0 ) + { + NSSize aPaperSize = { static_cast<CGFloat>(TenMuToPt(i_nWidth)), static_cast<CGFloat>(TenMuToPt(i_nHeight)) }; + [mpPrintInfo setPaperSize: aPaperSize]; + } + // this seems counterintuitive + mePageOrientation = i_eSetOrientation; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalInfoPrinter::SetData( sal_uLong i_nFlags, ImplJobSetup* io_pSetupData ) +{ + if( ! io_pSetupData || io_pSetupData->mnSystem != JOBSETUP_SYSTEM_MAC ) + return sal_False; + + + if( mpPrintInfo ) + { + if( (i_nFlags & SAL_JOBSET_ORIENTATION) != 0 ) + mePageOrientation = io_pSetupData->meOrientation; + + if( (i_nFlags & SAL_JOBSET_PAPERSIZE) != 0) + { + // set paper format + long width = 21000, height = 29700; + if( io_pSetupData->mePaperFormat == PAPER_USER ) + { + // #i101108# sanity check + if( io_pSetupData->mnPaperWidth && io_pSetupData->mnPaperHeight ) + { + width = io_pSetupData->mnPaperWidth; + height = io_pSetupData->mnPaperHeight; + } + } + else + { + PaperInfo aInfo( io_pSetupData->mePaperFormat ); + width = aInfo.getWidth(); + height = aInfo.getHeight(); + } + + setPaperSize( width, height, mePageOrientation ); + } + } + + return mpPrintInfo != nil; +} + +// ----------------------------------------------------------------------- + +sal_uLong AquaSalInfoPrinter::GetPaperBinCount( const ImplJobSetup* ) +{ + return 0; +} + +// ----------------------------------------------------------------------- + +OUString AquaSalInfoPrinter::GetPaperBinName( const ImplJobSetup*, sal_uLong ) +{ + return OUString(); +} + +// ----------------------------------------------------------------------- + +sal_uLong AquaSalInfoPrinter::GetCapabilities( const ImplJobSetup*, sal_uInt16 i_nType ) +{ + switch( i_nType ) + { + case PRINTER_CAPABILITIES_SUPPORTDIALOG: + return 0; + case PRINTER_CAPABILITIES_COPIES: + return 0xffff; + case PRINTER_CAPABILITIES_COLLATECOPIES: + return 0xffff; + case PRINTER_CAPABILITIES_SETORIENTATION: + return 1; + case PRINTER_CAPABILITIES_SETDUPLEX: + return 0; + case PRINTER_CAPABILITIES_SETPAPERBIN: + return 0; + case PRINTER_CAPABILITIES_SETPAPERSIZE: + return 1; + case PRINTER_CAPABILITIES_SETPAPER: + return 1; + case PRINTER_CAPABILITIES_EXTERNALDIALOG: + return officecfg::Office::Common::Misc::UseSystemPrintDialog::get() + ? 1 : 0; + case PRINTER_CAPABILITIES_PDF: + return 1; + case PRINTER_CAPABILITIES_USEPULLMODEL: + return 1; + default: break; + }; + return 0; +} + +// ----------------------------------------------------------------------- + +void AquaSalInfoPrinter::GetPageInfo( const ImplJobSetup*, + long& o_rOutWidth, long& o_rOutHeight, + long& o_rPageOffX, long& o_rPageOffY, + long& o_rPageWidth, long& o_rPageHeight ) +{ + 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]; + o_rPageWidth = static_cast<long>( double(aPaperSize.width) * fXScaling ); + o_rPageHeight = static_cast<long>( double(aPaperSize.height) * fYScaling ); + + NSRect aImageRect = [mpPrintInfo imageablePageBounds]; + o_rPageOffX = static_cast<long>( aImageRect.origin.x * fXScaling ); + o_rPageOffY = static_cast<long>( (aPaperSize.height - aImageRect.size.height - aImageRect.origin.y) * fYScaling ); + o_rOutWidth = static_cast<long>( aImageRect.size.width * fXScaling ); + o_rOutHeight = static_cast<long>( aImageRect.size.height * fYScaling ); + + if( mePageOrientation == ORIENTATION_LANDSCAPE ) + { + std::swap( o_rOutWidth, o_rOutHeight ); + std::swap( o_rPageWidth, o_rPageHeight ); + std::swap( o_rPageOffX, o_rPageOffY ); + } + } +} + +static Size getPageSize( vcl::PrinterController& i_rController, sal_Int32 i_nPage ) +{ + Size aPageSize; + uno::Sequence< PropertyValue > aPageParms( i_rController.getPageParameters( i_nPage ) ); + for( sal_Int32 nProperty = 0, nPropertyCount = aPageParms.getLength(); nProperty < nPropertyCount; ++nProperty ) + { + if ( aPageParms[ nProperty ].Name == "PageSize" ) + { + awt::Size aSize; + aPageParms[ nProperty].Value >>= aSize; + aPageSize.Width() = aSize.Width; + aPageSize.Height() = aSize.Height; + break; + } + } + return aPageSize; +} + +sal_Bool AquaSalInfoPrinter::StartJob( const OUString* i_pFileName, + const OUString& i_rJobName, + const OUString& /*i_rAppName*/, + ImplJobSetup* i_pSetupData, + vcl::PrinterController& i_rController + ) +{ + if( mbJob ) + return sal_False; + + sal_Bool bSuccess = sal_False; + bool bWasAborted = false; + AquaSalInstance* pInst = GetSalData()->mpFirstInstance; + PrintAccessoryViewState aAccViewState; + sal_Int32 nAllPages = 0; + + // reset IsLastPage + i_rController.setLastPage( sal_False ); + + // update job data + if( i_pSetupData ) + SetData( ~0, i_pSetupData ); + + // do we want a progress panel ? + sal_Bool bShowProgressPanel = sal_True; + beans::PropertyValue* pMonitor = i_rController.getValue( OUString( "MonitorVisible" ) ); + if( pMonitor ) + pMonitor->Value >>= bShowProgressPanel; + if( ! i_rController.isShowDialogs() ) + bShowProgressPanel = sal_False; + + // possibly create one job for collated output + sal_Bool bSinglePrintJobs = sal_False; + beans::PropertyValue* pSingleValue = i_rController.getValue( OUString( "PrintCollateAsSingleJobs" ) ); + if( pSingleValue ) + { + pSingleValue->Value >>= bSinglePrintJobs; + } + + // FIXME: jobStarted() should be done after the print dialog has ended (if there is one) + // how do I know when that might be ? + i_rController.jobStarted(); + + + int nCopies = i_rController.getPrinter()->GetCopyCount(); + int nJobs = 1; + if( bSinglePrintJobs ) + { + nJobs = nCopies; + nCopies = 1; + } + + for( int nCurJob = 0; nCurJob < nJobs; nCurJob++ ) + { + aAccViewState.bNeedRestart = true; + do + { + if( aAccViewState.bNeedRestart ) + { + mnCurPageRangeStart = 0; + mnCurPageRangeCount = 0; + nAllPages = i_rController.getFilteredPageCount(); + } + + aAccViewState.bNeedRestart = false; + + Size aCurSize( 21000, 29700 ); + if( nAllPages > 0 ) + { + mnCurPageRangeCount = 1; + aCurSize = getPageSize( i_rController, mnCurPageRangeStart ); + Size aNextSize( aCurSize ); + + // print pages up to a different size + while( mnCurPageRangeCount + mnCurPageRangeStart < nAllPages ) + { + aNextSize = getPageSize( i_rController, mnCurPageRangeStart + mnCurPageRangeCount ); + if( aCurSize == aNextSize // same page size + || + (aCurSize.Width() == aNextSize.Height() && aCurSize.Height() == aNextSize.Width()) // same size, but different orientation + ) + { + mnCurPageRangeCount++; + } + else + break; + } + } + else + mnCurPageRangeCount = 0; + + // now for the current run + mnStartPageOffsetX = mnStartPageOffsetY = 0; + // setup the paper size and orientation + // do this on our associated Printer object, since that is + // out interface to the applications which occasionally rely on the paper + // information (e.g. brochure printing scales to the found paper size) + // also SetPaperSizeUser has the advantage that we can share a + // platform independent paper matching algorithm + boost::shared_ptr<Printer> pPrinter( i_rController.getPrinter() ); + pPrinter->SetMapMode( MapMode( MAP_100TH_MM ) ); + pPrinter->SetPaperSizeUser( aCurSize, true ); + + // 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: pPath forKey: NSPrintSavePath]; + [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 accesory view + pReleaseAfterUse = [AquaPrintAccessoryView setupPrinterPanel: pPrintOperation withController: &i_rController withState: &aAccViewState]; + + bSuccess = sal_True; + mbJob = true; + pInst->startedPrintJob(); + [pPrintOperation runOperation]; + pInst->endedPrintJob(); + 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 XRenderadble::render one last time with IsLastPage = sal_True + i_rController.setLastPage( sal_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; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalInfoPrinter::EndJob() +{ + mnStartPageOffsetX = mnStartPageOffsetY = 0; + mbJob = false; + return sal_True; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalInfoPrinter::AbortJob() +{ + mbJob = false; + + // FIXME: implementation + return sal_False; +} + +// ----------------------------------------------------------------------- + +SalGraphics* AquaSalInfoPrinter::StartPage( ImplJobSetup* i_pSetupData, sal_Bool i_bNewJobData ) +{ + if( i_bNewJobData && i_pSetupData ) + SetPrinterData( i_pSetupData ); + + CGContextRef rContext = reinterpret_cast<CGContextRef>([[NSGraphicsContext currentContext] graphicsPort]); + + SetupPrinterGraphics( rContext ); + + return mpGraphics; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalInfoPrinter::EndPage() +{ + mpGraphics->InvalidateContext(); + return sal_True; +} + +// ----------------------------------------------------------------------- + +sal_uLong AquaSalInfoPrinter::GetErrorCode() const +{ + return 0; +} + +// ======================================================================= + +AquaSalPrinter::AquaSalPrinter( AquaSalInfoPrinter* i_pInfoPrinter ) : + mpInfoPrinter( i_pInfoPrinter ) +{ +} + +// ----------------------------------------------------------------------- + +AquaSalPrinter::~AquaSalPrinter() +{ +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalPrinter::StartJob( const OUString* i_pFileName, + const OUString& i_rJobName, + const OUString& i_rAppName, + ImplJobSetup* i_pSetupData, + vcl::PrinterController& i_rController ) +{ + return mpInfoPrinter->StartJob( i_pFileName, i_rJobName, i_rAppName, i_pSetupData, i_rController ); +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalPrinter::StartJob( const OUString* /*i_pFileName*/, + const OUString& /*i_rJobName*/, + const OUString& /*i_rAppName*/, + sal_uLong /*i_nCopies*/, + bool /*i_bCollate*/, + bool /*i_bDirect*/, + ImplJobSetup* ) +{ + OSL_FAIL( "should never be called" ); + return sal_False; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalPrinter::EndJob() +{ + return mpInfoPrinter->EndJob(); +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalPrinter::AbortJob() +{ + return mpInfoPrinter->AbortJob(); +} + +// ----------------------------------------------------------------------- + +SalGraphics* AquaSalPrinter::StartPage( ImplJobSetup* i_pSetupData, sal_Bool i_bNewJobData ) +{ + return mpInfoPrinter->StartPage( i_pSetupData, i_bNewJobData ); +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalPrinter::EndPage() +{ + return mpInfoPrinter->EndPage(); +} + +// ----------------------------------------------------------------------- + +sal_uLong AquaSalPrinter::GetErrorCode() +{ + return mpInfoPrinter->GetErrorCode(); +} + +void AquaSalInfoPrinter::InitPaperFormats( const ImplJobSetup* ) +{ + m_aPaperFormats.clear(); + m_bPapersInit = true; + + if( mpPrinter ) + { + 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 ); + } + } + } + } + } + } +} + +const PaperInfo* AquaSalInfoPrinter::matchPaper( long i_nWidth, long i_nHeight, Orientation& o_rOrientation ) const +{ + if( ! m_bPapersInit ) + const_cast<AquaSalInfoPrinter*>(this)->InitPaperFormats( NULL ); + + const PaperInfo* pMatch = NULL; + o_rOrientation = ORIENTATION_PORTRAIT; + for( int n = 0; n < 2 ; n++ ) + { + for( size_t i = 0; i < m_aPaperFormats.size(); i++ ) + { + if( abs( m_aPaperFormats[i].getWidth() - i_nWidth ) < 50 && + 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 000000000000..9ac4b5aeb61f --- /dev/null +++ b/vcl/osx/salsys.cxx @@ -0,0 +1,190 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "rtl/ustrbuf.hxx" + +#include "vcl/button.hxx" + +#include "osx/salsys.h" +#include "osx/saldata.hxx" +#include "osx/salinst.h" +#include "quartz/utils.h" + +#include "svids.hrc" + + +// ======================================================================= + +AquaSalSystem::~AquaSalSystem() +{ +} + +unsigned int AquaSalSystem::GetDisplayScreenCount() +{ + NSArray* pScreens = [NSScreen screens]; + return pScreens ? [pScreens count] : 1; +} + +Rectangle AquaSalSystem::GetDisplayScreenPosSizePixel( unsigned int nScreen ) +{ + NSArray* pScreens = [NSScreen screens]; + Rectangle aRet; + NSScreen* pScreen = nil; + if( pScreens && nScreen < [pScreens count] ) + pScreen = [pScreens objectAtIndex: nScreen]; + else + pScreen = [NSScreen mainScreen]; + + if( pScreen ) + { + NSRect aFrame = [pScreen frame]; + aRet = Rectangle( Point( static_cast<long int>(aFrame.origin.x), static_cast<long int>(aFrame.origin.y) ), + Size( static_cast<long int>(aFrame.size.width), static_cast<long int>(aFrame.size.height) ) ); + } + return aRet; +} + +OUString AquaSalSystem::GetDisplayScreenName( unsigned int nScreen ) +{ + NSArray* pScreens = [NSScreen screens]; + OUString aRet; + if( nScreen < [pScreens count] ) + { + ResMgr* pMgr = ImplGetResMgr(); + if( pMgr ) + { + OUString aScreenName(ResId(SV_MAC_SCREENNNAME, *pMgr).toString()); + aRet = aScreenName.replaceAll("%d", OUString::number(nScreen)); + } + } + return aRet; +} + +static NSString* getStandardString( int nButtonId, bool bUseResources ) +{ + OUString aText; + if( bUseResources ) + { + aText = Button::GetStandardText( nButtonId ); + } + if( aText.isEmpty() ) // this is for bad cases, we might be missing the vcl resource + { + switch( nButtonId ) + { + case BUTTON_OK: aText = "OK";break; + case BUTTON_ABORT: aText = "Abort";break; + case BUTTON_CANCEL: aText = "Cancel";break; + case BUTTON_RETRY: aText = "Retry";break; + case BUTTON_YES: aText = "Yes";break; + case BUTTON_NO : aText = "No";break; + } + } + return aText.isEmpty() ? nil : CreateNSString( aText); +} + +int AquaSalSystem::ShowNativeMessageBox( const OUString& rTitle, + const OUString& rMessage, + int nButtonCombination, + int nDefaultButton, bool bUseResources) +{ + NSString* pTitle = CreateNSString( rTitle ); + NSString* pMessage = CreateNSString( rMessage ); + + struct id_entry + { + int nCombination; + int nDefaultButton; + int nTextIds[3]; + } aButtonIds[] = + { + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_OK, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK, { BUTTON_OK, -1, -1 } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_OK_CANCEL, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK, { BUTTON_OK, BUTTON_CANCEL, -1 } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_OK_CANCEL, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_CANCEL, { BUTTON_CANCEL, BUTTON_OK, -1 } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_ABORT_RETRY_IGNORE, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_ABORT, { BUTTON_ABORT, BUTTON_IGNORE, BUTTON_RETRY } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_ABORT_RETRY_IGNORE, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_RETRY, { BUTTON_RETRY, BUTTON_IGNORE, BUTTON_ABORT } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_ABORT_RETRY_IGNORE, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_IGNORE, { BUTTON_IGNORE, BUTTON_IGNORE, BUTTON_ABORT } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_YES_NO_CANCEL, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_YES, { BUTTON_YES, BUTTON_NO, BUTTON_CANCEL } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_YES_NO_CANCEL, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_NO, { BUTTON_NO, BUTTON_YES, BUTTON_CANCEL } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_YES_NO_CANCEL, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_CANCEL, { BUTTON_CANCEL, BUTTON_YES, BUTTON_NO } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_YES_NO, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_YES, { BUTTON_YES, BUTTON_NO, -1 } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_YES_NO, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_NO, { BUTTON_NO, BUTTON_YES, -1 } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_RETRY_CANCEL, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_RETRY, { BUTTON_RETRY, BUTTON_CANCEL, -1 } }, + { SALSYSTEM_SHOWNATIVEMSGBOX_BTNCOMBI_RETRY_CANCEL, SALSYSTEM_SHOWNATIVEMSGBOX_BTN_CANCEL, { BUTTON_CANCEL, BUTTON_RETRY, -1 } } + }; + + NSString* pDefText = nil; + NSString* pAltText = nil; + NSString* pOthText = nil; + + unsigned int nC; + for( nC = 0; nC < sizeof(aButtonIds)/sizeof(aButtonIds[0]); nC++ ) + { + if( aButtonIds[nC].nCombination == nButtonCombination ) + { + if( aButtonIds[nC].nDefaultButton == nDefaultButton ) + { + if( aButtonIds[nC].nTextIds[0] != -1 ) + pDefText = getStandardString( + aButtonIds[nC].nTextIds[0], bUseResources ); + if( aButtonIds[nC].nTextIds[1] != -1 ) + pAltText = getStandardString( + aButtonIds[nC].nTextIds[1], bUseResources ); + if( aButtonIds[nC].nTextIds[2] != -1 ) + pOthText = getStandardString( + aButtonIds[nC].nTextIds[2], bUseResources ); + break; + } + } + } + + + int nResult = NSRunAlertPanel( pTitle, pMessage, pDefText, pAltText, pOthText ); + + if( pTitle ) + [pTitle release]; + if( pMessage ) + [pMessage release]; + if( pDefText ) + [pDefText release]; + if( pAltText ) + [pAltText release]; + if( pOthText ) + [pOthText release]; + + int nRet = 0; + if( nC < sizeof(aButtonIds)/sizeof(aButtonIds[0]) && nResult >= 1 && nResult <= 3 ) + { + int nPressed = aButtonIds[nC].nTextIds[nResult-1]; + switch( nPressed ) + { + case BUTTON_NO: nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_NO; break; + case BUTTON_YES: nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_YES; break; + case BUTTON_OK: nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_OK; break; + case BUTTON_CANCEL: nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_CANCEL; break; + case BUTTON_ABORT: nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_ABORT; break; + case BUTTON_RETRY: nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_RETRY; break; + case BUTTON_IGNORE: nRet = SALSYSTEM_SHOWNATIVEMSGBOX_BTN_IGNORE; break; + } + } + + 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 000000000000..ad0959048964 --- /dev/null +++ b/vcl/osx/saltimer.cxx @@ -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 . + */ + + +#include "osx/saltimer.h" +#include "osx/salnstimer.h" +#include "osx/saldata.hxx" +#include "osx/salframe.h" +#include "osx/salinst.h" + +// ======================================================================= + +NSTimer* AquaSalTimer::pRunningTimer = nil; +bool AquaSalTimer::bDispatchTimer = false; + + +void ImplSalStartTimer( sal_uLong nMS ) +{ + SalData* pSalData = GetSalData(); + if( pSalData->mpFirstInstance->isNSAppThread() ) + { + AquaSalTimer::bDispatchTimer = true; + NSTimeInterval aTI = double(nMS)/1000.0; + if( AquaSalTimer::pRunningTimer != nil ) + { + if( [AquaSalTimer::pRunningTimer timeInterval] == aTI ) + // set new fire date + [AquaSalTimer::pRunningTimer setFireDate: [NSDate dateWithTimeIntervalSinceNow: aTI]]; + else + { + [AquaSalTimer::pRunningTimer invalidate]; + AquaSalTimer::pRunningTimer = nil; + } + } + if( AquaSalTimer::pRunningTimer == nil ) + { + AquaSalTimer::pRunningTimer = [NSTimer scheduledTimerWithTimeInterval: aTI + target: [[[TimerCallbackCaller alloc] init] autorelease] + selector: @selector(timerElapsed:) + userInfo: nil + repeats: YES]; + /* #i84055# add timer to tracking run loop mode, + so they also elapse while e.g. life resize + */ + [[NSRunLoop currentRunLoop] addTimer: AquaSalTimer::pRunningTimer forMode: NSEventTrackingRunLoopMode]; + } + } + else + { + SalData::ensureThreadAutoreleasePool(); + // post an event so we can get into the main thread + NSPoint aPt = { 0, 0 }; + NSEvent* pEvent = [NSEvent otherEventWithType: NSApplicationDefined + location: aPt + modifierFlags: 0 + timestamp: [NSDate timeIntervalSinceReferenceDate] + windowNumber: 0 + context: nil + subtype: AquaSalInstance::AppStartTimerEvent + data1: (int)nMS + data2: 0 ]; + if( pEvent ) + [NSApp postEvent: pEvent atStart: YES]; + } +} + +void ImplSalStopTimer() +{ + AquaSalTimer::bDispatchTimer = false; +} + +void AquaSalTimer::handleStartTimerEvent( NSEvent* pEvent ) +{ + ImplSVData* pSVData = ImplGetSVData(); + if( pSVData->mpSalTimer ) + { + NSTimeInterval posted = [pEvent timestamp] + NSTimeInterval([pEvent data1])/1000.0; + NSTimeInterval current = [NSDate timeIntervalSinceReferenceDate]; + if( (posted - current) <= 0.0 ) + { + YIELD_GUARD; + // timer already elapsed since event posted + pSVData->mpSalTimer->CallCallback(); + } + ImplSalStartTimer( sal_uLong( [pEvent data1] ) ); + } + +} + +AquaSalTimer::AquaSalTimer( ) +{ +} + +AquaSalTimer::~AquaSalTimer() +{ + ImplSalStopTimer(); +} + +void AquaSalTimer::Start( sal_uLong nMS ) +{ + ImplSalStartTimer( nMS ); +} + +void AquaSalTimer::Stop() +{ + ImplSalStopTimer(); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/salvd.cxx b/vcl/osx/salvd.cxx new file mode 100644 index 000000000000..3f69ab9f5f7c --- /dev/null +++ b/vcl/osx/salvd.cxx @@ -0,0 +1,261 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "vcl/svapp.hxx" +#include "vcl/sysdata.hxx" + +#include "osx/salvd.h" +#include "osx/salinst.h" +#include "quartz/salgdi.h" +#include "osx/saldata.hxx" +#include "osx/salframe.h" + +// ----------------------------------------------------------------------- + +SalVirtualDevice* AquaSalInstance::CreateVirtualDevice( SalGraphics* pGraphics, + long nDX, long nDY, sal_uInt16 nBitCount, const SystemGraphicsData *pData ) +{ + // #i92075# can be called first in a thread + SalData::ensureThreadAutoreleasePool(); + + return new AquaSalVirtualDevice( static_cast< AquaSalGraphics* >( pGraphics ), nDX, nDY, nBitCount, pData ); +} + +// ----------------------------------------------------------------------- + +void AquaSalInstance::DestroyVirtualDevice( SalVirtualDevice* pDevice ) +{ + delete pDevice; +} + +// ======================================================================= + +AquaSalVirtualDevice::AquaSalVirtualDevice( AquaSalGraphics* pGraphic, long nDX, long nDY, sal_uInt16 nBitCount, const SystemGraphicsData *pData ) +: mbGraphicsUsed( false ) +, mxBitmapContext( NULL ) +, mnBitmapDepth( 0 ) +, mxLayer( NULL ) +{ + if( pGraphic && pData && pData->rCGContext ) + { + // Create virtual device based on existing SystemGraphicsData + // We ignore nDx and nDY, as the desired size comes from the SystemGraphicsData + mbForeignContext = true; // the mxContext is from pData + mpGraphics = new AquaSalGraphics( /*pGraphic*/ ); + mpGraphics->SetVirDevGraphics( mxLayer, pData->rCGContext ); + } + else + { + // create empty new virtual device + mbForeignContext = false; // the mxContext is created within VCL + mpGraphics = new AquaSalGraphics(); // never fails + mnBitmapDepth = nBitCount; + + // inherit resolution from reference device + if( pGraphic ) + { + AquaSalFrame* pFrame = pGraphic->getGraphicsFrame(); + if( pFrame && AquaSalFrame::isAlive( pFrame ) ) + { + mpGraphics->setGraphicsFrame( pFrame ); + mpGraphics->copyResolution( *pGraphic ); + } + } + + if( nDX && nDY ) + SetSize( nDX, nDY ); + + // NOTE: if SetSize does not succeed, we just ignore the nDX and nDY + } +} + +// ----------------------------------------------------------------------- + +AquaSalVirtualDevice::~AquaSalVirtualDevice() +{ + if( mpGraphics ) + { + mpGraphics->SetVirDevGraphics( NULL, NULL ); + delete mpGraphics; + mpGraphics = 0; + } + Destroy(); +} + +// ----------------------------------------------------------------------- + +void AquaSalVirtualDevice::Destroy() +{ + if( mbForeignContext ) { + // Do not delete mxContext that we have received from outside VCL + mxLayer = NULL; + return; + } + + if( mxLayer ) + { + if( mpGraphics ) + mpGraphics->SetVirDevGraphics( NULL, NULL ); + CGLayerRelease( mxLayer ); + mxLayer = NULL; + } + + if( mxBitmapContext ) + { + void* pRawData = CGBitmapContextGetData( mxBitmapContext ); + rtl_freeMemory( pRawData ); + CGContextRelease( mxBitmapContext ); + mxBitmapContext = NULL; + } +} + +// ----------------------------------------------------------------------- + +SalGraphics* AquaSalVirtualDevice::GetGraphics() +{ + if( mbGraphicsUsed || !mpGraphics ) + return 0; + + mbGraphicsUsed = true; + return mpGraphics; +} + +// ----------------------------------------------------------------------- + +void AquaSalVirtualDevice::ReleaseGraphics( SalGraphics* ) +{ + mbGraphicsUsed = false; +} + +// ----------------------------------------------------------------------- + +sal_Bool AquaSalVirtualDevice::SetSize( long nDX, long nDY ) +{ + if( mbForeignContext ) + { + // Do not delete/resize mxContext that we have received from outside VCL + return true; + } + + if( mxLayer ) + { + const CGSize aSize = CGLayerGetSize( mxLayer ); + if( (nDX == aSize.width) && (nDY == aSize.height) ) + { + // Yay, we do not have to do anything :) + return true; + } + } + + Destroy(); + + // create a Quartz layer matching to the intended virdev usage + CGContextRef xCGContext = NULL; + if( mnBitmapDepth && (mnBitmapDepth < 16) ) + { + mnBitmapDepth = 8; // TODO: are 1bit vdevs worth it? + const CGColorSpaceRef aCGColorSpace = GetSalData()->mxGraySpace; + const CGBitmapInfo aCGBmpInfo = kCGImageAlphaNone; + const int nBytesPerRow = (mnBitmapDepth * nDX + 7) / 8; + + void* pRawData = rtl_allocateMemory( nBytesPerRow * nDY ); + mxBitmapContext = ::CGBitmapContextCreate( pRawData, nDX, nDY, + mnBitmapDepth, nBytesPerRow, aCGColorSpace, aCGBmpInfo ); + xCGContext = mxBitmapContext; + } + else + { + // default to a NSView target context + AquaSalFrame* pSalFrame = mpGraphics->getGraphicsFrame(); + if( !pSalFrame || !AquaSalFrame::isAlive( pSalFrame )) + { + if( !GetSalData()->maFrames.empty() ) + { + // get the first matching frame + pSalFrame = *GetSalData()->maFrames.begin(); + } + else + { + // ensure we don't reuse a dead AquaSalFrame on the very + // unlikely case of no other frame to use + pSalFrame = NULL; + } + // update the frame reference + mpGraphics->setGraphicsFrame( pSalFrame ); + } + if( pSalFrame ) + { + // #i91990# + NSWindow* pNSWindow = pSalFrame->getNSWindow(); + if ( pNSWindow ) + { + NSGraphicsContext* pNSContext = [NSGraphicsContext graphicsContextWithWindow: pNSWindow]; + if( pNSContext ) + xCGContext = reinterpret_cast<CGContextRef>([pNSContext graphicsPort]); + } + else + { + // fall back to a bitmap context + mnBitmapDepth = 32; + const CGColorSpaceRef aCGColorSpace = GetSalData()->mxRGBSpace; + const CGBitmapInfo aCGBmpInfo = kCGImageAlphaNoneSkipFirst; + const int nBytesPerRow = (mnBitmapDepth * nDX) / 8; + + void* pRawData = rtl_allocateMemory( nBytesPerRow * nDY ); + mxBitmapContext = ::CGBitmapContextCreate( pRawData, nDX, nDY, + 8, nBytesPerRow, aCGColorSpace, aCGBmpInfo ); + xCGContext = mxBitmapContext; + } + } + } + + DBG_ASSERT( xCGContext, "no context" ); + + const CGSize aNewSize = { static_cast<CGFloat>(nDX), static_cast<CGFloat>(nDY) }; + mxLayer = CGLayerCreateWithContext( xCGContext, aNewSize, NULL ); + + if( mxLayer && mpGraphics ) + { + // get the matching Quartz context + CGContextRef xDrawContext = CGLayerGetContext( mxLayer ); + mpGraphics->SetVirDevGraphics( mxLayer, xDrawContext, mnBitmapDepth ); + } + + return (mxLayer != NULL); +} + +// ----------------------------------------------------------------------- + +void AquaSalVirtualDevice::GetSize( long& rWidth, long& rHeight ) +{ + if( mxLayer ) + { + const CGSize aSize = CGLayerGetSize( mxLayer ); + rWidth = static_cast<long>(aSize.width); + rHeight = static_cast<long>(aSize.height); + } + else + { + rWidth = 0; + rHeight = 0; + } +} + +/* 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 000000000000..d4fa284c2ddb --- /dev/null +++ b/vcl/osx/service_entry.cxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include "osl/diagnose.h" + +#include "vcl/svapp.hxx" + +#include "osx/saldata.hxx" +#include "osx/salinst.h" + +#include "DragSource.hxx" +#include "DropTarget.hxx" +#include "clipboard.hxx" + +using namespace ::osl; +using namespace ::rtl; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::cppu; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::datatransfer::clipboard; + + +uno::Reference< XInterface > AquaSalInstance::CreateClipboard( const Sequence< Any >& i_rArguments ) +{ + if ( Application::IsHeadlessModeEnabled() ) + return SalInstance::CreateClipboard( i_rArguments ); + + SalData* pSalData = GetSalData(); + if( ! pSalData->mxClipboard.is() ) + pSalData->mxClipboard = uno::Reference<XInterface>(static_cast< XClipboard* >(new AquaClipboard()), UNO_QUERY); + return pSalData->mxClipboard; +} + +uno::Reference<XInterface> AquaSalInstance::CreateDragSource() +{ + if ( Application::IsHeadlessModeEnabled() ) + return SalInstance::CreateDragSource(); + + return uno::Reference<XInterface>(static_cast< XInitialization* >(new DragSource()), UNO_QUERY); +} + +uno::Reference<XInterface> AquaSalInstance::CreateDropTarget() +{ + if ( Application::IsHeadlessModeEnabled() ) + return SalInstance::CreateDropTarget(); + + return uno::Reference<XInterface>(static_cast< XInitialization* >(new DropTarget()), UNO_QUERY); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/vclnsapp.mm b/vcl/osx/vclnsapp.mm new file mode 100644 index 000000000000..18079fb1ca9b --- /dev/null +++ b/vcl/osx/vclnsapp.mm @@ -0,0 +1,517 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include "sal/config.h" + +#include <vector> + +#include "vcl/window.hxx" +#include "vcl/svapp.hxx" +#include "vcl/cmdevt.hxx" + +#include "osx/vclnsapp.h" +#include "osx/salinst.h" +#include "osx/saldata.hxx" +#include "osx/salframe.h" +#include "osx/salframeview.h" +#include "quartz/utils.h" + +#include "impimagetree.hxx" + +#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)sendEvent:(NSEvent*)pEvent +{ + NSEventType eType = [pEvent type]; + if( eType == NSApplicationDefined ) + GetSalData()->mpFirstInstance->handleAppDefinedEvent( pEvent ); + else if( eType == NSKeyDown && ([pEvent modifierFlags] & NSCommandKeyMask) != 0 ) + { + NSWindow* pKeyWin = [NSApp keyWindow]; + if( pKeyWin && [pKeyWin isKindOfClass: [SalFrameWindow class]] ) + { + AquaSalFrame* pFrame = [(SalFrameWindow*)pKeyWin getSalFrame]; + // handle Cmd-W + // FIXME: the correct solution would be to handle this in framework + // in the menu code + // however that is currently being revised, so let's use a preliminary solution here + // this hack is based on assumption + // a) Cmd-W is the same in all languages in OOo's menu conig + // b) Cmd-W is the same in all languages in on MacOS + // for now this seems to be true + unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)); + if( (pFrame->mnStyleMask & NSClosableWindowMask) != 0 ) + { + if( nModMask == NSCommandKeyMask + && [[pEvent charactersIgnoringModifiers] isEqualToString: @"w"] ) + { + // Note: gcc 4.2.1 (in the 10.6 SDK) tells us + // 'NSWindow' may not respond to + // '-windowShouldClose:' . Is that a bogus + // warning, or is this code bogus? No idea. + // Anyway, so that we can compile also against + // this SDK with -Werror, use objc_msgSend + // instead. + + // Instead of: + // [pFrame->getNSWindow() windowShouldClose: nil]; + // do: + objc_msgSend(pFrame->getNSWindow(), @selector(windowShouldClose:), nil); + + return; + } + } + + /* + * #i98949# - Cmd-M miniaturize window, Cmd-Option-M miniaturize all windows + */ + if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"m"] ) + { + if ( nModMask == NSCommandKeyMask && ([pFrame->getNSWindow() styleMask] & NSMiniaturizableWindowMask) ) + { + [pFrame->getNSWindow() performMiniaturize: nil]; + return; + } + + if ( nModMask == ( NSCommandKeyMask | NSAlternateKeyMask ) ) + { + [NSApp miniaturizeAll: nil]; + return; + } + } + + // #i90083# handle frame switching + // FIXME: lousy workaround + if( (nModMask & (NSControlKeyMask|NSAlternateKeyMask)) == 0 ) + { + if( [[pEvent characters] isEqualToString: @"<"] || + [[pEvent characters] isEqualToString: @"~"] ) + { + [self cycleFrameForward: pFrame]; + return; + } + else if( [[pEvent characters] isEqualToString: @">"] || + [[pEvent characters] isEqualToString: @"`"] ) + { + [self cycleFrameBackward: pFrame]; + 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 & SAL_FRAME_STYLE_FLOAT) == 0 ) + { + [[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 == 0 || ! [pMainMenu performKeyEquivalent: pEvent]) ) + { + [[pKeyWin contentView] keyDown: pEvent]; + bHandled = GetSalData()->maKeyEventAnswer[ pEvent ]; + } + else + bHandled = true; // event handled already or main menu just handled it + + GetSalData()->maKeyEventAnswer.erase( pEvent ); + if( bHandled ) + return; + } + else if( pKeyWin ) + { + // #i94601# a window not of vcl's making has the focus. + // Since our menus do not invoke the usual commands + // try to play nice with native windows like the file dialog + // and emulate them + // precondition: this ONLY works because CMD-V (paste), CMD-C (copy) and CMD-X (cut) are + // NOT localized, that is the same in all locales. Should this be + // different in any locale, this hack will fail. + unsigned int nModMask = ([pEvent modifierFlags] & (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)); + if( nModMask == NSCommandKeyMask ) + { + + if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"v"] ) + { + if( [NSApp sendAction: @selector(paste:) to: nil from: nil] ) + return; + } + else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"c"] ) + { + if( [NSApp sendAction: @selector(copy:) to: nil from: nil] ) + return; + } + else if( [[pEvent charactersIgnoringModifiers] isEqualToString: @"x"] ) + { + if( [NSApp sendAction: @selector(cut:) to: nil from: nil] ) + return; + } + } + } + } + [super sendEvent: pEvent]; +} + +-(void)sendSuperEvent:(NSEvent*)pEvent +{ + [super sendEvent: pEvent]; +} + +-(void)cycleFrameForward: (AquaSalFrame*)pCurFrame +{ + // find current frame in list + std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames ); + std::list< AquaSalFrame* >::iterator it = rFrames.begin(); + for( ; it != rFrames.end() && *it != pCurFrame; ++it ) + ; + if( it != rFrames.end() ) + { + // now find the next frame (or end) + do + { + ++it; + if( it != rFrames.end() ) + { + if( (*it)->mpDockMenuEntry != NULL && + (*it)->mbShown ) + { + [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp]; + return; + } + } + } while( it != rFrames.end() ); + // cycle around, find the next up to pCurFrame + it = rFrames.begin(); + while( *it != pCurFrame ) + { + if( (*it)->mpDockMenuEntry != NULL && + (*it)->mbShown ) + { + [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp]; + return; + } + ++it; + } + } +} + +-(void)cycleFrameBackward: (AquaSalFrame*)pCurFrame +{ + // do the same as cycleFrameForward only with a reverse iterator + + // find current frame in list + std::list< AquaSalFrame* >& rFrames( GetSalData()->maFrames ); + std::list< AquaSalFrame* >::reverse_iterator it = rFrames.rbegin(); + for( ; it != rFrames.rend() && *it != pCurFrame; ++it ) + ; + if( it != rFrames.rend() ) + { + // now find the next frame (or end) + do + { + ++it; + if( it != rFrames.rend() ) + { + if( (*it)->mpDockMenuEntry != NULL && + (*it)->mbShown ) + { + [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp]; + return; + } + } + } while( it != rFrames.rend() ); + // cycle around, find the next up to pCurFrame + it = rFrames.rbegin(); + while( *it != pCurFrame ) + { + if( (*it)->mpDockMenuEntry != NULL && + (*it)->mbShown ) + { + [(*it)->getNSWindow() makeKeyAndOrderFront: NSApp]; + return; + } + ++it; + } + } +} + +-(NSMenu*)applicationDockMenu:(NSApplication *)sender +{ + (void)sender; + return AquaSalInstance::GetDynamicDockMenu(); +} + +-(BOOL)application: (NSApplication*)app openFile: (NSString*)pFile +{ + (void)app; + std::vector<OUString> aFile; + aFile.push_back( GetOUString( pFile ) ); + if( ! AquaSalInstance::isOnCommandLine( aFile[0] ) ) + { + const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_OPEN, aFile); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + } + 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 rtl::OUString aFile( GetOUString( pFile ) ); + if( ! AquaSalInstance::isOnCommandLine( aFile ) ) + { + aFileList.push_back( aFile ); + } + } + + if( !aFileList.empty() ) + { + // we have no back channel here, we have to assume success, in which case + // replyToOpenOrPrint does not need to be called according to documentation + // [app replyToOpenOrPrint: NSApplicationDelegateReplySuccess]; + const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_OPEN, aFileList); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + } +} + +-(BOOL)application: (NSApplication*)app printFile: (NSString*)pFile +{ + (void)app; + std::vector<OUString> aFile; + aFile.push_back( GetOUString( pFile ) ); + const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_PRINT, aFile); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + 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 an bShowPrintPanels + std::vector<OUString> aFileList; + + NSEnumerator* it = [files objectEnumerator]; + NSString* pFile = nil; + + while( (pFile = [it nextObject]) != nil ) + { + aFileList.push_back( GetOUString( pFile ) ); + } + const ApplicationEvent* pAppEvent = new ApplicationEvent(ApplicationEvent::TYPE_PRINT, aFileList); + AquaSalInstance::aAppEventList.push_back( pAppEvent ); + // we have no back channel here, we have to assume success + // correct handling would be NSPrintingReplyLater and then send [app replyToOpenOrPrint] + return NSPrintingSuccess; +} + +-(NSApplicationTerminateReply)applicationShouldTerminate: (NSApplication *) app +{ + (void)app; + NSApplicationTerminateReply aReply = NSTerminateNow; + { + YIELD_GUARD; + + SalData* pSalData = GetSalData(); + if( ! pSalData->maFrames.empty() ) + { + // the following QueryExit will likely present a message box, activate application + [NSApp activateIgnoringOtherApps: YES]; + aReply = pSalData->maFrames.front()->CallCallback( SALEVENT_SHUTDOWN, NULL ) ? NSTerminateCancel : NSTerminateNow; + } + + if( aReply == NSTerminateNow ) + { + ApplicationEvent aEv(ApplicationEvent::TYPE_PRIVATE_DOSHUTDOWN); + GetpApp()->AppEvent( aEv ); + ImplImageTreeSingletonRef()->shutDown(); + // DeInitVCL should be called in ImplSVMain - unless someon _exits first which + // can occur in Desktop::doShutdown for example + } + } + + return aReply; +} + +-(void)systemColorsChanged: (NSNotification*) pNotification +{ + (void)pNotification; + YIELD_GUARD; + + const SalData* pSalData = GetSalData(); + if( !pSalData->maFrames.empty() ) + pSalData->maFrames.front()->CallCallback( SALEVENT_SETTINGSCHANGED, NULL ); +} + +-(void)screenParametersChanged: (NSNotification*) pNotification +{ + (void)pNotification; + YIELD_GUARD; + + SalData* pSalData = GetSalData(); + std::list< AquaSalFrame* >::iterator it; + for( it = pSalData->maFrames.begin(); it != pSalData->maFrames.end(); ++it ) + { + (*it)->screenParametersChanged(); + } +} + +-(void)scrollbarVariantChanged: (NSNotification*) pNotification +{ + (void)pNotification; + GetSalData()->mpFirstInstance->delayedSettingsChanged( true ); +} + +-(void)scrollbarSettingsChanged: (NSNotification*) pNotification +{ + (void)pNotification; + GetSalData()->mpFirstInstance->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(); + if( pSalData->mpMainController && pSalData->mpMainController->remoteControl) + { + // [remoteControl startListening: self]; + // does crash because the right thing to do is + // [GetSalData()->mpMainController->remoteControl startListening: self]; + // but the instance variable 'remoteControl' is declared protected + // workaround : declare remoteControl instance variable as public in RemoteMainController.m + + [pSalData->mpMainController->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(); + if( pSalData->mpMainController && pSalData->mpMainController->remoteControl) + { + // [remoteControl stopListening: self]; + // does crash because the right thing to do is + // [GetSalData()->mpMainController->remoteControl stopListening: self]; + // but the instance variable 'remoteControl' is declared protected + // workaround : declare remoteControl instance variable as public in RemoteMainController.m + + [pSalData->mpMainController->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: */ |