/* * tkWinDialog.c -- * * Contains the Windows implementation of the common dialog boxes. * * Copyright (c) 1996-1997 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * SCCS: @(#) tkWinDialog.c 1.10 97/10/21 11:29:18 * */ #include "tkWinInt.h" #include "tkFileFilter.h" #include /* includes common dialog functionality */ #include /* includes common dialog template defines */ #include /* includes the common dialog error codes */ #if ((TK_MAJOR_VERSION == 4) && (TK_MINOR_VERSION <= 2)) /* * The following function is implemented on tk4.3 and after only */ #define Tk_GetHWND TkWinGetHWND #endif #define SAVE_FILE 0 #define OPEN_FILE 1 /*---------------------------------------------------------------------- * MsgTypeInfo -- * * This structure stores the type of available message box in an * easy-to-process format. Used by th Tk_MessageBox() function *---------------------------------------------------------------------- */ typedef struct MsgTypeInfo { char * name; int type; int numButtons; char * btnNames[3]; } MsgTypeInfo; #define NUM_TYPES 6 static MsgTypeInfo msgTypeInfo[NUM_TYPES] = { {"abortretryignore", MB_ABORTRETRYIGNORE, 3, {"abort", "retry", "ignore"}}, {"ok", MB_OK, 1, {"ok" }}, {"okcancel", MB_OKCANCEL, 2, {"ok", "cancel" }}, {"retrycancel", MB_RETRYCANCEL, 2, {"retry", "cancel" }}, {"yesno", MB_YESNO, 2, {"yes", "no" }}, {"yesnocancel", MB_YESNOCANCEL, 3, {"yes", "no", "cancel"}} }; /* * The following structure is used in the GetOpenFileName() and * GetSaveFileName() calls. */ typedef struct _OpenFileData { Tcl_Interp * interp; TCHAR szFile[MAX_PATH+1]; } OpenFileData; /* * The following structure is used in the ChooseColor() call. */ typedef struct _ChooseColorData { Tcl_Interp * interp; char * title; /* Title of the color dialog */ } ChooseColorData; static int GetFileName _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv, int isOpen)); static UINT CALLBACK ColorDlgHookProc _ANSI_ARGS_((HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)); static int MakeFilter _ANSI_ARGS_((Tcl_Interp *interp, OPENFILENAME *ofnPtr, char * string)); static int ParseFileDlgArgs _ANSI_ARGS_((Tcl_Interp * interp, OPENFILENAME *ofnPtr, int argc, char ** argv, int isOpen)); static int ProcessCDError _ANSI_ARGS_((Tcl_Interp * interp, DWORD dwErrorCode, HWND hWnd)); /* *---------------------------------------------------------------------- * * 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(interp, cmdName, argc, argv) 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 Windows * platform. See the user documentation for details on what it * does. * * Results: * See user documentation. * * Side effects: * A dialog window is created the first time this procedure is called. * This window is not destroyed and will be reused the next time the * application invokes the "tk_chooseColor" command. * *---------------------------------------------------------------------- */ int Tk_ChooseColorCmd(clientData, interp, argc, argv) 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); ChooseColorData custData; int oldMode; CHOOSECOLOR chooseColor; char * colorStr = NULL; int i; int winCode, tclCode; XColor * colorPtr = NULL; static inited = 0; static long dwCustColors[16]; static long oldColor; /* the color selected last time */ custData.title = NULL; if (!inited) { /* * dwCustColors stores the custom color which the user can * modify. We store these colors in a fixed array so that the next * time the color dialog pops up, the same set of custom colors * remain in the dialog. */ for (i=0; i<16; i++) { dwCustColors[i] = (RGB(255-i*10, i, i*10)) ; } oldColor = RGB(0xa0,0xa0,0xa0); inited = 1; } /* * 1. Parse the arguments */ chooseColor.lStructSize = sizeof(CHOOSECOLOR) ; chooseColor.hwndOwner = 0; /* filled in below */ chooseColor.hInstance = 0; chooseColor.rgbResult = oldColor; chooseColor.lpCustColors = (LPDWORD) dwCustColors ; chooseColor.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ENABLEHOOK; chooseColor.lCustData = (LPARAM)&custData; chooseColor.lpfnHook = ColorDlgHookProc; chooseColor.lpTemplateName = NULL; for (i=1; ired/0x100), (colorPtr->green/0x100), (colorPtr->blue/0x100)); } /* * 2. Popup the dialog */ oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); winCode = ChooseColor(&chooseColor); (void) Tcl_SetServiceMode(oldMode); /* * Clear the interp result since anything may have happened during the * modal loop. */ Tcl_ResetResult(interp); /* * 3. Process the result of the dialog */ if (winCode) { /* * User has selected a color */ char result[100]; sprintf(result, "#%02x%02x%02x", GetRValue(chooseColor.rgbResult), GetGValue(chooseColor.rgbResult), GetBValue(chooseColor.rgbResult)); Tcl_AppendResult(interp, result, NULL); tclCode = TCL_OK; oldColor = chooseColor.rgbResult; } else { /* * User probably pressed Cancel, or an error occurred */ tclCode = ProcessCDError(interp, CommDlgExtendedError(), chooseColor.hwndOwner); } if (colorPtr) { Tk_FreeColor(colorPtr); } return tclCode; arg_missing: Tcl_AppendResult(interp, "value for \"", argv[argc-1], "\" missing", NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ColorDlgHookProc -- * * Gets called during the execution of the color dialog. It processes * the "interesting" messages that Windows send to the dialog. * * Results: * TRUE if the message has been processed, FALSE otherwise. * * Side effects: * Changes the title of the dialog window when it is popped up. * *---------------------------------------------------------------------- */ static UINT CALLBACK ColorDlgHookProc(hDlg, uMsg, wParam, lParam) HWND hDlg; /* Handle to the color dialog */ UINT uMsg; /* Type of message */ WPARAM wParam; /* word param, interpretation depends on uMsg*/ LPARAM lParam; /* long param, interpretation depends on uMsg*/ { CHOOSECOLOR * ccPtr; ChooseColorData * pCustData; switch (uMsg) { case WM_INITDIALOG: /* Save the pointer to CHOOSECOLOR so that we can use it later */ SetWindowLong(hDlg, DWL_USER, lParam); /* Set the title string of the dialog */ ccPtr = (CHOOSECOLOR*)lParam; pCustData = (ChooseColorData*)(ccPtr->lCustData); if (pCustData->title && *(pCustData->title)) { SetWindowText(hDlg, (LPCSTR)pCustData->title); } return TRUE; } return FALSE; } /* *---------------------------------------------------------------------- * * Tk_GetOpenFileCmd -- * * This procedure implements the "open file" dialog box for the * Windows platform. See the user documentation for details on what * it does. * * Results: * See user documentation. * * Side effects: * A dialog window is created the first this procedure is called. * This window is not destroyed and will be reused the next time * the application invokes the "tk_getOpenFile" or * "tk_getSaveFile" command. * *---------------------------------------------------------------------- */ int Tk_GetOpenFileCmd(clientData, interp, argc, argv) 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: * Same as Tk_GetOpenFileCmd. * * Side effects: * Same as Tk_GetOpenFileCmd. * *---------------------------------------------------------------------- */ int Tk_GetSaveFileCmd(clientData, interp, argc, argv) 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 GetOpenFileName() or GetSaveFileName(). * * Results: * See user documentation. * * Side effects: * See user documentation. * *---------------------------------------------------------------------- */ static int GetFileName(clientData, interp, argc, argv, isOpen) 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() */ { OPENFILENAME openFileName, *ofnPtr; int tclCode, winCode, oldMode; OpenFileData *custData; char buffer[MAX_PATH+1]; ofnPtr = &openFileName; /* * 1. Parse the arguments. */ if (ParseFileDlgArgs(interp, ofnPtr, argc, argv, isOpen) != TCL_OK) { return TCL_ERROR; } custData = (OpenFileData*) ofnPtr->lCustData; /* * 2. Call the common dialog function. */ oldMode = Tcl_SetServiceMode(TCL_SERVICE_ALL); GetCurrentDirectory(MAX_PATH+1, buffer); if (isOpen) { winCode = GetOpenFileName(ofnPtr); } else { winCode = GetSaveFileName(ofnPtr); } SetCurrentDirectory(buffer); (void) Tcl_SetServiceMode(oldMode); /* * Clear the interp result since anything may have happened during the * modal loop. */ Tcl_ResetResult(interp); if (ofnPtr->lpstrInitialDir != NULL) { ckfree((char*) ofnPtr->lpstrInitialDir); } /* * 3. Process the results. */ if (winCode) { char *p; Tcl_ResetResult(interp); for (p = custData->szFile; p && *p; p++) { /* * Change the pathname to the Tcl "normalized" pathname, where * back slashes are used instead of forward slashes */ if (*p == '\\') { *p = '/'; } } Tcl_AppendResult(interp, custData->szFile, NULL); tclCode = TCL_OK; } else { tclCode = ProcessCDError(interp, CommDlgExtendedError(), ofnPtr->hwndOwner); } if (custData) { ckfree((char*)custData); } if (ofnPtr->lpstrFilter) { ckfree((char*)ofnPtr->lpstrFilter); } return tclCode; } /* *---------------------------------------------------------------------- * * ParseFileDlgArgs -- * * Parses the arguments passed to tk_getOpenFile and tk_getSaveFile. * * Results: * A standard TCL return value. * * Side effects: * The OPENFILENAME structure is initialized and modified according * to the arguments. * *---------------------------------------------------------------------- */ static int ParseFileDlgArgs(interp, ofnPtr, argc, argv, isOpen) Tcl_Interp * interp; /* Current interpreter. */ OPENFILENAME *ofnPtr; /* Info about the file dialog */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ int isOpen; /* true if we should call GetOpenFileName(), * false if we should call GetSaveFileName() */ { OpenFileData * custData; int i; Tk_Window parent = Tk_MainWindow(interp); int doneFilter = 0; int windowsMajorVersion; Tcl_DString buffer; custData = (OpenFileData*)ckalloc(sizeof(OpenFileData)); custData->interp = interp; strcpy(custData->szFile, ""); /* Fill in the OPENFILENAME structure to */ ofnPtr->lStructSize = sizeof(OPENFILENAME); ofnPtr->hwndOwner = 0; /* filled in below */ ofnPtr->lpstrFilter = NULL; ofnPtr->lpstrCustomFilter = NULL; ofnPtr->nMaxCustFilter = 0; ofnPtr->nFilterIndex = 0; ofnPtr->lpstrFile = custData->szFile; ofnPtr->nMaxFile = sizeof(custData->szFile); ofnPtr->lpstrFileTitle = NULL; ofnPtr->nMaxFileTitle = 0; ofnPtr->lpstrInitialDir = NULL; ofnPtr->lpstrTitle = NULL; ofnPtr->nFileOffset = 0; ofnPtr->nFileExtension = 0; ofnPtr->lpstrDefExt = NULL; ofnPtr->lpfnHook = NULL; ofnPtr->lCustData = (DWORD)custData; ofnPtr->lpTemplateName = NULL; ofnPtr->Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST; windowsMajorVersion = LOBYTE(LOWORD(GetVersion())); if (windowsMajorVersion >= 4) { /* * Use the "explorer" style file selection box on platforms that * support it (Win95 and NT4.0, both have a major version number * of 4) */ ofnPtr->Flags |= OFN_EXPLORER; } if (isOpen) { ofnPtr->Flags |= OFN_FILEMUSTEXIST; } else { ofnPtr->Flags |= OFN_OVERWRITEPROMPT; } for (i=1; ilpstrDefExt = argv[v]; if (ofnPtr->lpstrDefExt[0] == '.') { /* Windows will insert the dot for us */ ofnPtr->lpstrDefExt ++; } } else if (strncmp(argv[i], "-filetypes", len)==0) { if (v==argc) {goto arg_missing;} if (MakeFilter(interp, ofnPtr, argv[v]) != TCL_OK) { return TCL_ERROR; } doneFilter = 1; } else if (strncmp(argv[i], "-initialdir", len)==0) { if (v==argc) {goto arg_missing;} if (Tcl_TranslateFileName(interp, argv[v], &buffer) == NULL) { return TCL_ERROR; } ofnPtr->lpstrInitialDir = ckalloc(Tcl_DStringLength(&buffer)+1); strcpy((char*)ofnPtr->lpstrInitialDir, Tcl_DStringValue(&buffer)); Tcl_DStringFree(&buffer); } else if (strncmp(argv[i], "-initialfile", len)==0) { if (v==argc) {goto arg_missing;} if (Tcl_TranslateFileName(interp, argv[v], &buffer) == NULL) { return TCL_ERROR; } strcpy(ofnPtr->lpstrFile, Tcl_DStringValue(&buffer)); Tcl_DStringFree(&buffer); } else if (strncmp(argv[i], "-parent", len)==0) { 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;} ofnPtr->lpstrTitle = argv[v]; } else { Tcl_AppendResult(interp, "unknown option \"", argv[i], "\", must be -defaultextension, ", "-filetypes, -initialdir, -initialfile, -parent or -title", NULL); return TCL_ERROR; } } if (!doneFilter) { if (MakeFilter(interp, ofnPtr, "") != TCL_OK) { return TCL_ERROR; } } if (Tk_WindowId(parent) == None) { Tk_MakeWindowExist(parent); } ofnPtr->hwndOwner = Tk_GetHWND(Tk_WindowId(parent)); return TCL_OK; arg_missing: Tcl_AppendResult(interp, "value for \"", argv[argc-1], "\" missing", NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * MakeFilter -- * * Allocate a buffer to store the filters in a format understood by * Windows * * Results: * A standard TCL return value. * * Side effects: * ofnPtr->lpstrFilter is modified. * *---------------------------------------------------------------------- */ static int MakeFilter(interp, ofnPtr, string) Tcl_Interp *interp; /* Current interpreter. */ OPENFILENAME *ofnPtr; /* Info about the file dialog */ char *string; /* String value of the -filetypes option */ { char *filterStr; char *p; int pass; FileFilterList flist; FileFilter *filterPtr; TkInitFileFilters(&flist); if (TkGetFileFilters(interp, &flist, string, 1) != TCL_OK) { return TCL_ERROR; } if (flist.filters == NULL) { /* * Use "All Files (*.*) as the default filter is none is specified */ char *defaultFilter = "All Files (*.*)"; p = filterStr = (char*)ckalloc(30 * sizeof(char)); strcpy(p, defaultFilter); p+= strlen(defaultFilter); *p++ = '\0'; *p++ = '*'; *p++ = '.'; *p++ = '*'; *p++ = '\0'; *p++ = '\0'; *p = '\0'; } else { /* We format the filetype into a string understood by Windows: * {"Text Documents" {.doc .txt} {TEXT}} becomes * "Text Documents (*.doc,*.txt)\0*.doc;*.txt\0" * * See the Windows OPENFILENAME manual page for details on the filter * string format. */ /* * Since we may only add asterisks (*) to the filter, we need at most * twice the size of the string to format the filter */ filterStr = ckalloc(strlen(string) * 3); for (filterPtr = flist.filters, p = filterStr; filterPtr; filterPtr = filterPtr->next) { char *sep; FileFilterClause *clausePtr; /* * First, put in the name of the file type */ strcpy(p, filterPtr->name); p+= strlen(filterPtr->name); *p++ = ' '; *p++ = '('; for (pass = 1; pass <= 2; pass++) { /* * In the first pass, we format the extensions in the * name field. In the second pass, we format the extensions in * the filter pattern field */ sep = ""; for (clausePtr=filterPtr->clauses;clausePtr; clausePtr=clausePtr->next) { GlobPattern *globPtr; for (globPtr=clausePtr->patterns; globPtr; globPtr=globPtr->next) { strcpy(p, sep); p+= strlen(sep); strcpy(p, globPtr->pattern); p+= strlen(globPtr->pattern); if (pass==1) { sep = ","; } else { sep = ";"; } } } if (pass == 1) { if (pass == 1) { *p ++ = ')'; } } *p ++ = '\0'; } } /* * Windows requires the filter string to be ended by two NULL * characters. */ *p++ = '\0'; *p = '\0'; } if (ofnPtr->lpstrFilter != NULL) { ckfree((char*)ofnPtr->lpstrFilter); } ofnPtr->lpstrFilter = filterStr; TkFreeFileFilters(&flist); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tk_MessageBoxCmd -- * * This procedure implements the MessageBox window for the * Windows platform. See the user documentation for details on what * it does. * * Results: * See user documentation. * * Side effects: * None. The MessageBox window will be destroy before this procedure * returns. * *---------------------------------------------------------------------- */ int Tk_MessageBoxCmd(clientData, interp, argc, argv) ClientData clientData; /* Main window associated with interpreter. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { int flags; Tk_Window parent = Tk_MainWindow(interp); HWND hWnd; char *message = ""; char *title = ""; int icon = MB_ICONINFORMATION; int type = MB_OK; int i, j; char *result; int code, oldMode; char *defaultBtn = NULL; int defaultBtnIdx = -1; for (i=1; iresult may have been changed by some * background scripts. Call Tcl_SetResult() to make sure that any stuff * lingering in interp->result will not appear in the result of * this command. */ Tcl_SetResult(interp, result, TCL_STATIC); return TCL_OK; arg_missing: Tcl_AppendResult(interp, "value for \"", argv[argc-1], "\" missing", NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ProcessCDError -- * * This procedure gets called if a Windows-specific error message * has occurred during the execution of a common dialog or the * user has pressed the CANCEL button. * * Results: * If an error has indeed happened, returns a standard TCL result * that reports the error code in string format. If the user has * pressed the CANCEL button (dwErrorCode == 0), resets * interp->result to the empty string. * * Side effects: * interp->result is changed. * *---------------------------------------------------------------------- */ static int ProcessCDError(interp, dwErrorCode, hWnd) Tcl_Interp * interp; /* Current interpreter. */ DWORD dwErrorCode; /* The Windows-specific error code */ HWND hWnd; /* window in which the error happened*/ { char *string; Tcl_ResetResult(interp); switch(dwErrorCode) { case 0: /* User has hit CANCEL */ return TCL_OK; case CDERR_DIALOGFAILURE: string="CDERR_DIALOGFAILURE"; break; case CDERR_STRUCTSIZE: string="CDERR_STRUCTSIZE"; break; case CDERR_INITIALIZATION: string="CDERR_INITIALIZATION"; break; case CDERR_NOTEMPLATE: string="CDERR_NOTEMPLATE"; break; case CDERR_NOHINSTANCE: string="CDERR_NOHINSTANCE"; break; case CDERR_LOADSTRFAILURE: string="CDERR_LOADSTRFAILURE"; break; case CDERR_FINDRESFAILURE: string="CDERR_FINDRESFAILURE"; break; case CDERR_LOADRESFAILURE: string="CDERR_LOADRESFAILURE"; break; case CDERR_LOCKRESFAILURE: string="CDERR_LOCKRESFAILURE"; break; case CDERR_MEMALLOCFAILURE: string="CDERR_MEMALLOCFAILURE"; break; case CDERR_MEMLOCKFAILURE: string="CDERR_MEMLOCKFAILURE"; break; case CDERR_NOHOOK: string="CDERR_NOHOOK"; break; case PDERR_SETUPFAILURE: string="PDERR_SETUPFAILURE"; break; case PDERR_PARSEFAILURE: string="PDERR_PARSEFAILURE"; break; case PDERR_RETDEFFAILURE: string="PDERR_RETDEFFAILURE"; break; case PDERR_LOADDRVFAILURE: string="PDERR_LOADDRVFAILURE"; break; case PDERR_GETDEVMODEFAIL: string="PDERR_GETDEVMODEFAIL"; break; case PDERR_INITFAILURE: string="PDERR_INITFAILURE"; break; case PDERR_NODEVICES: string="PDERR_NODEVICES"; break; case PDERR_NODEFAULTPRN: string="PDERR_NODEFAULTPRN"; break; case PDERR_DNDMMISMATCH: string="PDERR_DNDMMISMATCH"; break; case PDERR_CREATEICFAILURE: string="PDERR_CREATEICFAILURE"; break; case PDERR_PRINTERNOTFOUND: string="PDERR_PRINTERNOTFOUND"; break; case CFERR_NOFONTS: string="CFERR_NOFONTS"; break; case FNERR_SUBCLASSFAILURE: string="FNERR_SUBCLASSFAILURE"; break; case FNERR_INVALIDFILENAME: string="FNERR_INVALIDFILENAME"; break; case FNERR_BUFFERTOOSMALL: string="FNERR_BUFFERTOOSMALL"; break; default: string="unknown error"; } Tcl_AppendResult(interp, "Win32 internal error: ", string, NULL); return TCL_ERROR; }