X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/d7b284e5c62523c9a43e26977672b4ff7c5b8981..6cef0db28018fd2644ee4e38af715872e5242459:/src/mac/carbon/dataobj.cpp diff --git a/src/mac/carbon/dataobj.cpp b/src/mac/carbon/dataobj.cpp index 5de37f5e06..cdec68b05d 100644 --- a/src/mac/carbon/dataobj.cpp +++ b/src/mac/carbon/dataobj.cpp @@ -14,23 +14,23 @@ #if wxUSE_DATAOBJ +#include "wx/dataobj.h" + #ifndef WX_PRECOMP -#include "wx/intl.h" + #include "wx/intl.h" + #include "wx/log.h" + #include "wx/dcmemory.h" + #include "wx/image.h" #endif -#include "wx/log.h" -#include "wx/dataobj.h" -#include "wx/dcmemory.h" #include "wx/mstream.h" -#include "wx/image.h" #include "wx/metafile.h" #include "wx/tokenzr.h" +#include "wx/mac/uma.h" -#include "wx/mac/private.h" - -#ifndef __DARWIN__ -#include +#ifdef __DARWIN__ + #include #endif @@ -46,102 +46,156 @@ wxDataFormat::wxDataFormat() wxDataFormat::wxDataFormat( wxDataFormatId vType ) { + m_format = 0; + m_type = wxDF_INVALID; SetType( vType ); } wxDataFormat::wxDataFormat( const wxChar *zId ) { + m_format = 0; + m_type = wxDF_INVALID; SetId( zId ); } wxDataFormat::wxDataFormat( const wxString& rId ) { + m_format = 0; + m_type = wxDF_INVALID; SetId( rId ); } +wxDataFormat::wxDataFormat(const wxDataFormat& rFormat) +{ + if ( rFormat.m_format ) + m_format = (NativeFormat) CFStringCreateCopy(NULL, (CFStringRef)rFormat.m_format); + else + m_format = 0; + m_type = rFormat.m_type; + m_id = rFormat.m_id; +} + wxDataFormat::wxDataFormat( NativeFormat vFormat ) { + m_format = 0; + m_type = wxDF_INVALID; SetId( vFormat ); } +wxDataFormat::~wxDataFormat() +{ + if ( m_format != 0 ) + { + CFRelease( (CFStringRef) m_format ); + m_format = 0; + } +} + +// in order to be correct for 10.3 we restrict to the available types there +// http://developer.apple.com/qa/qa2005/qa1406.html +// TODO : Use UTCoreTypes.h constants once we support 10.4+ only + +wxDataFormat& wxDataFormat::operator=(const wxDataFormat& rFormat) +{ + if ( m_format != 0 ) + { + CFRelease( (CFStringRef) m_format ); + m_format = 0; + } + if ( rFormat.m_format ) + m_format = (NativeFormat) CFStringCreateCopy(NULL, (CFStringRef)rFormat.m_format); + m_type = rFormat.m_type; + m_id = rFormat.m_id; + return *this; +} + void wxDataFormat::SetType( wxDataFormatId dataType ) { m_type = dataType; + if ( m_format != 0 ) + { + CFRelease( (CFStringRef) m_format ); + m_format = 0; + } switch (m_type) { case wxDF_TEXT: - m_format = kScrapFlavorTypeText; + m_format = (long) CFStringCreateCopy( NULL, CFSTR("public.plain-text") ); break; case wxDF_UNICODETEXT: - m_format = kScrapFlavorTypeUnicode; + m_format = (long) CFStringCreateCopy( NULL, CFSTR("public.utf16-plain-text") ); break; case wxDF_BITMAP: + m_format = (long) CFStringCreateCopy( NULL, CFSTR("public.tiff") ); + break; case wxDF_METAFILE: - m_format = kScrapFlavorTypePicture; + m_format = (long) CFStringCreateCopy( NULL, CFSTR("com.adobe.pdf") ); break; case wxDF_FILENAME: - m_format = kDragFlavorTypeHFS; + m_format = (long) CFStringCreateCopy( NULL, CFSTR("public.file-url") ); break; default: wxFAIL_MSG( wxT("invalid data format") ); - - // NB: this translates to '????' ASCII but it can't be used in the code - // because '??' will get parsed as a trigraph! - m_format = 0x3f3f3f3f; break; } } wxString wxDataFormat::GetId() const { - wxCHECK_MSG( !IsStandard(), wxEmptyString, - wxT("name of predefined format cannot be retrieved") ); - - return m_id; + return wxCFStringRef(wxCFRetain((CFStringRef)m_format)).AsString(); } void wxDataFormat::SetId( NativeFormat format ) { - m_format = format; - - switch (m_format) + if ( m_format != 0 ) + { + CFRelease( (CFStringRef) m_format ); + m_format = 0; + } + m_format = (NativeFormat) CFStringCreateCopy(NULL, (CFStringRef)format); + if ( UTTypeConformsTo( (CFStringRef)format, CFSTR("public.utf16-plain-text") ) ) { - case kScrapFlavorTypeText: - m_type = wxDF_TEXT; - break; - - case kScrapFlavorTypeUnicode: m_type = wxDF_UNICODETEXT; - break; - - case kScrapFlavorTypePicture: + } + else if ( UTTypeConformsTo( (CFStringRef)format, CFSTR("public.plain-text") ) ) + { + m_type = wxDF_TEXT; + } + else if ( UTTypeConformsTo( (CFStringRef)format, CFSTR("public.tiff") ) ) + { m_type = wxDF_BITMAP; - break; - - case kDragFlavorTypeHFS: + } + else if ( UTTypeConformsTo( (CFStringRef)format, CFSTR("com.adobe.pdf") ) ) + { + m_type = wxDF_METAFILE; + } + else if ( UTTypeConformsTo( (CFStringRef)format, CFSTR("public.file-url") ) ) + { m_type = wxDF_FILENAME; - break; - - default: + } + else + { m_type = wxDF_PRIVATE; - char text[5]; - strncpy( text, (char*)&format, 4 ); - text[4] = 0; - m_id = wxString::FromAscii( text ); - break; + m_id = wxCFStringRef( (CFStringRef) CFRetain((CFStringRef) format )).AsString(); } } -void wxDataFormat::SetId( const wxChar* zId ) +void wxDataFormat::SetId( const wxString& zId ) { m_type = wxDF_PRIVATE; m_id = zId; - m_format = 'WXPR'; + if ( m_format != 0 ) + { + CFRelease( (CFStringRef) m_format ); + m_format = 0; + } + // since it is private, no need to conform to anything ... + m_format = (long) wxCFRetain( (CFStringRef) wxCFStringRef(m_id) ); } bool wxDataFormat::operator==(const wxDataFormat& format) const @@ -149,7 +203,7 @@ bool wxDataFormat::operator==(const wxDataFormat& format) const if (IsStandard() || format.IsStandard()) return (format.m_type == m_type); else - return (m_id == format.m_id); + return ( UTTypeConformsTo( (CFStringRef) m_format , (CFStringRef) format.m_format ) ); } //------------------------------------------------------------------------- @@ -171,14 +225,14 @@ bool wxDataObject::IsSupportedFormat( const wxDataFormat& rFormat, Direction vDi } else { - wxDataFormat* pFormats = new wxDataFormat[nFormatCount]; + wxDataFormat *pFormats = new wxDataFormat[nFormatCount]; GetAllFormats( pFormats, vDir ); for (size_t n = 0; n < nFormatCount; n++) { if (pFormats[n] == rFormat) { - found = true; + found = true; break; } } @@ -189,12 +243,307 @@ bool wxDataObject::IsSupportedFormat( const wxDataFormat& rFormat, Direction vDi return found; } +void wxDataObject::AddToPasteboard( void * pb, int itemID ) +{ + PasteboardRef pasteboard = (PasteboardRef) pb; + // get formats from wxDataObjects + wxDataFormat *array = new wxDataFormat[ GetFormatCount() ]; + GetAllFormats( array ); + + for (size_t i = 0; i < GetFormatCount(); i++) + { + wxDataFormat thisFormat = array[ i ]; + + // add four bytes at the end for data objs like text that + // have a datasize = strlen but still need a buffer for the + // string including trailing zero + + size_t datasize = GetDataSize( thisFormat ); + size_t sz = datasize + 4; + void* buf = malloc( sz ); + if ( buf != NULL ) + { + // empty the buffer because in some case GetDataHere does not fill buf + memset( buf, 0, sz ); + if ( GetDataHere( array[ i ], buf ) ) + { + int counter = 1 ; + if ( thisFormat.GetType() == wxDF_FILENAME ) + { + // the data is D-normalized UTF8 strings of filenames delimited with \n + char *fname = strtok((char*) buf,"\n"); + while (fname != NULL) + { + // translate the filepath into a fileurl and put that into the pasteobard + CFStringRef path = CFStringCreateWithBytes(NULL,(UInt8*)fname,strlen(fname),kCFStringEncodingUTF8,false); + CFURLRef url = CFURLCreateWithFileSystemPath(NULL, path , kCFURLPOSIXPathStyle, false); + CFRelease(path); + CFDataRef data = CFURLCreateData(NULL,url,kCFStringEncodingUTF8,true); + CFRelease(url); + PasteboardPutItemFlavor( pasteboard, (PasteboardItemID) counter, + (CFStringRef) thisFormat.GetFormatId() , data, kPasteboardFlavorNoFlags); + CFRelease( data ); + counter++; + fname = strtok (NULL,"\n"); + } + + } + else + { + CFDataRef data = CFDataCreate( kCFAllocatorDefault, (UInt8*)buf, datasize ); + if ( thisFormat.GetType() == wxDF_TEXT ) + PasteboardPutItemFlavor( pasteboard, (PasteboardItemID) itemID, + CFSTR("com.apple.traditional-mac-plain-text") , data, kPasteboardFlavorNoFlags); + else + PasteboardPutItemFlavor( pasteboard, (PasteboardItemID) itemID, + (CFStringRef) thisFormat.GetFormatId() , data, kPasteboardFlavorNoFlags); + CFRelease( data ); + } + } + free( buf ); + } + } + + delete [] array; +} + +bool wxDataObject::IsFormatInPasteboard( void * pb, const wxDataFormat &dataFormat ) +{ + PasteboardRef pasteboard = (PasteboardRef) pb; + bool hasData = false; + OSStatus err = noErr; + ItemCount itemCount; + + // we synchronize here once again, so we don't mind which flags get returned + PasteboardSynchronize( pasteboard ); + + err = PasteboardGetItemCount( pasteboard, &itemCount ); + if ( err == noErr ) + { + for( UInt32 itemIndex = 1; itemIndex <= itemCount && hasData == false ; itemIndex++ ) + { + PasteboardItemID itemID; + CFArrayRef flavorTypeArray; + CFIndex flavorCount; + + err = PasteboardGetItemIdentifier( pasteboard, itemIndex, &itemID ); + if ( err != noErr ) + continue; + + err = PasteboardCopyItemFlavors( pasteboard, itemID, &flavorTypeArray ); + if ( err != noErr ) + continue; + + flavorCount = CFArrayGetCount( flavorTypeArray ); + + for( CFIndex flavorIndex = 0; flavorIndex < flavorCount && hasData == false ; flavorIndex++ ) + { + CFStringRef flavorType; + + flavorType = (CFStringRef)CFArrayGetValueAtIndex( flavorTypeArray, + flavorIndex ); + + wxDataFormat flavorFormat( (wxDataFormat::NativeFormat) flavorType ); + if ( dataFormat == flavorFormat ) + hasData = true; + else if ( dataFormat.GetType() == wxDF_UNICODETEXT && flavorFormat.GetType() == wxDF_TEXT ) + hasData = true; + } + CFRelease (flavorTypeArray); + } + } + + return hasData; +} + +bool wxDataObject::GetFromPasteboard( void * pb ) +{ + PasteboardRef pasteboard = (PasteboardRef) pb; + size_t formatcount = GetFormatCount() + 1; + wxDataFormat *array = new wxDataFormat[ formatcount ]; + array[0] = GetPreferredFormat(); + GetAllFormats( &array[1] ); + ItemCount itemCount = 0; + wxString filenamesPassed; + bool transferred = false; + + // we synchronize here once again, so we don't mind which flags get returned + PasteboardSynchronize( pasteboard ); + + OSStatus err = PasteboardGetItemCount( pasteboard, &itemCount ); + if ( err == noErr ) + { + for (size_t i = 0; !transferred && i < formatcount; i++) + { + // go through the data in our order of preference + wxDataFormat dataFormat = array[ i ]; + + for( UInt32 itemIndex = 1; itemIndex <= itemCount && transferred == false ; itemIndex++ ) + { + PasteboardItemID itemID = 0; + CFArrayRef flavorTypeArray = NULL; + CFIndex flavorCount = 0; + + err = PasteboardGetItemIdentifier( pasteboard, itemIndex, &itemID ); + if ( err != noErr ) + continue; + + err = PasteboardCopyItemFlavors( pasteboard, itemID, &flavorTypeArray ); + if ( err != noErr ) + continue; + + flavorCount = CFArrayGetCount( flavorTypeArray ); + + for( CFIndex flavorIndex = 0; !transferred && flavorIndex < flavorCount ; flavorIndex++ ) + { + CFStringRef flavorType; + CFDataRef flavorData; + CFIndex flavorDataSize; + + flavorType = (CFStringRef)CFArrayGetValueAtIndex( flavorTypeArray, + flavorIndex ); + + wxDataFormat flavorFormat( (wxDataFormat::NativeFormat) flavorType ); + + if ( dataFormat == flavorFormat ) + { + err = PasteboardCopyItemFlavorData( pasteboard, itemID, flavorType , &flavorData ); + if ( err == noErr ) + { + flavorDataSize = CFDataGetLength( flavorData ); + if (dataFormat.GetType() == wxDF_FILENAME ) + { + // revert the translation and decomposition to arrive at a proper utf8 string again + CFURLRef url = CFURLCreateWithBytes( kCFAllocatorDefault, CFDataGetBytePtr( flavorData ), flavorDataSize, kCFStringEncodingUTF8, NULL ); + CFStringRef cfString = CFURLCopyFileSystemPath( url, kCFURLPOSIXPathStyle ); + CFRelease( url ); + CFMutableStringRef cfMutableString = CFStringCreateMutableCopy(NULL, 0, cfString); + CFRelease( cfString ); + CFStringNormalize(cfMutableString,kCFStringNormalizationFormC); + wxString path = wxCFStringRef(cfMutableString).AsString(); + if (!path.empty()) + filenamesPassed += path + wxT("\n"); + } + else + { + // because some data implementation expect trailing a trailing NUL, we add some headroom + void *buf = malloc( flavorDataSize + 4 ); + if ( buf ) + { + memset( buf, 0, flavorDataSize + 4 ); + memcpy( buf, CFDataGetBytePtr( flavorData ), flavorDataSize ); + + if (dataFormat.GetType() == wxDF_TEXT) + wxMacConvertNewlines10To13( (char*) buf ); + SetData( flavorFormat, flavorDataSize, buf ); + transferred = true; + free( buf ); + } + } + CFRelease (flavorData); + } + } + else if ( dataFormat.GetType() == wxDF_UNICODETEXT && flavorFormat.GetType() == wxDF_TEXT ) + { + err = PasteboardCopyItemFlavorData( pasteboard, itemID, flavorType, &flavorData ); + if ( err == noErr ) + { + flavorDataSize = CFDataGetLength( flavorData ); + void *asciibuf = malloc( flavorDataSize + 1 ); + if ( asciibuf ) + { + memset( asciibuf, 0, flavorDataSize + 1 ); + memcpy( asciibuf, CFDataGetBytePtr( flavorData ), flavorDataSize ); + CFRelease (flavorData); + + SetData( wxDF_TEXT, flavorDataSize, asciibuf ); + transferred = true; + free( asciibuf ); + } + else + CFRelease (flavorData); + } + } + } + CFRelease( flavorTypeArray ); + } + if (filenamesPassed.length() > 0) + { + wxCharBuffer buf = filenamesPassed.fn_str(); + SetData( wxDF_FILENAME, strlen( buf ), (const char*)buf ); + transferred = true; + } + } + } + return transferred; +} + +bool wxDataObject::HasDataInPasteboard( void * pb ) +{ + PasteboardRef pasteboard = (PasteboardRef) pb; + size_t formatcount = GetFormatCount() + 1; + wxDataFormat *array = new wxDataFormat[ formatcount ]; + array[0] = GetPreferredFormat(); + GetAllFormats( &array[1] ); + ItemCount itemCount = 0; + bool hasData = false; + + // we synchronize here once again, so we don't mind which flags get returned + PasteboardSynchronize( pasteboard ); + + OSStatus err = PasteboardGetItemCount( pasteboard, &itemCount ); + if ( err == noErr ) + { + for (size_t i = 0; !hasData && i < formatcount; i++) + { + // go through the data in our order of preference + wxDataFormat dataFormat = array[ i ]; + + for( UInt32 itemIndex = 1; itemIndex <= itemCount && hasData == false ; itemIndex++ ) + { + PasteboardItemID itemID = 0; + CFArrayRef flavorTypeArray = NULL; + CFIndex flavorCount = 0; + + err = PasteboardGetItemIdentifier( pasteboard, itemIndex, &itemID ); + if ( err != noErr ) + continue; + + err = PasteboardCopyItemFlavors( pasteboard, itemID, &flavorTypeArray ); + if ( err != noErr ) + continue; + + flavorCount = CFArrayGetCount( flavorTypeArray ); + + for( CFIndex flavorIndex = 0; !hasData && flavorIndex < flavorCount ; flavorIndex++ ) + { + CFStringRef flavorType; + + flavorType = (CFStringRef)CFArrayGetValueAtIndex( flavorTypeArray, + flavorIndex ); + + wxDataFormat flavorFormat( (wxDataFormat::NativeFormat) flavorType ); + + if ( dataFormat == flavorFormat || + dataFormat.GetType() == wxDF_UNICODETEXT && flavorFormat.GetType() == wxDF_TEXT ) + { + hasData = true; + } + } + CFRelease( flavorTypeArray ); + } + } + } + return hasData; +} + // ---------------------------------------------------------------------------- // wxTextDataObject // ---------------------------------------------------------------------------- #if wxUSE_UNICODE -void wxTextDataObject::GetAllFormats( wxDataFormat *formats, wxDataObjectBase::Direction dir ) const +void wxTextDataObject::GetAllFormats(wxDataFormat *formats, + wxDataObjectBase::Direction WXUNUSED(dir)) const { *formats++ = wxDataFormat( wxDF_TEXT ); *formats = wxDataFormat( wxDF_UNICODETEXT ); @@ -205,16 +554,16 @@ void wxTextDataObject::GetAllFormats( wxDataFormat *formats, wxDataObjectBase::D // wxFileDataObject // ---------------------------------------------------------------------------- -void wxFileDataObject::GetFileNames(wxCharBuffer &buf) const +void wxFileDataObject::GetFileNames( wxCharBuffer &buf ) const { wxString filenames; - + for (size_t i = 0; i < m_filenames.GetCount(); i++) { filenames += m_filenames[i]; filenames += wxT('\n'); } - + buf = filenames.fn_str(); } @@ -224,9 +573,11 @@ bool wxFileDataObject::GetDataHere( void *pBuf ) const return false; wxCharBuffer buf; - GetFileNames( buf ); + size_t buffLength; - memcpy( pBuf, (const char*) buf, strlen(buf) + 1 ); + GetFileNames( buf ); + buffLength = strlen( buf ); + memcpy( pBuf, (const char*)buf, buffLength + 1 ); return true; } @@ -234,22 +585,26 @@ bool wxFileDataObject::GetDataHere( void *pBuf ) const size_t wxFileDataObject::GetDataSize() const { wxCharBuffer buf; - GetFileNames( buf ); + size_t buffLength; - return strlen(buf) + 1; + GetFileNames( buf ); + buffLength = strlen( buf ); + // terminating 0 + return buffLength + 1; } -bool wxFileDataObject::SetData( size_t nSize, const void *pBuf ) +bool wxFileDataObject::SetData( size_t WXUNUSED(nSize), const void *pBuf ) { wxString filenames; + #if wxUSE_UNICODE - filenames = wxString( (const char*) pBuf , *wxConvFileName ); + filenames = wxString( (const char*)pBuf, *wxConvFileName ); #else - filenames = wxString( wxConvFileName->cMB2WX( pBuf ) , wxConvLocal ); + filenames = wxString (wxConvLocal.cWC2WX(wxConvFileName->cMB2WC( (const char*)pBuf))); #endif - m_filenames = wxStringTokenize( filenames , wxT("\n") , wxTOKEN_STRTOK ); - + m_filenames = wxStringTokenize( filenames, wxT("\n"), wxTOKEN_STRTOK ); + return true; } @@ -274,8 +629,7 @@ wxBitmapDataObject::wxBitmapDataObject( const wxBitmap& rBitmap ) if (m_bitmap.Ok()) { - m_pictHandle = m_bitmap.GetBitmapData()->GetPictHandle(); - m_pictCreated = false; + SetBitmap( rBitmap ); } } @@ -290,8 +644,24 @@ void wxBitmapDataObject::SetBitmap( const wxBitmap& rBitmap ) wxBitmapDataObjectBase::SetBitmap( rBitmap ); if (m_bitmap.Ok()) { - m_pictHandle = m_bitmap.GetBitmapData()->GetPictHandle(); - m_pictCreated = false; + CGImageRef cgImageRef = (CGImageRef) m_bitmap.CreateCGImage(); + + CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0); + CGImageDestinationRef destination = CGImageDestinationCreateWithData( data , kUTTypeTIFF , 1 , NULL ); + if ( destination ) + { + CGImageDestinationAddImage( destination, cgImageRef, NULL ); + CGImageDestinationFinalize( destination ); + CFRelease( destination ); + } + m_pictHandle = NewHandle(CFDataGetLength(data)); + if ( m_pictHandle ) + { + memcpy( *(Handle)m_pictHandle, (const char *)CFDataGetBytePtr(data), CFDataGetLength(data) ); + } + CFRelease( data ); + + CGImageRelease(cgImageRef); } } @@ -305,11 +675,9 @@ void wxBitmapDataObject::Clear() { if (m_pictHandle != NULL) { - if (m_pictCreated) - KillPicture( (PicHandle)m_pictHandle ); + DisposeHandle( (Handle) m_pictHandle ); m_pictHandle = NULL; } - m_pictCreated = false; } @@ -337,6 +705,17 @@ size_t wxBitmapDataObject::GetDataSize() const return 0; } +Handle MacCreateDataReferenceHandle(Handle theDataHandle) +{ + Handle dataRef = NULL; + OSErr err = noErr; + + // Create a data reference handle for our data. + err = PtrToHand( &theDataHandle, &dataRef, sizeof(Handle)); + + return dataRef; +} + bool wxBitmapDataObject::SetData( size_t nSize, const void *pBuf ) { Clear(); @@ -344,22 +723,29 @@ bool wxBitmapDataObject::SetData( size_t nSize, const void *pBuf ) if ((pBuf == NULL) || (nSize == 0)) return false; - PicHandle picHandle = (PicHandle)NewHandle( nSize ); + Handle picHandle = NewHandle( nSize ); memcpy( *picHandle, pBuf, nSize ); m_pictHandle = picHandle; + CGImageRef cgImageRef = 0; - // ownership is transferred to the bitmap - m_pictCreated = false; - Rect frame; - wxMacGetPictureBounds( picHandle, &frame ); - - wxMetafile mf; - mf.SetHMETAFILE( (WXHMETAFILE)m_pictHandle ); - wxMemoryDC mdc; - m_bitmap.Create( frame.right - frame.left, frame.bottom - frame.top ); - mdc.SelectObject( m_bitmap ); - mf.Play( &mdc ); - mdc.SelectObject( wxNullBitmap ); + CFDataRef data = CFDataCreateWithBytesNoCopy( kCFAllocatorDefault, (const UInt8*) pBuf, nSize, kCFAllocatorNull); + CGImageSourceRef source = CGImageSourceCreateWithData( data, NULL ); + if ( source ) + { + cgImageRef = CGImageSourceCreateImageAtIndex(source, 0, NULL); + } + CFRelease( source ); + CFRelease( data ); + + if ( cgImageRef ) + { + m_bitmap.Create( CGImageGetWidth(cgImageRef) , CGImageGetHeight(cgImageRef) ); + CGRect r = CGRectMake( 0 , 0 , CGImageGetWidth(cgImageRef) , CGImageGetHeight(cgImageRef) ); + // since our context is upside down we dont use CGContextDrawImage + wxMacDrawCGImage( (CGContextRef) m_bitmap.GetHBITMAP() , &r, cgImageRef ) ; + CGImageRelease(cgImageRef); + cgImageRef = NULL; + } return m_bitmap.Ok(); }