Tcl/Tk 烹调书 - Tcl/Tk 和 C


第 2 步: 注册用户命令。

模版

这个脚本的模版是在 Tcl/Tk 源代码发布中的 tkAppInit.c (在放置 Tk 源代码的目录下查找 - 象 ~pdsrc/TclTk/tk4.0 一类的东西)。复制一份 tkAppInit.c 到你的工作目录中。重命名为 myTclInit.c (在 ~cookbook/code/ch6 下的源代码中还有一个 Makefile 模版)

在下面的脚本中,使用粗体字来表示定制的程序代码,使用斜体字表示我们在代码中插入的注释:

myTclInit.c


#ifndef lint
static char sccsid[] = "@(#) tkAppInit.c 1.12 94/12/17 16:30:56";
#endif /* not lint */
#include "tcl.h"
#include "tk.h"



/*Include files for PHIGS & X */

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xatom.h>
#include <phigs/phigs.h>


/*Pconnid_x_drawable 是持有一个 X window 的 id 的一个结构,PHIGS 将在这个窗口中打开*/


Pconnid_x_drawable conn_id;

/* 在 phigscbs.c 中定义的过程 */ 
/* 在 phigscbs.c 中定义的一个基于 PHIGS 的 C 过程,它建立一个立方体和简单的文本串 */


extern int MakeCubeProc();


/* 在 phigscbs.c 中定义的一个基于 PHIGS 的 C 过程,它提供 PHIGS 变换矩阵来把对象(物体)旋转给定度数并帖出(post)这个结果(对象)。用户通过移动一个 Tk 缩放组件来输入旋转的角度。由  PHIGS 处理 3D 结构的编辑和更新工作站。*/


extern int rotate_boxProc();

/*在 phigscbs.c 中定义的一个基于 PHIGS 的 C 过程,它在发生一个外部事件或 PHIGS 工作站的状态改变的时候,例如帖出一个结构的时候,重画所有结构。PHIGS 处理工作站的更新。*/



extern int redrawProc();

/*在 phigscbs.c 中定义的一个基于 PHIGS 的 C 过程,它改变用 Tk 菜单条目选择的已显示的 3D 对象的颜色。PHIGS 处理 3D 结构的编辑并更新工作站。*/



extern int ChColProc();

/*在 phigscbs.c 中定义的一个基于 PHIGS 的 C 过程,它关闭 PHIGS 工作站和关闭 PHIGS 并在用户选择 Tk Quit 按钮的时候退出应用。*/


extern int CleanupAndQuitProc();


Tk_Window 是 Tk 表示一个窗口的记号(token)。在建立一个新窗口时返回这个记号。你可以使用它来查询信息或操纵一个 Tk 组件。

过程 "Tcl_AppInit" 在下述过程之后定义,调用 "Tk_CreateMainWindow" 来建立应用的主窗口。



Tk_Window mainwin, win;

/* 这个过程在 Tk 画布组件中安装 PHIGS 工作站并做一些家务。这个过程通过用户命令 SetupPhigs 向 Tcl/Tk 解释器注册。向 SetupPhigs 传递一个参数 - 画布组件的路径名。*/



int SetupPhigsProc (ClientData clientdata, Tcl_Interp *interp,
	 int argc, char *argv[])
{
Window winid;

Pxphigs_info	xphigs_info;
unsigned long mask;
XSetWindowAttributes	win_attrs;
Display *dsp;

在调用这个过程的时候,画布组件的路径名作为 argv[1] 参数来传递。Tk_NameToWindow 返回在叫做"mainwin"的主窗口中的画布组件的记号。

使用这个 "win" 记号来把必要的连接标识符赋予给 PHIGS 的画布。

Tk_Display 接受一个 Tk_Window 记号作为参数并返回一个指针,它指向结构显示器 - 画布组件的 X 显示器。

Tk_WindowId 返回画布窗口的 X 标识符。

 


win = Tk_NameToWindow(interp, (char *)argv[1], (Tk_Window) mainwin);
dsp= Tk_Display(win);
winid = Tk_WindowId(win);

conn_id.display = dsp;
conn_id.drawable_id = winid;


告诉 PHIGS 不监视调色板等。并以适当监视权限来打开 PHIGS 工作站。

 



mask = PXPHIGS_INFO_FLAGS_NO_MON;
xphigs_info.flags.no_monitor =1;


popen_xphigs (PDEF_ERR_FILE,PDEF_MEM_SIZE,mask,&xphigs_info);

win_attrs.backing_store = NotUseful;
XChangeWindowAttributes( dsp,winid, CWBackingStore, &win_attrs);

使用 Tk_CreateEventHandler 为指定的窗口事件设置处理器;在用第一个参数给出的窗口中的,通过给定的掩码选择的特定事件发生的时候,调用特定的过程。


Tk_CreateEventHandler(win, ExposureMask,
	(Tk_EventProc *) redrawProc,NULL);

return 0;
}

