/Users/craigcornelius/Projects/SPRING Mac Release 0.2/displayreplicator.cpp

Go to the documentation of this file.
00001 // $Id: displayreplicator.cpp,v 1.61 2006/05/31 00:39:08 craig Exp $
00002 //
00003 // DisplayReplicator code:
00004 //      National Biocomputation Center (http://biocomp.stanford.edu)
00005 //
00006 // Next steps on this code:
00007 //      - doublebuffering
00008 //      - events working correctly
00009 //
00010 // Notes:
00011 //      - by default, we are expecting to be running TCP- it's more reliable and fast enough
00012 //  - we actually implemented a reliable UDP for this, but it greatly increased the code's
00013 //      complexity and provided no real increase in performance
00014 //
00015 
00016 #ifdef _WIN32
00017 
00018 //#include <windows.h>
00019 #include <winsock.h>
00020 
00021 #else
00022 
00023 #include <string.h>
00024 #include <unistd.h>
00025 #include <pthread.h>
00026 
00027 #endif
00028 
00029 #include "displayreplicator.h"
00030 
00031 #ifdef _WIN32
00032 #include <glut.h>
00033 #else
00034 #include <GLUT/glut.h>
00035 #include <OpenGL/gl.h>
00036 #endif
00037 
00038 //#include <mui.h>
00039 #include <time.h>
00040 #include <sys/timeb.h>
00041 #include <stdlib.h>
00042 #include <stdio.h>
00043 #include "timer.h"
00044 //#include <pthread.h>
00045 
00046 #include <iostream>
00047 
00048 #include "glui.h"
00049 
00050 using namespace std;
00051 
00052 const char* DisplayReplicator::rcsid = "@(#) $Id: displayreplicator.cpp,v 1.61 2006/05/31 00:39:08 craig Exp $ $Copyright: (c)2001 National Biocomputation Center, Stanford University $";
00053 
00054 #define BLOCKSIZE_X 16
00055 #define BLOCKSIZE_Y     16
00056 #define MAXBLOCKS_X     2048/BLOCKSIZE_X
00057 #define MAXBLOCKS_Y     2048/BLOCKSIZE_Y
00058 #define PERCENT_CHANGE_MAX (-100)
00059 #define RGB_CHANGE_MAX (25)
00060 
00061 #define MAX_WIDTH 640
00062 #define MAX_HEIGHT 480
00063 #define MAX_LENGTH_JPEG 100000
00064 
00065 char old_data[2][MAXBLOCKS_X][MAXBLOCKS_Y][BLOCKSIZE_X][BLOCKSIZE_Y][3];
00066 bool oldDataInitialized = false;
00067 bool headerInitialized = false;
00068 int do_switchboard = 1;
00069 
00070 //static char data[2][MAXBLOCKS_X*MAXBLOCKS_Y*BLOCKSIZE_X*BLOCKSIZE_Y*3];
00071 const char* handshake = "displayserver v3.0";
00072 
00073 
00074 #ifndef SGI
00075 #ifndef __APPLE__
00076 LPBYTE      OutputBuffer = new unsigned char[MAX_LENGTH_JPEG];     // receive pointer to DIB
00077 DWORD      OutputLength;     // receive length of DIB
00078 BITMAPINFO OutputBitmapInfo;  // receives DIB BITMAPINFO
00079 LPBYTE      OutputBuffer2;     // receive pointer to DIB
00080 DWORD      OutputLength2;     // receive length of DIB
00081 BITMAPINFO OutputBitmapInfo2;  // receives DIB BITMAPINFO
00082 #endif // __APPLE__
00083 #endif // SGI
00084 
00085 void DisplayReplicator::InitHeader(int conf_width, int conf_height)
00086 {
00087 #ifndef SGI
00088 #ifndef __APPLE__
00089     int main_size = conf_width*conf_height*3;
00090 #define PUT_2B(array,offset,value)  \
00091     (array[offset] = (char) ((value) & 0xFF), \
00092     array[offset+1] = (char) (((value) >> 8) & 0xFF))
00093 #define PUT_4B(array,offset,value)  \
00094     (array[offset] = (char) ((value) & 0xFF), \
00095     array[offset+1] = (char) (((value) >> 8) & 0xFF), \
00096     array[offset+2] = (char) (((value) >> 16) & 0xFF), \
00097     array[offset+3] = (char) (((value) >> 24) & 0xFF))
00098     
00099     WHeader = (BYTE*)calloc(54+main_size,sizeof(BYTE));
00100     
00101     WHeader[0] = 66; // ASCII 'B'
00102     WHeader[1] = 77; // ASCII 'M'
00103     PUT_4B(WHeader,2,main_size+54);
00104     PUT_4B(WHeader,10,54);
00105     PUT_4B(WHeader,14,40);
00106     PUT_4B(WHeader,18,conf_width);
00107     PUT_4B(WHeader,22,conf_height);
00108     PUT_2B(WHeader,26,1);
00109     PUT_2B(WHeader,28,24);
00110     PUT_4B(WHeader,34,main_size);
00111 #endif // __APPLE__
00112 #endif
00113 }
00114 
00115 int DisplayReplicator::debug = 0;
00116 
00117 
00118 DisplayReplicator::DisplayReplicator(int port_in, source_type source_in)
00119 {
00120     num_socks = 0;
00121     port = port_in;
00122     
00123     mode = unknown;
00124     source = source_in;
00125     
00126     width = height = -1;
00127     height = MAX_HEIGHT;
00128     keyfn = NULL; specialfn = NULL; mousefn = NULL; motionfn = NULL;
00129     
00130     // allocate memory for largest will need
00131     block_size = BLOCKSIZE_X * BLOCKSIZE_Y * 3;
00132     block = new char[block_size];
00133     buf_size = block_size + 4;
00134     
00135     if  (buf_size < MAX_BUF_SIZE)
00136         g_buf = new char[MAX_BUF_SIZE];
00137     else
00138         g_buf = new char[buf_size];
00139     
00140     gl_buf = new char[640*480*3];
00141     
00142     
00143     //buf = new char[buf_size];
00144     buf = g_buf;
00145     
00146     // compression
00147     res_reduction = 1;
00148     compression = JPEG;
00149     //compression = color_24bit;
00150     //compression = color_16bit;
00151     //compression = color_8bit;
00152     //compression = greyscale;
00153     comparison_threshold = (int) (PERCENT_CHANGE_MAX / 100.0f * block_size);
00154     from_windows = 0;
00155     
00156     switch (compression) {
00157     default:
00158     case color_24bit:   // 24bits: RGB
00159         comp_size = BLOCKSIZE_X * BLOCKSIZE_Y * 3;
00160         break;
00161     case color_16bit:   // 16 bits: R(6), G(5), B(5)
00162         comp_size = BLOCKSIZE_X * BLOCKSIZE_Y * 2;
00163         break;
00164     case color_8bit:    // 8bits: R(3), G(3), B(2):  rrrgggbb
00165         comp_size = BLOCKSIZE_X * BLOCKSIZE_Y * 1;
00166         break;
00167     case greyscale:     // 8bits: R(3), G(3), B(2):  rrrgggbb
00168         comp_size = BLOCKSIZE_X * BLOCKSIZE_Y * 1;
00169         break;
00170     }  // end switch
00171     
00172     buf_size = comp_size + 4;
00173     if  (buf_size < MAX_BUF_SIZE)
00174         g_buf = new char[MAX_BUF_SIZE];
00175     else
00176         g_buf = new char[buf_size];
00177     buf = g_buf;
00178     
00179     JPEG_data = new char[MAX_WIDTH*MAX_HEIGHT*3];
00180     
00181     mySocketBuffer = new char[MAX_BUF_SIZE];
00182     curBufLen = 0;
00183     
00184     memset(old_data, 0, sizeof(old_data));
00185 }
00186 
00187 DisplayReplicator::~DisplayReplicator()
00188 {
00189     for (int i=0; i<num_socks; i++)
00190         if (sock[i]) delete sock[i];
00191         mode = unknown;
00192         width = height = -1;
00193         keyfn = NULL; specialfn = NULL; mousefn = NULL; motionfn = NULL;
00194         if (block) { delete block; block = NULL; }
00195         if (g_buf) { delete buf; buf = NULL; }
00196         if (mySocketBuffer) { delete mySocketBuffer; mySocketBuffer = NULL; }
00197 }
00198 
00199 
00200 void DisplayReplicator::setCompression(compression_type comp)
00201 {
00202     compression = comp;
00203 }
00204 
00205 // sends size bytes from buf to other side.  size should always be < buf_size
00206 int DisplayReplicator::send_buf_out_socket(int sock_index, int size, char *whichBuf)
00207 {
00208     if (whichBuf == NULL)
00209         whichBuf = buf;
00210     
00211     if (debug == 3)
00212         cerr << "DisplayReplicator::send_buf_out_socket(" << sock_index 
00213         << ", " << size << ")\n";
00214     
00215     // locals- assume doing all (start and end are inclusive)
00216     int start = 0, end = num_socks-1;
00217     int ret_val = 0;    // assume everything okay
00218     
00219     // if supposed to send to just one, set it up
00220     if (sock_index != -1) {
00221         start = sock_index; end = sock_index;
00222     }
00223     
00224     // loop over all the sockets we're supposed to do
00225     for (int i=start; i<=end; i++) {
00226         if (!sock[i]) continue;
00227 
00228         int status = sock[i]->BufferedSend(whichBuf, size);
00229 
00230         //int status = sock[i]->Send(whichBuf, size);
00231         if (!status) {
00232             cerr << "LOST CONNECTION ON SOCKET " << i << endl;
00233             Disconnect(i);
00234             ret_val = -1;
00235             continue;
00236         }
00237     }
00238     return(ret_val);    // success
00239 }
00240 
00241 // user-level function for setting compression parameters
00242 void DisplayReplicator::SetParams(int rr_in, compression_type compression_in,
00243                                   int comparison_threshold_in, int from_win)
00244 {
00245     res_reduction = rr_in; 
00246     compression = compression_in;
00247     comparison_threshold = comparison_threshold_in;
00248     
00249     // encode it
00250     buf[0] = 3; buf[1] = char(res_reduction); buf[2] = char(compression);
00251     buf[3] = char(from_win);
00252     
00253     // and send to the other side
00254     send_buf_out_socket(-1, buf_size);
00255 }
00256 
00257 // compares the given two blocks
00258 int DisplayReplicator::compare_blocks(char* block1, char* block2)
00259 {
00260     // if threshold is < 0, don't even bother- assume compare fails
00261     if ((comparison_threshold < 0) || (!oldDataInitialized)) return(1);
00262     
00263     // otherwise, compare
00264     int s = 0;
00265     for (int i=0; i<block_size; i++) {
00266         char c1 = block1[i], c2 = block2[i], cdiff;
00267         if (c1 > c2) cdiff = c1 - c2;
00268         else cdiff = c2 - c1;
00269         if (cdiff >= RGB_CHANGE_MAX) {
00270             if (comparison_threshold == s) return (1);
00271             ++s;
00272         }
00273     }
00274     return(0);
00275 }
00276 
00277 int DisplayReplicator::encode_block_into_buf(int buf_code, 
00278                                              int x_loc, int y_loc, int seq_id, char* block)
00279 {
00280     buf[0] = (char)0xe2;
00281     if (compression != JPEG) 
00282     {
00283         buf[1] = 0x00;
00284         buf[2] = (unsigned char)(buf_code); 
00285         buf[3]= (unsigned char)(x_loc); 
00286         buf[4] = (unsigned char)(y_loc); 
00287     }
00288     else 
00289     {
00290         buf[1] = 0x01;
00291         buf[2] = (unsigned char)(buf_code);
00292         buf[3] = (unsigned char)(from_windows);
00293         buf[4] = 0x00;
00294     }
00295     
00296     int reduction_factor = 1 << (res_reduction-1);
00297     
00298     int x,y;
00299     int buf_index = 5;  // header info
00300     switch (compression) {
00301     default:
00302     case color_24bit:   // 24bits: RGB
00303         for ( x=0; x<BLOCKSIZE_X; x += reduction_factor)
00304             for ( y=0; y<BLOCKSIZE_Y; y += reduction_factor)
00305             {
00306                 int block_index = x*BLOCKSIZE_Y*3+y*3;
00307                 buf[buf_index++] = (unsigned char)block[block_index++];
00308                 buf[buf_index++] = (unsigned char)block[block_index++];
00309                 buf[buf_index++] = (unsigned char)block[block_index++];
00310             }
00311             break;
00312             
00313     case color_16bit:   // 16 bits: R(6), G(5), B(5)
00314         for ( x=0; x<BLOCKSIZE_X; x += reduction_factor)
00315             for ( y=0; y<BLOCKSIZE_Y; y += reduction_factor)
00316             {
00317                 int block_index = x*BLOCKSIZE_Y*3+y*3;
00318                 unsigned char r = block[block_index+0];
00319                 unsigned char g = block[block_index+1];
00320                 unsigned char b = block[block_index+2];
00321                 buf[buf_index+0] = (r >> 2) << 2 | (g >> 6);  // rrrrrrgg
00322                 buf[buf_index+1] = ((g&0x3f) >> 3) << 5 | (b >> 3); //gggbbbbb
00323                 buf_index += 2;
00324             }
00325             break;
00326             
00327     case color_8bit:    // 8bits: R(3), G(3), B(2):  rrrgggbb
00328         for ( x=0; x<BLOCKSIZE_X; x += reduction_factor)
00329             for ( y=0; y<BLOCKSIZE_Y; y += reduction_factor)
00330             {
00331                 int block_index = x*BLOCKSIZE_Y*3+y*3;
00332                 unsigned char r = block[block_index+0];
00333                 unsigned char g = block[block_index+1];
00334                 unsigned char b = block[block_index+2];
00335                 buf[buf_index] = (r & 0xe0) | ((g&0xe0) >> 3) | ((b&0xc0) >> 6);
00336                 buf_index++;
00337             }
00338             break;
00339             
00340     case greyscale:
00341         for ( x=0; x<BLOCKSIZE_X; x += reduction_factor)
00342             for ( y=0; y<BLOCKSIZE_Y; y += reduction_factor)
00343             {
00344                 int block_index = x*BLOCKSIZE_Y*3+y*3;
00345                 unsigned char r = block[block_index+0];
00346                 unsigned char g = block[block_index+1];
00347                 unsigned char b = block[block_index+2];
00348                 buf[buf_index] = (r + g + b)/3;         // greyscale intensity
00349                 buf_index++;
00350             }
00351             break;
00352     case JPEG: {
00353 #ifndef SGI
00354 #ifndef __APPLE__
00355         int lBufferSize = width*height*3;
00356         if (!headerInitialized)
00357         {
00358             InitHeader(width, height);
00359             headerInitialized = true;
00360         }
00361         memcpy(WHeader + 54,block,lBufferSize);
00362 
00363 #ifdef _WIN32           
00364         CompressToJPEG(WHeader,lBufferSize + 54,&OutputBuffer,&OutputLength);
00365 #endif
00366 
00367         memcpy(buf+5, OutputBuffer, OutputLength);
00368         return (OutputLength+5);
00369 #endif // __APPLE__
00370 #endif
00371                }
00372     }  // end switch
00373     // and return the number of bytes encoded
00374     return(buf_index);
00375 }
00376 
00377 void DisplayReplicator::decode_block_from_buf(int* buf_code_p, 
00378                                               int* x_p, int* y_p, int* seq_id_p, char* block, int JPEG_size)
00379 {
00380     *buf_code_p = int((unsigned char)buf[2]);
00381     *x_p = int((unsigned char)buf[3]);
00382     *y_p = int((unsigned char)buf[4]);
00383     *seq_id_p = 1;
00384     int x,y;
00385     
00386     int reduction_factor = 1 << (res_reduction-1);
00387     
00388     if (reduction_factor == 1)
00389     {
00390         
00391         char *ptBuf = buf + 4;
00392         char *ptBlock = block;
00393         
00394         switch (compression) {
00395         default:
00396         case color_24bit:       // 24bits: RGB
00397             memcpy(block, buf + 4, block_size);
00398             break;
00399         case color_16bit:       // 16 bits: R(6), G(5), B(5)
00400             for ( x=0; x<BLOCKSIZE_X; ++x) {
00401                 for ( y=0; y<BLOCKSIZE_Y; ++y) {
00402                     *(ptBlock++) = *ptBuf & 0xfc;
00403                     *ptBlock = *ptBuf << 6;
00404                     *(ptBlock++) |= (*(++ptBuf) & 0xe0) >> 2;
00405                     *(ptBlock++) = *(ptBuf++) << 3;
00406                 } // end for y
00407             }// end for x
00408             break;
00409         case color_8bit:        // 8bits: R(3), G(3), B(2): rrrgggbb
00410             for ( x=0; x<BLOCKSIZE_X; ++x) {
00411                 for ( y=0; y<BLOCKSIZE_Y; ++y) {
00412                     *(ptBlock++) = *ptBuf & 0xe0;
00413                     *(ptBlock++) = (*ptBuf & 0x1c) << 3;
00414                     *(ptBlock++) = (*(ptBuf++) & 0x03) << 6;
00415                 }// end for y
00416             }// end for x
00417             break;
00418         case greyscale: // 8bits: R(3), G(3), B(2): rrrgggbb
00419             for ( x=0; x<BLOCKSIZE_X; ++x) {
00420                 for ( y=0; y<BLOCKSIZE_Y; ++y) {
00421                     *(ptBlock++) = *ptBuf; // greyscale intensity
00422                     *(ptBlock++) = *ptBuf; // greyscale intensity
00423                     *(ptBlock++) = *(ptBuf++); // greyscale intensity
00424                 }// end for y
00425             }// end for x
00426             break;
00427         case JPEG: {
00428 #ifndef SGI
00429 #ifndef __APPLE__
00430 #ifdef WIN_32
00431             ExpandJPEGTo24BitDIB((BYTE*)ptBuf,JPEG_size,&OutputBuffer2,&OutputLength2);
00432 #endif
00433 
00434             memcpy(ptBlock,OutputBuffer2, OutputLength2);
00435             delete OutputBuffer2;
00436 #endif // __APPLE__
00437 #endif
00438             break;
00439                    }
00440         }  // end switch
00441     }
00442     else
00443     {
00444         cerr << "Oups\n";
00445         int buf_index = 4;
00446         for ( x=0; x<BLOCKSIZE_X; x += reduction_factor) {
00447             for ( y=0; y<BLOCKSIZE_Y; y += reduction_factor) {
00448                 int block_index = x*BLOCKSIZE_Y*3+y*3;
00449                 unsigned char r,g,b;
00450                 switch (compression) {
00451                 default:
00452                 case color_24bit:       // 24bits: RGB
00453                     r = buf[buf_index+0];
00454                     g = buf[buf_index+1];
00455                     b = buf[buf_index+2];
00456                     buf_index += 3;
00457                     break;
00458                 case color_16bit:       // 16 bits: R(6), G(5), B(5)
00459                     r = buf[buf_index] & 0xfc;
00460                     g = (buf[buf_index] << 6) | ((buf[buf_index+1]&0xe0) >> 2);
00461                     b = (buf[buf_index+1] << 3);
00462                     buf_index += 2;
00463                     break;
00464                 case color_8bit:        // 8bits: R(3), G(3), B(2): rrrgggbb
00465                     r = buf[buf_index] & 0xe0;
00466                     g = (buf[buf_index] & 0x1c) << 3;
00467                     b = (buf[buf_index] & 0x03) << 6;
00468                     buf_index++;
00469                     break;
00470                 case greyscale: // 8bits: R(3), G(3), B(2): rrrgggbb
00471                     r = g = b = buf[buf_index]; // greyscale intensity
00472                     buf_index++;
00473                     break;
00474                 }  // end switch
00475                 
00476                 
00477                 // set values
00478                 for (int xx=0; xx<reduction_factor; xx++) {
00479                     for (int yy=0; yy<reduction_factor; yy++) {
00480                         int subblock_index = block_index + xx*BLOCKSIZE_Y*3 +yy*3;
00481                         block[subblock_index + 0] = r;
00482                         block[subblock_index + 1] = g;
00483                         block[subblock_index + 2] = b;
00484                     } // for yy
00485                 } // for xx
00486             } // for y
00487         } // for x
00488     } // End reduction==1
00489 }
00490 
00491 // block_index is the index of the block in the processed blocks list or -1
00492 int DisplayReplicator::SendBlock(int buf_code, int x, int y,
00493                                  char* block, int which_socket, int is_a_resend, bool flushBuffer)
00494 {
00495         int len;
00496 
00497     // if flushBuffer==true with block==NULL, then flush buffer and exit
00498     if ((flushBuffer) && (block == NULL))
00499     {
00500         if (debug) cerr << "Flush Required with Block = NULL and curBufLen = " << curBufLen << "\n";
00501         if (curBufLen > 0)
00502             send_buf_out_socket(which_socket, curBufLen, (char *)mySocketBuffer);
00503         curBufLen = 0;
00504         return -1;
00505     }
00506     
00507     // keep track of it in our set
00508     int seq_id = -1;
00509     
00510     
00511     // encode info of block into buf
00512     len = encode_block_into_buf(buf_code, x, y, seq_id, (char*)block);
00513     
00514     if ((len > buf_size) && (debug))
00515         cerr << "Warning : size of encoded block (=" << len 
00516         << ") > size of buffer (=" << buf_size << ")\n";
00517     
00518     // debugging
00519     if (debug == 3) 
00520         cerr << "Sending block " << int(buf_code) << ", " << x << ", " << y << endl;
00521     
00522     // Place the block in Buffer and flush if needed / required
00523     
00524     // Flush Is required
00525     if(flushBuffer)
00526     {
00527         if (debug) cerr << "Flush Required ";
00528         // If appending block to buffer cause buffer-overflow, send buffer now
00529         // and then take care of block (that is reset curBufLen to 0)
00530         if ((curBufLen + len >= MAX_BUF_SIZE) && (curBufLen > 0))
00531         {
00532             if (debug) cerr << "causing BufferOverflow ";
00533             send_buf_out_socket(which_socket, curBufLen, (char *)mySocketBuffer);
00534             curBufLen = 0;
00535         }
00536         
00537         if (debug) cerr << "with curBufLen = " << curBufLen << " and len = " << len << "\n";
00538         // For performance purposes ... if buffer is currently empty, send block normally
00539         // (avoid memcpy of 'else' case)
00540         if (curBufLen == 0)
00541             send_buf_out_socket(which_socket, len, NULL);
00542         
00543         else // Append new block and sends everything, resetting buffer as well
00544         {
00545             memcpy(&mySocketBuffer[curBufLen], buf, len);
00546             send_buf_out_socket(which_socket, curBufLen + len, (char *)mySocketBuffer);
00547             curBufLen = 0;
00548         }
00549     }
00550     
00551     else // Flush was not required
00552     {
00553         // If len > MAX_BUF_SIZE, no buffering is possible... Immediate flushing is required
00554         if (len > MAX_BUF_SIZE)
00555         {
00556             if (debug) cerr << "Flush Forced with MAX_BUF_SIZE < len (=" << len << ")\n";
00557             send_buf_out_socket(which_socket, len, NULL);       
00558         }
00559         // If buffer overflow, force flush and then append block to buffer
00560         else if (curBufLen + len >= MAX_BUF_SIZE)
00561         {
00562             if (debug) cerr << "Flush Forced with BufferOverflow, curBufLen = " << curBufLen << "\n";
00563             send_buf_out_socket(which_socket, curBufLen, (char *)mySocketBuffer);
00564             memcpy(&mySocketBuffer[0], buf, len);
00565             curBufLen = len;
00566         }
00567         else //otherwise just append block to buffer
00568         {
00569             memcpy(&mySocketBuffer[curBufLen], buf, len);
00570             curBufLen += len;
00571         }
00572     }
00573     
00574     // return length of message for performance stats
00575     return(len);
00576 }
00577 
00578 void DisplayReplicator::PaintBlock(int which_buffer, char xloc, char yloc, 
00579                                    char* block,bool paintAll)
00580 {
00581     if (!paintAll)
00582     {
00583         glDrawBuffer(which_buffer);     
00584         glRasterPos2i(xloc * BLOCKSIZE_X, yloc * BLOCKSIZE_Y);
00585         glDrawPixels(BLOCKSIZE_X, BLOCKSIZE_Y, GL_RGB, GL_UNSIGNED_BYTE, block);
00586     }
00587     else
00588     {
00589         glDrawBuffer(which_buffer);     
00590         glRasterPos2i(xloc * BLOCKSIZE_X, yloc * BLOCKSIZE_Y);
00591         if (!from_windows)
00592             glDrawPixels(width, height, GL_RGB, GL_UNSIGNED_BYTE, block);
00593         else {
00594 #ifdef _WIN32
00595             glDrawPixels(width, height, GL_BGR_EXT, GL_UNSIGNED_BYTE, block);
00596 #else
00597             glDrawPixels(width, height, GL_BGR, GL_UNSIGNED_BYTE, block);
00598 #endif
00599         }
00600     }
00601 }
00602 
00603 void DisplayReplicator::Reshape(int w, int h)
00604 { width = w; height = h; 
00605 #ifndef SGI
00606 #ifndef __APPLE__
00607 delete WHeader; 
00608 InitHeader(w,h);
00609 headerInitialized = true;
00610 #endif // __APPLE__
00611 #endif
00612 }
00613 
00614 void DisplayReplicator::SendKeyEvent(char key, int x, int y)
00615 {
00616     if (debug) cerr << "Sending key " << key << " @ " << x << ", " << y << endl;
00617     sprintf(buf, "%c %d %d %d", 'k', key, x, y);
00618     send_buf_out_socket(0, buf_size);
00619 }
00620 
00621 void DisplayReplicator::SendSpecialKeyEvent(int key, int x, int y)
00622 {
00623     if (debug) cerr << "Sending special key " << key << " @ " << x << ", " 
00624         << y << endl;
00625     sprintf(buf, "%c %d %d %d", 's', key, x, y);
00626     send_buf_out_socket(0, buf_size);
00627 }
00628 
00629 void DisplayReplicator::SendMouseEvent(int button, int state, int x, int y)
00630 {
00631     if (debug) cerr << "Sending mouse " << button << "," << state << " @ " 
00632         << x << ", " << y << endl;
00633     sprintf(buf, "%c %d %d %d %d", 'b', button, state, x, y);
00634     send_buf_out_socket(0, buf_size);
00635 }
00636 
00637 void DisplayReplicator::SendMotionEvent(int x, int y)
00638 {
00639     if (debug) cerr << "Sending motion @ " << x << ", " << y << endl;
00640     sprintf(buf, "%c %d %d", 'm', x, y);
00641     send_buf_out_socket(0, buf_size);
00642 }
00643 
00644 void DisplayReplicator::RegisterEventHandlers(
00645                                               void(*keyfn_in)(unsigned char,int,int),
00646                                               void(*specialfn_in)(int,int,int),
00647                                               void(*mousefn_in)(int,int,int,int),
00648                                               void(*motionfn_in)(int,int) )
00649 {
00650     keyfn = keyfn_in;
00651     specialfn = specialfn_in;
00652     mousefn = mousefn_in;
00653     motionfn = motionfn_in;
00654 }
00655 
00656 void DisplayReplicator::SendScreenRefresh(int which_socket)
00657 {
00658     if (debug == 3) 
00659         cerr << "SendScreenRefresh()\n";
00660     
00661     if (num_socks == 0) return;
00662     
00663     // encode it
00664     buf[0] = 0;
00665     
00666     // send the block out
00667     send_buf_out_socket(which_socket, buf_size);
00668 }
00669 
00670 // sends the whole screen to display[i]
00671 void DisplayReplicator::SendWholeScreen(int which_buffer, int sock_index)
00672 {
00673     if (debug == 3) 
00674         cerr << "SendWholeScreen(" << which_buffer << ", " << sock_index 
00675         << ")\n";
00676     
00677     if (num_socks == 0) return;
00678     
00679     glReadBuffer(which_buffer); 
00680     
00681     int num_blocks_x = width/BLOCKSIZE_X;
00682     int num_blocks_y = height/BLOCKSIZE_Y;
00683     for (int x=0; x<num_blocks_x; x++) {
00684         for (int y=0; y<num_blocks_y; y++) {
00685             int xloc = x * BLOCKSIZE_X, yloc = y * BLOCKSIZE_Y;
00686             glReadPixels(xloc,yloc, BLOCKSIZE_X,BLOCKSIZE_Y, GL_RGB, 
00687                 GL_UNSIGNED_BYTE, &block);
00688             
00689             // encode the buffer and location of the block
00690             int buf_code = 1;
00691             if (which_buffer == GL_FRONT_RIGHT) buf_code = 2;
00692             
00693             // send to other side
00694             SendBlock(buf_code, x, y, block, sock_index);
00695         }
00696     }
00697     
00698     // and tell them to update
00699     SendScreenRefresh(sock_index);
00700 }
00701 
00702 void DisplayReplicator::SendScreenUpdates(int which_buffer, void* data_in)
00703 {
00704     if (debug == 3) cerr << "SendScreenUpdates(" << which_buffer << ")\n";
00705     
00706     if (num_socks == 0)
00707     {   //cerr <<  "No socket in SendScreenUpdate\n";
00708         return; }
00709     //else
00710     //cerr << "Good, we have " << num_socks << " connections in SendScreenUpdate\n";
00711     
00712     
00713     // get the data into local memory (from user memory or screen)
00714     //char *data = new char[width * height * 3];
00715     // for now, do static allocation, otherwise JPEG crashes
00716     
00717         // !!! Dangerous to limit the size!
00718         static char data[1024 * 1024 * 3];      
00719     
00720     switch (source) {
00721     case source_memory:
00722         memcpy(data, data_in, width*height*3);
00723         break;
00724     case source_screen: // read the screen at that location into data
00725         //cerr << "doing read from buffer " << int(which_buffer) 
00726         //<< "with width=" << width << " and height=" << height
00727         //<< " into " << int(data) << endl;
00728         glReadBuffer(which_buffer);     
00729                 // NOTE: Please use GL_BGR not GL_RGB for compatibility with windows DIB.
00730         // Note: ReadPixels is fast (can go video speed)
00731 #ifdef _WIN32
00732         glReadPixels(0,0, width,height, GL_BGR_EXT, GL_UNSIGNED_BYTE, &data);
00733 #else
00734         glReadPixels(0,0, width,height, GL_BGR, GL_UNSIGNED_BYTE, &data);
00735 #endif
00736         break;
00737     }  // end switch
00738     
00739     // get a code for the buffer
00740     int buf_code = 1;   // left
00741     if (which_buffer == GL_FRONT_RIGHT) buf_code = 2;  // right
00742     
00743     // big loop to look over and send blocks
00744     Timer timer;
00745     int blocks_sent1 = 0, blocks_sent2 = 0, bytes_sent = 0;
00746     if (compression != JPEG) // Backward compatibility
00747     {
00748         int num_blocks_x = width/BLOCKSIZE_X;
00749         int num_blocks_y = height/BLOCKSIZE_Y;
00750         for (int x=0; x<num_blocks_x; x++) {
00751             for (int y=0; y<num_blocks_y; y++) {
00752                 // get the block number of where we are
00753                 int xloc = x * BLOCKSIZE_X, yloc = y * BLOCKSIZE_Y;
00754                 
00755                 { // copy over the data from the buffer into the block
00756                     for (int yy=0; yy<BLOCKSIZE_Y; yy++) {
00757                         memcpy(&(block[yy*BLOCKSIZE_X*3]), 
00758                             &(data[(yloc+yy)*width*3+xloc*3]), BLOCKSIZE_X*3);
00759                     }
00760                 }
00761                 
00762                 // compare them against our old buffer
00763                 int diff = compare_blocks(block, (char*)old_data[buf_code-1][x][y]);
00764                 
00765                 // if diff, send `em
00766                 
00767                 if (diff) {
00768                     
00769                     // send to other side but do not flush each time.... buffers instead
00770                     int len = SendBlock(buf_code, x, y, block, -1, 0, false);
00771                     /*       static int nbBlocks;
00772                     nbBlocks++;
00773                     if (!(nbBlocks%1000000))
00774                     cerr << "NbBlocks : " << nbBlocks / 1000000 << " Million\n"; */
00775                     // update the old data buffer
00776                     memcpy(old_data[buf_code-1][x][y], block, block_size);
00777                     
00778                     // keep stats
00779                     if (buf_code == 1)
00780                         blocks_sent1++;
00781                     else
00782                         blocks_sent2++;
00783                     //blocks_sent++;
00784                     bytes_sent += len * num_socks;
00785                 }
00786             }
00787         }
00788         //cerr << "Block Sent1 : " << blocks_sent1 << " Block Sent2 : " << blocks_sent2 << " buffCode = "  << buf_code << "\n";
00789         // Flush buffer at the end
00790         SendBlock(0, 0, 0, NULL, -1, 0, true);
00791         
00792         // and tell them all to update
00793         //SendScreenRefresh(-1);
00794         
00795         
00796         oldDataInitialized = true;
00797     }
00798     else // We are doing JPEG compression
00799     {
00800         int len = SendBlock(buf_code,0,0,data,-1,0,true);
00801         bytes_sent += len;
00802     }
00803     
00804     if (debug == 1) {
00805         int bps = int(bytes_sent/timer.GetElapsedTime());
00806         //      cerr << blocks_sent << " blocks sent times " << num_socks << " clients: ";
00807         cerr << bps * 8/1024 << " Kbits/second,relative\n";
00808         cerr << "    seeing " << 1.0/global_timer.GetElapsedTime() 
00809             << " updates per second, absolute\n";
00810         global_timer.Reset();
00811     }
00812 }
00813 
00814 void DisplayReplicator::ReceiveScreenUpdate(int which_buffer, char* data, int JPEG_size)
00815 {
00816     int buf_code, x, y, seq_id;
00817     
00818     // decode it
00819     if (compression != JPEG)
00820         decode_block_from_buf(&buf_code, &x, &y, &seq_id, block,JPEG_size);
00821     else
00822         decode_block_from_buf(&buf_code, &x, &y, &seq_id, JPEG_data,JPEG_size);
00823     
00824     
00825     // and write it (to memory or to the screen)
00826     switch (source) {
00827     case source_screen:
00828         if (compression != JPEG)
00829             PaintBlock(which_buffer, x, y, block);
00830         else
00831             PaintBlock(which_buffer, x, y, JPEG_data, true);
00832         break;
00833     case source_memory:
00834         if (!data) break;
00835         // get the pixel location of where we are
00836         int xloc = x * BLOCKSIZE_X, yloc = y * BLOCKSIZE_Y;
00837         
00838         // copy over the data from the block into the buffer
00839         for (int yy=0; yy<BLOCKSIZE_Y; yy++) {
00840             memcpy(&(data[(yloc+yy)*width*3+xloc*3]),
00841                 &(block[yy*BLOCKSIZE_X*3]), BLOCKSIZE_X*3);
00842         }
00843         break;
00844     }  // end switch
00845     
00846 }
00847 
00848 
00849 void DisplayReplicator::Connect(char* server, bool switchboard, char channel, char* optBuffer, bool blocking)
00850 {
00851     // make one
00852     cerr << "connecting to " << server << "on port " << port << "..." << endl;
00853     int cur_sock = num_socks++;
00854     
00855     // create the socket
00856     sock[cur_sock] = new Socket(server,Socket::tcp,port);
00857     if (!sock[cur_sock]) { num_socks--; return; }
00858     
00859     do
00860     {
00861         int status;
00862         if (!switchboard) {
00863             // after a connection is made, send handshake data
00864             strcpy(buf, handshake);
00865             //  status = send_buf_out_socket(cur_sock, buf_size);
00866             cerr << "sent handshake of [" << buf << "]\n";
00867         }
00868         else {
00869             char system_info[80]; 
00870 #ifdef _WIN32
00871             sprintf(system_info, "WIN32 PC");
00872 #elif linux
00873             sprintf(system_info, "Linux PC");
00874 #else
00875             sprintf(system_info,"Unix Machine");
00876 #endif
00877             cerr << "channel " << int(channel) << "\n";
00878             char InitBuf[256];
00879             // make string for given channel and say who we are
00880             if (channel != -1)
00881                 sprintf(InitBuf, "%c%c%s", (char)CHANNEL_REQUEST_CODE, (char)channel, system_info);
00882             else
00883                 sprintf(InitBuf, "%c%s", (char)SYSTEM_INFO_CODE, system_info);
00884             
00885             if (optBuffer != NULL) strcat(InitBuf, optBuffer);
00886             cerr << "Switchboard initbuf = [" << InitBuf << "]\n";
00887             memcpy(buf,InitBuf,256);
00888             status = send_buf_out_socket(cur_sock, buf_size);
00889             
00890         }
00891         
00892 //#define DO_HANDSHAKE
00893 #ifdef DO_HANDSHAKE
00894         // test their response
00895         sock[cur_sock]->BufferedReceive(buf,buf_size);
00896         cout << "RESP: [" << buf << "]\n";
00897         if (strncmp(buf,"ACK", 3) == 0) {
00898             
00899             if (debug)
00900                 cerr << "Received Confirmation of ACK\n";
00901             
00902             // setup to do the session
00903 #else
00904             if (1) {
00905                 if (debug)
00906                     cerr << "Not doing handshake- assumed okay\n";
00907                 
00908 #endif
00909                 blocking = false;
00910                 // send the whole screen to bring them up to speed
00911                 //              SendWholeScreen(GL_FRONT_LEFT, cur_sock);
00912                 //              SendWholeScreen(GL_FRONT_RIGHT, cur_sock);
00913                 
00914             } else {
00915                 // end the session
00916                 cerr << "Connection rejected!  (buf = [" << buf << "])\n";
00917                 // If blocking try again, and again....
00918                 if (!blocking)
00919                 {
00920                     Disconnect(cur_sock);
00921                     return;
00922                 }
00923             }
00924         } while (blocking);
00925         
00926         // set our mode of operation
00927         mode = sender;
00928         
00929         // send them our current compression parameters
00930         //SetParams(res_reduction, compression, comparison_threshold,from_windows);
00931     }
00932     
00933     void DisplayReplicator::Disconnect(int connection_to_kill)
00934     {
00935         if (debug) cerr << "Disconnect(" << connection_to_kill << ")\n";
00936         if (connection_to_kill >= num_socks) return;
00937         delete sock[connection_to_kill];
00938         for (int i=connection_to_kill; i<num_socks-1; i++)
00939             sock[i] = sock[i+1];
00940         num_socks--;
00941     }
00942     
00943     
00944     void DisplayReplicator::Listen()
00945     {
00946         // wait for message from network to start
00947         cerr << "Waiting for communication...";
00948         int cur_sock = num_socks;
00949         
00950         // make new socket
00951         sock[cur_sock] = new Socket(NULL,Socket::tcp,port+cur_sock);
00952         if (!sock[cur_sock]) return;
00953         
00954         // otherwise, we've got one
00955         cerr << "\n\n\n" << num_socks++ << "\n\n\n\n";
00956         cerr << endl;
00957         
00958 #ifdef DO_HANDSHAKE
00959         // after a connection is made, get handshake data
00960         cerr << "received connection" << endl;
00961         //char* ackbuf = new char[buf_size];
00962         sock[cur_sock]->BufferedReceive(buf,buf_size);
00963         
00964         // debugging
00965         cout << "Got handshake of [" << buf << "]\n";
00966         
00967         // test their hello
00968         if (strncmp(buf,handshake, strlen(handshake)) == 0) {
00969             // if handshake successful send ACK and start a session
00970             sprintf(buf, "ACK");
00971             send_buf_out_socket(cur_sock, buf_size);
00972             cerr << "handshake okay- Connection established.\n";
00973         } else {
00974             // else send NAK
00975             cerr << "NAK\n";
00976             sprintf(buf, "NAK");
00977             send_buf_out_socket(cur_sock, buf_size);
00978             
00979             // end the session
00980             Disconnect(cur_sock);
00981         }
00982         //delete ack_buf;
00983 #endif
00984         
00985         // set our mode of operation
00986         mode = receiver;
00987     }
00988     
00989     // sender should use nonblocking to just see if anything's there
00990     // receiver (displayserver) should block so it can detect a broken connection
00991     void DisplayReplicator::Update(int nonblocking)
00992     {
00993         if (debug == 4) cerr << "Update(" << nonblocking << ")\n";
00994         
00995         // shortcut
00996         if (num_socks == 0) return;
00997         
00998         //int nbBlocks = (int) ((float)width / BLOCKSIZE_X * height / BLOCKSIZE_Y); // keep metrics
00999         int num_messages = 0, num_bytes = 0;
01000         Timer timer;
01001         
01002         do {
01003             // if they have anything they've sent us, fake it as if it were local
01004             for (int i=0; i<num_socks; i++) {
01005                 
01006                 if (debug == 4)
01007                     cerr << "Queue on socket " << i << " is "
01008                     << sock[i]->InputQueueSize() << endl;
01009                 
01010                 
01011                 int g_count = 0;
01012                 
01013                 //int maxBlocks = width / BLOCKSIZE_X * height / BLOCKSIZE_Y;
01014                 int curLen = 0;
01015                 g_count++;
01016                 if (!(g_count%100)) cerr << "g_count = " << g_count << "\n";
01017                 
01018                 if (debug == 5)
01019                     cerr << "Incoming! - command=" << int(buf[0]) << endl;
01020                 
01021                 // get the message
01022                 int status = 1;
01023                 
01024                 // receive the message
01025                 //status = sock[i]->BufferedReceive(g_buf, MAX_BUF_SIZE, !nonblocking);
01026                 status = sock[i]->BufferedReceive(g_buf, MAX_BUF_SIZE);
01027                 buf = g_buf;
01028                 
01029                 
01030                 if (!status) {
01031                     if (debug) cerr << "2:LOST CONNECTION ON SOCKET " << i << endl;
01032                     Disconnect(i);
01033                     continue;
01034                 }
01035                 
01036                 if (debug)
01037                     cerr << "status " << status << " comp " << comp_size + 4 << "\n";
01038                 
01039                 while (curLen*buf_size  < status)
01040                 {   
01041                     
01042                     if (debug) {
01043                         cerr << " curLen " << curLen;
01044                         cerr << " buf[0] " << (int)buf[0];
01045                         cerr << " buf[1] " << (int)buf[1];
01046                         cerr << " buf[2] " << (int)buf[2];
01047                         cerr << " buf[3] " << (int)buf[3] << "\n";
01048                     }
01049                     
01050                     // based on operation code, do stuff
01051                     switch (buf[0]) {
01052                     case 0:
01053                         //glClear(GL_COLOR_BUFFER_BIT);
01054                         glutPostRedisplay();
01055                         break;
01056                     case 1: ReceiveScreenUpdate(GL_FRONT_LEFT, buf, status);
01057                         //cerr << "     Left Buffer \n";
01058                         break;
01059                     case 2: ReceiveScreenUpdate(GL_FRONT_RIGHT, buf, status);
01060                         //cerr << "Right Buffer \n";
01061                         break;
01062                     case 3: {
01063                         res_reduction = int(buf[1]);
01064                         compression = compression_type(buf[2]);
01065                         from_windows = int(buf[3]);
01066                         cerr << "received res_reduce = " << int(buf[1])
01067                             << ", compression = " << int(buf[2]) << endl;
01068                         break;
01069                             }
01070                     case 'q': Disconnect(i); break;
01071                     case 'k': {
01072                         int x, y, dummykey;
01073                         sscanf(buf, "%*c %d %d %d", &dummykey, &x, &y);
01074                         char key = char(dummykey);
01075                         if (debug) cerr << "Got key [" << key << "] at "
01076                             << x << ", " << y << endl;
01077                         if (keyfn) (*keyfn)(key,x,y);
01078                         break;
01079                               }
01080                     case 's': {
01081                         int key; int x, y;
01082                         sscanf(buf, "%*c %d %d %d", &key, &x, &y);
01083                         if (debug) cerr << "Got special key [" << key << "] at "
01084                             << x << ", " << y << endl;
01085                         if (specialfn) (*specialfn)(key,x,y);
01086                         break;
01087                               }
01088                     case 'b': {
01089                         int button, state, x, y;
01090                         sscanf(buf, "%*c %d %d %d %d", &button, &state, &x, &y);
01091                         if (debug) cerr << "Got mouse " << button << "," << state
01092                             << " at " << x << ", " << y << endl;
01093                         if (mousefn) (*mousefn)(button,state,x,y);
01094                         break;
01095                               }
01096                     case 'm': {
01097                         int x, y;
01098                         sscanf(buf, "%*c %d %d", &x, &y);
01099                         if (debug) cerr << "Got motion at " << x << ", " << y << endl;
01100                         if (motionfn) (*motionfn)(x,y);
01101                         break;
01102                               }
01103                     default: break;     // unknown
01104                         
01105                     }  // end switch
01106                     ++curLen;
01107                     buf += comp_size + 4 ;
01108                    
01109                     if (compression == JPEG) curLen = status; // make sure we get out of the inner while
01110                 }
01111                 
01112                 buf = g_buf;
01113                 
01114                 // keep metrics
01115                 num_messages++;
01116                 num_bytes += status;
01117                 //if (buf[0] == 0) break;
01118                 //if (sock[i]->InputQueueSize() <= 0) g_count = 0;
01119 }  // end for
01120       } while(1); //sock[i]->InputQueueSize() > 0);
01121       
01122       
01123       // debugging
01124       if (debug == 2) {
01125           int bps = int(num_bytes/timer.GetElapsedTime());
01126           cerr << num_messages << " messages received from " << num_socks
01127               << " servers: ";
01128           cerr << bps * 8/1024 << " Kbits/second,relative\n";
01129           cerr << "seeing " << 1.0/global_timer.GetElapsedTime()
01130               << " updates per second, absolute\n";
01131           global_timer.Reset();
01132       }
01133 }
01134 
01135 GLUI_StaticText* ac = NULL;
01136 char new_machine_name[300];
01137 int new_proto = 0;
01138 void DisplayReplicator::Connect_CB(int dr_p_in)
01139 {
01140     DisplayReplicator* dr_p = (DisplayReplicator*)dr_p_in;
01141     
01142     // and do the connect
01143     dr_p->Connect(new_machine_name,do_switchboard,-1,": Spring code");
01144     
01145     // update the num connections table
01146     char buf[80];
01147     sprintf(buf, "Num Active Connections = %d", dr_p->NumConnections());
01148     if (ac) ac->set_text(buf);
01149 }
01150 
01151 int new_rr = 1;
01152 int new_ct = 0;
01153 int new_compare = 0;
01154 void DisplayReplicator::Params_CB(int dr_p_in)
01155 {
01156     DisplayReplicator* dr_p = (DisplayReplicator*)dr_p_in;
01157     dr_p->SetParams(new_rr, DisplayReplicator::compression_type(new_ct),
01158         new_compare);
01159 }
01160 
01161 
01162 void DisplayReplicator::Menu()
01163 {
01164     extern int main_window_id;
01165     extern void Close(int);
01166     
01167     GLUI *glui = GLUI_Master.create_glui((const char*)"Display Replicator", 0, -1, -1);
01168     { GLUI_Panel* p = glui->add_panel("Current info");
01169     p->set_alignment(GLUI_ALIGN_LEFT);
01170     
01171     char buf[80];
01172     sprintf(buf, "Num Active Connections = %d", NumConnections());
01173     ac = glui->add_statictext_to_panel(p, buf);
01174     
01175     // read in other values from current settings
01176     GetParams(&new_rr, (compression_type*)&new_ct, &new_compare);
01177     
01178     // make the compression panel
01179     GLUI_Spinner* s0 = glui->add_spinner_to_panel(p, "Resolution Reduction",
01180         GLUI_SPINNER_INT, &new_rr);
01181     s0->set_int_limits(1,6);
01182     GLUI_Listbox* lb0 = glui->add_listbox_to_panel(p, "Compression Type",
01183         &new_ct);
01184     lb0->add_item((int)DisplayReplicator::JPEG, (const char*)"JPEG");
01185     lb0->add_item((int)DisplayReplicator::color_24bit, (const char*)"24bit Color");
01186     lb0->add_item((int)DisplayReplicator::color_16bit, (const char*)"16bit Color");
01187     lb0->add_item((int)DisplayReplicator::color_8bit, (const char*)"8bit Color");
01188     lb0->add_item((int)DisplayReplicator::greyscale, (const char*)"Greyscale");
01189     GLUI_Spinner* s1 = glui->add_spinner_to_panel(p, (const char*)"Comparison Threshold",
01190         GLUI_SPINNER_INT, &new_compare);
01191     s1->set_int_limits(-1,1000);
01192     
01193     glui->add_button_to_panel(p, "Apply", (int)this,
01194         (GLUI_Update_CB)Params_CB);
01195     }
01196     
01197     { GLUI_Panel* p = glui->add_panel("");
01198     GLUI_EditText* et1 = glui->add_edittext_to_panel(p, "Connect to:",
01199         GLUI_EDITTEXT_TEXT, new_machine_name);
01200     
01201     et1->set_text("biocomp.stanford.edu");
01202     
01203     et1->set_w(300); et1->set_h(20);
01204     
01205     GLUI_Checkbox* cb1 = glui->add_checkbox_to_panel(p,"Use Switchboard", &do_switchboard);
01206     glui->add_button_to_panel(p, "Connect", (int)this,
01207         (GLUI_Update_CB)Connect_CB);
01208     }
01209     
01210     glui->add_separator();
01211     { GLUI_Panel* pe = glui->add_panel("");
01212     glui->add_button_to_panel(pe, "Close", (int)glui,
01213         (GLUI_Update_CB)Close);
01214     }
01215     glui->set_main_gfx_window(main_window_id);
01216 }
01217 

Generated on Thu Aug 30 11:03:13 2007 for SPRING Mac by  doxygen 1.5.3