Skip to content

Commit

Permalink
Add Cocoa clipboard handler to replace Carbon/SDL2 handlers for Mac O…
Browse files Browse the repository at this point in the history
…S. (#295)

* Add Cocoa clipboard handler.
* Update Xcode project.
  • Loading branch information
AliceLR committed Apr 3, 2021
1 parent eb04c50 commit 25f90e6
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 18 deletions.
32 changes: 19 additions & 13 deletions arch/xcode/MegaZeux.xcodeproj/project.pbxproj
Expand Up @@ -7,7 +7,8 @@
objects = {

/* Begin PBXBuildFile section */
BF00DC0C2085644A00CD2DD2 /* clipboard_sdl2.c in Sources */ = {isa = PBXBuildFile; fileRef = BF00DC0B2085644A00CD2DD2 /* clipboard_sdl2.c */; };
482C0E8F2617D38E002D9030 /* vio.h in Headers */ = {isa = PBXBuildFile; fileRef = 482C0E8E2617D38E002D9030 /* vio.h */; };
482C0E912617D49D002D9030 /* clipboard_cocoa.m in Sources */ = {isa = PBXBuildFile; fileRef = 482C0E902617D49C002D9030 /* clipboard_cocoa.m */; };
BF10127F1FCCA7C2008EEDB6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF10127E1FCCA7C2008EEDB6 /* Assets.xcassets */; };
BF1012A61FCCA993008EEDB6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF1012A51FCCA993008EEDB6 /* Assets.xcassets */; };
BF1012BA1FCCABB5008EEDB6 /* Vorbis.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF1012B71FCCABB4008EEDB6 /* Vorbis.framework */; };
Expand Down Expand Up @@ -83,7 +84,7 @@
BFD5B0482465AFAE00BC91E9 /* zip_stream.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0332465AFAC00BC91E9 /* zip_stream.h */; };
BFD5B0492465AFAE00BC91E9 /* dir.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0342465AFAC00BC91E9 /* dir.h */; };
BFD5B04A2465AFAE00BC91E9 /* zip_stream.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0352465AFAC00BC91E9 /* zip_stream.c */; };
BFD5B04B2465AFAE00BC91E9 /* vfile.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0362465AFAC00BC91E9 /* vfile.c */; };
BFD5B04B2465AFAE00BC91E9 /* vio.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0362465AFAC00BC91E9 /* vio.c */; };
BFD5B04C2465AFAE00BC91E9 /* path.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0372465AFAC00BC91E9 /* path.c */; };
BFD5B04D2465AFAE00BC91E9 /* zip_deflate.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0382465AFAC00BC91E9 /* zip_deflate.h */; };
BFD5B04E2465AFAE00BC91E9 /* fsafeopen.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0392465AFAC00BC91E9 /* fsafeopen.h */; };
Expand All @@ -92,7 +93,7 @@
BFD5B0512465AFAE00BC91E9 /* zip_implode.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03C2465AFAD00BC91E9 /* zip_implode.h */; };
BFD5B0522465AFAE00BC91E9 /* memfile.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03D2465AFAD00BC91E9 /* memfile.h */; };
BFD5B0532465AFAE00BC91E9 /* zip_shrink.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03E2465AFAD00BC91E9 /* zip_shrink.h */; };
BFD5B0542465AFAE00BC91E9 /* vfile_posix.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03F2465AFAD00BC91E9 /* vfile_posix.h */; };
BFD5B0542465AFAE00BC91E9 /* vio_posix.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B03F2465AFAD00BC91E9 /* vio_posix.h */; };
BFD5B0552465AFAE00BC91E9 /* zip_dict.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0402465AFAD00BC91E9 /* zip_dict.h */; };
BFD5B0562465AFAE00BC91E9 /* dir.c in Sources */ = {isa = PBXBuildFile; fileRef = BFD5B0412465AFAD00BC91E9 /* dir.c */; };
BFD5B0582465AFEB00BC91E9 /* hashtable.h in Headers */ = {isa = PBXBuildFile; fileRef = BFD5B0572465AFEB00BC91E9 /* hashtable.h */; };
Expand Down Expand Up @@ -370,7 +371,8 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
BF00DC0B2085644A00CD2DD2 /* clipboard_sdl2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = clipboard_sdl2.c; path = ../../src/editor/clipboard_sdl2.c; sourceTree = "<group>"; };
482C0E8E2617D38E002D9030 /* vio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vio.h; path = ../../src/io/vio.h; sourceTree = "<group>"; };
482C0E902617D49C002D9030 /* clipboard_cocoa.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = clipboard_cocoa.m; path = ../../src/editor/clipboard_cocoa.m; sourceTree = "<group>"; };
BF1012781FCCA7C2008EEDB6 /* MegaZeux.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MegaZeux.app; sourceTree = BUILT_PRODUCTS_DIR; };
BF10127E1FCCA7C2008EEDB6 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
BF1012831FCCA7C2008EEDB6 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down Expand Up @@ -430,7 +432,7 @@
BF6058D9216B3725001B738C /* settings.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = settings.c; path = ../../src/settings.c; sourceTree = "<group>"; };
BFA1FB062536752500BB429F /* ice_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = ice_load.c; path = ../../contrib/libxmp/src/loaders/ice_load.c; sourceTree = "<group>"; };
BFA52FD0233AC48100A90CB4 /* audio_reality.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = audio_reality.h; path = ../../src/audio/audio_reality.h; sourceTree = "<group>"; };
BFA52FD1233AC48100A90CB4 /* audio_reality.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = audio_reality.cpp; path = ../../src/audio/audio_reality.cpp; sourceTree = "<group>"; };
BFA52FD1233AC48100A90CB4 /* audio_reality.cpp */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.cpp; fileEncoding = 4; name = audio_reality.cpp; path = ../../src/audio/audio_reality.cpp; sourceTree = "<group>"; };
BFC0B26520681A9000D28296 /* hmn_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = hmn_load.c; path = ../../contrib/libxmp/src/loaders/hmn_load.c; sourceTree = "<group>"; };
BFC0B26620681A9000D28296 /* flt_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = flt_load.c; path = ../../contrib/libxmp/src/loaders/flt_load.c; sourceTree = "<group>"; };
BFC0B26720681A9000D28296 /* st_load.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = st_load.c; path = ../../contrib/libxmp/src/loaders/st_load.c; sourceTree = "<group>"; };
Expand Down Expand Up @@ -460,7 +462,7 @@
BFD5B0332465AFAC00BC91E9 /* zip_stream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_stream.h; path = ../../src/io/zip_stream.h; sourceTree = "<group>"; };
BFD5B0342465AFAC00BC91E9 /* dir.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = dir.h; path = ../../src/io/dir.h; sourceTree = "<group>"; };
BFD5B0352465AFAC00BC91E9 /* zip_stream.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_stream.c; path = ../../src/io/zip_stream.c; sourceTree = "<group>"; };
BFD5B0362465AFAC00BC91E9 /* vfile.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vfile.c; path = ../../src/io/vfile.c; sourceTree = "<group>"; };
BFD5B0362465AFAC00BC91E9 /* vio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vio.c; path = ../../src/io/vio.c; sourceTree = "<group>"; };
BFD5B0372465AFAC00BC91E9 /* path.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = path.c; path = ../../src/io/path.c; sourceTree = "<group>"; };
BFD5B0382465AFAC00BC91E9 /* zip_deflate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_deflate.h; path = ../../src/io/zip_deflate.h; sourceTree = "<group>"; };
BFD5B0392465AFAC00BC91E9 /* fsafeopen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = fsafeopen.h; path = ../../src/io/fsafeopen.h; sourceTree = "<group>"; };
Expand All @@ -469,7 +471,7 @@
BFD5B03C2465AFAD00BC91E9 /* zip_implode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_implode.h; path = ../../src/io/zip_implode.h; sourceTree = "<group>"; };
BFD5B03D2465AFAD00BC91E9 /* memfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = memfile.h; path = ../../src/io/memfile.h; sourceTree = "<group>"; };
BFD5B03E2465AFAD00BC91E9 /* zip_shrink.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_shrink.h; path = ../../src/io/zip_shrink.h; sourceTree = "<group>"; };
BFD5B03F2465AFAD00BC91E9 /* vfile_posix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vfile_posix.h; path = ../../src/io/vfile_posix.h; sourceTree = "<group>"; };
BFD5B03F2465AFAD00BC91E9 /* vio_posix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vio_posix.h; path = ../../src/io/vio_posix.h; sourceTree = "<group>"; };
BFD5B0402465AFAD00BC91E9 /* zip_dict.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip_dict.h; path = ../../src/io/zip_dict.h; sourceTree = "<group>"; };
BFD5B0412465AFAD00BC91E9 /* dir.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = dir.c; path = ../../src/io/dir.c; sourceTree = "<group>"; };
BFD5B0572465AFEB00BC91E9 /* hashtable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = hashtable.h; path = ../../src/hashtable.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -816,8 +818,9 @@
BFD5B03D2465AFAD00BC91E9 /* memfile.h */,
BFD5B0372465AFAC00BC91E9 /* path.c */,
BFD5B03A2465AFAD00BC91E9 /* path.h */,
BFD5B03F2465AFAD00BC91E9 /* vfile_posix.h */,
BFD5B0362465AFAC00BC91E9 /* vfile.c */,
BFD5B03F2465AFAD00BC91E9 /* vio_posix.h */,
BFD5B0362465AFAC00BC91E9 /* vio.c */,
482C0E8E2617D38E002D9030 /* vio.h */,
BFD5B0322465AFAC00BC91E9 /* vfile.h */,
BFD5B0382465AFAC00BC91E9 /* zip_deflate.h */,
BFD5B02D2465AFAB00BC91E9 /* zip_deflate64.h */,
Expand Down Expand Up @@ -1054,7 +1057,7 @@
BFFF18401FCDCF6600BDEC58 /* char_ed.c */,
BFFF18271FCDCF6300BDEC58 /* char_ed.h */,
BFFF18211FCDCF6200BDEC58 /* clipboard.h */,
BF00DC0B2085644A00CD2DD2 /* clipboard_sdl2.c */,
482C0E902617D49C002D9030 /* clipboard_cocoa.m */,
BFFF18241FCDCF6300BDEC58 /* configure.c */,
BFFF18351FCDCF6500BDEC58 /* configure.h */,
BFFF183A1FCDCF6600BDEC58 /* debug.c */,
Expand Down Expand Up @@ -1114,6 +1117,7 @@
BF6058F7216B3725001B738C /* game_player.h in Headers */,
BFFF17CA1FCDCCF300BDEC58 /* hmn_extras.h in Headers */,
BFFF17031FCDC76900BDEC58 /* str.h in Headers */,
482C0E8F2617D38E002D9030 /* vio.h in Headers */,
BFFF17011FCDC76900BDEC58 /* sprite.h in Headers */,
BFD5B04D2465AFAE00BC91E9 /* zip_deflate.h in Headers */,
BFFF16BE1FCDC76900BDEC58 /* block.h in Headers */,
Expand Down Expand Up @@ -1186,7 +1190,7 @@
BFFF16C81FCDC76900BDEC58 /* error.h in Headers */,
BFFF16D11FCDC76900BDEC58 /* game.h in Headers */,
BFFF18111FCDCD1200BDEC58 /* xm.h in Headers */,
BFD5B0542465AFAE00BC91E9 /* vfile_posix.h in Headers */,
BFD5B0542465AFAE00BC91E9 /* vio_posix.h in Headers */,
BFFF17CD1FCDCCF300BDEC58 /* md5.h in Headers */,
BF6058EC216B3725001B738C /* core.h in Headers */,
BFD5B0512465AFAE00BC91E9 /* zip_implode.h in Headers */,
Expand Down Expand Up @@ -1503,7 +1507,7 @@
BFFF16D81FCDC76900BDEC58 /* idput.c in Sources */,
BFFF17C21FCDCCF300BDEC58 /* smix.c in Sources */,
BFA52FD3233AC48100A90CB4 /* audio_reality.cpp in Sources */,
BFD5B04B2465AFAE00BC91E9 /* vfile.c in Sources */,
BFD5B04B2465AFAE00BC91E9 /* vio.c in Sources */,
BFFF18031FCDCD1200BDEC58 /* okt_load.c in Sources */,
BFFF16ED1FCDC76900BDEC58 /* render_glsl.c in Sources */,
BFFF17BF1FCDCCF300BDEC58 /* player.c in Sources */,
Expand Down Expand Up @@ -1558,6 +1562,7 @@
BFFF18661FCDCF7C00BDEC58 /* debug.c in Sources */,
BFFF18701FCDCF7C00BDEC58 /* macro.c in Sources */,
BFFF18721FCDCF7C00BDEC58 /* pal_ed.c in Sources */,
482C0E912617D49D002D9030 /* clipboard_cocoa.m in Sources */,
BFFF18681FCDCF7C00BDEC58 /* edit_di.c in Sources */,
BF6058B5216B3683001B738C /* buffer.c in Sources */,
BFFF186A1FCDCF7C00BDEC58 /* edit.c in Sources */,
Expand All @@ -1568,7 +1573,6 @@
BFFF18781FCDCF7C00BDEC58 /* robo_ed.c in Sources */,
BF6058B2216B3683001B738C /* edit_menu.c in Sources */,
BFFF187C1FCDCF7C00BDEC58 /* select.c in Sources */,
BF00DC0C2085644A00CD2DD2 /* clipboard_sdl2.c in Sources */,
BFFF186C1FCDCF7C00BDEC58 /* fill.c in Sources */,
BFFF187A1FCDCF7C00BDEC58 /* robot.c in Sources */,
BFFF18841FCDCF7C00BDEC58 /* world.c in Sources */,
Expand Down Expand Up @@ -1774,6 +1778,7 @@
NEED_PNG_WRITE_SCREEN,
"DEBUG=1",
LIBXMP_NO_DEPACKERS,
LIBXMP_NO_PROWIZARD,
);
HEADER_SEARCH_PATHS = (
SDL2.framework/Headers,
Expand Down Expand Up @@ -1804,6 +1809,7 @@
GCC_PREPROCESSOR_DEFINITIONS = (
NEED_PNG_WRITE_SCREEN,
LIBXMP_NO_DEPACKERS,
LIBXMP_NO_PROWIZARD,
);
HEADER_SEARCH_PATHS = (
SDL2.framework/Headers,
Expand Down
15 changes: 10 additions & 5 deletions src/editor/Makefile.in
Expand Up @@ -49,16 +49,21 @@ editor_objs := \
ifeq (${PLATFORM},mingw)
editor_objs += ${editor_obj}/clipboard_win32.o

# Mac OS X clipboard handler via Cocoa
else ifeq (${PLATFORM},darwin)
editor_ldflags += -framework Cocoa
#editor_objs += ${editor_obj}/clipboard_carbon.o
editor_objs += ${editor_obj}/clipboard_cocoa.o

${editor_obj}/%.o: ${editor_src}/%.m
$(if ${V},,@echo " OBJC " $<)
${CC} -MD ${core_cflags} ${editor_flags} ${editor_spec} -c $< -o $@

# Use the SDL2 clipboard handler
else ifeq (${BUILD_LIBSDL2},1)
editor_ldflags += ${SDL_LDFLAGS}
editor_objs += ${editor_obj}/clipboard_sdl2.o

# Mac OS X clipboard handler via Carbon
else ifeq (${PLATFORM},darwin)
editor_ldflags += -framework Cocoa
editor_objs += ${editor_obj}/clipboard_carbon.o

# X11 clipboard handler (requires SDL 1.2)
else ifneq (${X11DIR},)
editor_flags += ${X11_CFLAGS}
Expand Down
128 changes: 128 additions & 0 deletions src/editor/clipboard_cocoa.m
@@ -0,0 +1,128 @@
/* MegaZeux
*
* Copyright (C) 2021 Alice Rowan <petrifiedrowan@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include <Foundation/Foundation.h>
#include <AppKit/NSPasteboard.h>

#include <stdlib.h>
#include <string.h>

#include "clipboard.h"

/**
* Native MacOS clipboard handler using AppKit. Unlike the SDL2 clipboard
* handler, this handler treats the input text as being encoded in Mac OS Roman
* (unlike ISO Latin-1, Windows 1292, etc, all 256 codepoints of Mac OS Roman
* will be encoded as valid UTF-8 symbols).
*
* Notes:
* - Literal arrays require LLVM 3.1, so explicitly allocate NSArrays.
* - @autoreleasepool requires LLVM 3.0, so use NSAutoreleasePool directly.
*/

/**
* Send an array of extended ASCII lines to the system clipboard via Cocoa.
* These lines will be re-encoded to UTF-8 using Mac OS Roman codepoints.
*/
void copy_buffer_to_clipboard(char **buffer, int lines, int total_length)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

char *buf = cmalloc(total_length + 1);
char *pos = buf;
for(int i = 0; i < lines; i++)
{
size_t len = strlen(buffer[i]);
memcpy(pos, buffer[i], len);
pos += len;
*(pos++) = '\n';
}
buf[total_length] = '\0';

/* Convert to NSString. NOTE: this function requires 10.3+, haven't
* looked for a compelling way to do this for 10.0.
*/
NSString *string = [[[NSString alloc] initWithBytes:buf length:total_length
encoding:NSMacOSRomanStringEncoding] autorelease];
free(buf);

NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];

#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060

[pasteboard clearContents];
[pasteboard writeObjects:[NSArray arrayWithObject:string]];

#else /* VERSION_MIN < 1060 */

[pasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
[pasteboard setString:string forType:NSStringPboardType];

#endif /* VERSION_MIN < 1060 */

[pool release];
}

/**
* Fetch an extended ASCII buffer from the system clipboard via Cocoa.
* These lines will be re-encoded from UTF-8 using Mac OS Roman codepoints.
*/
char *get_clipboard_buffer(void)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];

#if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 1060

NSArray *for_classes = [NSArray arrayWithObject:[NSString class]];
NSArray *items = [pasteboard readObjectsForClasses:for_classes options:nil];

NSString *string = items && [items count] ? [items objectAtIndex:0] : nil;
if(!string)
goto err;

#else /* VERSION_MIN < 1060 */

NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
if(![pasteboard availableTypeFromArray:types])
goto err;

NSString *string = [pasteboard stringForType:NSStringPboardType];
if(!string)
goto err;

#endif /* VERSION_MIN < 1060 */

NSData *chrdata = [string dataUsingEncoding:NSMacOSRomanStringEncoding
allowLossyConversion:true];
if(!chrdata)
goto err;

size_t buf_len = [chrdata length];
char *buf = cmalloc(buf_len + 1);

[chrdata getBytes:buf length:buf_len];
buf[buf_len] = '\0';
[pool release];
return buf;

err:
[pool release];
return NULL;
}

0 comments on commit 25f90e6

Please sign in to comment.