diff options
Diffstat (limited to 'tk8.6/macosx/tkMacOSXDialog.c')
-rw-r--r-- | tk8.6/macosx/tkMacOSXDialog.c | 2165 |
1 files changed, 2165 insertions, 0 deletions
diff --git a/tk8.6/macosx/tkMacOSXDialog.c b/tk8.6/macosx/tkMacOSXDialog.c new file mode 100644 index 0000000..322519a --- /dev/null +++ b/tk8.6/macosx/tkMacOSXDialog.c @@ -0,0 +1,2165 @@ +/* + * tkMacOSXDialog.c -- + * + * Contains the Mac implementation of the common dialog boxes. + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * Copyright 2001-2009, Apple Inc. + * Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net> + * Copyright (c) 2017 Christian Gollwitzer. + * + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. + */ + +#include "tkMacOSXPrivate.h" +#include "tkFileFilter.h" +#include "tkMacOSXConstants.h" + +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1090 +#define modalOK NSOKButton +#define modalCancel NSCancelButton +#else +#define modalOK NSModalResponseOK +#define modalCancel NSModalResponseCancel +#endif // MAC_OS_X_VERSION_MIN_REQUIRED < 1090 +#define modalOther -1 +#define modalError -2 + +/* + * Vars for filtering in "open file" and "save file" dialogs. + */ + +typedef struct { + bool doFileTypes; /* Show the accessory view which + * displays the filter menu */ + bool preselectFilter; /* A filter was selected by the + * typevariable. */ + bool userHasSelectedFilter; /* The user has changed the filter in + * the accessory view. */ + NSMutableArray *fileTypeNames; /* Array of names, e.g. "Text + * document". */ + NSMutableArray *fileTypeExtensions; /* Array of allowed extensions per + * name, e.g. "txt", "doc". */ + NSMutableArray *fileTypeLabels; /* Displayed string, e.g. "Text + * document (.txt, .doc)". */ + NSMutableArray *fileTypeAllowsAll; /* Boolean if the all pattern (*.*) is + * included. */ + NSMutableArray *allowedExtensions; /* Set of all allowed extensions. */ + bool allowedExtensionsAllowAll; /* Set of all allowed extensions + * includes *.* */ + NSUInteger fileTypeIndex; /* Index of currently selected + * filter. */ +} filepanelFilterInfo; + +static filepanelFilterInfo filterInfo; +static NSOpenPanel *openpanel; +static NSSavePanel *savepanel; + +static const char *const colorOptionStrings[] = { + "-initialcolor", "-parent", "-title", NULL +}; +enum colorOptions { + COLOR_INITIAL, COLOR_PARENT, COLOR_TITLE +}; + +static const char *const openOptionStrings[] = { + "-defaultextension", "-filetypes", "-initialdir", "-initialfile", + "-message", "-multiple", "-parent", "-title", "-typevariable", + "-command", NULL +}; +enum openOptions { + OPEN_DEFAULT, OPEN_FILETYPES, OPEN_INITDIR, OPEN_INITFILE, + OPEN_MESSAGE, OPEN_MULTIPLE, OPEN_PARENT, OPEN_TITLE, + OPEN_TYPEVARIABLE, OPEN_COMMAND, +}; +static const char *const saveOptionStrings[] = { + "-defaultextension", "-filetypes", "-initialdir", "-initialfile", + "-message", "-parent", "-title", "-typevariable", "-command", + "-confirmoverwrite", NULL +}; +enum saveOptions { + SAVE_DEFAULT, SAVE_FILETYPES, SAVE_INITDIR, SAVE_INITFILE, + SAVE_MESSAGE, SAVE_PARENT, SAVE_TITLE, SAVE_TYPEVARIABLE, SAVE_COMMAND, + SAVE_CONFIRMOW +}; +static const char *const chooseOptionStrings[] = { + "-initialdir", "-message", "-mustexist", "-parent", "-title", "-command", + NULL +}; +enum chooseOptions { + CHOOSE_INITDIR, CHOOSE_MESSAGE, CHOOSE_MUSTEXIST, CHOOSE_PARENT, + CHOOSE_TITLE, CHOOSE_COMMAND, +}; +typedef struct { + Tcl_Interp *interp; + Tcl_Obj *cmdObj; + int multiple; +} FilePanelCallbackInfo; + +static const char *const alertOptionStrings[] = { + "-default", "-detail", "-icon", "-message", "-parent", "-title", + "-type", "-command", NULL +}; +enum alertOptions { + ALERT_DEFAULT, ALERT_DETAIL, ALERT_ICON, ALERT_MESSAGE, ALERT_PARENT, + ALERT_TITLE, ALERT_TYPE, ALERT_COMMAND, +}; +typedef struct { + Tcl_Interp *interp; + Tcl_Obj *cmdObj; + int typeIndex; +} AlertCallbackInfo; +static const char *const alertTypeStrings[] = { + "abortretryignore", "ok", "okcancel", "retrycancel", "yesno", + "yesnocancel", NULL +}; +enum alertTypeOptions { + TYPE_ABORTRETRYIGNORE, TYPE_OK, TYPE_OKCANCEL, TYPE_RETRYCANCEL, + TYPE_YESNO, TYPE_YESNOCANCEL +}; +static const char *const alertIconStrings[] = { + "error", "info", "question", "warning", NULL +}; +enum alertIconOptions { + ICON_ERROR, ICON_INFO, ICON_QUESTION, ICON_WARNING +}; +static const char *const alertButtonStrings[] = { + "abort", "retry", "ignore", "ok", "cancel", "no", "yes", NULL +}; + +static const NSString *const alertButtonNames[][3] = { + [TYPE_ABORTRETRYIGNORE] = {@"Abort", @"Retry", @"Ignore"}, + [TYPE_OK] = {@"OK"}, + [TYPE_OKCANCEL] = {@"OK", @"Cancel"}, + [TYPE_RETRYCANCEL] = {@"Retry", @"Cancel"}, + [TYPE_YESNO] = {@"Yes", @"No"}, + [TYPE_YESNOCANCEL] = {@"Yes", @"No", @"Cancel"}, +}; +static const NSAlertStyle alertStyles[] = { + [ICON_ERROR] = NSWarningAlertStyle, + [ICON_INFO] = NSInformationalAlertStyle, + [ICON_QUESTION] = NSWarningAlertStyle, + [ICON_WARNING] = NSCriticalAlertStyle, +}; + +/* + * Need to map from 'alertButtonStrings' and its corresponding integer, index + * to the native button index, which is 1, 2, 3, from right to left. This is + * necessary to do for each separate '-type' of button sets. + */ + +static const short alertButtonIndexAndTypeToNativeButtonIndex[][7] = { + /* abort retry ignore ok cancel yes no */ + [TYPE_ABORTRETRYIGNORE] = {1, 2, 3, 0, 0, 0, 0}, + [TYPE_OK] = {0, 0, 0, 1, 0, 0, 0}, + [TYPE_OKCANCEL] = {0, 0, 0, 1, 2, 0, 0}, + [TYPE_RETRYCANCEL] = {0, 1, 0, 0, 2, 0, 0}, + [TYPE_YESNO] = {0, 0, 0, 0, 0, 2, 1}, + [TYPE_YESNOCANCEL] = {0, 0, 0, 0, 3, 2, 1}, +}; + +/* + * Need also the inverse mapping, from NSAlertFirstButtonReturn etc to the + * descriptive button text string index. + */ + +static const short alertNativeButtonIndexAndTypeToButtonIndex[][3] = { + [TYPE_ABORTRETRYIGNORE] = {0, 1, 2}, + [TYPE_OK] = {3, 0, 0}, + [TYPE_OKCANCEL] = {3, 4, 0}, + [TYPE_RETRYCANCEL] = {1, 4, 0}, + [TYPE_YESNO] = {6, 5, 0}, + [TYPE_YESNOCANCEL] = {6, 5, 4}, +}; + +/* + * Construct a file URL from directory and filename. Either may be nil. If both + * are nil, returns nil. + */ + +static NSURL * +getFileURL( + NSString *directory, + NSString *filename) +{ + NSURL *url = nil; + if (directory) { + url = [NSURL fileURLWithPath:directory isDirectory:YES]; + } + if (filename) { + url = [NSURL URLWithString:filename relativeToURL:url]; + } + return url; +} + +#pragma mark TKApplication(TKDialog) + +@implementation TKApplication(TKDialog) + +- (void) tkFilePanelDidEnd: (NSSavePanel *) panel + returnCode: (NSInteger) returnCode contextInfo: (void *) contextInfo +{ + FilePanelCallbackInfo *callbackInfo = contextInfo; + + if (returnCode == modalOK) { + Tcl_Obj *resultObj; + + if (callbackInfo->multiple) { + resultObj = Tcl_NewListObj(0, NULL); + for (NSURL *url in [(NSOpenPanel*)panel URLs]) { + Tcl_ListObjAppendElement(callbackInfo->interp, resultObj, + Tcl_NewStringObj([[url path] UTF8String], -1)); + } + } else { + resultObj = Tcl_NewStringObj([[[panel URL]path] UTF8String], -1); + } + if (callbackInfo->cmdObj) { + Tcl_Obj **objv, **tmpv; + int objc, result = Tcl_ListObjGetElements(callbackInfo->interp, + callbackInfo->cmdObj, &objc, &objv); + + if (result == TCL_OK && objc) { + tmpv = ckalloc(sizeof(Tcl_Obj *) * (objc + 2)); + memcpy(tmpv, objv, sizeof(Tcl_Obj *) * objc); + tmpv[objc] = resultObj; + TkBackgroundEvalObjv(callbackInfo->interp, objc + 1, tmpv, + TCL_EVAL_GLOBAL); + ckfree(tmpv); + } + } else { + Tcl_SetObjResult(callbackInfo->interp, resultObj); + } + } else if (returnCode == modalCancel) { + Tcl_ResetResult(callbackInfo->interp); + } + if (panel == [NSApp modalWindow]) { + [NSApp stopModalWithCode:returnCode]; + } + if (callbackInfo->cmdObj) { + Tcl_DecrRefCount(callbackInfo->cmdObj); + ckfree(callbackInfo); + } +} + +- (void) tkAlertDidEnd: (NSAlert *) alert returnCode: (NSInteger) returnCode + contextInfo: (void *) contextInfo +{ + AlertCallbackInfo *callbackInfo = contextInfo; + + if (returnCode >= NSAlertFirstButtonReturn) { + Tcl_Obj *resultObj = Tcl_NewStringObj(alertButtonStrings[ + alertNativeButtonIndexAndTypeToButtonIndex[callbackInfo-> + typeIndex][returnCode - NSAlertFirstButtonReturn]], -1); + + if (callbackInfo->cmdObj) { + Tcl_Obj **objv, **tmpv; + int objc, result = Tcl_ListObjGetElements(callbackInfo->interp, + callbackInfo->cmdObj, &objc, &objv); + + if (result == TCL_OK && objc) { + tmpv = ckalloc(sizeof(Tcl_Obj *) * (objc + 2)); + memcpy(tmpv, objv, sizeof(Tcl_Obj *) * objc); + tmpv[objc] = resultObj; + TkBackgroundEvalObjv(callbackInfo->interp, objc + 1, tmpv, + TCL_EVAL_GLOBAL); + ckfree(tmpv); + } + } else { + Tcl_SetObjResult(callbackInfo->interp, resultObj); + } + } + if ([alert window] == [NSApp modalWindow]) { + [NSApp stopModalWithCode:returnCode]; + } + if (callbackInfo->cmdObj) { + Tcl_DecrRefCount(callbackInfo->cmdObj); + ckfree(callbackInfo); + } +} + +- (void)selectFormat:(id)sender { + NSPopUpButton *button = (NSPopUpButton *)sender; + filterInfo.fileTypeIndex = [button indexOfSelectedItem]; + + if ([[filterInfo.fileTypeAllowsAll objectAtIndex:filterInfo.fileTypeIndex] boolValue]) { + [openpanel setAllowsOtherFileTypes:YES]; + + /* + * setAllowsOtherFileTypes might have no effect; it's inherited from + * the NSSavePanel, where it has the effect that it does not append an + * extension. Setting the allowed file types to nil allows selecting + * any file. + */ + + [openpanel setAllowedFileTypes:nil]; + } else { + NSMutableArray *allowedtypes = + [filterInfo.fileTypeExtensions objectAtIndex:filterInfo.fileTypeIndex]; + [openpanel setAllowedFileTypes:allowedtypes]; + [openpanel setAllowsOtherFileTypes:NO]; + } + + filterInfo.userHasSelectedFilter = true; +} + +- (void)saveFormat:(id)sender { + NSPopUpButton *button = (NSPopUpButton *)sender; + filterInfo.fileTypeIndex = [button indexOfSelectedItem]; + + if ([[filterInfo.fileTypeAllowsAll objectAtIndex:filterInfo.fileTypeIndex] boolValue]) { + [savepanel setAllowsOtherFileTypes:YES]; + [savepanel setAllowedFileTypes:nil]; + } else { + NSMutableArray *allowedtypes = + [filterInfo.fileTypeExtensions objectAtIndex:filterInfo.fileTypeIndex]; + [savepanel setAllowedFileTypes:allowedtypes]; + [savepanel setAllowsOtherFileTypes:NO]; + } + + filterInfo.userHasSelectedFilter = true; +} + +@end + +#pragma mark - + +/* + *---------------------------------------------------------------------- + * + * Tk_ChooseColorObjCmd -- + * + * This procedure implements the color dialog box for the Mac platform. + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *---------------------------------------------------------------------- + */ + +int +Tk_ChooseColorObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + int result = TCL_ERROR; + Tk_Window parent, tkwin = clientData; + const char *title = NULL; + int i; + NSColor *color = nil, *initialColor = nil; + NSColorPanel *colorPanel; + NSInteger returnCode, numberOfComponents = 0; + + for (i = 1; i < objc; i += 2) { + int index; + const char *value; + + if (Tcl_GetIndexFromObjStruct(interp, objv[i], colorOptionStrings, + sizeof(char *), "option", TCL_EXACT, &index) != TCL_OK) { + goto end; + } + if (i + 1 == objc) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "value for \"%s\" missing", Tcl_GetString(objv[i]))); + Tcl_SetErrorCode(interp, "TK", "COLORDIALOG", "VALUE", NULL); + goto end; + } + value = Tcl_GetString(objv[i + 1]); + + switch (index) { + case COLOR_INITIAL: { + XColor *colorPtr; + + colorPtr = Tk_GetColor(interp, tkwin, value); + if (colorPtr == NULL) { + goto end; + } + initialColor = TkMacOSXGetNSColor(NULL, colorPtr->pixel); + Tk_FreeColor(colorPtr); + break; + } + case COLOR_PARENT: + parent = Tk_NameToWindow(interp, value, tkwin); + if (parent == NULL) { + goto end; + } + break; + case COLOR_TITLE: + title = value; + break; + } + } + colorPanel = [NSColorPanel sharedColorPanel]; + [colorPanel orderOut:NSApp]; + [colorPanel setContinuous:NO]; + [colorPanel setBecomesKeyOnlyIfNeeded:NO]; + [colorPanel setShowsAlpha: NO]; + [colorPanel _setUseModalAppearance:YES]; + if (title) { + NSString *s = [[NSString alloc] initWithUTF8String:title]; + + [colorPanel setTitle:s]; + [s release]; + } + if (initialColor) { + [colorPanel setColor:initialColor]; + } + returnCode = [NSApp runModalForWindow:colorPanel]; + if (returnCode == modalOK) { + color = [[colorPanel color] colorUsingColorSpace: + [NSColorSpace deviceRGBColorSpace]]; + numberOfComponents = [color numberOfComponents]; + } + if (color && numberOfComponents >= 3 && numberOfComponents <= 4) { + CGFloat components[4]; + char colorstr[8]; + + [color getComponents:components]; + snprintf(colorstr, 8, "#%02x%02x%02x", + (short)(components[0] * 255), + (short)(components[1] * 255), + (short)(components[2] * 255)); + Tcl_SetObjResult(interp, Tcl_NewStringObj(colorstr, 7)); + } else { + Tcl_ResetResult(interp); + } + result = TCL_OK; + +end: + return result; +} + +/* + * Dissect the -filetype nested lists and store the information in the + * filterInfo structure. + */ + +static int +parseFileFilters( + Tcl_Interp *interp, + Tcl_Obj *fileTypesPtr, + Tcl_Obj *typeVariablePtr) +{ + + if (!fileTypesPtr) { + filterInfo.doFileTypes = false; + return TCL_OK; + } + + FileFilterList fl; + + TkInitFileFilters(&fl); + if (TkGetFileFilters(interp, &fl, fileTypesPtr, 0) != TCL_OK) { + TkFreeFileFilters(&fl); + return TCL_ERROR; + } + + filterInfo.doFileTypes = (fl.filters != NULL); + + filterInfo.fileTypeIndex = 0; + filterInfo.fileTypeExtensions = [NSMutableArray array]; + filterInfo.fileTypeNames = [NSMutableArray array]; + filterInfo.fileTypeLabels = [NSMutableArray array]; + filterInfo.fileTypeAllowsAll = [NSMutableArray array]; + + filterInfo.allowedExtensions = [NSMutableArray array]; + filterInfo.allowedExtensionsAllowAll = NO; + + if (filterInfo.doFileTypes) { + for (FileFilter *filterPtr = fl.filters; filterPtr; + filterPtr = filterPtr->next) { + NSString *name = [[NSString alloc] initWithUTF8String: filterPtr->name]; + + [filterInfo.fileTypeNames addObject:name]; + [name release]; + NSMutableArray *clauseextensions = [NSMutableArray array]; + NSMutableArray *displayextensions = [NSMutableArray array]; + bool allowsAll = NO; + + for (FileFilterClause *clausePtr = filterPtr->clauses; clausePtr; + clausePtr = clausePtr->next) { + + for (GlobPattern *globPtr = clausePtr->patterns; globPtr; + globPtr = globPtr->next) { + const char *str = globPtr->pattern; + while (*str && (*str == '*' || *str == '.')) { + str++; + } + if (*str) { + NSString *extension = [[NSString alloc] initWithUTF8String:str]; + if (![filterInfo.allowedExtensions containsObject:extension]) { + [filterInfo.allowedExtensions addObject:extension]; + } + + [clauseextensions addObject:extension]; + [displayextensions addObject:[@"." stringByAppendingString:extension]]; + + [extension release]; + } else { + /* + * It is the all pattern (*, .* or *.*) + */ + + allowsAll = YES; + filterInfo.allowedExtensionsAllowAll = YES; + [displayextensions addObject:@"*"]; + } + } + } + [filterInfo.fileTypeExtensions addObject:clauseextensions]; + [filterInfo.fileTypeAllowsAll addObject:[NSNumber numberWithBool:allowsAll]]; + + NSMutableString *label = [[NSMutableString alloc] initWithString:name]; + [label appendString:@" ("]; + [label appendString:[displayextensions componentsJoinedByString:@", "]]; + [label appendString:@")"]; + [filterInfo.fileTypeLabels addObject:label]; + [label release]; + } + + /* + * Check if the typevariable exists and matches one of the names. + */ + + filterInfo.preselectFilter = false; + filterInfo.userHasSelectedFilter = false; + if (typeVariablePtr) { + /* + * Extract the variable content as a NSString. + */ + + Tcl_Obj *selectedFileTypeObj = Tcl_ObjGetVar2(interp, + typeVariablePtr, NULL, TCL_GLOBAL_ONLY); + + /* + * Check that the typevariable exists. + */ + + if (selectedFileTypeObj != NULL) { + const char *selectedFileType = + Tcl_GetString(selectedFileTypeObj); + NSString *selectedFileTypeStr = + [[NSString alloc] initWithUTF8String:selectedFileType]; + NSUInteger index = + [filterInfo.fileTypeNames indexOfObject:selectedFileTypeStr]; + + if (index != NSNotFound) { + filterInfo.fileTypeIndex = index; + filterInfo.preselectFilter = true; + } + } + } + + } + + TkFreeFileFilters(&fl); + return TCL_OK; +} + +static bool +filterCompatible( + NSString *extension, + int filterIndex) +{ + NSMutableArray *allowedExtensions = + [filterInfo.fileTypeExtensions objectAtIndex: filterIndex]; + + /* + * If this contains the all pattern, accept any extension. + */ + + if ([[filterInfo.fileTypeAllowsAll objectAtIndex:filterIndex] boolValue]) { + return true; + } + + return [allowedExtensions containsObject: extension]; +} + +/* + *---------------------------------------------------------------------- + * + * Tk_GetOpenFileObjCmd -- + * + * This procedure implements the "open file" dialog box for the Mac + * platform. See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See user documentation. + *---------------------------------------------------------------------- + */ + +int +Tk_GetOpenFileObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tk_Window tkwin = clientData; + char *str; + int i, result = TCL_ERROR, haveParentOption = 0; + int index, len, multiple = 0; + Tcl_Obj *cmdObj = NULL, *typeVariablePtr = NULL, *fileTypesPtr = NULL; + FilePanelCallbackInfo callbackInfoStruct; + FilePanelCallbackInfo *callbackInfo = &callbackInfoStruct; + NSString *directory = nil, *filename = nil; + NSString *message = nil, *title = nil; + NSWindow *parent; + openpanel = [NSOpenPanel openPanel]; + NSInteger modalReturnCode = modalError; + BOOL parentIsKey = NO; + + for (i = 1; i < objc; i += 2) { + if (Tcl_GetIndexFromObjStruct(interp, objv[i], openOptionStrings, + sizeof(char *), "option", TCL_EXACT, &index) != TCL_OK) { + goto end; + } + if (i + 1 == objc) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "value for \"%s\" missing", Tcl_GetString(objv[i]))); + Tcl_SetErrorCode(interp, "TK", "FILEDIALOG", "VALUE", NULL); + goto end; + } + switch (index) { + case OPEN_DEFAULT: + break; + case OPEN_FILETYPES: + fileTypesPtr = objv[i + 1]; + break; + case OPEN_INITDIR: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + if (len) { + directory = [[[NSString alloc] initWithUTF8String:str] + autorelease]; + } + break; + case OPEN_INITFILE: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + if (len) { + filename = [[[NSString alloc] initWithUTF8String:str] + autorelease]; + } + break; + case OPEN_MESSAGE: + message = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + break; + case OPEN_MULTIPLE: + if (Tcl_GetBooleanFromObj(interp, objv[i + 1], + &multiple) != TCL_OK) { + goto end; + } + break; + case OPEN_PARENT: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + tkwin = Tk_NameToWindow(interp, str, tkwin); + if (!tkwin) { + goto end; + } + haveParentOption = 1; + break; + case OPEN_TITLE: + title = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + break; + case OPEN_TYPEVARIABLE: + typeVariablePtr = objv[i + 1]; + break; + case OPEN_COMMAND: + cmdObj = objv[i+1]; + break; + } + } + + if (title) { + [openpanel setTitle:title]; + + /* + * From OSX 10.11, the title string is silently ignored in the open + * panel. Prepend the title to the message in this case. NOTE should + * be conditional on OSX version, but -mmacosx-version-min does not + * revert this behaviour + */ + + if (message) { + NSString *fullmessage = + [[NSString alloc] initWithFormat:@"%@\n%@", title, message]; + [message release]; + [title release]; + message = fullmessage; + } else { + message = title; + } + } + + if (message) { + [openpanel setMessage:message]; + [message release]; + } + + [openpanel setAllowsMultipleSelection:multiple]; + + if (parseFileFilters(interp, fileTypesPtr, typeVariablePtr) != TCL_OK) { + goto end; + } + + if (filterInfo.doFileTypes) { + NSView *accessoryView = [[NSView alloc] + initWithFrame:NSMakeRect(0.0, 0.0, 300, 32.0)]; + NSTextField *label = [[NSTextField alloc] + initWithFrame:NSMakeRect(0, 0, 60, 22)]; + + [label setEditable:NO]; + [label setStringValue:@"Filter:"]; + [label setBordered:NO]; + [label setBezeled:NO]; + [label setDrawsBackground:NO]; + + NSPopUpButton *popupButton = [[NSPopUpButton alloc] + initWithFrame:NSMakeRect(50.0, 2, 240, 22.0) pullsDown:NO]; + + [popupButton addItemsWithTitles:filterInfo.fileTypeLabels]; + [popupButton setAction:@selector(selectFormat:)]; + + [accessoryView addSubview:label]; + [accessoryView addSubview:popupButton]; + + if (filterInfo.preselectFilter) { + /* + * A specific filter was selected from the typevariable. Select it + * and open the accessory view. + */ + + [popupButton selectItemAtIndex:filterInfo.fileTypeIndex]; + + /* + * On OSX > 10.11, the options are not visible by default. Ergo + * allow all file types + [openpanel setAllowedFileTypes:filterInfo.fileTypeExtensions[filterInfo.fileTypeIndex]]; + */ + [openpanel setAllowedFileTypes:filterInfo.allowedExtensions]; + } else { + [openpanel setAllowedFileTypes:filterInfo.allowedExtensions]; + } + + if (filterInfo.allowedExtensionsAllowAll) { + [openpanel setAllowsOtherFileTypes:YES]; + } else { + [openpanel setAllowsOtherFileTypes:NO]; + } + + [openpanel setAccessoryView:accessoryView]; + } else { + /* + * No filters are given. Allow picking all files. + */ + + [openpanel setAllowsOtherFileTypes:YES]; + } + + if (cmdObj) { + callbackInfo = ckalloc(sizeof(FilePanelCallbackInfo)); + if (Tcl_IsShared(cmdObj)) { + cmdObj = Tcl_DuplicateObj(cmdObj); + } + Tcl_IncrRefCount(cmdObj); + } + + callbackInfo->cmdObj = cmdObj; + callbackInfo->interp = interp; + callbackInfo->multiple = multiple; + parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window); + if (haveParentOption && parent && ![parent attachedSheet]) { + parentIsKey = [parent isKeyWindow]; + if (directory || filename) { + NSURL *fileURL = getFileURL(directory, filename); + + [openpanel setDirectoryURL:fileURL]; + } + + [openpanel beginSheetModalForWindow:parent + completionHandler:^(NSInteger returnCode) { + [NSApp tkFilePanelDidEnd:openpanel + returnCode:returnCode + contextInfo:callbackInfo ]; + }]; + modalReturnCode = cmdObj ? modalOther : + [NSApp runModalForWindow:openpanel]; + } else { + if (directory || filename) { + NSURL *fileURL = getFileURL(directory, filename); + + [openpanel setDirectoryURL:fileURL]; + } + + modalReturnCode = [openpanel runModal]; + [NSApp tkFilePanelDidEnd:openpanel returnCode:modalReturnCode + contextInfo:callbackInfo]; + } + result = (modalReturnCode != modalError) ? TCL_OK : TCL_ERROR; + if (parentIsKey) { + [parent makeKeyWindow]; + } + + if ((typeVariablePtr && (modalReturnCode == NSOKButton)) + && filterInfo.doFileTypes) { + /* + * The -typevariable must be set to the selected file type, if the + * dialog was not cancelled. + */ + + NSUInteger selectedFilterIndex = filterInfo.fileTypeIndex; + NSString *selectedFilter = NULL; + + if (filterInfo.userHasSelectedFilter) { + selectedFilterIndex = filterInfo.fileTypeIndex; + selectedFilter = [filterInfo.fileTypeNames objectAtIndex:selectedFilterIndex]; + } else { + /* + * Difficult case: the user has not touched the filter settings, + * but we must return something in the typevariable. First check if + * the preselected type is compatible with the selected file, + * otherwise choose the first compatible type from the list, + * finally fall back to the empty string. + */ + + NSURL *selectedFile; + + if (multiple) { + /* + * Use the first file in the case of multiple selection. + * Anyway it is not overly useful here. + */ + selectedFile = [[openpanel URLs] objectAtIndex:0]; + } else { + selectedFile = [openpanel URL]; + } + + NSString *extension = [selectedFile pathExtension]; + + if (filterInfo.preselectFilter && + filterCompatible(extension, filterInfo.fileTypeIndex)) { + selectedFilterIndex = filterInfo.fileTypeIndex; // The preselection from the typevariable + selectedFilter = [filterInfo.fileTypeNames objectAtIndex:selectedFilterIndex]; + } else { + // scan the list + NSUInteger i; + + for (i = 0; i < [filterInfo.fileTypeNames count]; i++) { + if (filterCompatible(extension, i)) { + selectedFilterIndex = i; + break; + } + } + if (i == selectedFilterIndex) { + selectedFilter = [filterInfo.fileTypeNames objectAtIndex:selectedFilterIndex]; + } else { + selectedFilter = @""; + } + } + } + + Tcl_ObjSetVar2(interp, typeVariablePtr, NULL, + Tcl_NewStringObj([selectedFilter UTF8String], -1), + TCL_GLOBAL_ONLY); + } + + end: + return result; +} + +/* + *---------------------------------------------------------------------- + * + * Tk_GetSaveFileObjCmd -- + * + * This procedure implements the "save file" dialog box for the Mac + * platform. See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See user documentation. + * + *---------------------------------------------------------------------- + */ + +int +Tk_GetSaveFileObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tk_Window tkwin = clientData; + char *str; + int i, result = TCL_ERROR, haveParentOption = 0; + int confirmOverwrite = 1; + int index, len; + Tcl_Obj *cmdObj = NULL, *typeVariablePtr = NULL, *fileTypesPtr = NULL; + FilePanelCallbackInfo callbackInfoStruct; + FilePanelCallbackInfo *callbackInfo = &callbackInfoStruct; + NSString *directory = nil, *filename = nil, *defaultType = nil; + NSString *message = nil, *title = nil; + NSWindow *parent; + savepanel = [NSSavePanel savePanel]; + NSInteger modalReturnCode = modalError; + BOOL parentIsKey = NO; + + for (i = 1; i < objc; i += 2) { + if (Tcl_GetIndexFromObjStruct(interp, objv[i], saveOptionStrings, + sizeof(char *), "option", TCL_EXACT, &index) != TCL_OK) { + goto end; + } + if (i + 1 == objc) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "value for \"%s\" missing", Tcl_GetString(objv[i]))); + Tcl_SetErrorCode(interp, "TK", "FILEDIALOG", "VALUE", NULL); + goto end; + } + switch (index) { + case SAVE_DEFAULT: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + while (*str && (*str == '*' || *str == '.')) { + str++; + } + if (*str) { + defaultType = [[[NSString alloc] initWithUTF8String:str] + autorelease]; + } + break; + case SAVE_FILETYPES: + fileTypesPtr = objv[i + 1]; + break; + case SAVE_INITDIR: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + if (len) { + directory = [[[NSString alloc] initWithUTF8String:str] + autorelease]; + } + break; + case SAVE_INITFILE: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + if (len) { + filename = [[[NSString alloc] initWithUTF8String:str] + autorelease]; + [savepanel setNameFieldStringValue:filename]; + } + break; + case SAVE_MESSAGE: + message = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + break; + case SAVE_PARENT: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + tkwin = Tk_NameToWindow(interp, str, tkwin); + if (!tkwin) { + goto end; + } + haveParentOption = 1; + break; + case SAVE_TITLE: + title = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + break; + case SAVE_TYPEVARIABLE: + typeVariablePtr = objv[i + 1]; + break; + case SAVE_COMMAND: + cmdObj = objv[i+1]; + break; + case SAVE_CONFIRMOW: + if (Tcl_GetBooleanFromObj(interp, objv[i + 1], + &confirmOverwrite) != TCL_OK) { + goto end; + } + break; + } + } + + if (title) { + [savepanel setTitle:title]; + + /* + * From OSX 10.11, the title string is silently ignored, if the save + * panel is a sheet. Prepend the title to the message in this case. + * NOTE: should be conditional on OSX version, but -mmacosx-version-min + * does not revert this behaviour. + */ + + if (haveParentOption) { + if (message) { + NSString *fullmessage = + [[NSString alloc] initWithFormat:@"%@\n%@",title,message]; + [message release]; + [title release]; + message = fullmessage; + } else { + message = title; + } + } + } + + if (message) { + [savepanel setMessage:message]; + [message release]; + } + + if (parseFileFilters(interp, fileTypesPtr, typeVariablePtr) != TCL_OK) { + goto end; + } + + if (filterInfo.doFileTypes) { + NSView *accessoryView = [[NSView alloc] + initWithFrame:NSMakeRect(0.0, 0.0, 300, 32.0)]; + NSTextField *label = [[NSTextField alloc] + initWithFrame:NSMakeRect(0, 0, 60, 22)]; + + [label setEditable:NO]; + [label setStringValue:NSLocalizedString(@"Format:", nil)]; + [label setBordered:NO]; + [label setBezeled:NO]; + [label setDrawsBackground:NO]; + + NSPopUpButton *popupButton = [[NSPopUpButton alloc] + initWithFrame:NSMakeRect(50.0, 2, 340, 22.0) pullsDown:NO]; + + [popupButton addItemsWithTitles:filterInfo.fileTypeLabels]; + [popupButton selectItemAtIndex:filterInfo.fileTypeIndex]; + [popupButton setAction:@selector(saveFormat:)]; + + [accessoryView addSubview:label]; + [accessoryView addSubview:popupButton]; + + [savepanel setAccessoryView:accessoryView]; + + [savepanel setAllowedFileTypes:[filterInfo.fileTypeExtensions objectAtIndex:filterInfo.fileTypeIndex]]; + [savepanel setAllowsOtherFileTypes:filterInfo.allowedExtensionsAllowAll]; + } else if (defaultType) { + /* + * If no filetypes are given, defaultextension is an alternative way to + * specify the attached extension. Just propose this extension, but + * don't display an accessory view. + */ + + NSMutableArray *AllowedFileTypes = [NSMutableArray array]; + + [AllowedFileTypes addObject:defaultType]; + [savepanel setAllowedFileTypes:AllowedFileTypes]; + [savepanel setAllowsOtherFileTypes:YES]; + } + + [savepanel setCanSelectHiddenExtension:YES]; + [savepanel setExtensionHidden:NO]; + + if (cmdObj) { + callbackInfo = ckalloc(sizeof(FilePanelCallbackInfo)); + if (Tcl_IsShared(cmdObj)) { + cmdObj = Tcl_DuplicateObj(cmdObj); + } + Tcl_IncrRefCount(cmdObj); + } + callbackInfo->cmdObj = cmdObj; + callbackInfo->interp = interp; + callbackInfo->multiple = 0; + + parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window); + if (haveParentOption && parent && ![parent attachedSheet]) { + parentIsKey = [parent isKeyWindow]; + if (directory) { + [savepanel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]]; + } + + /* + * Check for file name, otherwise set to empty string; crashes with + * uncaught exception if set to nil. + */ + + if (filename) { + [savepanel setNameFieldStringValue:filename]; + } else { + [savepanel setNameFieldStringValue:@""]; + } + [savepanel beginSheetModalForWindow:parent + completionHandler:^(NSInteger returnCode) { + [NSApp tkFilePanelDidEnd:savepanel + returnCode:returnCode + contextInfo:callbackInfo]; + }]; + modalReturnCode = cmdObj ? modalOther : + [NSApp runModalForWindow:savepanel]; + } else { + if (directory) { + [savepanel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]]; + } + + /* + * Check for file name, otherwise set to empty string; crashes with + * uncaught exception if set to nil. + */ + + if (filename) { + [savepanel setNameFieldStringValue:filename]; + } else { + [savepanel setNameFieldStringValue:@""]; + } + modalReturnCode = [savepanel runModal]; + [NSApp tkFilePanelDidEnd:savepanel returnCode:modalReturnCode + contextInfo:callbackInfo]; + } + result = (modalReturnCode != modalError) ? TCL_OK : TCL_ERROR; + if (parentIsKey) { + [parent makeKeyWindow]; + } + + if (typeVariablePtr && (modalReturnCode == NSOKButton) + && filterInfo.doFileTypes) { + /* + * The -typevariable must be set to the selected file type, if the + * dialog was not cancelled. + */ + + NSString *selectedFilter = + [filterInfo.fileTypeNames objectAtIndex:filterInfo.fileTypeIndex]; + Tcl_ObjSetVar2(interp, typeVariablePtr, NULL, + Tcl_NewStringObj([selectedFilter UTF8String], -1), + TCL_GLOBAL_ONLY); + } + + end: + return result; +} + +/* + *---------------------------------------------------------------------- + * + * Tk_ChooseDirectoryObjCmd -- + * + * This procedure implements the "tk_chooseDirectory" dialog box for the + * MacOS X platform. See the user documentation for details on what it + * does. + * + * Results: + * See user documentation. + * + * Side effects: + * A modal dialog window is created. + * + *---------------------------------------------------------------------- + */ + +int +Tk_ChooseDirectoryObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tk_Window tkwin = clientData; + char *str; + int i, result = TCL_ERROR, haveParentOption = 0; + int index, len, mustexist = 0; + Tcl_Obj *cmdObj = NULL; + FilePanelCallbackInfo callbackInfoStruct; + FilePanelCallbackInfo *callbackInfo = &callbackInfoStruct; + NSString *directory = nil; + NSString *message, *title; + NSWindow *parent; + NSOpenPanel *panel = [NSOpenPanel openPanel]; + NSInteger modalReturnCode = modalError; + BOOL parentIsKey = NO; + + for (i = 1; i < objc; i += 2) { + if (Tcl_GetIndexFromObjStruct(interp, objv[i], chooseOptionStrings, + sizeof(char *), "option", TCL_EXACT, &index) != TCL_OK) { + goto end; + } + if (i + 1 == objc) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "value for \"%s\" missing", Tcl_GetString(objv[i]))); + Tcl_SetErrorCode(interp, "TK", "DIRDIALOG", "VALUE", NULL); + goto end; + } + switch (index) { + case CHOOSE_INITDIR: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + if (len) { + directory = [[[NSString alloc] initWithUTF8String:str] + autorelease]; + } + break; + case CHOOSE_MESSAGE: + message = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + [panel setMessage:message]; + [message release]; + break; + case CHOOSE_MUSTEXIST: + if (Tcl_GetBooleanFromObj(interp, objv[i + 1], + &mustexist) != TCL_OK) { + goto end; + } + break; + case CHOOSE_PARENT: + str = Tcl_GetStringFromObj(objv[i + 1], &len); + tkwin = Tk_NameToWindow(interp, str, tkwin); + if (!tkwin) { + goto end; + } + haveParentOption = 1; + break; + case CHOOSE_TITLE: + title = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + [panel setTitle:title]; + [title release]; + break; + case CHOOSE_COMMAND: + cmdObj = objv[i+1]; + break; + } + } + [panel setPrompt:@"Choose"]; + [panel setCanChooseFiles:NO]; + [panel setCanChooseDirectories:YES]; + [panel setCanCreateDirectories:!mustexist]; + if (cmdObj) { + callbackInfo = ckalloc(sizeof(FilePanelCallbackInfo)); + if (Tcl_IsShared(cmdObj)) { + cmdObj = Tcl_DuplicateObj(cmdObj); + } + Tcl_IncrRefCount(cmdObj); + } + callbackInfo->cmdObj = cmdObj; + callbackInfo->interp = interp; + callbackInfo->multiple = 0; + + /* + * Check for directory value, set to root if not specified; otherwise + * crashes with exception because of nil string parameter. + */ + + if (!directory) { + directory = @"/"; + } + parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window); + if (haveParentOption && parent && ![parent attachedSheet]) { + parentIsKey = [parent isKeyWindow]; + [panel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]]; + [panel beginSheetModalForWindow:parent + completionHandler:^(NSInteger returnCode) { + [NSApp tkFilePanelDidEnd:panel + returnCode:returnCode + contextInfo:callbackInfo]; + }]; + modalReturnCode = cmdObj ? modalOther : [NSApp runModalForWindow:panel]; + } else { + [panel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]]; + modalReturnCode = [panel runModal]; + [NSApp tkFilePanelDidEnd:panel returnCode:modalReturnCode + contextInfo:callbackInfo]; + } + result = (modalReturnCode != modalError) ? TCL_OK : TCL_ERROR; + if (parentIsKey) { + [parent makeKeyWindow]; + } + end: + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TkAboutDlg -- + * + * Displays the default Tk About box. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkAboutDlg(void) +{ + NSImage *image; + NSString *path = [NSApp tkFrameworkImagePath: @"Tk.tiff"]; + + if (path) { + image = [[[NSImage alloc] initWithContentsOfFile:path] autorelease]; + } else { + image = [NSApp applicationIconImage]; + } + + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + + [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; + [dateFormatter setDateFormat:@"Y"]; + + NSString *year = [dateFormatter stringFromDate:[NSDate date]]; + + [dateFormatter release]; + + /* + * This replaces the old about dialog with a standard alert that displays + * correctly on 10.14. + */ + + NSString *version = @"Tcl " TCL_PATCH_LEVEL " & Tk " TCL_PATCH_LEVEL; + NSString *url = @"www.tcl-lang.org"; + NSTextView *credits = [[NSTextView alloc] initWithFrame:NSMakeRect(0,0,300,300)]; + NSFont *font = [NSFont systemFontOfSize:[NSFont systemFontSize]]; + NSDictionary *textAttributes = [NSDictionary dictionaryWithObject:font + forKey:NSFontAttributeName]; + + [credits insertText: [[NSAttributedString alloc] + initWithString:[NSString stringWithFormat: @"\n" + "Tcl and Tk are distributed under a modified BSD license: " + "www.tcl.tk/software/tcltk/license.html\n\n" + "%1$C 1987-%2$@ Tcl Core Team and Contributers.\n\n" + "%1$C 2011-%2$@ Kevin Walzer/WordTech Communications LLC.\n\n" + "%1$C 2014-%2$@ Marc Culler.\n\n" + "%1$C 2002-2012 Daniel A. Steffen.\n\n" + "%1$C 2001-2009 Apple Inc.\n\n" + "%1$C 2001-2002 Jim Ingham & Ian Reid\n\n" + "%1$C 1998-2000 Jim Ingham & Ray Johnson\n\n" + "%1$C 1998-2000 Scriptics Inc.\n\n" + "%1$C 1996-1997 Sun Microsystems Inc.", 0xA9, year] + attributes:textAttributes] + replacementRange:NSMakeRange(0,0)]; + [credits setDrawsBackground:NO]; + [credits setEditable:NO]; + + NSAlert *about = [[NSAlert alloc] init]; + + [[about window] setTitle:@"About Tcl & Tk"]; + [about setMessageText: version]; + [about setInformativeText:url]; + about.accessoryView = credits; + [about runModal]; + [about release]; +} + +/* + *---------------------------------------------------------------------- + * + * TkMacOSXStandardAboutPanelObjCmd -- + * + * Implements the ::tk::mac::standardAboutPanel command. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * none + * + *---------------------------------------------------------------------- + */ + +int +TkMacOSXStandardAboutPanelObjCmd( + ClientData clientData, /* Unused. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + if (objc > 1) { + Tcl_WrongNumArgs(interp, 1, objv, NULL); + return TCL_ERROR; + } + TkAboutDlg(); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * Tk_MessageBoxObjCmd -- + * + * Implements the tk_messageBox in native Mac OS X style. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * none + * + *---------------------------------------------------------------------- + */ + +int +Tk_MessageBoxObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tk_Window tkwin = clientData; + char *str; + int i, result = TCL_ERROR, haveParentOption = 0; + int index, typeIndex, iconIndex, indexDefaultOption = 0; + int defaultNativeButtonIndex = 1; /* 1, 2, 3: right to left */ + Tcl_Obj *cmdObj = NULL; + AlertCallbackInfo callbackInfoStruct, *callbackInfo = &callbackInfoStruct; + NSString *message, *title; + NSWindow *parent; + NSArray *buttons; + NSAlert *alert = [NSAlert new]; + NSInteger modalReturnCode = 1; + BOOL parentIsKey = NO; + + iconIndex = ICON_INFO; + typeIndex = TYPE_OK; + for (i = 1; i < objc; i += 2) { + if (Tcl_GetIndexFromObjStruct(interp, objv[i], alertOptionStrings, + sizeof(char *), "option", TCL_EXACT, &index) != TCL_OK) { + goto end; + } + if (i + 1 == objc) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "value for \"%s\" missing", Tcl_GetString(objv[i]))); + Tcl_SetErrorCode(interp, "TK", "MSGBOX", "VALUE", NULL); + goto end; + } + switch (index) { + case ALERT_DEFAULT: + /* + * Need to postpone processing of this option until we are sure to + * know the '-type' as well. + */ + + indexDefaultOption = i; + break; + + case ALERT_DETAIL: + message = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + [alert setInformativeText:message]; + [message release]; + break; + + case ALERT_ICON: + if (Tcl_GetIndexFromObjStruct(interp, objv[i + 1], alertIconStrings, + sizeof(char *), "-icon value", TCL_EXACT, &iconIndex) != TCL_OK) { + goto end; + } + break; + + case ALERT_MESSAGE: + message = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + [alert setMessageText:message]; + [message release]; + break; + + case ALERT_PARENT: + str = Tcl_GetString(objv[i + 1]); + tkwin = Tk_NameToWindow(interp, str, tkwin); + if (!tkwin) { + goto end; + } + haveParentOption = 1; + break; + + case ALERT_TITLE: + title = [[NSString alloc] initWithUTF8String: + Tcl_GetString(objv[i + 1])]; + [[alert window] setTitle:title]; + [title release]; + break; + + case ALERT_TYPE: + if (Tcl_GetIndexFromObjStruct(interp, objv[i + 1], alertTypeStrings, + sizeof(char *), "-type value", TCL_EXACT, &typeIndex) != TCL_OK) { + goto end; + } + break; + case ALERT_COMMAND: + cmdObj = objv[i+1]; + break; + } + } + if (indexDefaultOption) { + /* + * Any '-default' option needs to know the '-type' option, which is + * why we do this here. + */ + + if (Tcl_GetIndexFromObjStruct(interp, objv[indexDefaultOption + 1], + alertButtonStrings, sizeof(char *), "-default value", + TCL_EXACT, &index) != TCL_OK) { + goto end; + } + + /* + * Need to map from "ok" etc. to 1, 2, 3, right to left. + */ + + defaultNativeButtonIndex = + alertButtonIndexAndTypeToNativeButtonIndex[typeIndex][index]; + if (!defaultNativeButtonIndex) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("Illegal default option", -1)); + Tcl_SetErrorCode(interp, "TK", "MSGBOX", "DEFAULT", NULL); + goto end; + } + } + [alert setIcon:[NSApp applicationIconImage]]; + [alert setAlertStyle:alertStyles[iconIndex]]; + i = 0; + while (i < 3 && alertButtonNames[typeIndex][i]) { + [alert addButtonWithTitle:(NSString*) alertButtonNames[typeIndex][i++]]; + } + buttons = [alert buttons]; + for (NSButton *b in buttons) { + NSString *ke = [b keyEquivalent]; + + if (([ke isEqualToString:@"\r"] || [ke isEqualToString:@"\033"]) && + ![b keyEquivalentModifierMask]) { + [b setKeyEquivalent:@""]; + } + } + [[buttons objectAtIndex: [buttons count]-1] setKeyEquivalent: @"\033"]; + [[buttons objectAtIndex: defaultNativeButtonIndex-1] + setKeyEquivalent: @"\r"]; + if (cmdObj) { + callbackInfo = ckalloc(sizeof(AlertCallbackInfo)); + if (Tcl_IsShared(cmdObj)) { + cmdObj = Tcl_DuplicateObj(cmdObj); + } + Tcl_IncrRefCount(cmdObj); + } + callbackInfo->cmdObj = cmdObj; + callbackInfo->interp = interp; + callbackInfo->typeIndex = typeIndex; + parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window); + if (haveParentOption && parent && ![parent attachedSheet]) { + parentIsKey = [parent isKeyWindow]; +#if MAC_OS_X_VERSION_MIN_REQUIRED > 1090 + [alert beginSheetModalForWindow:parent + completionHandler:^(NSModalResponse returnCode) { + [NSApp tkAlertDidEnd:alert + returnCode:returnCode + contextInfo:callbackInfo]; + }]; +#else + [alert beginSheetModalForWindow:parent + modalDelegate:NSApp + didEndSelector:@selector(tkAlertDidEnd:returnCode:contextInfo:) + contextInfo:callbackInfo]; +#endif + modalReturnCode = cmdObj ? 0 : + [NSApp runModalForWindow:[alert window]]; + } else { + modalReturnCode = [alert runModal]; + [NSApp tkAlertDidEnd:alert returnCode:modalReturnCode + contextInfo:callbackInfo]; + } + result = (modalReturnCode >= NSAlertFirstButtonReturn) ? TCL_OK : TCL_ERROR; + end: + [alert release]; + if (parentIsKey) { + [parent makeKeyWindow]; + } + return result; +} + +/* + *---------------------------------------------------------------------- + */ +#pragma mark [tk fontchooser] implementation (TIP 324) +/* + *---------------------------------------------------------------------- + */ + +#include "tkMacOSXEvent.h" +#include "tkMacOSXFont.h" + +typedef struct FontchooserData { + Tcl_Obj *titleObj; + Tcl_Obj *cmdObj; + Tk_Window parent; +} FontchooserData; + +enum FontchooserEvent { + FontchooserClosed, + FontchooserSelection +}; + +static void FontchooserEvent(int kind); +static Tcl_Obj * FontchooserCget(FontchooserData *fcdPtr, + int optionIndex); +static int FontchooserConfigureCmd(ClientData clientData, + Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[]); +static int FontchooserShowCmd(ClientData clientData, + Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[]); +static int FontchooserHideCmd(ClientData clientData, + Tcl_Interp *interp, int objc, + Tcl_Obj *const objv[]); +static void FontchooserParentEventHandler(ClientData clientData, + XEvent *eventPtr); +static void DeleteFontchooserData(ClientData clientData, + Tcl_Interp *interp); + +MODULE_SCOPE const TkEnsemble tkFontchooserEnsemble[]; +const TkEnsemble tkFontchooserEnsemble[] = { + { "configure", FontchooserConfigureCmd, NULL }, + { "show", FontchooserShowCmd, NULL }, + { "hide", FontchooserHideCmd, NULL }, + { NULL, NULL, NULL } +}; + +static Tcl_Interp *fontchooserInterp = NULL; +static NSFont *fontPanelFont = nil; +static NSMutableDictionary *fontPanelFontAttributes = nil; + +static const char *const fontchooserOptionStrings[] = { + "-parent", "-title", "-font", "-command", + "-visible", NULL +}; +enum FontchooserOption { + FontchooserParent, FontchooserTitle, FontchooserFont, FontchooserCmd, + FontchooserVisible +}; + +@implementation TKApplication(TKFontPanel) + +- (void) changeFont: (id) sender +{ + NSFontManager *fm = [NSFontManager sharedFontManager]; + + if ([fm currentFontAction] == NSViaPanelFontAction) { + NSFont *font = [fm convertFont:fontPanelFont]; + + if (![fontPanelFont isEqual:font]) { + [fontPanelFont release]; + fontPanelFont = [font retain]; + FontchooserEvent(FontchooserSelection); + } + } +} + +- (void) changeAttributes: (id) sender +{ + NSDictionary *attributes = [sender convertAttributes: + fontPanelFontAttributes]; + + if (![fontPanelFontAttributes isEqual:attributes]) { + [fontPanelFontAttributes setDictionary:attributes]; + FontchooserEvent(FontchooserSelection); + } +} + +- (NSUInteger) validModesForFontPanel: (NSFontPanel *) fontPanel +{ + return (NSFontPanelStandardModesMask & ~NSFontPanelAllEffectsModeMask) | + NSFontPanelUnderlineEffectModeMask | + NSFontPanelStrikethroughEffectModeMask; +} + +- (void) windowDidOrderOffScreen: (NSNotification *) notification +{ +#ifdef TK_MAC_DEBUG_NOTIFICATIONS + TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, notification); +#endif + if ([[notification object] isEqual:[[NSFontManager sharedFontManager] + fontPanel:NO]]) { + FontchooserEvent(FontchooserClosed); + } +} +@end + +/* + *---------------------------------------------------------------------- + * + * FontchooserEvent -- + * + * This processes events generated by user interaction with the font + * panel. + * + * Results: + * None. + * + * Side effects: + * Additional events may be place on the Tk event queue. + * + *---------------------------------------------------------------------- + */ + +static void +FontchooserEvent( + int kind) +{ + FontchooserData *fcdPtr; + Tcl_Obj *fontObj; + + if (!fontchooserInterp) { + return; + } + fcdPtr = Tcl_GetAssocData(fontchooserInterp, "::tk::fontchooser", NULL); + switch (kind) { + case FontchooserClosed: + if (fcdPtr->parent != None) { + TkSendVirtualEvent(fcdPtr->parent, "TkFontchooserVisibility", NULL); + fontchooserInterp = NULL; + } + break; + case FontchooserSelection: + fontObj = TkMacOSXFontDescriptionForNSFontAndNSFontAttributes( + fontPanelFont, fontPanelFontAttributes); + if (fontObj) { + if (fcdPtr->cmdObj) { + int objc, result; + Tcl_Obj **objv, **tmpv; + + result = Tcl_ListObjGetElements(fontchooserInterp, + fcdPtr->cmdObj, &objc, &objv); + if (result == TCL_OK) { + tmpv = ckalloc(sizeof(Tcl_Obj *) * (objc + 2)); + memcpy(tmpv, objv, sizeof(Tcl_Obj *) * objc); + tmpv[objc] = fontObj; + TkBackgroundEvalObjv(fontchooserInterp, objc + 1, tmpv, + TCL_EVAL_GLOBAL); + ckfree(tmpv); + } + } + TkSendVirtualEvent(fcdPtr->parent, "TkFontchooserFontChanged", NULL); + } + break; + } +} + +/* + *---------------------------------------------------------------------- + * + * FontchooserCget -- + * + * Helper for the FontchooserConfigure command to return the current value + * of any of the options (which may be NULL in the structure). + * + * Results: + * Tcl object of option value. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +FontchooserCget( + FontchooserData *fcdPtr, + int optionIndex) +{ + Tcl_Obj *resObj = NULL; + + switch(optionIndex) { + case FontchooserParent: + if (fcdPtr->parent != None) { + resObj = Tcl_NewStringObj( + ((TkWindow *) fcdPtr->parent)->pathName, -1); + } else { + resObj = Tcl_NewStringObj(".", 1); + } + break; + case FontchooserTitle: + if (fcdPtr->titleObj) { + resObj = fcdPtr->titleObj; + } else { + resObj = Tcl_NewObj(); + } + break; + case FontchooserFont: + resObj = TkMacOSXFontDescriptionForNSFontAndNSFontAttributes( + fontPanelFont, fontPanelFontAttributes); + if (!resObj) { + resObj = Tcl_NewObj(); + } + break; + case FontchooserCmd: + if (fcdPtr->cmdObj) { + resObj = fcdPtr->cmdObj; + } else { + resObj = Tcl_NewObj(); + } + break; + case FontchooserVisible: + resObj = Tcl_NewBooleanObj([[[NSFontManager sharedFontManager] + fontPanel:NO] isVisible]); + break; + default: + resObj = Tcl_NewObj(); + } + return resObj; +} + +/* + * ---------------------------------------------------------------------- + * + * FontchooserConfigureCmd -- + * + * Implementation of the 'tk fontchooser configure' ensemble command. See + * the user documentation for what it does. + * + * Results: + * See the user documentation. + * + * Side effects: + * Per-interp data structure may be modified + * + * ---------------------------------------------------------------------- + */ + +static int +FontchooserConfigureCmd( + ClientData clientData, /* Main window */ + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + Tk_Window tkwin = (Tk_Window)clientData; + FontchooserData *fcdPtr = Tcl_GetAssocData(interp, "::tk::fontchooser", + NULL); + int i, r = TCL_OK; + + /* + * With no arguments we return all the options in a dict + */ + + if (objc == 1) { + Tcl_Obj *keyObj, *valueObj; + Tcl_Obj *dictObj = Tcl_NewDictObj(); + + for (i = 0; r == TCL_OK && fontchooserOptionStrings[i] != NULL; ++i) { + keyObj = Tcl_NewStringObj(fontchooserOptionStrings[i], -1); + valueObj = FontchooserCget(fcdPtr, i); + r = Tcl_DictObjPut(interp, dictObj, keyObj, valueObj); + } + if (r == TCL_OK) { + Tcl_SetObjResult(interp, dictObj); + } + return r; + } + + for (i = 1; i < objc; i += 2) { + int optionIndex, len; + + if (Tcl_GetIndexFromObjStruct(interp, objv[i], fontchooserOptionStrings, + sizeof(char *), "option", 0, &optionIndex) != TCL_OK) { + return TCL_ERROR; + } + if (objc == 2) { + /* + * With one option and no arg, return the current value. + */ + + Tcl_SetObjResult(interp, FontchooserCget(fcdPtr, optionIndex)); + return TCL_OK; + } + if (i + 1 == objc) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "value for \"%s\" missing", Tcl_GetString(objv[i]))); + Tcl_SetErrorCode(interp, "TK", "FONTDIALOG", "VALUE", NULL); + return TCL_ERROR; + } + switch (optionIndex) { + case FontchooserVisible: { + const char *msg = "cannot change read-only option " + "\"-visible\": use the show or hide command"; + + Tcl_SetObjResult(interp, Tcl_NewStringObj(msg, -1)); + Tcl_SetErrorCode(interp, "TK", "FONTDIALOG", "READONLY", NULL); + return TCL_ERROR; + } + case FontchooserParent: { + Tk_Window parent = Tk_NameToWindow(interp, + Tcl_GetString(objv[i+1]), tkwin); + + if (parent == None) { + return TCL_ERROR; + } + if (fcdPtr->parent) { + Tk_DeleteEventHandler(fcdPtr->parent, StructureNotifyMask, + FontchooserParentEventHandler, fcdPtr); + } + fcdPtr->parent = parent; + Tk_CreateEventHandler(fcdPtr->parent, StructureNotifyMask, + FontchooserParentEventHandler, fcdPtr); + break; + } + case FontchooserTitle: + if (fcdPtr->titleObj) { + Tcl_DecrRefCount(fcdPtr->titleObj); + } + Tcl_GetStringFromObj(objv[i+1], &len); + if (len) { + fcdPtr->titleObj = objv[i+1]; + if (Tcl_IsShared(fcdPtr->titleObj)) { + fcdPtr->titleObj = Tcl_DuplicateObj(fcdPtr->titleObj); + } + Tcl_IncrRefCount(fcdPtr->titleObj); + } else { + fcdPtr->titleObj = NULL; + } + break; + case FontchooserFont: + Tcl_GetStringFromObj(objv[i+1], &len); + if (len) { + Tk_Font f = Tk_AllocFontFromObj(interp, tkwin, objv[i+1]); + + if (!f) { + return TCL_ERROR; + } + [fontPanelFont autorelease]; + fontPanelFont = [TkMacOSXNSFontForFont(f) retain]; + [fontPanelFontAttributes setDictionary: + TkMacOSXNSFontAttributesForFont(f)]; + [fontPanelFontAttributes removeObjectsForKeys:[NSArray + arrayWithObjects:NSFontAttributeName, + NSLigatureAttributeName, NSKernAttributeName, nil]]; + Tk_FreeFont(f); + } else { + [fontPanelFont release]; + fontPanelFont = nil; + [fontPanelFontAttributes removeAllObjects]; + } + + NSFontManager *fm = [NSFontManager sharedFontManager]; + NSFontPanel *fp = [fm fontPanel:NO]; + + [fp setPanelFont:fontPanelFont isMultiple:NO]; + [fm setSelectedFont:fontPanelFont isMultiple:NO]; + [fm setSelectedAttributes:fontPanelFontAttributes + isMultiple:NO]; + if ([fp isVisible]) { + TkSendVirtualEvent(fcdPtr->parent, + "TkFontchooserFontChanged", NULL); + } + break; + case FontchooserCmd: + if (fcdPtr->cmdObj) { + Tcl_DecrRefCount(fcdPtr->cmdObj); + } + Tcl_GetStringFromObj(objv[i+1], &len); + if (len) { + fcdPtr->cmdObj = objv[i+1]; + if (Tcl_IsShared(fcdPtr->cmdObj)) { + fcdPtr->cmdObj = Tcl_DuplicateObj(fcdPtr->cmdObj); + } + Tcl_IncrRefCount(fcdPtr->cmdObj); + } else { + fcdPtr->cmdObj = NULL; + } + break; + } + } + return TCL_OK; +} + +/* + * ---------------------------------------------------------------------- + * + * FontchooserShowCmd -- + * + * Implements the 'tk fontchooser show' ensemble command. The per-interp + * configuration data for the dialog is held in an interp associated + * structure. + * + * Results: + * See the user documentation. + * + * Side effects: + * Font Panel may be shown. + * + * ---------------------------------------------------------------------- + */ + +static int +FontchooserShowCmd( + ClientData clientData, /* Main window */ + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + FontchooserData *fcdPtr = Tcl_GetAssocData(interp, "::tk::fontchooser", + NULL); + + if (fcdPtr->parent == None) { + fcdPtr->parent = (Tk_Window) clientData; + Tk_CreateEventHandler(fcdPtr->parent, StructureNotifyMask, + FontchooserParentEventHandler, fcdPtr); + } + + NSFontManager *fm = [NSFontManager sharedFontManager]; + NSFontPanel *fp = [fm fontPanel:YES]; + + if ([fp delegate] != NSApp) { + [fp setDelegate:NSApp]; + } + if (![fp isVisible]) { + [fm orderFrontFontPanel:NSApp]; + TkSendVirtualEvent(fcdPtr->parent, "TkFontchooserVisibility", NULL); + } + fontchooserInterp = interp; + + return TCL_OK; +} + +/* + * ---------------------------------------------------------------------- + * + * FontchooserHideCmd -- + * + * Implementation of the 'tk fontchooser hide' ensemble. See the user + * documentation for details. + * + * Results: + * See the user documentation. + * + * Side effects: + * Font Panel may be hidden. + * + * ---------------------------------------------------------------------- + */ + +static int +FontchooserHideCmd( + ClientData clientData, /* Main window */ + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + NSFontPanel *fp = [[NSFontManager sharedFontManager] fontPanel:NO]; + + if ([fp isVisible]) { + [fp orderOut:NSApp]; + } + return TCL_OK; +} + +/* + * ---------------------------------------------------------------------- + * + * FontchooserParentEventHandler -- + * + * Event handler for StructureNotify events on the font chooser's parent + * window. + * + * Results: + * None. + * + * Side effects: + * Font chooser parent info is cleared and font panel is hidden. + * + * ---------------------------------------------------------------------- + */ + +static void +FontchooserParentEventHandler( + ClientData clientData, + XEvent *eventPtr) +{ + FontchooserData *fcdPtr = clientData; + + if (eventPtr->type == DestroyNotify) { + Tk_DeleteEventHandler(fcdPtr->parent, StructureNotifyMask, + FontchooserParentEventHandler, fcdPtr); + fcdPtr->parent = NULL; + FontchooserHideCmd(NULL, NULL, 0, NULL); + } +} + +/* + * ---------------------------------------------------------------------- + * + * DeleteFontchooserData -- + * + * Clean up the font chooser configuration data when the interp is + * destroyed. + * + * Results: + * None. + * + * Side effects: + * per-interp configuration data is destroyed. + * + * ---------------------------------------------------------------------- + */ + +static void +DeleteFontchooserData( + ClientData clientData, + Tcl_Interp *interp) +{ + FontchooserData *fcdPtr = clientData; + + if (fcdPtr->titleObj) { + Tcl_DecrRefCount(fcdPtr->titleObj); + } + if (fcdPtr->cmdObj) { + Tcl_DecrRefCount(fcdPtr->cmdObj); + } + ckfree(fcdPtr); + + if (fontchooserInterp == interp) { + fontchooserInterp = NULL; + } +} + +/* + * ---------------------------------------------------------------------- + * + * TkInitFontchooser -- + * + * Associate the font chooser configuration data with the Tcl interpreter. + * There is one font chooser per interp. + * + * Results: + * None. + * + * Side effects: + * per-interp configuration data is destroyed. + * + * ---------------------------------------------------------------------- + */ + +MODULE_SCOPE int +TkInitFontchooser( + Tcl_Interp *interp, + ClientData clientData) +{ + FontchooserData *fcdPtr = ckalloc(sizeof(FontchooserData)); + + bzero(fcdPtr, sizeof(FontchooserData)); + Tcl_SetAssocData(interp, "::tk::fontchooser", DeleteFontchooserData, + fcdPtr); + if (!fontPanelFontAttributes) { + fontPanelFontAttributes = [NSMutableDictionary new]; + } + return TCL_OK; +} + +/* + * Local Variables: + * mode: objc + * c-basic-offset: 4 + * fill-column: 79 + * coding: utf-8 + * End: + */ |