diff options
author | Tor Lillqvist <tml@collabora.com> | 2019-05-24 01:51:00 +0300 |
---|---|---|
committer | Tor Lillqvist <tml@collabora.com> | 2019-05-28 12:39:09 +0300 |
commit | b8c1f54d67c5ae90a868f9b33747c43b97c23351 (patch) | |
tree | 37ea8331c239d83d7c919a36a5262e6634ee7dbf /vcl/ios | |
parent | 1c7a2eff17f09feaa2709eaab60855f8e1aa7469 (diff) |
tdf#124752: Add system clipboard interface for iOS
Based on the corresponding macOS code. Work in progress. The image
support ifdeffed out still (because it uses some macOS specific APIs
for which I couldn't right away find the equivalent iOS ones).
I made it much simpler than the macOS code. I dropped the keeping of a
local in-process clipboard completely. Firstly, as far as I see, the
iOS clipboard API (UIPasteboard etc) does not even offer the
possibility to separately offer some formats and actually provide the
data on request. Secondly, we must be prepared anyway that the system
can kill an iOS app at any stage while the user is using some other
app, so we need to make sure everything that is copied goes onto the
system clipboard right away anyway.
I had to disable the copying of HTML to the clipboard as that lead to
a mysterious assertion failure. See comment in
DataFlavorMapper::openOfficeToSystemFlavor(). But RTF seems to work
well, too. I assume RTF is what gets used for cross-application
copy/paste (and cross-device, even, through Apple's Universal
Clipboard thing, where you can copy/paste between your Macs and iOS
devices on the same network).
I am not sure how relevant the various application/x-openoffice-foo
formats are.
Change-Id: I174495e33d86fc3990996c229243c05d6cbfcda7
Diffstat (limited to 'vcl/ios')
-rw-r--r-- | vcl/ios/DataFlavorMapping.cxx | 703 | ||||
-rw-r--r-- | vcl/ios/DataFlavorMapping.hxx | 131 | ||||
-rw-r--r-- | vcl/ios/HtmlFmtFlt.cxx | 172 | ||||
-rw-r--r-- | vcl/ios/HtmlFmtFlt.hxx | 41 | ||||
-rw-r--r-- | vcl/ios/clipboard.cxx | 185 | ||||
-rw-r--r-- | vcl/ios/clipboard.hxx | 111 | ||||
-rw-r--r-- | vcl/ios/iOSTransferable.cxx | 174 | ||||
-rw-r--r-- | vcl/ios/iOSTransferable.hxx | 73 |
8 files changed, 1590 insertions, 0 deletions
diff --git a/vcl/ios/DataFlavorMapping.cxx b/vcl/ios/DataFlavorMapping.cxx new file mode 100644 index 000000000000..b57209fd2621 --- /dev/null +++ b/vcl/ios/DataFlavorMapping.cxx @@ -0,0 +1,703 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include "DataFlavorMapping.hxx" +#include "HtmlFmtFlt.hxx" +#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include <com/sun/star/datatransfer/XMimeContentType.hpp> +#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/processfactory.hxx> + +#include <rtl/ustring.hxx> +#include <sal/log.hxx> +#include <osl/endian.h> + +#include <cassert> +#include <cstring> + +#include <premac.h> +#include <UIKit/UIKit.h> +#include <MobileCoreServices/MobileCoreServices.h> +#include <postmac.h> + +using namespace css::datatransfer; +using namespace css::uno; +using namespace css::lang; +using namespace cppu; + +namespace +{ +bool ImageToPNG(css::uno::Sequence<sal_Int8> const& rImgData, + css::uno::Sequence<sal_Int8>& rPngData) +{ +#if 1 + // Skip this complexity for now. Work in progress. + (void)rImgData; + (void)rPngData; + return false; +#else + NSData* pData = [NSData dataWithBytesNoCopy:const_cast<sal_Int8*>(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:@{}]; + if (!pOut) + return false; + + const size_t nPngSize = [pOut length]; + rPngData.realloc(nPngSize); + [pOut getBytes:rPngData.getArray() length:nPngSize]; + return (nPngSize > 0); +#endif +} + +bool PNGToImage(css::uno::Sequence<sal_Int8> const& rPngData, + css::uno::Sequence<sal_Int8>& rImgData) +{ +#if 1 + (void)rPngData; + (void)rImgData; + return false; +#else + 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:@{}]; + if (!pOut) + return false; + + const size_t nImgSize = [pOut length]; + rImgData.realloc(nImgSize); + [pOut getBytes:rImgData.getArray() length:nImgSize]; + return (nImgSize > 0); +#endif +} + +/* Determine whether or not a DataFlavor is valid. + */ +bool isValidFlavor(const DataFlavor& aFlavor) +{ + size_t len = aFlavor.MimeType.getLength(); + Type dtype = aFlavor.DataType; + return ((len > 0) + && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get()) + || (dtype == cppu::UnoType<OUString>::get()))); +} + +OUString NSStringToOUString(const NSString* cfString) +{ + assert(cfString && "Invalid parameter"); + + const char* utf8Str = [cfString UTF8String]; + unsigned int len = rtl_str_getLength(utf8Str); + + return OUString(utf8Str, len, RTL_TEXTENCODING_UTF8); +} + +NSString* OUStringToNSString(const OUString& ustring) +{ + OString utf8Str = OUStringToOString(ustring, RTL_TEXTENCODING_UTF8); + return [NSString stringWithCString:utf8Str.getStr() encoding:NSUTF8StringEncoding]; +} + +NSString* PBTYPE_PLAINTEXT = (__bridge NSString*)kUTTypePlainText; +NSString* PBTYPE_RTF = (__bridge NSString*)kUTTypeRTF; +NSString* PBTYPE_PNG = (__bridge NSString*)kUTTypePNG; +NSString* PBTYPE_JPEG = (__bridge NSString*)kUTTypeJPEG; +NSString* PBTYPE_HTML = (__bridge NSString*)kUTTypeHTML; +NSString* PBTYPE_PDF = (__bridge NSString*)kUTTypePDF; +NSString* PBTYPE_SESX + = @"application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""; +NSString* PBTYPE_SLSDX = @"application/" + @"x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link " + @"Source Descriptor (XML)\""; +NSString* PBTYPE_LSX + = @"application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\""; +NSString* PBTYPE_EOX = @"application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star " + @"Embedded Object (XML)\""; +NSString* PBTYPE_SVXB + = @"application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\""; +NSString* PBTYPE_GDIMF = @"application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\""; +NSString* PBTYPE_SODX = @"application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star " + @"Object Descriptor (XML)\""; +NSString* PBTYPE_DUMMY_INTERNAL = @"application/x-openoffice-internal"; + +const char* FLAVOR_SESX + = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""; +const char* FLAVOR_SLSDX = "application/" + "x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link " + "Source Descriptor (XML)\""; +const char* FLAVOR_LSX + = "application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\""; +const char* FLAVOR_EOX + = "application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\""; +const char* FLAVOR_SVXB + = "application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\""; +const char* FLAVOR_GDIMF + = "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\""; +const char* FLAVOR_SODX = "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star " + "Object Descriptor (XML)\""; +const char* FLAVOR_DUMMY_INTERNAL = "application/x-openoffice-internal"; + +struct FlavorMap +{ + NSString* SystemFlavor; + const char* OOoFlavor; + const char* HumanPresentableName; + bool DataTypeOUString; // sequence<byte> otherwise +}; + +static const FlavorMap flavorMap[] + = { { PBTYPE_PLAINTEXT, "text/plain;charset=utf-16", "Unicode Text (UTF-16)", true }, + { PBTYPE_RTF, "text/rtf", "Rich Text Format", false }, + { PBTYPE_PNG, "image/png", "Portable Network Graphics", false }, + { PBTYPE_JPEG, "image/jpeg", "Portable Network Graphics", false }, + // Nope, sorry. See comment in openOfficeToSystemFlavor() below. + // { PBTYPE_HTML, "text/html", "Plain HTML", false }, + { PBTYPE_PDF, "application/pdf", "PDF File", false }, + { PBTYPE_SESX, FLAVOR_SESX, "Star Embed Source (XML)", false }, + { PBTYPE_SLSDX, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false }, + { PBTYPE_LSX, FLAVOR_LSX, "Star Link Source (XML)", false }, + { PBTYPE_EOX, FLAVOR_EOX, "Star Embedded Object (XML)", false }, + { PBTYPE_SVXB, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false }, + { PBTYPE_GDIMF, FLAVOR_GDIMF, "GDIMetaFile", false }, + { PBTYPE_SODX, FLAVOR_SODX, "Star Object Descriptor (XML)", false }, + { PBTYPE_DUMMY_INTERNAL, FLAVOR_DUMMY_INTERNAL, "internal data", false } }; + +#define SIZE_FLAVOR_MAP (sizeof(flavorMap) / sizeof(FlavorMap)) + +inline bool isByteSequenceType(const Type& theType) +{ + return (theType == cppu::UnoType<Sequence<sal_Int8>>::get()); +} + +inline bool isOUStringType(const Type& theType) +{ + return (theType == cppu::UnoType<OUString>::get()); +} + +} // unnamed namespace + +/* A base class for other data provider. + */ +class DataProviderBaseImpl : public DataProvider +{ +public: + DataProviderBaseImpl(const Any& data); + DataProviderBaseImpl(id data); + virtual ~DataProviderBaseImpl() override; + +protected: + Any mData; + //NSData* mSystemData; + id mSystemData; +}; + +DataProviderBaseImpl::DataProviderBaseImpl(const Any& data) + : mData(data) + , mSystemData(nil) +{ +} + +DataProviderBaseImpl::DataProviderBaseImpl(id data) + : mSystemData(data) +{ + [mSystemData retain]; +} + +DataProviderBaseImpl::~DataProviderBaseImpl() +{ + if (mSystemData) + { + [mSystemData release]; + } +} + +class UniDataProvider : public DataProviderBaseImpl +{ +public: + UniDataProvider(const Any& data); + UniDataProvider(NSData* data); + + NSData* getSystemData() override; + Any getOOoData() override; +}; + +UniDataProvider::UniDataProvider(const Any& data) + : DataProviderBaseImpl(data) +{ +} + +UniDataProvider::UniDataProvider(NSData* data) + : DataProviderBaseImpl(data) +{ +} + +NSData* UniDataProvider::getSystemData() +{ + OUString ustr; + mData >>= ustr; + + OString strUtf8; + ustr.convertToString(&strUtf8, RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS); + + return [NSData dataWithBytes:strUtf8.getStr() length:strUtf8.getLength()]; +} + +Any UniDataProvider::getOOoData() +{ + Any oOOData; + + if (mSystemData) + { + oOOData <<= OUString(static_cast<const 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); + + NSData* getSystemData() override; + Any getOOoData() override; +}; + +ByteSequenceDataProvider::ByteSequenceDataProvider(const Any& data) + : DataProviderBaseImpl(data) +{ +} + +ByteSequenceDataProvider::ByteSequenceDataProvider(NSData* data) + : DataProviderBaseImpl(data) +{ +} + +NSData* ByteSequenceDataProvider::getSystemData() +{ + Sequence<sal_Int8> rawData; + mData >>= rawData; + + return [NSData dataWithBytes:rawData.getArray() length:rawData.getLength()]; +} + +Any ByteSequenceDataProvider::getOOoData() +{ + Any oOOData; + + if (mSystemData) + { + unsigned int flavorDataLength = [mSystemData length]; + Sequence<sal_Int8> byteSequence; + byteSequence.realloc(flavorDataLength); + memcpy(byteSequence.getArray(), [mSystemData bytes], flavorDataLength); + oOOData <<= byteSequence; + } + else + { + oOOData = mData; + } + + return oOOData; +} + +class HTMLFormatDataProvider : public DataProviderBaseImpl +{ +public: + HTMLFormatDataProvider(NSData* data); + + NSData* getSystemData() override; + Any getOOoData() override; +}; + +HTMLFormatDataProvider::HTMLFormatDataProvider(NSData* data) + : DataProviderBaseImpl(data) +{ +} + +NSData* HTMLFormatDataProvider::getSystemData() +{ + Sequence<sal_Int8> textHtmlData; + mData >>= textHtmlData; + + Sequence<sal_Int8> htmlFormatData = TextHtmlToHTMLFormat(textHtmlData); + + return [NSData dataWithBytes:htmlFormatData.getArray() length:htmlFormatData.getLength()]; +} + +Any HTMLFormatDataProvider::getOOoData() +{ + Any oOOData; + + if (mSystemData) + { + unsigned int flavorDataLength = [mSystemData length]; + Sequence<sal_Int8> unkHtmlData; + + unkHtmlData.realloc(flavorDataLength); + memcpy(unkHtmlData.getArray(), [mSystemData bytes], flavorDataLength); + + Sequence<sal_Int8>* pPlainHtml = &unkHtmlData; + Sequence<sal_Int8> plainHtml; + + if (isHTMLFormat(unkHtmlData)) + { + plainHtml = HTMLFormatToTextHtml(unkHtmlData); + pPlainHtml = &plainHtml; + } + + oOOData <<= *pPlainHtml; + } + else + { + oOOData = mData; + } + + return oOOData; +} + +class PNGDataProvider : public DataProviderBaseImpl +{ +public: + PNGDataProvider(const Any&); + PNGDataProvider(NSData*); + + NSData* getSystemData() override; + Any getOOoData() override; +}; + +PNGDataProvider::PNGDataProvider(const Any& data) + : DataProviderBaseImpl(data) +{ +} + +PNGDataProvider::PNGDataProvider(NSData* data) + : DataProviderBaseImpl(data) +{ +} + +NSData* PNGDataProvider::getSystemData() +{ + Sequence<sal_Int8> pngData; + mData >>= pngData; + + Sequence<sal_Int8> imgData; + NSData* sysData = nullptr; + if (PNGToImage(pngData, imgData)) + sysData = [NSData dataWithBytes:imgData.getArray() length:imgData.getLength()]; + + return sysData; +} + +Any PNGDataProvider::getOOoData() +{ + Any oOOData; + + if (mSystemData) + { + const unsigned int flavorDataLength = [mSystemData length]; + Sequence<sal_Int8> imgData(flavorDataLength); + memcpy(imgData.getArray(), [mSystemData bytes], flavorDataLength); + + Sequence<sal_Int8> pngData; + if (ImageToPNG(imgData, pngData)) + oOOData <<= pngData; + } + else + { + oOOData = mData; + } + + return oOOData; +} + +DataFlavorMapper::DataFlavorMapper() +{ + Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); + mrXMimeCntFactory = MimeContentTypeFactory::create(xContext); +} + +DataFlavorMapper::~DataFlavorMapper() +{ + // release potential NSStrings + for (OfficeOnlyTypes::iterator it = maOfficeOnlyTypes.begin(); it != maOfficeOnlyTypes.end(); + ++it) + { + [it->second release]; + it->second = nil; + } +} + +DataFlavor DataFlavorMapper::systemToOpenOfficeFlavor(const NSString* systemDataFlavor) const +{ + DataFlavor oOOFlavor; + + for (size_t i = 0; i < SIZE_FLAVOR_MAP; i++) + { + if ([systemDataFlavor + caseInsensitiveCompare:const_cast<NSString*>(flavorMap[i].SystemFlavor)] + == NSOrderedSame) + { + oOOFlavor.MimeType = OUString::createFromAscii(flavorMap[i].OOoFlavor); + oOOFlavor.HumanPresentableName + = OUString::createFromAscii(flavorMap[i].HumanPresentableName); + oOOFlavor.DataType = flavorMap[i].DataTypeOUString + ? cppu::UnoType<OUString>::get() + : cppu::UnoType<Sequence<sal_Int8>>::get(); + return oOOFlavor; + } + } // for + + // look if this might be an internal type; if it comes in here it must have + // been through openOfficeToSystemFlavor before, so it should then be in the map + OUString aTryFlavor(NSStringToOUString(systemDataFlavor)); + if (maOfficeOnlyTypes.find(aTryFlavor) != maOfficeOnlyTypes.end()) + { + oOOFlavor.MimeType = aTryFlavor; + oOOFlavor.HumanPresentableName.clear(); + oOOFlavor.DataType = cppu::UnoType<Sequence<sal_Int8>>::get(); + } + + return oOOFlavor; +} + +NSString* DataFlavorMapper::openOfficeToSystemFlavor(const DataFlavor& oOOFlavor, + bool& rbInternal) const +{ + NSString* sysFlavor = nullptr; + rbInternal = false; + + for (size_t i = 0; i < SIZE_FLAVOR_MAP; ++i) + { + if (oOOFlavor.MimeType.startsWith(OUString::createFromAscii(flavorMap[i].OOoFlavor))) + { + sysFlavor = flavorMap[i].SystemFlavor; + } + } + + if (!sysFlavor) + { + // For some reason, if we allow text/html, we get an OSL_ENSURE failure in xmloff that + // apparently is a symptom of something being seriously wrong: + // xmloff/source/transform/OOo2Oasis.cxx:1925: duplicate doc handler + // Because is then followed a bit later by an assertion failure: + // Assertion failed: (!m_pFirst && !m_pLast && "There are still indices registered"), function ~SwIndexReg, file [...]/sw/source/core/bastyp/index.cxx, line 226 + + if (oOOFlavor.MimeType == "text/html") + return nil; + + rbInternal = true; + OfficeOnlyTypes::const_iterator it = maOfficeOnlyTypes.find(oOOFlavor.MimeType); + + if (it == maOfficeOnlyTypes.end()) + sysFlavor = maOfficeOnlyTypes[oOOFlavor.MimeType] + = OUStringToNSString(oOOFlavor.MimeType); + else + sysFlavor = it->second; + } + + return sysFlavor; +} + +NSString* DataFlavorMapper::openOfficeImageToSystemFlavor(UIPasteboard* pPasteboard) +{ + if ([pPasteboard containsPasteboardTypes:@[ PBTYPE_PNG ]]) + return PBTYPE_PNG; + else if ([pPasteboard containsPasteboardTypes:@[ PBTYPE_JPEG ]]) + return PBTYPE_JPEG; + else if ([pPasteboard containsPasteboardTypes:@[ PBTYPE_PDF ]]) + return PBTYPE_PDF; + return @""; +} + +DataProviderPtr_t +DataFlavorMapper::getDataProvider(const NSString* systemFlavor, + Reference<XTransferable> const& rTransferable) const +{ + DataProviderPtr_t dp; + + try + { + DataFlavor oOOFlavor = systemToOpenOfficeFlavor(systemFlavor); + + Any data = rTransferable->getTransferData(oOOFlavor); + + if (isByteSequenceType(data.getValueType())) + { + if ([systemFlavor caseInsensitiveCompare:PBTYPE_PNG] == NSOrderedSame) + { + dp = DataProviderPtr_t(new PNGDataProvider(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, + NSData* systemData) +{ + DataProviderPtr_t dp; + + if ([systemFlavor caseInsensitiveCompare:PBTYPE_PLAINTEXT] == NSOrderedSame) + { + dp = DataProviderPtr_t(new UniDataProvider(systemData)); + } + else if ([systemFlavor caseInsensitiveCompare:PBTYPE_HTML] == NSOrderedSame) + { + dp = DataProviderPtr_t(new HTMLFormatDataProvider(systemData)); + } + else if ([systemFlavor caseInsensitiveCompare:PBTYPE_PNG] == NSOrderedSame) + { + dp = DataProviderPtr_t(new PNGDataProvider(systemData)); + } + else + { + dp = DataProviderPtr_t(new ByteSequenceDataProvider(systemData)); + } + + return dp; +} + +bool DataFlavorMapper::isValidMimeContentType(const OUString& contentType) const +{ + bool result = true; + + try + { + Reference<XMimeContentType> xCntType(mrXMimeCntFactory->createMimeContentType(contentType)); + } + catch (IllegalArgumentException&) + { + result = false; + } + + return result; +} + +NSArray* DataFlavorMapper::flavorSequenceToTypesArray( + const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors) const +{ + const sal_uInt32 nFlavors = flavors.getLength(); + NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:1]; + + bool bNeedDummyInternalFlavor(false); + + for (sal_uInt32 i = 0; i < nFlavors; i++) + { + if (flavors[i].MimeType.startsWith("image/bmp")) + { + [array addObject:PBTYPE_PNG]; + } + else + { + const NSString* str = openOfficeToSystemFlavor(flavors[i], bNeedDummyInternalFlavor); + + if (str != nil) + { + [str retain]; + [array addObject:str]; + } + } + } + + // #i89462# #i90747# + // in case no system flavor was found to report + // report at least one so D&D between OOo targets works + if ([array count] == 0 || bNeedDummyInternalFlavor) + { + [array addObject:PBTYPE_DUMMY_INTERNAL]; + } + + return [array autorelease]; +} + +css::uno::Sequence<css::datatransfer::DataFlavor> +DataFlavorMapper::typesArrayToFlavorSequence(NSArray* types) const +{ + int nFormats = [types count]; + Sequence<DataFlavor> flavors; + + for (int i = 0; i < nFormats; i++) + { + NSString* sysFormat = [types objectAtIndex:i]; + DataFlavor oOOFlavor = systemToOpenOfficeFlavor(sysFormat); + + if (isValidFlavor(oOOFlavor)) + { + flavors.realloc(flavors.getLength() + 1); + flavors[flavors.getLength() - 1] = oOOFlavor; + } + } + + return flavors; +} + +NSArray* DataFlavorMapper::getAllSupportedPboardTypes() +{ + NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:SIZE_FLAVOR_MAP]; + + for (sal_uInt32 i = 0; i < SIZE_FLAVOR_MAP; i++) + { + [array addObject:flavorMap[i].SystemFlavor]; + } + + return [array autorelease]; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/ios/DataFlavorMapping.hxx b/vcl/ios/DataFlavorMapping.hxx new file mode 100644 index 000000000000..9485e2561689 --- /dev/null +++ b/vcl/ios/DataFlavorMapping.hxx @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_IOS_DATAFLAVORMAPPING_HXX +#define INCLUDED_VCL_IOS_DATAFLAVORMAPPING_HXX + +#include <com/sun/star/datatransfer/DataFlavor.hpp> +#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> + +#include <premac.h> +#import <UIKit/UIKit.h> +#include <postmac.h> + +#include <memory> +#include <unordered_map> + +/* An interface to get the clipboard data in either + system or OOo format. + */ +class DataProvider +{ +public: + virtual ~DataProvider(){}; + + /* Get the clipboard data in the system format. + The caller has to retain/release the returned + CFDataRef on demand. + */ + virtual NSData* getSystemData() = 0; + + /* Get the clipboard data in OOo format. + */ + virtual css::uno::Any getOOoData() = 0; +}; + +typedef std::unique_ptr<DataProvider> DataProviderPtr_t; + +class DataFlavorMapper +{ +public: + /* Initialize a DataFavorMapper instance. Throws a RuntimeException in case the XMimeContentTypeFactory service + cannot be created. + */ + DataFlavorMapper(); + ~DataFlavorMapper(); + + /* Map a system data flavor to an OpenOffice data flavor. + Return an empty string if there is not suitable + mapping from a system data flavor to a OpenOffice data + flavor. + */ + css::datatransfer::DataFlavor systemToOpenOfficeFlavor(const NSString* systemDataFlavor) const; + + /* Map an OpenOffice data flavor to a system data flavor. + If there is no suitable mapping available NULL will + be returned. + */ + NSString* openOfficeToSystemFlavor(const css::datatransfer::DataFlavor& oooDataFlavor, + bool& rbInternal) const; + + /* Select the best available image data type + If there is no suitable mapping available NULL will + be returned. + */ + static NSString* openOfficeImageToSystemFlavor(UIPasteboard* pPasteboard); + + /* Get a data provider which is able to provide the data 'rTransferable' offers in a format that can + be put on to the system clipboard. + */ + DataProviderPtr_t getDataProvider( + const NSString* systemFlavor, + const css::uno::Reference<css::datatransfer::XTransferable>& rTransferable) const; + + /* Get a data provider which is able to provide 'systemData' in the OOo expected format. + */ + static DataProviderPtr_t getDataProvider(const NSString* systemFlavor, NSArray* systemData); + + /* Get a data provider which is able to provide 'systemData' in the OOo expected format. + */ + static DataProviderPtr_t getDataProvider(const NSString* systemFlavor, NSData* systemData); + + /* Translate a sequence of DataFlavors into a NSArray of system types. + Only those DataFlavors for which a suitable mapping to a system + type exist will be contained in the returned types array. + */ + NSArray* flavorSequenceToTypesArray( + const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors) const; + + /* Translate a NSArray of system types into a sequence of DataFlavors. + Only those types for which a suitable mapping to a DataFlavor + exist will be contained in the new DataFlavor Sequence. + */ + css::uno::Sequence<css::datatransfer::DataFlavor> + typesArrayToFlavorSequence(NSArray* types) const; + + /* Returns an NSArray containing all pasteboard types supported by OOo + */ + static NSArray* getAllSupportedPboardTypes(); + +private: + /* Determines if the provided Mime content type is valid. + */ + bool isValidMimeContentType(const OUString& contentType) const; + +private: + css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory; + typedef std::unordered_map<OUString, NSString*> OfficeOnlyTypes; + mutable OfficeOnlyTypes maOfficeOnlyTypes; +}; + +#endif // INCLUDED_VCL_IOS_DATAFLAVORMAPPING_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/ios/HtmlFmtFlt.cxx b/vcl/ios/HtmlFmtFlt.cxx new file mode 100644 index 000000000000..2f3c57f98452 --- /dev/null +++ b/vcl/ios/HtmlFmtFlt.cxx @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "HtmlFmtFlt.hxx" + +#include <rtl/string.h> +#include <osl/diagnose.h> + +#include <string> +#include <sstream> +#include <vector> +#include <iomanip> +#include <cassert> + +using namespace com::sun::star::uno; + +// converts the openoffice text/html clipboard format to the HTML Format +// well known under MS Windows +// the MS HTML Format has a header before the real html data + +// Version:1.0 Version number of the clipboard. Starting is 0.9 +// StartHTML: Byte count from the beginning of the clipboard to the start +// of the context, or -1 if no context +// EndHTML: Byte count from the beginning of the clipboard to the end +// of the context, or -1 if no context +// StartFragment: Byte count from the beginning of the clipboard to the +// start of the fragment +// EndFragment: Byte count from the beginning of the clipboard to the +// end of the fragment +// StartSelection: Byte count from the beginning of the clipboard to the +// start of the selection +// EndSelection: Byte count from the beginning of the clipboard to the +// end of the selection + +// StartSelection and EndSelection are optional +// The fragment should be preceded and followed by the HTML comments +// <!--StartFragment--> and <!--EndFragment--> (no space between !-- and the +// text + +namespace +{ +std::string GetHtmlFormatHeader(size_t startHtml, size_t endHtml, size_t startFragment, + size_t endFragment) +{ + std::ostringstream htmlHeader; + htmlHeader << "Version:1.0" << '\r' << '\n'; + htmlHeader << "StartHTML:" << std::setw(10) << std::setfill('0') << std::dec << startHtml + << '\r' << '\n'; + htmlHeader << "EndHTML:" << std::setw(10) << std::setfill('0') << std::dec << endHtml << '\r' + << '\n'; + htmlHeader << "StartFragment:" << std::setw(10) << std::setfill('0') << std::dec + << startFragment << '\r' << '\n'; + htmlHeader << "EndFragment:" << std::setw(10) << std::setfill('0') << std::dec << endFragment + << '\r' << '\n'; + return htmlHeader.str(); +} +} + +// the office always writes the start and end html tag in upper cases and +// without spaces both tags don't allow parameters +const std::string TAG_HTML = std::string("<html>"); +const std::string TAG_END_HTML = std::string("</html>"); + +// The body tag may have parameters so we need to search for the +// closing '>' manually e.g. <BODY param> #92840# +const std::string TAG_BODY = std::string("<body"); +const std::string TAG_END_BODY = std::string("</body"); + +Sequence<sal_Int8> SAL_CALL TextHtmlToHTMLFormat(Sequence<sal_Int8> const& aTextHtml) +{ + OSL_ASSERT(aTextHtml.getLength() > 0); + + if (aTextHtml.getLength() <= 0) + return Sequence<sal_Int8>(); + + // fill the buffer with dummy values to calc the exact length + std::string dummyHtmlHeader = GetHtmlFormatHeader(0, 0, 0, 0); + size_t lHtmlFormatHeader = dummyHtmlHeader.length(); + + std::string textHtml(reinterpret_cast<const 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* const HtmlStartTag = "<html"; + +Sequence<sal_Int8> HTMLFormatToTextHtml(const Sequence<sal_Int8>& aHTMLFormat) +{ + assert(isHTMLFormat(aHTMLFormat) && "No HTML Format provided"); + + Sequence<sal_Int8>& nonconstHTMLFormatRef = const_cast<Sequence<sal_Int8>&>(aHTMLFormat); + sal_Char* dataStart = reinterpret_cast<sal_Char*>(nonconstHTMLFormatRef.getArray()); + sal_Char* dataEnd = dataStart + nonconstHTMLFormatRef.getLength() - 1; + const sal_Char* htmlStartTag = strcasestr(dataStart, HtmlStartTag); + + assert(htmlStartTag && "Seems to be no HTML at all"); + + // It doesn't seem to be HTML? Well then simply return what has been + // provided in non-debug builds + if (htmlStartTag == nullptr) + { + return aHTMLFormat; + } + + sal_Int32 len = dataEnd - htmlStartTag; + Sequence<sal_Int8> plainHtmlData(len); + + memcpy(static_cast<void*>(plainHtmlData.getArray()), htmlStartTag, len); + + return plainHtmlData; +} + +/* A simple format detection. We are just comparing the first few bytes + of the provided byte sequence to see whether or not it is the MS + Office Html format. If it shows that this is not reliable enough we + can improve this +*/ +const char HtmlFormatStart[] = "Version:"; +int const HtmlFormatStartLen = (sizeof(HtmlFormatStart) - 1); + +bool isHTMLFormat(const Sequence<sal_Int8>& aHtmlSequence) +{ + if (aHtmlSequence.getLength() < HtmlFormatStartLen) + return false; + + return rtl_str_compareIgnoreAsciiCase_WithLength( + HtmlFormatStart, HtmlFormatStartLen, + reinterpret_cast<const sal_Char*>(aHtmlSequence.getConstArray()), HtmlFormatStartLen) + == 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/ios/HtmlFmtFlt.hxx b/vcl/ios/HtmlFmtFlt.hxx new file mode 100644 index 000000000000..b11b19857f2c --- /dev/null +++ b/vcl/ios/HtmlFmtFlt.hxx @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_OSX_HTMLFMTFLT_HXX +#define INCLUDED_VCL_OSX_HTMLFMTFLT_HXX + +#include <com/sun/star/uno/Sequence.hxx> + +/* Transform plain HTML into the format expected by MS Office. + */ +css::uno::Sequence<sal_Int8> TextHtmlToHTMLFormat(css::uno::Sequence<sal_Int8> const& aTextHtml); + +/* Transform the MS Office HTML format into plain HTML. + */ +css::uno::Sequence<sal_Int8> HTMLFormatToTextHtml(const css::uno::Sequence<sal_Int8>& aHTMLFormat); + +/* Detects whether the given byte sequence contains the MS Office Html format. + + @returns True if the MS Office Html format will be detected False otherwise. + */ +bool isHTMLFormat(const css::uno::Sequence<sal_Int8>& aHtmlSequence); + +#endif // INCLUDED_VCL_OSX_HTMLFMTFLT_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/ios/clipboard.cxx b/vcl/ios/clipboard.cxx new file mode 100644 index 000000000000..b60cda1bef4d --- /dev/null +++ b/vcl/ios/clipboard.cxx @@ -0,0 +1,185 @@ +/* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include "ios/iosinst.hxx" + +#include "clipboard.hxx" + +#include "DataFlavorMapping.hxx" +#include "iOSTransferable.hxx" +#include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <comphelper/processfactory.hxx> +#include <cppuhelper/supportsservice.hxx> + +iOSClipboard::iOSClipboard() + : WeakComponentImplHelper<XSystemClipboard, XServiceInfo>(m_aMutex) +{ + auto xContext = comphelper::getProcessComponentContext(); + + mrXMimeCntFactory = css::datatransfer::MimeContentTypeFactory::create(xContext); + + mpDataFlavorMapper.reset(new DataFlavorMapper()); + + mPasteboard = [UIPasteboard generalPasteboard]; + assert(mPasteboard != nil); +} + +iOSClipboard::~iOSClipboard() {} + +css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL iOSClipboard::getContents() +{ + osl::MutexGuard aGuard(m_aMutex); + + return css::uno::Reference<css::datatransfer::XTransferable>( + new iOSTransferable(mrXMimeCntFactory, mpDataFlavorMapper, mPasteboard)); +} + +void SAL_CALL iOSClipboard::setContents( + const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable, + const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& /*xClipboardOwner*/) +{ + NSArray* types = xTransferable.is() ? mpDataFlavorMapper->flavorSequenceToTypesArray( + xTransferable->getTransferDataFlavors()) + : [NSArray array]; + + osl::ClearableMutexGuard aGuard(m_aMutex); + + NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:1]; + NSArray* array = @[ dict ]; + + for (sal_uInt32 i = 0; i < [types count]; ++i) + { + DataProviderPtr_t dp = mpDataFlavorMapper->getDataProvider(types[i], xTransferable); + + if (dp.get() != nullptr) + { + NSData* pBoardData = (NSData*)dp->getSystemData(); + dict[types[i]] = pBoardData; + } + } + [mPasteboard setItems:array options:@{}]; + + // We don't keep a copy of the clipboard contents around in-process, so fire the lost clipboard + // ownership event right away. + // fireLostClipboardOwnershipEvent(xClipboardOwner, xTransferable); + + // fireClipboardChangedEvent(xTransferable); +} + +OUString SAL_CALL iOSClipboard::getName() { return OUString(); } + +sal_Int8 SAL_CALL iOSClipboard::getRenderingCapabilities() { return 0; } + +void SAL_CALL iOSClipboard::addClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) +{ + osl::MutexGuard aGuard(m_aMutex); + + if (!listener.is()) + throw css::lang::IllegalArgumentException( + "empty reference", static_cast<css::datatransfer::clipboard::XClipboardEx*>(this), 1); + + mClipboardListeners.push_back(listener); +} + +void SAL_CALL iOSClipboard::removeClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) +{ + osl::MutexGuard aGuard(m_aMutex); + + if (!listener.is()) + throw css::lang::IllegalArgumentException( + "empty reference", static_cast<css::datatransfer::clipboard::XClipboardEx*>(this), 1); + + mClipboardListeners.remove(listener); +} + +void iOSClipboard::fireClipboardChangedEvent( + css::uno::Reference<css::datatransfer::XTransferable> xNewContents) +{ + osl::ClearableMutexGuard aGuard(m_aMutex); + + std::list<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> listeners( + mClipboardListeners); + css::datatransfer::clipboard::ClipboardEvent aEvent; + + if (!listeners.empty()) + { + aEvent = css::datatransfer::clipboard::ClipboardEvent(static_cast<OWeakObject*>(this), + xNewContents); + } + + aGuard.clear(); + + while (!listeners.empty()) + { + if (listeners.front().is()) + { + try + { + listeners.front()->changedContents(aEvent); + } + catch (const css::uno::RuntimeException&) + { + } + } + listeners.pop_front(); + } +} + +void iOSClipboard::fireLostClipboardOwnershipEvent( + css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const& oldOwner, + css::uno::Reference<css::datatransfer::XTransferable> const& oldContent) +{ + assert(oldOwner.is()); + + try + { + oldOwner->lostOwnership(static_cast<css::datatransfer::clipboard::XClipboardEx*>(this), + oldContent); + } + catch (const css::uno::RuntimeException&) + { + } +} + +OUString SAL_CALL iOSClipboard::getImplementationName() +{ + return OUString("com.sun.star.datatransfer.clipboard.iOSClipboard"); +} + +sal_Bool SAL_CALL iOSClipboard::supportsService(const OUString& ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> SAL_CALL iOSClipboard::getSupportedServiceNames() +{ + return { OUString("com.sun.star.datatransfer.clipboard.SystemClipboard") }; +} + +css::uno::Reference<css::uno::XInterface> +IosSalInstance::CreateClipboard(const css::uno::Sequence<css::uno::Any>&) +{ + return css::uno::Reference<css::uno::XInterface>( + static_cast<cppu::OWeakObject*>(new iOSClipboard())); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/ios/clipboard.hxx b/vcl/ios/clipboard.hxx new file mode 100644 index 000000000000..144e9c3acdee --- /dev/null +++ b/vcl/ios/clipboard.hxx @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_IOS_CLIPBOARD_HXX +#define INCLUDED_VCL_IOS_CLIPBOARD_HXX + +#include "DataFlavorMapping.hxx" +#include <rtl/ustring.hxx> +#include <sal/types.h> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardEx.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardOwner.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp> +#include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp> +#include <com/sun/star/datatransfer/clipboard/XSystemClipboard.hpp> +#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <cppuhelper/basemutex.hxx> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> + +#include <list> + +#include <premac.h> +#import <UIKit/UIKit.h> +#include <postmac.h> + +class iOSClipboard + : public ::cppu::BaseMutex, + public ::cppu::WeakComponentImplHelper<css::datatransfer::clipboard::XSystemClipboard, + css::lang::XServiceInfo> +{ +public: + iOSClipboard(); + + virtual ~iOSClipboard() override; + iOSClipboard(const iOSClipboard&) = delete; + iOSClipboard& operator=(const iOSClipboard&) = delete; + + // XClipboard + + css::uno::Reference<css::datatransfer::XTransferable> SAL_CALL getContents() override; + + void SAL_CALL setContents( + const css::uno::Reference<css::datatransfer::XTransferable>& xTransferable, + const css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner>& xClipboardOwner) + override; + + OUString SAL_CALL getName() override; + + // XClipboardEx + + sal_Int8 SAL_CALL getRenderingCapabilities() override; + + // XClipboardNotifier + + void SAL_CALL addClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) + override; + + void SAL_CALL removeClipboardListener( + const css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>& listener) + override; + + // XServiceInfo + + OUString SAL_CALL getImplementationName() override; + + sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + +private: + /* Notify the current clipboard owner that he is no longer the clipboard owner. */ + void fireLostClipboardOwnershipEvent( + css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> const& oldOwner, + css::uno::Reference<css::datatransfer::XTransferable> const& oldContent); + + /* Notify all registered XClipboardListener that the clipboard content has changed. */ + void + fireClipboardChangedEvent(css::uno::Reference<css::datatransfer::XTransferable> xNewContents); + +private: + css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory; + std::list<css::uno::Reference<css::datatransfer::clipboard::XClipboardListener>> + mClipboardListeners; + css::uno::Reference<css::datatransfer::clipboard::XClipboardOwner> mXClipboardOwner; + std::shared_ptr<DataFlavorMapper> mpDataFlavorMapper; + UIPasteboard* mPasteboard; +}; + +#endif // INCLUDED_VCL_IOS_CLIPBOARD_HXX + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/ios/iOSTransferable.cxx b/vcl/ios/iOSTransferable.cxx new file mode 100644 index 000000000000..b1bc4043ea9e --- /dev/null +++ b/vcl/ios/iOSTransferable.cxx @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <sal/types.h> +#include <osl/diagnose.h> + +#include "iOSTransferable.hxx" + +#include "DataFlavorMapping.hxx" + +using namespace std; +using namespace osl; +using namespace cppu; +using namespace com::sun::star::uno; +using namespace com::sun::star::datatransfer; +using namespace com::sun::star::lang; + +namespace +{ +bool isValidFlavor(const DataFlavor& aFlavor) +{ + size_t len = aFlavor.MimeType.getLength(); + Type dtype = aFlavor.DataType; + return ((len > 0) + && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get()) + || (dtype == cppu::UnoType<OUString>::get()))); +} + +bool cmpAllContentTypeParameter(const Reference<XMimeContentType>& xLhs, + const Reference<XMimeContentType>& xRhs) +{ + Sequence<OUString> xLhsFlavors = xLhs->getParameters(); + Sequence<OUString> xRhsFlavors = xRhs->getParameters(); + + // Stop here if the number of parameters is different already + if (xLhsFlavors.getLength() != xRhsFlavors.getLength()) + return false; + + try + { + OUString pLhs; + OUString pRhs; + + for (sal_Int32 i = 0; i < xLhsFlavors.getLength(); i++) + { + pLhs = xLhs->getParameterValue(xLhsFlavors[i]); + pRhs = xRhs->getParameterValue(xLhsFlavors[i]); + + if (!pLhs.equalsIgnoreAsciiCase(pRhs)) + { + return false; + } + } + } + catch (IllegalArgumentException&) + { + return false; + } + + return true; +} + +} // unnamed namespace + +iOSTransferable::iOSTransferable(const Reference<XMimeContentTypeFactory>& rXMimeCntFactory, + std::shared_ptr<DataFlavorMapper> pDataFlavorMapper, + UIPasteboard* pasteboard) + : mrXMimeCntFactory(rXMimeCntFactory) + , mDataFlavorMapper(pDataFlavorMapper) + , mPasteboard(pasteboard) +{ + [mPasteboard retain]; + + initClipboardItemList(); +} + +iOSTransferable::~iOSTransferable() { [mPasteboard release]; } + +Any SAL_CALL iOSTransferable::getTransferData(const DataFlavor& aFlavor) +{ + if (!isValidFlavor(aFlavor) || !isDataFlavorSupported(aFlavor)) + { + throw UnsupportedFlavorException("Unsupported data flavor", + static_cast<XTransferable*>(this)); + } + + bool bInternal(false); + NSString* sysFormat = (aFlavor.MimeType.startsWith("image/png")) + ? DataFlavorMapper::openOfficeImageToSystemFlavor(mPasteboard) + : mDataFlavorMapper->openOfficeToSystemFlavor(aFlavor, bInternal); + DataProviderPtr_t dp; + + NSData* sysData = [mPasteboard dataForPasteboardType:sysFormat]; + dp = DataFlavorMapper::getDataProvider(sysFormat, sysData); + + if (dp.get() == nullptr) + { + throw UnsupportedFlavorException("Unsupported data flavor", + static_cast<XTransferable*>(this)); + } + + return dp->getOOoData(); +} + +Sequence<DataFlavor> SAL_CALL iOSTransferable::getTransferDataFlavors() { return mFlavorList; } + +sal_Bool SAL_CALL iOSTransferable::isDataFlavorSupported(const DataFlavor& aFlavor) +{ + for (sal_Int32 i = 0; i < mFlavorList.getLength(); i++) + if (compareDataFlavors(aFlavor, mFlavorList[i])) + return true; + + return false; +} + +void iOSTransferable::initClipboardItemList() +{ + NSArray* pboardFormats = [mPasteboard pasteboardTypes]; + + if (pboardFormats == nullptr) + { + throw RuntimeException("Cannot get clipboard data", static_cast<XTransferable*>(this)); + } + + mFlavorList = mDataFlavorMapper->typesArrayToFlavorSequence(pboardFormats); +} + +/* Compares two DataFlavors. Returns true if both DataFlavor have the same media type + and the number of parameter and all parameter values do match otherwise false + is returned. + */ +bool iOSTransferable::compareDataFlavors(const DataFlavor& lhs, const DataFlavor& rhs) +{ + try + { + Reference<XMimeContentType> xLhs(mrXMimeCntFactory->createMimeContentType(lhs.MimeType)); + Reference<XMimeContentType> xRhs(mrXMimeCntFactory->createMimeContentType(rhs.MimeType)); + + if (!xLhs->getFullMediaType().equalsIgnoreAsciiCase(xRhs->getFullMediaType()) + || !cmpAllContentTypeParameter(xLhs, xRhs)) + { + return false; + } + } + catch (IllegalArgumentException&) + { + OSL_FAIL("Invalid content type detected"); + return false; + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/ios/iOSTransferable.hxx b/vcl/ios/iOSTransferable.hxx new file mode 100644 index 000000000000..5c685dba8949 --- /dev/null +++ b/vcl/ios/iOSTransferable.hxx @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#ifndef INCLUDED_VCL_IOS_IOSTRANSFERABLE_HXX +#define INCLUDED_VCL_IOS_IOSTRANSFERABLE_HXX + +#include <com/sun/star/datatransfer/XTransferable.hpp> +#include <cppuhelper/implbase.hxx> +#include <com/sun/star/datatransfer/XMimeContentTypeFactory.hpp> +#include <com/sun/star/datatransfer/XMimeContentType.hpp> + +#include "DataFlavorMapping.hxx" + +#include <premac.h> +#import <UIKit/UIKit.h> +#include <postmac.h> + +#include <memory> +#include <vector> + +class iOSTransferable : public ::cppu::WeakImplHelper<css::datatransfer::XTransferable> +{ +public: + explicit iOSTransferable( + css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> const& rXMimeCntFactory, + std::shared_ptr<DataFlavorMapper> pDataFlavorMapper, UIPasteboard* pasteboard); + + virtual ~iOSTransferable() override; + iOSTransferable(const iOSTransferable&) = delete; + iOSTransferable& operator=(const iOSTransferable&) = delete; + + // XTransferable + + virtual css::uno::Any SAL_CALL + getTransferData(const css::datatransfer::DataFlavor& aFlavor) override; + + css::uno::Sequence<css::datatransfer::DataFlavor> SAL_CALL getTransferDataFlavors() override; + + sal_Bool SAL_CALL isDataFlavorSupported(const css::datatransfer::DataFlavor& aFlavor) override; + + // Helper functions not part of the XTransferable interface + + void initClipboardItemList(); + + bool compareDataFlavors(const css::datatransfer::DataFlavor& lhs, + const css::datatransfer::DataFlavor& rhs); + +private: + css::uno::Sequence<css::datatransfer::DataFlavor> mFlavorList; + css::uno::Reference<css::datatransfer::XMimeContentTypeFactory> mrXMimeCntFactory; + std::shared_ptr<DataFlavorMapper> mDataFlavorMapper; + UIPasteboard* mPasteboard; +}; + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |