/*
* 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 <[email protected]>
* 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)
- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url {
(void)sender;
(void)url;
return YES;
}
- (void)panel:(id)sender didChangeToDirectoryURL:(NSURL *)url {
(void)sender; (void)url;
}
- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError {
(void)sender; (void)url;
*outError = nil;
return YES;
}
- (void) tkFilePanelDidEnd: (NSSavePanel *) panel
returnCode: (NSModalResponse) 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 = (Tcl_Obj **)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 = (Tcl_Obj **)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 -
static NSInteger showOpenSavePanel(
NSSavePanel *panel,
NSWindow *parent,
FilePanelCallbackInfo *callbackInfo)
{
NSInteger modalReturnCode;
if (parent && ![parent attachedSheet]) {
[panel beginSheetModalForWindow:parent
completionHandler:^(NSModalResponse returnCode) {
[NSApp tkFilePanelDidEnd:panel
returnCode:returnCode
contextInfo:callbackInfo ];
}];
modalReturnCode = callbackInfo->cmdObj ? modalOther :
[panel runModal];
} else {
modalReturnCode = [panel runModal];
[NSApp tkFilePanelDidEnd:panel returnCode:modalReturnCode
contextInfo:callbackInfo];
}
return modalReturnCode;
}
/*
*----------------------------------------------------------------------
*
* 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.
*/
if ([NSApp macMinorVersion] > 10) {
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) {
NSTextField *label = [[NSTextField alloc]
initWithFrame:NSMakeRect(0, 0, 60, 22)];
NSPopUpButton *popupButton = [[NSPopUpButton alloc]
initWithFrame:NSMakeRect(50.0, 2, 240, 22.0) pullsDown:NO];
NSView *accessoryView = [[NSView alloc]
initWithFrame:NSMakeRect(0.0, 0.0, 300, 32.0)];
[label setEditable:NO];
[label setStringValue:@"Filter:"];
[label setBordered:NO];
[label setBezeled:NO];
[label setDrawsBackground: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) {
if (Tcl_IsShared(cmdObj)) {
cmdObj = Tcl_DuplicateObj(cmdObj);
}
Tcl_IncrRefCount(cmdObj);
}
callbackInfo = (FilePanelCallbackInfo *)ckalloc(sizeof(FilePanelCallbackInfo));
callbackInfo->cmdObj = cmdObj;
callbackInfo->interp = interp;
callbackInfo->multiple = multiple;
if (directory || filename) {
NSURL *fileURL = getFileURL(directory, filename);
[openpanel setDirectoryURL:fileURL];
}
if (haveParentOption) {
parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window);
parentIsKey = parent && [parent isKeyWindow];
} else {
parent = nil;
parentIsKey = False;
}
modalReturnCode = showOpenSavePanel(openpanel, parent, 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;
NSString *extension;
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];
}
extension = [selectedFile pathExtension];
if (filterInfo.preselectFilter &&
filterCompatible(extension, filterInfo.fileTypeIndex)) {
selectedFilterIndex = filterInfo.fileTypeIndex; // The preselection from the typevariable
selectedFilter = [filterInfo.fileTypeNames objectAtIndex:selectedFilterIndex];
} else {
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) {
if (Tcl_IsShared(cmdObj)) {
cmdObj = Tcl_DuplicateObj(cmdObj);
}
Tcl_IncrRefCount(cmdObj);
}
callbackInfo = (FilePanelCallbackInfo *)ckalloc(sizeof(FilePanelCallbackInfo));
callbackInfo->cmdObj = cmdObj;
callbackInfo->interp = interp;
callbackInfo->multiple = 0;
if (directory) {
[savepanel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]];
}
/*
* Check for file name and set to the empty string if nil. This prevents a crash
* with an uncaught exception.
*/
if (filename) {
[savepanel setNameFieldStringValue:filename];
} else {
[savepanel setNameFieldStringValue:@""];
}
if (haveParentOption) {
parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window);
parentIsKey = parent && [parent isKeyWindow];
} else {
parent = nil;
parentIsKey = False;
}
modalReturnCode = showOpenSavePanel(savepanel, parent, 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) {
if (Tcl_IsShared(cmdObj)) {
cmdObj = Tcl_DuplicateObj(cmdObj);
}
Tcl_IncrRefCount(cmdObj);
}
callbackInfo = (FilePanelCallbackInfo *)ckalloc(sizeof(FilePanelCallbackInfo));
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);
[panel setDirectoryURL:[NSURL fileURLWithPath:directory isDirectory:YES]];
if (haveParentOption) {
parent = TkMacOSXDrawableWindow(((TkWindow *) tkwin)->window);
parentIsKey = parent && [parent isKeyWindow];
} else {
parent = nil;
parentIsKey = False;
}
modalReturnCode = showOpenSavePanel(panel, parent, 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 dummy, /* Unused. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
(void)dummy;
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) {
if (Tcl_IsShared(cmdObj)) {
cmdObj = Tcl_DuplicateObj(cmdObj);
}
Tcl_IncrRefCount(cmdObj);
}
callbackInfo = (AlertCallbackInfo *)ckalloc(sizeof(AlertCallbackInfo));
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 :
[alert runModal];
} 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 "tkMacOSXInt.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];
(void)sender;
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
{
(void)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 = (Tcl_Obj **)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_NewWideIntObj([[[NSFontManager sharedFontManager]
fontPanel:NO] isVisible] != 0);
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);
(void)objc;
(void)objv;
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 dummy, /* Main window */
Tcl_Interp *interp,
int objc,
Tcl_Obj *const objv[])
{
NSFontPanel *fp = [[NSFontManager sharedFontManager] fontPanel:NO];
(void)dummy;
(void)interp;
(void)objc;
(void)objv;
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 dummy)
{
FontchooserData *fcdPtr = (FontchooserData *)ckalloc(sizeof(FontchooserData));
(void)dummy;
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:
*/