/*
 * The following variable is a special hack that is needed in order for
 * Sun shared libraries to be used for Tcl.
 */

#ifdef NEED_MATHERR
extern int matherr();
int *tclDummyMathPtr = (int *) matherr;
#endif

int
Tcl_AppInit(interp)
    Tcl_Interp *interp;		/* 给应用的解释器。*/
{


    mainwin = Tk_MainWindow(interp);

    if (Tcl_Init(interp) == TCL_ERROR) {
	return TCL_ERROR;
    }
    if (Tk_Init(interp) == TCL_ERROR) {
	return TCL_ERROR;
    }
/* 为包含的包调用 init 过程。每个调用看起来都象下面这样:
 * if (Mod_Init(interp) == TCL_ERROR) {
 * return TCL_ERROR;
 * }
 *
 * 这里的 "Mod" 是模块的名字。
 */
/*
 * 为特定于应用的命令调用 Tcl_CreateCommand,
 * 它们已经被上面调用的 init 过程建立了。
 */

	Tcl_CreateCommand(interp, "SetupPhigs",
		(Tcl_CmdProc *)SetupPhigsProc,(ClientData )NULL,
				(Tcl_CmdDeleteProc *) NULL);
	Tcl_CreateCommand(interp, "ChCol",
		(Tcl_CmdProc *) ChColProc,(ClientData )NULL,
		(Tcl_CmdDeleteProc *) NULL);
	Tcl_CreateCommand(interp, "CleanupAndQuit",
		(Tcl_CmdProc *) CleanupAndQuitProc,(ClientData )NULL,
		(Tcl_CmdDeleteProc *) NULL);
	Tcl_CreateCommand(interp, "rotate_box",
		(Tcl_CmdProc *) rotate_boxProc,(ClientData )NULL,
		(Tcl_CmdDeleteProc *) NULL);
	
	Tcl_CreateCommand(interp, "MakeCube",
		(Tcl_CmdProc *) MakeCubeProc,(ClientData )NULL,
		(Tcl_CmdDeleteProc *) NULL);


/*
 * 指定在应用在交互式运行时调用的一个特定于用户的启动文件。典型的启动文件是 "~/.apprc",
 * 这里的 "app" 是应用的名字。如果删除了这一行,则在任何条件下都不运行特定于用户的启动文件。
 */

	tcl_RcFileName ="~/.myapprc";

        return TCL_OK;
}

Tcl_CreateCommand 向要在其中使用命令的解释器 interp 注册一个用户命令。给 Tcl_CreateCommand  的第二个参数是在 Tcl/Tk 脚本中使用的命令的名字。第三个参数是命令过程。 使用 clientData 来传递与命令相关的对象的的地址,而 deleteProc 指定在命令被删除时要调用的过程。用它来释放作为而与命令相关的对象,如 clientData。 

在这个例子中,我们建立了可以在 Tcl 脚本中使用的五个命令,它们可被我们的这个版本的 Tcl 解释器分析。

注意设置任何启动文件都需要 tcl_RcFileName。



/*
 *----------------------------------------------------------------------
 *
 * main --
 *
 *	This is the main program for the application.
 *
 * Results:
 *	None: Tk_Main never returns here, so this procedure never
 *	returns either.
 *
 * Side effects:
 *	Whatever the application does.
 *
 *----------------------------------------------------------------------
 */

int
main(argc, argv)
    int argc;			/* Number of command-line arguments. */
    char **argv;		/* Values of command-line arguments. */
{
    Tk_Main(argc, argv,Tcl_AppInit);
    return 0;			/* Needed only to prevent compiler warning. */
}



Makefile

复制并采用在~cookbook/code/ch6 下面的那个 Makefile 并编译来建立一个扩展的 Tcl 解释器来包含你的命令。如果你执行二进制文件 myapp ,将见到一个使用这个 Tcl 解释器的 wish shell: