/* * tkMacDialog.c -- * * Contains the Mac implementation of the common dialog boxes. * * Copyright (c) 1996 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkMacDialog.c,v 1.2 1998/09/14 18:23:35 stanton Exp $ * */ #include #include #include #include #include #include #include #include #include #include "tkPort.h" #include "tkInt.h" #include "tclMacInt.h" #include "tkFileFilter.h" /* * The following are ID's for resources that are defined in tkMacResource.r */ #define OPEN_BOX 130 #define OPEN_POPUP 131 #define OPEN_MENU 132 #define OPEN_POPUP_ITEM 10 #define SAVE_FILE 0 #define OPEN_FILE 1 #define MATCHED 0 #define UNMATCHED 1 /* * The following structure is used in the GetFileName() function. It stored * information about the file dialog and the file filters. */ typedef struct _OpenFileData { Tcl_Interp * interp; char * initialFile; /* default file to appear in the * save dialog */ char * defExt; /* default extension (not used on the * Mac) */ FileFilterList fl; /* List of file filters. */ SInt16 curType; /* The filetype currently being * listed */ int isOpen; /* True if this is an Open dialog, * false if it is a Save dialog. */ MenuHandle menu; /* Handle of the menu in the popup*/ short dialogId; /* resource ID of the dialog */ int popupId; /* resource ID of the popup */ short popupItem; /* item number of the popup in the * dialog */ int usePopup; /* True if we show the popup menu (this * is an open operation and the * -filetypes option is set) */ } OpenFileData; static pascal Boolean FileFilterProc _ANSI_ARGS_((CInfoPBPtr pb, void *myData)); static int GetFileName _ANSI_ARGS_ (( ClientData clientData, Tcl_Interp *interp, int argc, char **argv, int isOpen )); static Boolean MatchOneType _ANSI_ARGS_((CInfoPBPtr pb, OpenFileData * myDataPtr, FileFilter * filterPtr)); static pascal short OpenHookProc _ANSI_ARGS_((short item, DialogPtr theDialog, OpenFileData * myDataPtr)); static int ParseFileDlgArgs _ANSI_ARGS_ ((Tcl_Interp * interp, OpenFileData * myDataPtr, int argc, char ** argv, int isOpen)); /* * Filter and hook functions used by the tk_getOpenFile and tk_getSaveFile * commands. */ static FileFilterYDUPP openFilter = NULL; static DlgHookYDUPP openHook = NULL; static DlgHookYDUPP saveHook = NULL; /* *---------------------------------------------------------------------- * * EvalArgv -- * * Invokes the Tcl procedure with the arguments. argv[0] is set by * the caller of this function. It may be different than cmdName. * The TCL command will see argv[0], not cmdName, as its name if it * invokes [lindex [info level 0] 0] * * Results: * TCL_ERROR if the command does not exist and cannot be autoloaded. * Otherwise, return the result of the evaluation of the command. * * Side effects: * The command may be autoloaded. * *---------------------------------------------------------------------- */ static int EvalArgv( Tcl_Interp *interp, /* Current interpreter. */ char * cmdName, /* Name of the TCL command to call */ int argc, /* Number of arguments. */ char **argv) /* Argument strings. */ { Tcl_CmdInfo cmdInfo; if (!Tcl_GetCommandInfo(interp, cmdName, &cmdInfo)) { char * cmdArgv[2]; /* * This comand is not in the interpreter yet -- looks like we * have to auto-load it */ if (!Tcl_GetCommandInfo(interp, "auto_load", &cmdInfo)) { Tcl_ResetResult(interp); Tcl_AppendResult(interp, "cannot execute command \"auto_load\"", NULL); return TCL_ERROR; } cmdArgv[0] = "auto_load"; cmdArgv[1] = cmdName; if ((*cmdInfo.proc)(cmdInfo.clientData, interp, 2, cmdArgv)!= TCL_OK){ return TCL_ERROR; } if (!Tcl_GetCommandInfo(interp, cmdName, &cmdInfo)) { Tcl_ResetResult(interp); Tcl_AppendResult(interp, "cannot auto-load command \"", cmdName, "\"",NULL); return TCL_ERROR; } } return (*cmdInfo.proc)(cmdInfo.clientData, interp, argc, argv); } /* *---------------------------------------------------------------------- * * Tk_ChooseColorCmd -- * * 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_ChooseColorCmd( ClientData clientData, /* Main window associated with interpreter. */ Tcl_Interp *interp, /* Current interpreter. */ int argc, /* Number of arguments. */ char **argv) /* Argument strings. */ { Tk_Window parent = Tk_MainWindow(interp); char * colorStr = NULL; XColor * colorPtr = NULL; char * title = "Choose a color:"; int i, version; long response = 0; OSErr err = noErr; char buff[40]; static RGBColor in; static inited = 0; /* * Use the gestalt manager to determine how to bring * up the color picker. If versin 2.0 isn't available * we can assume version 1.0 is available as it comes with * Color Quickdraw which Tk requires to run at all. */ err = Gestalt(gestaltColorPicker, &response); if ((err == noErr) || (response == 0x0200L)) { version = 2; } else { version = 1; } for (i=1; ired; in.green = colorPtr->green; in.blue = colorPtr->blue; } if (version == 1) { /* * Use version 1.0 of the color picker */ RGBColor out; Str255 prompt; Point point = {-1, -1}; prompt[0] = strlen(title); strncpy((char*) prompt+1, title, 255); if (GetColor(point, prompt, &in, &out)) { /* * user selected a color */ sprintf(buff, "#%02x%02x%02x", out.red >> 8, out.green >> 8, out.blue >> 8); Tcl_SetResult(interp, buff, TCL_VOLATILE); /* * Save it for the next time */ in.red = out.red; in.green = out.green; in.blue = out.blue; } else { Tcl_ResetResult(interp); } } else { /* * Version 2.0 of the color picker is available. Let's use it */ ColorPickerInfo cpinfo; cpinfo.theColor.profile = 0L; cpinfo.theColor.color.rgb.red = in.red; cpinfo.theColor.color.rgb.green = in.green; cpinfo.theColor.color.rgb.blue = in.blue; cpinfo.dstProfile = 0L; cpinfo.flags = CanModifyPalette | CanAnimatePalette; cpinfo.placeWhere = kDeepestColorScreen; cpinfo.pickerType = 0L; cpinfo.eventProc = NULL; cpinfo.colorProc = NULL; cpinfo.colorProcData = NULL; cpinfo.prompt[0] = strlen(title); strncpy((char*)cpinfo.prompt+1, title, 255); if ((PickColor(&cpinfo) == noErr) && cpinfo.newColorChosen) { sprintf(buff, "#%02x%02x%02x", cpinfo.theColor.color.rgb.red >> 8, cpinfo.theColor.color.rgb.green >> 8, cpinfo.theColor.color.rgb.blue >> 8); Tcl_SetResult(interp, buff, TCL_VOLATILE); in.blue = cpinfo.theColor.color.rgb.red; in.green = cpinfo.theColor.color.rgb.green; in.blue = cpinfo.theColor.color.rgb.blue; } else { Tcl_ResetResult(interp); } } if (colorPtr) { Tk_FreeColor(colorPtr); } return TCL_OK; arg_missing: Tcl_AppendResult(interp, "value for \"", argv[argc-1], "\" missing", NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * Tk_GetOpenFileCmd -- * * 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_GetOpenFileCmd( ClientData clientData, /* Main window associated with interpreter. */ Tcl_Interp *interp, /* Current interpreter. */ int argc, /* Number of arguments. */ char **argv) /* Argument strings. */ { return GetFileName(clientData, interp, argc, argv, OPEN_FILE); } /* *---------------------------------------------------------------------- * * Tk_GetSaveFileCmd -- * * Same as Tk_GetOpenFileCmd but opens a "save file" dialog box * instead * * Results: * A standard Tcl result. * * Side effects: * See user documentation. *---------------------------------------------------------------------- */ int Tk_GetSaveFileCmd( ClientData clientData, /* Main window associated with interpreter. */ Tcl_Interp *interp, /* Current interpreter. */ int argc, /* Number of arguments. */ char **argv) /* Argument strings. */ { return GetFileName(clientData, interp, argc, argv, SAVE_FILE); } /* *---------------------------------------------------------------------- * * GetFileName -- * * Calls the Mac file dialog functions for the user to choose a * file to or save. * * Results: * A standard Tcl result. * * Side effects: * If the user selects a file, the native pathname of the file * is returned in interp->result. Otherwise an empty string * is returned in interp->result. * *---------------------------------------------------------------------- */ static int GetFileName( ClientData clientData, /* Main window associated with interpreter. */ Tcl_Interp *interp, /* Current interpreter. */ int argc, /* Number of arguments. */ char **argv, /* Argument strings. */ int isOpen) /* true if we should call GetOpenFileName(), * false if we should call GetSaveFileName() */ { int code = TCL_OK; int i; OpenFileData myData, *myDataPtr; StandardFileReply reply; Point mypoint; Str255 str; myDataPtr = &myData; if (openFilter == NULL) { openFilter = NewFileFilterYDProc(FileFilterProc); openHook = NewDlgHookYDProc(OpenHookProc); saveHook = NewDlgHookYDProc(OpenHookProc); } /* * 1. Parse the arguments. */ if (ParseFileDlgArgs(interp, myDataPtr, argc, argv, isOpen) != TCL_OK) { return TCL_ERROR; } /* * 2. Set the items in the file types popup. */ /* * Delete all the entries inside the popup menu, in case there's any * left overs from previous invocation of this command */ if (myDataPtr->usePopup) { FileFilter * filterPtr; for (i=CountMItems(myDataPtr->menu); i>0; i--) { /* * The item indices are one based. Also, if we delete from * the beginning, the items may be re-numbered. So we * delete from the end */ DeleteMenuItem(myDataPtr->menu, i); } if (myDataPtr->fl.filters) { for (filterPtr=myDataPtr->fl.filters; filterPtr; filterPtr=filterPtr->next) { strncpy((char*)str+1, filterPtr->name, 254); str[0] = strlen(filterPtr->name); AppendMenu(myDataPtr->menu, (ConstStr255Param) str); } } else { myDataPtr->usePopup = 0; } } /* * 3. Call the toolbox file dialog function. */ SetPt(&mypoint, -1, -1); TkpSetCursor(NULL); if (myDataPtr->isOpen) { if (myDataPtr->usePopup) { CustomGetFile(openFilter, (short) -1, NULL, &reply, myDataPtr->dialogId, mypoint, openHook, NULL, NULL, NULL, (void*)myDataPtr); } else { StandardGetFile(NULL, -1, NULL, &reply); } } else { Str255 prompt, def; strcpy((char*)prompt+1, "Save as"); prompt[0] = strlen("Save as"); if (myDataPtr->initialFile) { strncpy((char*)def+1, myDataPtr->initialFile, 254); def[0] = strlen(myDataPtr->initialFile); } else { def[0] = 0; } if (myDataPtr->usePopup) { /* * Currently this never gets called because we don't use * popup for the save dialog. */ CustomPutFile(prompt, def, &reply, myDataPtr->dialogId, mypoint, saveHook, NULL, NULL, NULL, myDataPtr); } else { StandardPutFile(prompt, def, &reply); } } Tcl_ResetResult(interp); if (reply.sfGood) { int length; Handle pathHandle = NULL; char * pathName = NULL; FSpPathFromLocation(&reply.sfFile, &length, &pathHandle); if (pathHandle != NULL) { HLock(pathHandle); pathName = (char *) ckalloc((unsigned) (length + 1)); strcpy(pathName, *pathHandle); HUnlock(pathHandle); DisposeHandle(pathHandle); /* * Return the full pathname of the selected file */ Tcl_SetResult(interp, pathName, TCL_DYNAMIC); } } done: TkFreeFileFilters(&myDataPtr->fl); return code; } /* *---------------------------------------------------------------------- * * ParseFileDlgArgs -- * * Parses the arguments passed to tk_getOpenFile and tk_getSaveFile. * * Results: * A standard TCL return value. * * Side effects: * The OpenFileData structure is initialized and modified according * to the arguments. * *---------------------------------------------------------------------- */ static int ParseFileDlgArgs( Tcl_Interp * interp, /* Current interpreter. */ OpenFileData * myDataPtr, /* Information about the file dialog */ int argc, /* Number of arguments */ char ** argv, /* Argument strings */ int isOpen) /* TRUE if this is an "open" dialog */ { int i; myDataPtr->interp = interp; myDataPtr->initialFile = NULL; myDataPtr->curType = 0; TkInitFileFilters(&myDataPtr->fl); if (isOpen) { myDataPtr->isOpen = 1; myDataPtr->usePopup = 1; myDataPtr->menu = GetMenu(OPEN_MENU); myDataPtr->dialogId = OPEN_BOX; myDataPtr->popupId = OPEN_POPUP; myDataPtr->popupItem = OPEN_POPUP_ITEM; if (myDataPtr->menu == NULL) { Debugger(); } } else { myDataPtr->isOpen = 0; myDataPtr->usePopup = 0; } for (i=1; idefExt = argv[v]; } else if (strncmp(argv[i], "-filetypes", len)==0) { if (v==argc) {goto arg_missing;} if (TkGetFileFilters(interp, &myDataPtr->fl,argv[v],0) != TCL_OK) { return TCL_ERROR; } } else if (strncmp(argv[i], "-initialdir", len)==0) { FSSpec dirSpec; char * dirName; Tcl_DString dstring; long dirID; OSErr err; Boolean isDirectory; if (v==argc) {goto arg_missing;} if (Tcl_TranslateFileName(interp, argv[v], &dstring) == NULL) { return TCL_ERROR; } dirName = dstring.string; if (FSpLocationFromPath(strlen(dirName), dirName, &dirSpec) != noErr) { Tcl_AppendResult(interp, "bad directory \"", argv[v], "\"", NULL); return TCL_ERROR; } err = FSpGetDirectoryID(&dirSpec, &dirID, &isDirectory); if ((err != noErr) || !isDirectory) { Tcl_AppendResult(interp, "bad directory \"", argv[v], "\"", NULL); return TCL_ERROR; } /* * Make sure you negate -dirSpec.vRefNum because the standard file * package wants it that way ! */ LMSetSFSaveDisk(-dirSpec.vRefNum); LMSetCurDirStore(dirID); Tcl_DStringFree(&dstring); } else if (strncmp(argv[i], "-initialfile", len)==0) { if (v==argc) {goto arg_missing;} myDataPtr->initialFile = argv[v]; } else if (strncmp(argv[i], "-parent", len)==0) { /* * Ignored on the Mac, but make sure that it's a valid window * pathname */ Tk_Window parent; if (v==argc) {goto arg_missing;} parent=Tk_NameToWindow(interp, argv[v], Tk_MainWindow(interp)); if (parent == NULL) { return TCL_ERROR; } } else if (strncmp(argv[i], "-title", len)==0) { if (v==argc) {goto arg_missing;} /* * This option is ignored on the Mac because the Mac file * dialog do not support titles. */ } else { Tcl_AppendResult(interp, "unknown option \"", argv[i], "\", must be -defaultextension, ", "-filetypes, -initialdir, -initialfile, -parent or -title", NULL); return TCL_ERROR; } } return TCL_OK; arg_missing: Tcl_AppendResult(interp, "value for \"", argv[argc-1], "\" missing", NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * OpenHookProc -- * * Gets called for various events that occur in the file dialog box. * Initializes the popup menu or rebuild the file list depending on * the type of the event. * * Results: * A standard result understood by the Mac file dialog event dispatcher. * * Side effects: * The contents in the file dialog may be changed depending on * the type of the event. *---------------------------------------------------------------------- */ static pascal short OpenHookProc( short item, /* Event description. */ DialogPtr theDialog, /* The dialog where the event occurs. */ OpenFileData * myDataPtr) /* Information about the file dialog. */ { short ignore; Rect rect; Handle handle; int newType; switch (item) { case sfHookFirstCall: if (myDataPtr->usePopup) { /* * Set the popup list to display the selected type. */ GetDialogItem(theDialog, myDataPtr->popupItem, &ignore, &handle, &rect); SetControlValue((ControlRef) handle, myDataPtr->curType + 1); } return sfHookNullEvent; case OPEN_POPUP_ITEM: if (myDataPtr->usePopup) { GetDialogItem(theDialog, myDataPtr->popupItem, &ignore, &handle, &rect); newType = GetCtlValue((ControlRef) handle) - 1; if (myDataPtr->curType != newType) { if (newType<0 || newType>myDataPtr->fl.numFilters) { /* * Sanity check. Looks like the user selected an * non-existent menu item?? Don't do anything. */ } else { myDataPtr->curType = newType; } return sfHookRebuildList; } } break; } return item; } /* *---------------------------------------------------------------------- * * FileFilterProc -- * * Filters files according to file types. Get called whenever the * file list needs to be updated inside the dialog box. * * Results: * Returns MATCHED if the file should be shown in the listbox, returns * UNMATCHED otherwise. * * Side effects: * If MATCHED is returned, the file is shown in the listbox. * *---------------------------------------------------------------------- */ static pascal Boolean FileFilterProc( CInfoPBPtr pb, /* Information about the file */ void *myData) /* Client data for this file dialog */ { int i; OpenFileData * myDataPtr = (OpenFileData*)myData; FileFilter * filterPtr; if (myDataPtr->fl.numFilters == 0) { /* * No types have been specified. List all files by default */ return MATCHED; } if (pb->dirInfo.ioFlAttrib & 0x10) { /* * This is a directory: always show it */ return MATCHED; } if (myDataPtr->usePopup) { i = myDataPtr->curType; for (filterPtr=myDataPtr->fl.filters; filterPtr && i>0; i--) { filterPtr = filterPtr->next; } if (filterPtr) { return MatchOneType(pb, myDataPtr, filterPtr); } else { return UNMATCHED; } } else { /* * We are not using the popup menu. In this case, the file is * considered matched if it matches any of the file filters. */ for (filterPtr=myDataPtr->fl.filters; filterPtr; filterPtr=filterPtr->next) { if (MatchOneType(pb, myDataPtr, filterPtr) == MATCHED) { return MATCHED; } } return UNMATCHED; } } /* *---------------------------------------------------------------------- * * MatchOneType -- * * Match a file with one file type in the list of file types. * * Results: * Returns MATCHED if the file matches with the file type; returns * UNMATCHED otherwise. * * Side effects: * None * *---------------------------------------------------------------------- */ static Boolean MatchOneType( CInfoPBPtr pb, /* Information about the file */ OpenFileData * myDataPtr, /* Information about this file dialog */ FileFilter * filterPtr) /* Match the file described by pb against * this filter */ { FileFilterClause * clausePtr; /* * A file matches with a file type if it matches with at least one * clause of the type. * * If the clause has both glob patterns and ostypes, the file must * match with at least one pattern AND at least one ostype. * * If the clause has glob patterns only, the file must match with at least * one pattern. * * If the clause has mac types only, the file must match with at least * one mac type. * * If the clause has neither glob patterns nor mac types, it's * considered an error. */ for (clausePtr=filterPtr->clauses; clausePtr; clausePtr=clausePtr->next) { int macMatched = 0; int globMatched = 0; GlobPattern * globPtr; MacFileType * mfPtr; if (clausePtr->patterns == NULL) { globMatched = 1; } if (clausePtr->macTypes == NULL) { macMatched = 1; } for (globPtr=clausePtr->patterns; globPtr; globPtr=globPtr->next) { char filename[256]; int len; char * p, *q, *ext; if (pb->hFileInfo.ioNamePtr == NULL) { continue; } p = (char*)(pb->hFileInfo.ioNamePtr); len = p[0]; strncpy(filename, p+1, len); filename[len] = '\0'; ext = globPtr->pattern; if (ext[0] == '\0') { /* * We don't want any extensions: OK if the filename doesn't * have "." in it */ for (q=filename; *q; q++) { if (*q == '.') { goto glob_unmatched; } } goto glob_matched; } if (Tcl_StringMatch(filename, ext)) { goto glob_matched; } else { goto glob_unmatched; } glob_unmatched: continue; glob_matched: globMatched = 1; break; } for (mfPtr=clausePtr->macTypes; mfPtr; mfPtr=mfPtr->next) { if (pb->hFileInfo.ioFlFndrInfo.fdType == mfPtr->type) { macMatched = 1; break; } } if (globMatched && macMatched) { return MATCHED; } } return UNMATCHED; } /* *---------------------------------------------------------------------- * * Tk_MessageBoxCmd -- * * This procedure implements the MessageBox window 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_MessageBoxCmd( ClientData clientData, /* Main window associated with interpreter. */ Tcl_Interp *interp, /* Current interpreter. */ int argc, /* Number of arguments. */ char **argv) /* Argument strings. */ { return EvalArgv(interp, "tkMessageBox", argc, argv); }