1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243
|
Instructions for creating your own version of DsTool
====================================================
Table of Contents:
I. Motivation
II. Creating your own version of DsTool
III. Executing your own version of DsTool
IV. Linking external libraries into DsTool
V. Adding your own model to DsTool
VI. Adding animation to your model
VII. Adding your own computational code to DsTool
A. Scripting in Tcl/Tk
B. Adding model-specific code
C. Adding general purpose code
D. Incorporating your code into DsTool
E. Accessing data already computed by DsTool
VIII. Adding your own Tcl/Tk code to DsTool
A. Changing default colors and fonts
B. Changing external program calls
C. Changing default values of other global variables
D. Creating Tcl/Tk windows in DsTool
I. Motivation
-------------
This file describes how to create a custom version of DsTool. You might want
to do this for many reasons. For example, you may want to change the default
color or font settings for DsTool, or use a different image or postscript
viewer than the default one. Maybe you want to add models that you are
studying in your own research to DsTool, and you want those models to have
accompanying animations. Perhaps you're feeling adventurous and want to
extend the capabilities of DsTool by writing your own Tcl/Tk windows or your
own computational code. All of these procedures (and more!) are described in
this file.
II. Creating your own version of DsTool
---------------------------------------
0) This assumes that DsTool has already been installed and compiled on your
network and that you have been able to run it! This file, before you
possibly copied it into your own directory, is stored in a subdirectory of
the DsTool directory called my_dstool.
1) You must first define some environmental variables:
setenv DSTOOL <the DsTool code directory>
setenv MY_DSTOOL <directory in which you will put your DsTool stuff>
setenv ARCH <hardware type -- should be: sun4 or solaris or iris or linux>
For example:
setenv DSTOOL /usr/local/src/dstool_tk
setenv MY_DSTOOL $HOME/my_dstool
setenv ARCH `cputype`
These environmental variables should be set for both compilation and
runtime so you may want to add them to your .cshrc file or another
appropriate place.
2) Now make sure the directory MY_DSTOOL exists and copy the source code in
$DSTOOL/my_dstool into $MY_DSTOOL.
mkdir $MY_DSTOOL
cp -r $DSTOOL/my_dstool/* $MY_DSTOOL
3) Now make your personal version of DsTool.
cd $MY_DSTOOL
make
III. Executing your own version of DsTool
-----------------------------------------
1) You must first define an environmental variable:
setenv MY_DSTOOL <directory where you put your DsTool stuff>
For example:
setenv MY_DSTOOL $HOME/my_dstool
2) Test it by running:
dstool_tk
There should be a message saying that the following binary is being
executed: $MY_DSTOOL/bin/$ARCH/my_dstool
Notes on running dstool_tk:
dstool_tk is a script distributed with DsTool which should already be
installed in someplace accessible to you. This script determines where to
look for the dstool_tk binary and the data files which DsTool needs. It uses
the environmental variables MY_DSTOOL, DSTOOL, and ARCH. If the MY_DSTOOL
environmental variable is set, then it tries to execute the binary
$MY_DSTOOL/bin/$ARCH/my_dstool, otherwise it will try to execute
$DSTOOL/bin/$ARCH/dstool_tk. A message is printed which gives the expanded
pathname of the binary which is being executed when the dstool_tk script is
run.
IV. Linking external libraries into DsTool
------------------------------------------
When you write the C code to define your model or execute your computational
code, you may want to use external libraries (NAG, IMSL, ADOLC, etc). To
compile them into DsTool, you will need to modify the file
$MY_DSTOOL/Makefile. Specifically, you will need to add the external library
to the list of user libraries. This might look something like the following:
NAGLIB = /usr/local/lib/libnag.a
USER_LIBS = $(NAGLIB) \
/usr/local/lang/SC1.0/libF77.a
These lines tell the compiler to link to the appropriate NAG and Fortran 77
libraries. If you compile DsTool on a different architecture, you may need
to change these lines to refer to the appropriate architecture-specific
libraries.
V. Adding your own model to DsTool
----------------------------------
We will describe how to compile a new model into DsTool. Please see the
User's manual for a description of how to write the C code for the model.
These instructions simply describe how to create the new executable. You may
find that you can simply modify a model already in DsTool - look in the
directory $DSTOOL/src/models.
1) Write the new model definition in a file which we shall assume is called
new_model_def.c and put this file in the directory $MY_DSTOOL. We shall
assume that the initialization procedure is called new_model_init().
2) Edit the file $MY_DSTOOL/user.c so that the new model is incorporated into
the list of known models. This involves first declaring the initialization
procedure:
extern int new_model_init();
then adding it to the list of user dynamical systems. This might now look
like:
struct DS_DataS USER_DS_Sel[]= {
{ 0, "Lorenz system", lorenz_init },
{ 0, "My new model", new_model_init }
};
The first number is an integer which indicates the category under which the
model should be listed. Initially, there is just one category, but
multiple categories can be set up by modifying the category list in the
following manner:
char *USER_DS_Category[] = { "User models", /* Category 0 */
"Project with Bob", /* Category 1 */
"My favorite dynamical systems" /* Category 2 */
};
3) Edit the file $MY_DSTOOL/Makefile to add new_model_def.c to the USER_SRCS
line and new_model_def.o to the USER_OBJS line. For example:
USER_SRCS = user.c new_model_def.c
USER_OBJS = user.o new_model_def.o
If your model file uses an external library, modify the file
$MY_DSTOOL/Makefile to include the appropriate library, as described in the
above section.
4) Recompile your DsTool binary by executing the 'make' command in the
directory $MY_DSTOOL. See the above section on creating your own version
of DsTool for more details on this and a list of the environmental
variables which must be set for it to work.
VI. Adding animation to your model
----------------------------------
This will assume that you already know how to use Geomview and a little bit
about Geomview's oogl language. You may also find it helpful to look at some
of the system examples.
There are two types of animations. The first is where objects are moved
simply by sending new transformation matrices to Geomview. This is how the
pendulum and restricted three body problem are implemented. Another way is
to write out a new Geomview object every time replacing an existing one. This
is how you make simulated water waves.
We will discuss how to animate using transformation objects first. We will
suppose that you have just created (and installed) a new dynamical system
called "mymodel" and saved it in the file $MY_DSTOOL/mymodel_def.c
1) Create an oogl file for Geomview where the transformation matrices that
need to be updated for animation have names ttt0, ttt1, ttt2, .... Save
this file in $MY_DSTOOL/oogl/mymodel.oogl
2) Edit the model file $MY_DSTOOL/mymodel_def.c by including the following
code before the "#include <ds_define.c>" statement:
#define GV
int (*gv_funct)() = mymodel_gv;
int (*gv_custom_funct)() = NULL;
int gv_n = 3;
static char *gv_filename = "mymodel.oogl";
The value of gv_n should be adjusted to reflect the number of
transformation matrices to be sent to geomview.
3) Continue editing the file $MY_DSTOOL/mymodel_def.c and create the function
mymodel_gv. This function should provide a mapping between the phase and
parameter spaces to the transformation matrices. Each transformation
matrix is 16 doubles which are arranged in a vector so that
f[4*i+j] = T[i][j] (i=0..3, j=0..3). The vectors for each transformation
matrix are concatenated together to make one vector of length 16*gv_n,
where gv_n is the number of transformation matrices. Here is a simple
example where two transformation matrices are used:
/* function used for geomview transformation matrices */
int
mymodel_gv(double *f, double *x, double *p)
{
int i;
/* zero matrix */
for (i=0; i<32; i++) f[i] = 0.0;
/* simply scale first object using p[0] */
f[0] = f[5] = f[10] = 1.0;
f[15] = p[0];
/* rotate second object in the x-y plane by theta=x[0] */
f[0] = f[5] = cos(x[0]);
f[1] = sin(x[0]);
f[4] = -f[1];
f[10] = f[15] = 1.0;
return 0;
}
4) Recompile your personal version of DsTool and see if it works!
If you need more complicated commands sent to Geomview than simply pumping out
transformation matrices in order to provide an animation then you need to
write a custom animation routine for the model.
1) Create an oogl file for Geomview which will be loaded when the model is
selected or through the animation window. Save this file in
$MY_DSTOOL/oogl/mymodel.oogl.
2) Edit the model file $MY_DSTOOL/mymodel_def.c by including the following
code before the "#include <ds_define.c>" statement:
#define GV
int (*gv_funct)() = NULL;
int (*gv_custom_funct)() = mymodel_gv_custom;
int gv_n = 0;
static char *gv_filename = "mymodel.oogl";
In this example we use no transformation matrices, but the two methods can
be combined!
3) Continue editing the file $MY_DSTOOL/mymodel_def.c and create the function
mymodel_gv_custom. This function should use the supplied phase space and
parameter values to write a Geomview object. It should use the procedure
geomview_send(char *) to send strings to Geomview. An example function
would look something like this:
/* function used for geomview animations */
int
mymodel_gv_custom(double *x, double *p)
{
/* wrap commands in progn so that no partial
updates are performed */
geomview_send("(progn\n");
/* send stuff now */
geomview_send("(read geometry {define ");
... AND SO ON ...
geomview_send(")\n");
return 0;
}
4) Recompile your personal version of DsTool and see if it works!
VII. Adding your own computational code to DsTool
-------------------------------------------------
Custom analysis code may be added to DsTool on a model-by-model basis or on a
general purpose basis. The installation of the computational routines in
these two cases is similar. In writing your computational code, you should
check the utility routines in $DSTOOL/src/math_utilities and
$DSTOOL/src/utilities to look for helpful routines. Many matrix utilities
(LU-factorization, Singular value decomposition, etc.) are included in these
directories. In addition, eigenvalue and eigenvector routines can be found in
the directory $DSTOOL/src/eigen.
VII.A. Scripting in Tcl/Tk
--------------------------
As mentioned in the User's manual (section 3.2.2), DsTool can be run entirely
by typing commands at the command prompt. For this reason, when doing simple
computations, it is probably easiest to write them as Tcl/Tk scripts.
Scripting can also be useful for developing routines, since you can modify
scripts and re-execute the same binary, without recompiling every time you
modify your code. Of course, due to the overhead of Tcl/Tk, routines
scripted in Tcl/Tk will be much slower than their C counterparts.
You can run scripts from the terminal by typing "source <filename>", or you
can create Tcl/Tk windows which run the scripts when a button is pressed. For
information on writing Tcl/Tk scripts, consult a comprehensive Tcl/Tk source.
A good on-line reference guide is:
http://www.sco.com/Technology/tcl/Tcl.html
VII.B. Adding model-specific code
---------------------------------
To add model-specific code, two extra C code routines need to be written. The
first should be thought of as initialization code which gets called when the
model is selected. This routine will install your computational code so that
it is usable. The second is cleanup code which gets called when the user
selects some other model. This routine will remove your computational code,
so that the user does not try to use it with another model. Throughout this
and the following sections, we will adhere to the following convention:
procedures using the name "mycode" will refer to the computational
(model-specific or general purpose) routines, and procedures using the name
"mymodel" will refer to routines which perform the operations of adding and
removing objects when the specified model is selected. Hence, procedures
using the name "mymodel" will appear only in this section.
0) Since your code may consist of several files, it would be nice to have it
all together in a subdirectory. Therefore, assume you have created a
subdirectory called MYCODE. Assume further that you have created a new
model definition called mymodel and it is in the file
$MY_DSTOOL/MYCODE/mymodel_def.c.
1) The initialization mymodel_init() in user.c should be as in section V, so
do not mention the subdirectory MYCODE there. However, do not add
mymodel_def.c to USER_SRCS in the Makefile, and do not add mymodel_def.o
to USER_OBJS. Instead, fill out in $MY_DSTOOL/Makefile:
SUBDIRS = tcl MYCODE
and
USER_LIBS = MYCODE/libmycode.a
The idea is to compile the files of MYCODE into the library libmycode.a.
This will happen by using the command "make all", which first creates the
library libmycode.a and then compiles your personal version of DsTool.
2) Edit $MY_DSTOOL/MYCODE/mymodel_def.c to include the following before the
"#include <ds_define.c>" line:
pm(PUT, "Model.Install", mymodel_install,
PUT, "Model.Clean", mymodel_clean,
NULL);
Include declarations for these routines right after the
"#include <model_headers.h>" line:
void mymodel_install(void);
void mymodel_clean(void);
3) Now create a new file, say mymodel.c, in the directory MYCODE, and write
the routine mymodel_install(). This routine can do whatever it wants, even
calling Tcl/Tk code to modify the interface. This is commonly done to add
new menu items to the window menu. The tcl_script(char *) command is used
for Tcl/Tk code. Tcl/Tk procedures may be written and put in files in the
$MY_DSTOOL/tcl/ directory. Here is a simple example:
/* file: mymodel.c */
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include "pm.h" /* these two files are needed to */
#include "utilities.h" /* use the procedure tcl_script */
#include "mymodel.h" /* you create these files */
#include "mycode.h" /* see 4) */
void
mymodel_install()
{
fprintf(stderr, "Executing mymodel_install.\n");
mycode_install();
tcl_script("mymodel(install)");
}
The procedure mycode_install() will install the Postmaster entries
necessary for the computational routines. It is important that this
routine is called before tcl_script("mymodel(install)") to set up the
interaction between the Postmaster and the Tcl/Tk variables correctly.
We will describe this further in section VII.D.
4) Now write the routine mymodel_clean(), also in $MY_DSTOOL/MYCODE/mymodel.c
to deallocate memory assigned in mymodel_install() or revert changes to the
interface. For example:
void
mymodel_clean()
{
fprintf(stderr, "Executing mymodel_clean.\n");
tcl_script("mymodel(clean)");
}
5) The file "mymodel.h" should be created and should contain something like:
/* file: mymodel.h */
#ifndef MYMODEL_H
#define MYMODEL_H
void mymodel_install(void);
void mymodel_clean(void);
#endif
6) The file "mycode.h" should be created and should contain something like:
/* file: mycode.h */
#ifndef MYCODE_H
#define MYCODE_H
void mycode_compute(void);
void mycode_install(void);
#endif
7) In order for all these files to communicate, you should write a Makefile in
the directory $MY_DSTOOL/MYCODE/ that links them together and defines the
library libmymodel.a. This should look like:
#
# Makefile for library
#
TOP = $(DSTOOL)
CONFIG = $(TOP)/config
CURRENT_DIR = ./MYCODE
INCLUDE_DIR = $(TOP)/src/include
include $(CONFIG)/Makefile.defs
LIB_NAME = libmycode.a
SRCS = mymodel_def.c mymodel.c mycode.c
OBJS = mymodel_def.o mymodel.o mycode.o
INCLUDE_FILES = mymodel.h mycode.h
all:: $(LIB_NAME)
include $(CONFIG)/Makefile.rules
8) You still need to write the two procedures called by tcl_script,
mymodel(install) and mymodel(clean). This is done in the file mymodel.tk,
in the directory $MY_DSTOOL/tcl/. As an example, we write the two
procedures such that when you select the model "mymodel" in DsTool, a new
menu option in "Panels" appears that causes a window to pop up. The Tcl/Tk
procedure that creates the window will be discussed below, but the command
that will cause the window to pop up is "window(open) mycode".
The first part of the file mymodel.tk looks like this:
# file: mymodel.tk
# procedure that gets called when mymodel is selected
proc mymodel(install) {} {
new_tcl_pm MyCode
cmd(add_to_panels) "Example menu item..." \
"window(open) mycode"
}
# procedure that gets called when another model is selected
# it must undo what the above procedure did
proc mymodel(clean) {} {
if [winfo exists [build_Wname mymodel]] {
window(dismiss) mymodel
}
remove_tcl_pm MyCode
cmd(remove_from_panels) "Example menu item..."
}
The procedures new_tcl_pm, remove_tcl_pm, cmd(add_to_panels), and
cmd(remove_from_panels) are known to DsTool already. The first two
procedures respectively create and remove Postmaster objects in Tcl/Tk
named MyCode. The latter two commands respectively add and remove a menu
item to the command ("cmd") window's popup menu "Panels".
One final note: Tcl/Tk is very picky about syntax. If you just cut and
paste the routines above into the file mycode.tk (with the spaces before
each line), Tcl/Tk will not pick up that they are procedures, and the
program will crash.
9) You need to edit the Makefile in $MY_DSTOOL/tcl/ in order to let DsTool
know about your mymodel.tk file. In $MY_DSTOOL/tcl/Makefile, fill out:
TK_SRCS = mymodel.tk
Now type "make" in the directory $MY_DSTOOL/tcl/.
10) The preceding steps have developed the framework for adding computational
code in the model-specific case. When your new model is selected, a new
option appears in the Panels menu which will cause a window to pop up.
When you select a different model, that option disappears. We have yet to
implement the computations that we want to perform, and write Tcl/Tk code
for the windows. Skip ahead to section VII.D to continue the installation.
VII.C. Adding general purpose code
----------------------------------
The procedure for adding general purpose code is much simpler than in the
model-specific case. Instead of editing model files so that menu options are
added and removed when different models are selected, you only need to add the
menu option once. The drawback, of course, is that you either have to make
your code more general, or realize that it won't be reliable and might crash
the program (or worse!) if you try to use it with another model. To make
things even more complicated, remember that DsTool models can either be
mappings or vector fields, so if you want your routine to be fully general,
you should prepare it for both of these types of models. An alternative is to
make your window appear differently for mappings and vector fields. For
example, assume you have a computational routine that will work only for
vector fields. When your window is opened (by mycode(build) -- see below),
you can first check whether the current model is a mapping or a vector field.
If it is a mapping, instead of opening your window, you can open a message
window saying that your routines will not work for mappings. Neglecting this
difficulty, the steps we take are:
0) Since your code may consist of several files, it would be nice to have it
all together in a subdirectory. Therefore, assume you have created a
subdirectory called MYCODE.
1) Fill out in $MY_DSTOOL/Makefile:
SUBDIRS = tcl MYCODE
and
USER_LIBS = MYCODE/libmycode.a
The idea is to compile the files of MYCODE into the library libmycode.a.
This will happen by using the command "make all", which first creates the
library libmycode.a and then compiles your personal version of DsTool.
2) Edit the file $MY_DSTOOL/user.c to include your installation procedure
(which we will describe how to write below). For now, assume it is called
mycode_install(). The appropriate lines in user.c should now look
something like:
/* ----------------------------------------------------------------
*
* INCLUDE USER COMPUTATIONAL MODULES HERE
*
* ----------------------------------------------------------------
*/
extern void mycode_install();
typedef void (*PFV)();
PFV user_install_procs[] = {
mycode_install,
(PFV) NULL
};
Be sure not to remove the "(PFV) NULL" entry. DsTool uses this to signal
the end of the array. The procedures in user_install_procs will be called
when your custom version of DsTool starts up, installing your code once and
for all.
3) Edit the file $MY_DSTOOL/tcl/my_app_init.tcl to make the menu option
appear. This should look something like:
proc my_app_init {} {
global COLOR FONT EXT
# Add changes to colors and fonts here
window(open) cmd
# Add new panel items here
cmd(add_to_panels) "Example menu item..." \
"window(open) mycode"
}
This line will cause a new option to appear in the Panels list, which when
pressed will execute the command "window(open) mycode", which opens the
window "mycode". Note that this line must go after the line
"window(open) cmd", since we are changing entries in the Command window.
4) The file "mycode.h" should be created in the directory $MY_DSTOOL/MYCODE,
and should contain something like:
/* file: mycode.h */
#ifndef MYCODE_H
#define MYCODE_H
void mycode_compute(void);
void mycode_install(void);
#endif
5) In order for all these files to communicate, you should write a Makefile in
the directory $MY_DSTOOL/MYCODE/ that links them together and defines the
library libmycode.a. This should look like:
#
# Makefile for library
#
TOP = $(DSTOOL)
CONFIG = $(TOP)/config
CURRENT_DIR = ./MYCODE
INCLUDE_DIR = $(TOP)/src/include
include $(CONFIG)/Makefile.defs
LIB_NAME = libmycode.a
SRCS = mycode.c
OBJS = mycode.o
INCLUDE_FILES = mycode.h
all:: $(LIB_NAME)
include $(CONFIG)/Makefile.rules
6) The preceding steps have developed the framework for adding computational
code in the general purpose case. When DsTool starts up, your custom
version will have an additional option in the Panels menu which will cause
a window to pop up. We have yet to implement the computations that we want
to perform, and write the Tcl/Tk code for the windows. Move on to the next
section to continue the installation.
VII.D. Incorporating your code into DsTool
------------------------------------------
If you followed the steps above, you have set up the necessary framework to
include your computational code. You still must build the Tk window and write
the computational procedures.
To do this, you will need to know a little about DsTool's memory storage
facility, the Postmaster. The Postmaster is the keeper of most of DsTool's
information, and offers an efficient way to pass information from DsTool to
Tcl/Tk and vice versa. All of the non-memory entries of the Postmaster are
shadowed in Tcl/Tk as parts of global arrays. For example, the Postmaster
entry Defaults.Precision is shadowed in Tcl/Tk by the global variable
Defaults(Precision). The command "tcl_to_pm <Obj>" sends the values of the
Tcl/Tk global array <Obj> to the Postmaster, and the command "pm_to_tcl <Obj>"
sends the values of the Postmaster entries of the object <Obj> to the Tcl/Tk
global array <Obj>. By using these commands together: first tcl_to_pm, then
pm_to_tcl, you can update the Postmaster with all of the current information
displayed in the Tcl/Tk interface windows, and return the information to
confirm the values contained in the Postmaster.
1) You first need to let DsTool know about your new Tcl/Tk routines. Edit the
Makefile in $MY_DSTOOL/tcl/ by adding the routine mycode.tk to the TK_SRCS
line. In the model-specific case, you will have two entries on that line
now, and in the general purpose case, you will have only one.
2) Write the procedure that creates the new window that is supposed to pop up
when we select "Example menu item...". Put this procedure in the file
mycode.tk. Our example will be a window with a read-write text field and
two buttons. The read-write text field will illustrate how to use the
Postmaster to send information from Tcl/Tk to DsTool and back again. One
of the buttons will be the "Dismiss" button to close the window. You can
choose to have the other button (for the computations) next to this button,
or right above it. In the first case, the procedure looks like this:
# procedure to open MyCode window
proc mycode(build) name {
global MyCode
build_Title $name "Example window"
build_LabelEntryColumns $name le0 \
{text "" {Number:}} \
{ientry "" {MyCode(Number)}}
bind_LabelEntryColumns $name.le0 1 <Return> mycode(update)
build_DismissButtonbar $name dbbar \
"window(dismiss) mycode" \
{"My Code" mycode(compute_mycode) }
}
If you want them above each other, write something like this:
# procedure to open MyCode window
proc mycode(build) name {
global MyCode
build_Title $name "Example window"
build_DismissButtonbar $name dbbar \
"window(dismiss) mycode" {}
build_LabelEntryColumns $name le0 \
{text "" {Number:}} \
{ientry "" {MyCode(Number)}}
bind_LabelEntryColumns $name.le0 1 <Return> mycode(update)
build_Buttonbar $name bb0 \
{"My Code" mycode(compute_mycode) }
pack $name.le0 -side top
pack $name.bb0 -side bottom
}
The standard routines build_DismissButtonbar, build_Buttonbar,
build_LabelEntryColumns, and bind_LabelEntryColumns are located in the file
$DSTOOL/tcl/utils.tk. The first three routines build the Tcl/Tk structures with the specified callbacks. The last command binds execution of the
command mycode(update) to the pressing of Return in column 1 (the entry
column) of the LabelEntryColumns widget.
The "global MyCode" line is important for accessing the global variable
MyCode(Number), instead of a new local variable that would be otherwise
automatically created by Tcl/Tk. Recall that the global variable
MyCode(Number) shadows a Postmaster entry, which will be used by our
example computational code.
As with the note in section VII.B.8, if you patch the above into a file,
make sure you remove the preceding white space, to conform to proper Tcl/Tk
syntax.
3) To finish the window, you should write enter, leave, and update procedures
for the mycode window. These should all go in the file mycode.tk, and
should look like the following:
proc mycode(enter) {} {
pm_to_tcl MyCode
}
proc mycode(leave) {} {
tcl_to_pm MyCode
}
proc mycode(update) {} {
tcl_to_pm MyCode
pm_to_tcl MyCode
}
These procedures update the Postmaster and/or the Tcl/Tk interface entries
when the user leaves, enters, or presses return in the mycode window.
4) In the previous two steps, you created the procedures that build and update
the window. In the procedure mycode(build), it says:
{"My Code" mycode(compute_mycode) }
The build routine interprets this as saying that when you click the
"My Code" button, the routine mycode(compute_mycode) should be called.
Write this procedure also in the file mycode.tk:
proc mycode(compute_mycode) {} {
begin_wait "Computing my code..."
tcl_to_pm MyCode
pm_to_tcl MyCode
pm EXEC MyCode.compute_mycode
end_wait "Done..."
}
The begin_wait and end_wait commands create messages on DsTool's Command
window to inform the user of the progress of the calculation. The tcl_to_pm
and pm_to_tcl commands update the Postmaster and the Tcl/Tk variables as
described above. Finally, the line "pm EXEC MyCode.compute_mycode" tells
the Postmaster to execute the command MyCode.compute_mycode.
5) The next step is to let the Postmaster know what the objects MyCode and
MyCode.compute_mycode are. This should be done in the procedure
mycode_install() in the new file $MY_DSTOOL/MYCODE/mycode.c. The file
should look like:
/* file: mycode.c */
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <pm.h>
#include "mycode.h"
void
mycode_install()
{
fprintf(stderr, "Executing mycode_install.\n");
/* create elements for window values in the Postmaster */
pm(CREATE_OBJ, "MyCode",
CREATE_ELEM, "MyCode.compute_mycode", FNCT,
CREATE_ELEM, "MyCode.Number", INT,
NULL);
/* initialize the function pointers in the Postmaster */
pm(INIT, "MyCode.compute_mycode",
PUT, "MyCode.compute_mycode", mycode_compute,
NULL);
}
The Postmaster will now execute the procedure mycode_compute when
"pm EXEC MyCode.compute_mycode" is called, which happens when the button
"My Code" is pressed. In DsTool, each Postmaster object has an
installation routine similar to the one above, but most are significantly
more complex.
6) It remains to write the procedure mycode_compute, which is the actual
computational code you want to execute. For our example, we simply put it
inside mycode.c. Recall the declaration of the procedure in mycode.h:
void mycode_compute(void);
Please note that Postmaster functions cannot have any parameters. If
information from DsTool is necessary, the procedure should get this
information from the Postmaster, via the MyCode or other Postmaster
objects. For demonstration, we just print something. Edit
$MY_DSTOOL/MYCODE/mycode.c by adding:
void mycode_compute()
{
fprintf(stderr, "Hello World\n");
}
7) Recompile your personal version of DsTool with the command "make all" and
see if it works!
VII.E. Accessing data already computed by DsTool
------------------------------------------------
Of course you want your computational code to interact with DsTool. For
example, you would like to use the function defined in mymodel_def.c, or use
the fixed points that you computed in DsTool. To give some intuition what
needs to be done, we give an example. The procedure executed when you click on
"My Code" is going to print the eigenvectors of the fixed point specified by
the read-write text field in the MyCode window (or nothing, if there haven't
been enough fixed points computed).
1) Just as you created the object "MyCode" that knows about all the things you
implemented so far, there is an object "Model", defined in
$DSTOOL/src/init/model_install.c, that knows all parameters in the current
model, that is, in mymodel_def.c. Similarly, the object "Fixed", defined in
$DSTOOL/src/fixed/fixed_install.c, knows the parameters in the Fixed
Points window, etc. Moreover, there is an object "Memory" that has access
to the data from previous computations in DsTool.
The file mycode.c needs the following additional include files:
#include "memory.h" /* memory handling routines */
#include "utilities.h" /* definitions used by tcl */
#include "math_utils.h" /* more definition for tcl */
#include "flow.h" /* used for computing the jacobian */
#include "eigen.h" /* eigenvalue routines -- includes rg */
and the following definition:
#define WORK_SIZE 5000
2) Our example function mycode_compute is basically a combination of the
routines in $DSTOOL/src/browser/browser.c and $DSTOOL/src/cont/cont_state.c.
First we find the right fixed point, then compute its eigenvalues and
print them out. Replace the trivial version of mycode_compute above with:
void mycode_compute(void)
{
int i,j,mode,ierror,*iwork,status = 0;
int format = *((int *) pm(GET, "Defaults.Precision", NULL));
memory m = (memory) pm(GET, "Memory.Fixed", NULL);
int n_varb, n_param, n_funcs, n_flows, number,
*p_color, *p_bi;
double *fd_step,*p_varb, *p_param, *p_bd,*wr,*wi,
**jac2,*dwork,**B;
Manifold *manifold;
/* check for NULL memory item */
if (m == NULL) return;
n_varb = *((int *) pm(GET, "Model.Varb_Dim", NULL))-1;
n_param = *((int *) pm(GET, "Model.Param_Dim", NULL));
n_funcs = *((int *) pm(GET, "Model.Funct_Dim", NULL));
n_flows = memory_nflows(m);
/* find correct fixed point */
number = *((int *) pm(GET, "MyCode.Number", NULL));
if ((number < 1) || (number > n_flows)) return;
status = memory_reset_read(m);
if (status) return;
status = memory_set_read(m, number, 1, 1,
NULL, NULL, NULL, NULL, NULL, NULL);
if (status) return;
/* read it */
status = memory_read_next_point(m, &p_varb, &p_param, &p_color,
&p_bd, &p_bi);
/* Set up manifold structure */
manifold = (Manifold *) calloc(1, sizeof(Manifold));
manifold->periodic_varb = ivector(0,n_varb);
manifold->period_start = dvector(0,n_varb);
manifold->period_end = dvector(0,n_varb);
manifold->type = *((int *) pm( GET, "Manifold.Type", NULL ));
pm( GET_LIST, "Manifold.Periodic_Varb", 0, n_varb-1,
manifold->periodic_varb, NULL);
pm( GET_LIST, "Manifold.Period_Start", 0, n_varb-1,
manifold->period_start, NULL);
pm( GET_LIST, "Manifold.Period_End", 0, n_varb-1,
manifold->period_end, NULL);
wr = dvector(0,n_varb);
wi = dvector(0,n_varb);
jac2 = dmatrix(0,n_varb+1,0,n_varb+1);
fd_step = dvector(0,n_varb);
dwork = dvector(0,WORK_SIZE);
for(i=0; i<n_varb; i++) fd_step[i] = 1.0e-6;
/* How do we compute the Jacobian? */
if( (void *) pm( GET, "Model.DfDx", NULL ) == NULL )
mode = FORW_DIFF;
else mode = ANALYTIC;
/* Compute it! */
if (*((int *) pm( GET, "Model.Mapping_Flag", NULL ))) {
ierror = dfdx( jac2, n_varb, fd_step, p_varb, p_param,
*((int *) pm( GET, "Flow.Skip_Size", NULL )),
manifold, FORWARD,
(void *) pm( GET, "Model.DS_Def", NULL ),
(void *) pm( GET, "Model.DfDx", NULL ),
mode, dwork );
} else {
ierror = dfdx( jac2, n_varb, fd_step, p_varb, p_param, 0,
manifold, FALSE,
(void *) pm( GET, "Model.DS_Def", NULL ),
(void *) pm( GET, "Model.DfDx", NULL ),
mode, dwork );
}
/* Now, find the eigenvalues */
iwork = ivector(0,WORK_SIZE);
B = dmatrix(0,n_varb+1,0,n_varb+1);
for(i=0; i<n_varb; i++)
for(j=0; j<n_varb; j++)
B[i+1][j+1] = jac2[i][j];
/* rg is the eigenvalue finder: it's based upon eispack routines,
* and is located in $DSTOOL/src/eigen/rg.c
*/
ierror = rg( n_varb , n_varb , B , &wr[0]-1 , &wi[0]-1 , FALSE ,
NULL , iwork , dwork );
/* Print out ev's */
for(i=0; i<n_varb; i++) {
fprintf (stdout, "%.*f + %.*fi\n", format, wr[i], format,wi[i]);
}
/* Free memory allocated by dvector and ivector */
free_ivector(iwork,0,WORK_SIZE);
free_dvector(fd_step,0,n_varb);
free_dvector(dwork,0,WORK_SIZE);
free_dvector(wr,0,n_varb);
free_dvector(wi,0,n_varb);
free_dmatrix(jac2,0,n_varb+1,0,n_varb+1);
/* Free memory from manifold */
free_ivector(manifold->periodic_varb,0,n_varb);
free_dvector(manifold->period_start,0,n_varb);
free_dvector(manifold->period_end,0,n_varb);
free(manifold);
}
3) Recompile your personal version of DsTool with the command "make all" and
see if it works!
VIII. Adding your own Tcl/Tk code to DsTool
-------------------------------------------
You may want to add your own Tcl/Tk code to DsTool for many reasons, ranging
from simply wanting to change the default color scheme to wanting to create
your own windows to control computations.
VIII.A. Changing default colors and fonts
-----------------------------------------
The simplest kind of customization is changing default colors or fonts. Since
the Tcl/Tk procedure my_app_init (in the file $MY_DSTOOL/tcl/my_app_init.tcl)
is called *after* app_init_tk and app_init_tcl, changes to default values of
global variables which are initialized on start-up can be made by adding the
appropriate lines to my_app_init. In the case of colors or fonts, a command
like:
set COLOR(views) white
will change the default background color for twoD and oneD view windows to
white. Note that changes to default colors and fonts must be made *before*
the line "window(open) cmd", so that the Command window is built with the new
defaults. Fonts should be changed to font names in standard notation, and
colors can be changed to either standard color names or hexadecimal notation.
The complete color and font lists (with defaults in parentheses) are:
Colors:
-------
COLOR(bg) -- widget background color (#acf)
COLOR(abg) -- active background color (#57f)
COLOR(hbg) -- not currently used
COLOR(views) -- background color for view windows (black)
COLOR(entry) -- entry widget background color (white)
COLOR(info) -- info (messages/entries) color (blue)
Fonts:
------
FONT(button) -- button font (-Adobe-Helvetica-Medium-R-Normal--*-120-*)
FONT(label) -- label font (-Adobe-Helvetica-Medium-R-Normal--*-120-*)
FONT(biglabel) -- big label font (-Adobe-Helvetica-Medium-R-Normal--*-140-*)
FONT(boldlabel) -- bold label font (-Adobe-Helvetica-Bold-R-Normal--*-120-*)
VIII.B. Changing external program calls
---------------------------------------
While DsTool is mostly self-contained, it is designed to use external programs
for some tasks. Specifically, DsTool calls external programs for viewing
images, postscript files, and html documentation, and for creating GIF and
TIFF files (actually, converting to those formats from X11 bitmaps). The
global Tcl/Tk array EXT contains the calls for all of the external programs
that DsTool will use. It contains the following entries (defaults in
brackets):
EXT(imgview) -- image (GIF, TIFF, PPM, X11 Bitmap) viewer [xv]
EXT(psview) -- postscript viewer [gs]
EXT(convertgif) -- image conversion tool, PPM -> GIF [convert]
EXT(converttiff)-- image conversion tool, PPM -> TIFF [convert]
EXT(browser) -- web browser [netscape]
and the actual default calls are (from app_init.tk):
set EXT(browser) {exec netscape $Html(Url) &}
set EXT(convertgif) {exec convert "ppm:$Snap(Directory)/snaptempgif" \
"tiff:$Snap(Directory)/$Snap(Filename)"}
set EXT(converttiff) {exec convert "ppm:$Snap(Directory)/snaptemptiff" \
"tiff:$Snap(Directory)/$Snap(Filename)"}
set EXT(imgview) {exec xv $fpath &}
set EXT(psview) {exec gs $fpath &}
To change an external program, you need to change the complete call. A
convenient place to do this is the procedure my_app_init (in the file
$MY_DSTOOL/tcl/my_app_init.tcl). The new calls can be placed anywhere in
that procedure, since the Command window does not use any external programs.
For example, the lines
set EXT(psview) {exec imagetool $fpath &}
set EXT(convertgif) {exec ppmtogif "$Snap(Directory)/snaptempgif" > \
"$Snap(Directory)/$Snap(Filename)"}
would change the external postscript viewer to imagetool, and the conversion
(PPM->GIF) utility to ppmtogif.
The syntax for these calls is important! The call needs to be enclosed in
curly braces. This keeps it from being evaluated until it is needed.
Eventually, it will be called using the "eval" command. The "exec" command
tells Tcl/Tk to execute the command which follows. The remainder of the line
should be the proper syntax for the external program, with dollar signs in
front of names of files which are to be accessed (e.g. fname for postscript
and image files, and Html(Url) for URLs). The quotes around the filenames are
important, as they force Tcl/Tk to evaluate the contained variables. Without
them, ppmtogif would be looking for the file $Save(Directory)/snaptempgif, and
would not be able to find it. The trailing ampersand is important so that
DsTool doesn't lose control to the external program. Note that the syntax for
the above calls had to be modified to conform to the appropriate calling
syntax for the new programs.
VIII.C. Changing default values of other global variables
---------------------------------------------------------
If you want to change another global variable, you should:
1) be very careful that you know what you're doing and how it will affect
the program. For example, you may want to change the Postmaster entry
Defaults.Precision, which controls the number of digits which are
displayed in the Tk windows, among other things. This is *not* advisable.
Due to the frequent interaction between Tcl/Tk and the Postmaster (via the
tcl_to_pm and pm_to_tcl commands), you will be introducing (possibly large)
roundoff errors into the Postmaster. Depending upon your application,
these errors may not be significant, but you should be aware of them.
2) be sure to declare it a global variable via the "global" statement, or if
it is part of an array, declare the array as a global variable (as with
COLOR, FONT, etc). If you fail to do this, Tcl/Tk will introduce a local
variable with the same name, and the global variable will not be changed.
3) be aware that my_app_init is called only once. The importance of this is
that if you wish to change default values for variables which are
initialized when windows are opened, you may not be able to change them.
You should check the appropriate Tcl/Tk script to see how the variable is
initialized. If it is initialized from the Postmaster, you can change the
default in the Postmaster by issuing the proper Postmaster commands in
my_app_init. For example, to have the auto-scaling factor default to 1.0,
put the command "pm PUT Defaults.Plot_Scale_Factor 1.0" in my_app_init.
When the Defaults window is opened, the auto-scaling factor will be 1.0.
However, if the variable is initialized in a different way, you may not be
able to change it easily.
VIII.D. Creating Tcl/Tk windows in DsTool
-----------------------------------------
The code for all of the user interface windows is located in the $DSTOOL/tcl
directory. Several utility routines are in utils.tk and utils.tcl. These
routines are used to create windows and widgets. It is recommended that you
use these routines in order to preserve continuity of the interface features.
For more information on Tcl/Tk and the use and design of widgets, a good
on-line reference is available at:
http://www.sco.com/Technology/tcl/Tcl.html
There are two basic steps involved in creating a window:
1) Write the user interface code in Tcl/Tk. Create a file, say my_panel.tk.
This file must contain a procedure called my_panel(build) that contains the
code for building the user interface components of the window. Each window
should have a dismiss button to close it. In addition, if Postmaster
entries are involved, you should write procedures my_panel(enter),
my_panel(leave), and my_panel(update), which update the Tcl/Tk variables
to their Postmaster counterparts, and vice versa.
2) Create a menu entry for the window. The command "cmd(add_to_panels)
'Example' 'window(open) my_panel'" adds an entry to the panel list which
has label "Example" and calls "window(open) my_panel" when selected. This
line can either be added to my_app_init or called from mymodel(install), as
described in sections VII.B and VII.C. The command "window(open) my_panel"
calls my_panel(build) to build the window.
An example: Selected Point window:
The Selected Point window code is in $DSTOOL/tcl/selected.tk, and contains six
procedures:
selected(build) -- builds the window.
selected(update) -- updates the Postmaster and Tcl/Tk variables
when the user presses the Return key.
selected(enter) -- updates the Postmaster and Tcl/Tk variables
when the user enters the window.
selected(leave) -- updates the Postmaster and Tcl/Tk variables
when the user leaves the window.
selected(copy) -- copies the final column to the initial column.
selected(save_point) -- saves the initial column point in the Postmaster's
memory.
For illustrative purposes, we now give a detailed description of some of the
procedures which are used in the Selected Point window. The first two are
utility routines; the next three are postmaster communication routines, and
the final five are for building the objects in the window.
array_to_list Model Varb_Names
Converts an array object into a list object. The list returned contains the
entries Model(Varb_Names,<i>) where <i> runs over all valid entries.
message_show "Initial point saved."
Displays the message "Initial point saved." in the command window.
tcl_to_pm Selected
Updates the Tcl/Tk variables in the global array Selected from their
corresponding Postmaster entries.
pm EXEC Selected.Copy
Executes the Postmaster command Selected.Copy, which copies the point in the
final point memory (of the Postmaster's Selected Point object) into the
initial point memory.
pm_to_tcl Selected Memory
Updates the entries in the Postmaster objects Selected and Memory from their
corresponding Tcl/Tk global variables.
build_Title $name "Selected point"
Builds the title of the window on the top frame border. The name of the
parent widget is $name, and "Selected point" is the string used for the title.
build_DismissButtonbar $name dbbar "window(dismiss) selected" \
{"Copy final to initial" selected(copy) } \
{"Save point" selected(save_point)}
Builds a dismiss button bar at the bottom of the window. The button bar has
a dismiss button, which calls "window(dismiss) selected" when pressed. In
addition, the button bar contains a button labelled "Copy final to initial",
which calls selected(copy) when pressed, and a button labelled "Save point",
which calls selected(save_point) when pressed. The name of the parent window
is $name, and $name.dbbar will be the name of the button bar.
build_CmdFrame $name cmd
Builds a command frame to contain other widgets. The name of the parent frame
is $name, and $name.cmd will be the name of the command frame.
build_LabelEntryColumnsScroll $cmd le0 250 \
[list label " " [ concat \
[array_to_list Model Varb_Names] \
[array_to_list Model Param_Names] ] \
] \
[list dentry "Initial" [ concat \
[array_to_list Selected Varb_Ic] \
[array_to_list Selected Param_Ic] ] \
] \
[list dlabel "Final" [ concat \
[array_to_list Selected Varb_Fc] \
[array_to_list Selected Param_Fc] ] \
]
Builds a scrollable widget consisting of three columns. The first column has
an empty header label, and then a list of the variables and parameters from
the current system. The second column is labelled "Initial", and consists of
the Tcl/Tk variables Selected(Varb_Ic,<i>) and Selected(Param_Ic,<i>). The
last column is labelled "Final", and consists of the Tcl/Tk variables
Selected(Varb_Fc,<i>) and Selected(Param_Ic,<i>). The name of the parent
widget is $cmd, and $cmd.le0 will be the name of the new widget. The number
250 specifies the height of the widget.
bind_LabelEntryColumns $cmd.le0 1 <Return> selected(update)
Binds the execution of a command to a key press in a column of the
LabelEntryColumns widget $cmd.le0. In this case, the column is 1 (the
"initial" column), the key is Return, and the command which is executed is
the Tcl/Tk procedure selected(update).
|