home *** CD-ROM | disk | FTP | other *** search
- /******************* start of original comments ********************/
- /*
- * Written by Douglas Thomson (1989/1990)
- *
- * This source code is released into the public domain.
- */
-
- /*
- * Name: dte - Doug's Text Editor program - hardware dependent module
- * Purpose: This file contains all the code that needs to be different on
- * different hardware.
- * File: hwibm.c
- * Author: Douglas Thomson
- * System: This particular version is for the IBM PC and close compatibles.
- * It write directly to video RAM, so it is faster than other
- * techniques, but will cause "snow" on most CGA cards. See the
- * file "hwibmcga.c" for a version that avoids snow.
- * The compiler is Turbo C 2.0, using one of the large data memory
- * models.
- * Date: October 10, 1989
- * Notes: This module has been kept as small as possible, to facilitate
- * porting between different systems.
- */
- /********************* end of original comments ********************/
-
-
- /*
- * These routines were rewritten for Microsoft C. They are pretty much system
- * dependent and pretty much Microsoft C dependent. I also renamed this file
- * "main.c" - easier to find the main function.
- *
- * New editor name: tde, the Thomson-Davis Editor.
- * Author: Frank Davis
- * Date: June 5, 1991, version 1.0
- * Date: July 29, 1991, version 1.1
- * Date: October 5, 1991, version 1.2
- * Date: January 20, 1992, version 1.3
- * Date: February 17, 1992, version 1.4
- * Date: April 1, 1992, version 1.5
- * Date: June 5, 1992, version 2.0
- *
- * This modification of Douglas Thomson's code is released into the
- * public domain, Frank Davis. You may distribute it freely.
- */
-
-
- char *greatest_composer_ever = "W. A. Mozart, 1756-1791";
-
-
- #include "tdestr.h" /* tde types */
- #include "common.h"
- #include "define.h"
- #include "help.h"
- #include "tdefunc.h"
- #include <dos.h> /* for renaming files */
- #ifdef __TURBOC__
- #include <dir.h> /* for searching the current path */
- #endif
- #include <bios.h> /* for direct BIOS keyboard input */
- #if defined( __TURBOC__ )
- #include <alloc.h> /* for memory allocation */
- #elif defined( __MSC__ )
- #include <malloc.h> /* for memory allocation */
- #endif
- #include <io.h> /* for file attribute code */
- #include <fcntl.h> /* open flags */
- #if defined( __MSC__ )
- #include <bios.h>
- #include <errno.h>
- #include <sys\types.h> /* S_IWRITE etc */
- #endif
- #include <sys\stat.h> /* S_IWRITE etc */
-
- #if defined( __MSC__ )
- void (interrupt far *old_control_c)( void ); /* variable for old CNTL-C */
- void (interrupt far *old_int1b)( void ); /* variable for old int 1b */
- #endif
-
- int full_screen_buffer[2000]; /* 25 lines * 80 columns = 2000 characters */
- /* (make it an int for the attribute) */
-
-
- /*
- * Default color settings. Incidentally, I'm color blind (mild red-green) and
- * the default colors look fine to me, Frank.
- */
- static int colors[2][12] = {
- { HERC_REVERSE, HERC_NORMAL, HERC_REVERSE, HERC_REVERSE, HERC_HIGH,
- HERC_NORMAL, HERC_NORMAL, HERC_HIGH, HERC_HIGH, HERC_HIGH, HERC_REVERSE,
- HERC_REVERSE },
- { COLOR_HEAD, COLOR_TEXT, COLOR_MODE, COLOR_BLOCK, COLOR_MESSAGE,
- COLOR_HELP, COLOR_DIAG, COLOR_EOF, COLOR_CURL, COLOR_RULER, COLOR_POINTER,
- COLOR_TEXT }
- };
-
-
- /*
- * original control-break checking flag
- */
- static int s_cbrk;
-
-
- /*
- * Name: main
- * Purpose: To do any system dependent command line argument processing,
- * and then call the main editor function.
- * Date: October 10, 1989
- * Passed: argc: number of command line arguments
- * argv: text of command line arguments
- */
- void main( int argc, char *argv[] )
- {
- #if defined( __MSC__ )
- union REGS inregs, outregs;
- #endif
-
- g_status.found_first = FALSE;
- g_status.arg = 1;
- g_status.argc = argc;
- g_status.argv = argv;
-
- /*
- * trap control-break to make it harmless, and turn checking off
- */
- #if defined( __MSC__ )
- inregs.h.ah = 0x33;
- inregs.h.al = 0;
- intdos( &inregs, &outregs );
- s_cbrk = outregs.h.dl;
- old_control_c = _dos_getvect( 0x23 );
- _dos_setvect( 0x23, harmless );
- old_int1b = _dos_getvect( 0x1b );
- _dos_setvect( 0x1b, ctrl_break );
- inregs.h.ah = 0x33;
- inregs.h.al = 1;
- inregs.h.dl = 0;
- intdos( &inregs, &outregs );
- #else
- s_cbrk = getcbrk( );
- ctrlbrk( harmless );
- setcbrk( 0 );
- #endif
-
- initialize( );
- editor( );
- terminate( );
- }
-
-
- /*
- * Name: error
- * Purpose: To report an error, and usually make the user type <ESC> before
- * continuing.
- * Date: June 5, 1991
- * Passed: kind: an indication of how serious the error was:
- * WARNING: continue after pressing a key
- * FATAL: abort the editor
- * line: line to display message
- * message: string to be printed
- * Notes: Show user the message and ask for a key if needed.
- */
- void error( int kind, int line, char *message )
- {
- char buff[MAX_COLS+2]; /* somewhere to store error before printing */
- register int c; /* character entered by user to continue */
- char line_buff[(MAX_COLS+2)*2]; /* buffer for char and attribute */
-
- /*
- * tell the user what kind of an error it is
- */
- switch (kind) {
- case FATAL:
- /*
- * fatal error
- */
- strcpy( buff, main1 );
- break;
- case WARNING:
- /*
- * warning
- */
- strcpy( buff, main2 );
- break;
- }
-
- /*
- * prepare the error message itself
- */
- strcat( buff, message );
-
- /*
- * tell the user how to continue editing if necessary
- */
- if (kind == WARNING)
- /*
- * press a key
- */
- strcat( buff, main3 );
-
- /*
- * output the error message
- */
- save_screen_line( 0, line, line_buff );
- set_prompt( buff, line );
-
- if (kind == FATAL) {
- /*
- * no point in making the user type <ESC>, since the program is
- * about to abort anyway...
- */
- terminate( );
- exit( 1 );
- }
-
- c = getkey( );
- restore_screen_line( 0, line, line_buff );
- if (g_status.wrapped) {
- g_status.wrapped = FALSE;
- show_search_message( CLR_SEARCH, g_display.mode_color );
- }
- }
-
-
- /*
- * Name: harmless
- * Purpose: Do nothing when control-C is pressed
- * Date: June 5, 1991
- * Notes: Interrupt 23, the Control-C handler, is a MS DOS system function.
- * Since we want to use Control-C as a regular function key,
- * let's do absolutely nothing when Control-C is pressed.
- */
- #if defined( __MSC__ )
- void interrupt far harmless( void )
- #else
- static int harmless(void)
- #endif
- {
- }
-
-
- /*
- * Name: ctrl_break
- * Purpose: Set our control-break flag when control-break is pressed.
- * Date: June 5, 1992
- * Notes: Control-break is a little different from Control-C. When
- * Control-C is pressed, MS DOS processes it as soon as
- * possible, which may be quite a while. On the other hand,
- * when Control-break is pressed on IBM and compatibles,
- * interrupt 0x1b is generated immediately. Since an interrupt
- * is generated immediately, we can gain control of run-away
- * functions, like recursive macros, by checking our Control-break
- * flag.
- */
- void interrupt far ctrl_break( void )
- {
- g_status.control_break = TRUE;
- }
-
-
- /*
- * Name: terminate
- * Purpose: To free all dynamic structures and unload anything we loaded
- * Date: June 5, 1991
- */
- void terminate( void )
- {
- union REGS inregs, outregs;
- register WINDOW *wp; /* register for scanning windows */
- WINDOW *w; /* free window */
- register file_infos *fp; /* register for scanning files */
- file_infos *f; /* free files */
-
- /*
- * restore control-break checking
- */
- #if defined( __MSC__ )
- _dos_setvect( 0x1b, old_int1b );
- _dos_setvect( 0x23, old_control_c );
- inregs.h.ah = 0x33;
- inregs.h.al = 1;
- inregs.h.dl = (char)s_cbrk;
- intdos( &inregs, &outregs );
- #else
- setcbrk( s_cbrk );
- #endif
-
- /*
- * free the text buffer
- */
- hfree( (void huge *)g_status.start_mem );
-
- /*
- * free the file structures if not already free.
- */
- fp = g_status.file_list;
- while (fp != NULL) {
- f = fp;
- fp = fp->next;
- free( f );
- }
-
- /*
- * free the window structures if not already free.
- */
- wp = g_status.window_list;
- while (wp != NULL) {
- w = wp;
- wp = wp->next;
- free( w );
- }
-
- /*
- * reset the cursor size and unload the 83/84 key keyboard utility
- */
- set_cursor_size( mode.cursor_size == SMALL_INS ? g_display.insert_cursor :
- g_display.overw_cursor );
- if (mode.enh_kbd == FALSE)
- simulate_enh_kbd( 0 );
- }
-
-
- /*
- * Name: hw_initialize
- * Purpose: To initialize the display ready for editor use.
- * Date: June 5, 1991
- */
- void hw_initialize( void )
- {
- struct vcfg cfg; /* defined in .h */
- unsigned paragraphs;
- long space; /* amount of memory to use */
- register int *clr;
-
- /*
- * set up screen size
- */
- g_display.ncols = MAX_COLS;
- g_display.nlines = MAX_LINES - 1;
- g_display.mode_line = MAX_LINES;
- g_display.line_length = MAX_LINE_LENGTH;
-
- /*
- * work out what kind of display is in use, and set attributes and
- * display address accordingly. Note that this will only work with
- * close IBM compatibles.
- */
-
- video_config( &cfg );
- g_display.display_address = (char far *)cfg.videomem;
-
- /*
- * Use an integer pointer to go thru the color array for setting up the
- * various color fields.
- */
- clr = cfg.color == FALSE ? &colors[0][0] : &colors[1][0];
-
- g_display.head_color = *clr++;
- g_display.text_color = *clr++;
- g_display.mode_color = *clr++;
- g_display.block_color = *clr++;
- g_display.message_color = *clr++;
- g_display.help_color = *clr++;
- g_display.diag_color = *clr++;
- g_display.eof_color = *clr++;
- g_display.curl_color = *clr++;
- g_display.ruler_color = *clr++;
- g_display.ruler_pointer = *clr++;
- g_display.hilited_file = *clr;
-
- /*
- * grab all the available memory for the text buffer
- */
- #if defined( __MSC__ )
- _dos_allocmem( 0xffff, ¶graphs );
- /*
- * A paragraph is 16 bytes. Convert paragraphs to bytes by shifting left
- * 4 bits.
- */
- space = (long)paragraphs << 4;
-
- /*
- * if using Microsoft C, allocate all available memory. If debugging in
- * in QC 2.5, uncomment the next lines so the debugger will have some room.
- */
- /* if (space > 12000l)
- space = 12000l; */
- if (space <= 0)
- return;
- #else
- space = farcoreleft() - 30000L;
- #endif
-
- #if defined( __MSC__ )
- if ((g_status.start_mem = (text_ptr)halloc( space, sizeof( char ))) == NULL)
- /*
- * out of memory
- */
- error( FATAL, g_display.nlines, main4 );
- #else
- if ((g_status.start_mem = farmalloc(space)) == NULL)
- error( FATAL, g_display.nlines, main4 );
- #endif
- g_status.max_mem = addltop( space, g_status.start_mem );
- }
-
-
- /*
- * Video BIOS Data Areas
- * The BIOS routines maintain several dynamic variables in an area of
- * memory called the Video Display Data Area. The following contains a
- * summary of these variables' addresses, their symbolic names, and
- * their contents. All addresses are relative to the 0x0000h segment.
- * From the IBM Technical Reference and other sources.
- *
- * Address Name Type Description
- * 0x0449 CRT_MODE Byte Current BIOS video number
- * 0x044a CRT_COLS Word Number of displayed character columns
- * 0x044c CRT_LEN Word Size of video buffer in bytes
- * 0x044e CRT_START Word Offset of start of video buffer
- * 0x0450 CURSOR_POSN Word Array of eight words containing the cursor
- * position for each of eight possible
- * video pages. The high-order byte of
- * each word contains the character row,
- * the low-order byte the character column
- * 0x0460 CURSOR_MODE Word Starting and ending lines for alphanumeric
- * cursor. The high-order byte contains
- * the starting (top) line; the low-order
- * byte contains the ending (bottom) line
- * 0x0462 ACTIVE_PAGE Byte Currently displayed video page number
- * 0x0463 ADDR_6845 Word I/O port address of CRT Controller's
- * Address register (3B4h for mono;
- * 3D4h for color)
- * 0x0465 CRT_MODE_SET Byte Current value for Mode Control register
- * (3B8h on MDA, 3D8h on CGA). On the
- * EGA and VGA, the value emulates those
- * used on the MDA and CGA.
- * 0x0466 CRT_PALETTE Byte Current value for the CGA Color Select
- * register (3D9h). On the EGA and VGA,
- * the value emulates those used on the
- * MDA and CGA.
- * 0x0467 io_rom_init Word Pointer to optional i/o rom init routine
- * 0x0469 io_rom_seg Word Pointer to io rom segment
- * 0x046b intr_flag Byte Flag to indicate an interrupt happened
- * 0x046c timer_low Word Low word of timer count
- * 0x046e timer_high Word High word of timer count
- * 0x0470 timer_ofl Byte Timer has rolled over since last count
- * 0x0471 bios_break Byte Bit 7 = 1 if Break Key has been hit
- * 0x0472 reset_flag Word Word = 1234h if keyboard reset underway
- * 0x0484 ROWS Byte Number of displayed character rows - 1
- * 0x0485 POINTS Word Height of character matrix
- * 0x0487 INFO Byte EGA and VGA display data
- * 0x0488 INFO_3 Byte Configuration switches for EGA and VGA
- * 0x0489 flags Byte Miscellaneous flags
- * 0x0496 kb_flag_3 Byte Additional keyboard flag
- * 0x048A DCC Byte Display Combination Code
- * 0x04A8 SAVE_PTR Dword Pointer to BIOS save area
- *
- */
- void video_config( struct vcfg *cfg )
- {
- #pragma pack( 1 ) /* Use pragma to force packing on byte boundaries. */
-
- struct LOWMEMVID
- {
- char vidmode; /* 0x449 */
- unsigned scrwid; /* 0x44A */
- unsigned scrlen; /* 0x44C */
- unsigned scroff; /* 0x44E */
- struct LOCATE
- {
- unsigned char col;
- unsigned char row;
- } csrpos[8]; /* 0x450 */
- struct CURSIZE
- {
- unsigned char end;
- unsigned char start;
- } csrsize; /* 0x460 */
- char page; /* 0x462 */
- unsigned addr_6845; /* 0x463 */
- char crt_mode_set; /* 0x465 */
- char crt_palette[30]; /* 0x466 */
- char rows; /* 0x484 */
- unsigned points; /* 0x485 */
- char ega_info; /* 0x487 */
- char info_3; /* 0x488 */
- char skip[13]; /* 0x489 */
- char kb_flag_3; /* 0x496 */
- } vid;
- struct LOWMEMVID _far *pvid = &vid;
- #pragma pack( ) /* revert to previously defined pack pragma. */
-
- union REGS in, out;
- unsigned char temp, active_display;
-
- /*
- * Move system information into our video structure.
- */
- _fmemmove( pvid, (void far *)0x00000449l, sizeof( vid ) );
-
- cfg->rescan = FALSE;
- in.x.ax = 0x1a00;
- int86( VIDEO_INT, &in, &out );
- temp = out.h.al;
- active_display = out.h.bl;
- if (temp == 0x1a && (active_display == 7 || active_display == 8))
- g_display.adapter = VGA;
- else {
- in.h.ah = 0x12;
- in.h.bl = 0x10;
- int86( VIDEO_INT, &in, &out );
- if (out.h.bl != 0x10) { /* EGA */
- if (vid.ega_info & 0x08)
- g_display.adapter = vid.addr_6845 == 0x3d4 ? CGA : MDA;
- else
- g_display.adapter = EGA;
- } else if (vid.addr_6845 == 0x3d4)
- g_display.adapter = CGA;
- else
- g_display.adapter = MDA;
- }
-
- if (g_display.adapter == CGA || g_display.adapter == EGA) {
- if (g_display.adapter == CGA)
- cfg->rescan = TRUE;
- g_display.insert_cursor = mode.cursor_size == SMALL_INS ? 0x0607 : 0x0407;
- g_display.overw_cursor = mode.cursor_size == SMALL_INS ? 0x0407 : 0x0607;
- } else {
- g_display.insert_cursor = mode.cursor_size == SMALL_INS ? 0x0b0c : 0x070b;
- g_display.overw_cursor = mode.cursor_size == SMALL_INS ? 0x070b : 0x0b0c;
- }
-
- cfg->mode = vid.vidmode;
- if (vid.addr_6845 == 0x3D4) {
- cfg->color = TRUE;
- cfg->videomem = (void far *)0xb8000000l;
- } else {
- cfg->color = FALSE;
- cfg->videomem = (void far *)0xb0000000l;
- }
-
- /*
- * Get keyboard type. Since there is no interrupt that determines
- * keyboard type, use this method. Look at bit 4 in keyboard flag3.
- * This method is not always foolproof on clones.
- */
- if ((vid.kb_flag_3 & 0x10) != 0)
- mode.enh_kbd = TRUE;
- else
- mode.enh_kbd = FALSE;
- if (mode.enh_kbd == FALSE)
- simulate_enh_kbd( 1 );
- install_ceh( &ceh );
- ceh.flag = OK;
- }
-
-
- /*
- * Name: hw_move
- * Purpose: To move data from one place to another as efficiently as
- * possible.
- * Date: June 5, 1991
- * Passed: dest: where to copy to
- * source: where to copy from
- * number: number of bytes to copy
- * Notes: moves may be (usually will be) overlapped. Although we can
- * move up to 64k-1 bytes at once, we can safely move only
- * 0xfff0 bytes at one time. Let's try only 0xf000.
- */
- void hw_move( text_ptr dest, text_ptr source, long number )
- {
- unsigned long s, d;
-
- s = ptoul( source );
- d = ptoul( dest );
- if (number < 0)
- /*
- * this should never happen...
- *
- * negative move contact me
- */
- error( WARNING, g_display.nlines, main5 );
- else if (s == d)
- /*
- * nothing to be done
- */
- ;
- else if (s > d) {
- while (number > 0xF000L) {
- dest = nptos( dest );
- source = nptos( source );
- _fmemmove( dest, source, 0xF000 );
- dest = addltop( 0xF000L, dest );
- source = addltop( 0xF000L, source );
- number -= 0xF000L;
- }
- dest = nptos( dest );
- source = nptos( source );
- _fmemmove( dest, source, (unsigned)number );
- } else {
- source = addltop( number, source );
- dest = addltop( number, dest );
- while (number > 0xF000L) {
- source = addltop( -0xF000L, source );
- source = nptos( source );
- dest = addltop( -0xF000L, dest );
- dest = nptos( dest );
- _fmemmove( dest, source, 0xF000 );
- number -= 0xF000L;
- }
- source = addltop( -number, source );
- dest = addltop( -number, dest );
- source = nptos( source );
- dest = nptos( dest );
- _fmemmove( dest, source, (unsigned)number );
- }
- }
-
-
- /*
- * Name: hw_fattrib
- * Purpose: To determine the current file attributes.
- * Date: December 26, 1991
- * Passed: name: name of file to be checked
- * Returns: use the function in the tdeasm file to get the DOS file
- * attributes. get_fattr() returns 0 or OK if no error.
- */
- int hw_fattrib( char *name )
- {
- register int rc;
- int fattr;
-
- rc = get_fattr( name, &fattr );
- return( rc == OK ? rc : ERROR );
- }
-
-
- /*
- * Name: change_mode
- * Purpose: To prompt for file access mode.
- * Date: January 11, 1992
- * Passed: name: name of file
- * line: line to display message
- * Returns: OK if file could be changed
- * ERROR otherwise
- * Notes: function is used to change file attributes for save_as function.
- */
- int change_mode( char *name, int line )
- {
- int result;
- int fattr;
- register int rc;
- char line_buff[(MAX_COLS+1)*2]; /* buffer for char and attribute */
-
- rc = OK;
- result = get_fattr( name, &fattr );
- if (result != OK)
- rc = ERROR;
- else if (result == OK && fattr & READ_ONLY) {
- /*
- * file is read only
- */
- save_screen_line( 0, line, line_buff );
- /*
- * file is write protected. overwrite anyway (y/n)?
- */
- set_prompt( main6, line );
- if (get_yn( ) != A_YES)
- rc = ERROR;
- if (rc == OK && set_fattr( name, ARCHIVE ) != OK)
- rc = ERROR;
- restore_screen_line( 0, line, line_buff );
- }
- return( rc );
- }
-
-
- /*
- * Name: write_file
- * Purpose: To write text to a file
- * way.
- * Date: June 5, 1991
- * Passed: name: name of disk file or device
- * mode: fopen flags to be used in open
- * start: first character in text buffer
- * end: last character (+1) in text buffer
- * block: write a file or a marked block
- * Returns: OK, or ERROR if anything went wrong
- */
- int write_file( char *name, char *mode, text_ptr start, text_ptr end,
- int block )
- {
- FILE *fp; /* file to be written */
- char *p;
- register int rc;
- register int len;
- int bc, ec, last_c;
- file_infos *file;
- long lend;
- long number;
-
- rc = OK;
- if ((fp = fopen( name, mode )) == NULL || ceh.flag == ERROR)
- rc = ERROR;
- else {
- start = cpf( start );
- if (block == LINE || block == BOX || block == STREAM) {
- if (g_status.marked_file == NULL)
- rc = ERROR;
- else if (block == BOX || block == STREAM) {
- file = g_status.marked_file;
- bc = file->block_bc;
- ec = file->block_ec;
- last_c = ec + 1 - bc;
- }
- if (block == STREAM) {
- len = linelen( start );
- if (ptoul( start ) == ptoul( end )) {
- end = len > ec ? start + ec + 1 : start + len + 1;
- start += bc < len ? bc : len;
- } else {
- len = linelen( end );
- end += len > ec ? ec + 1 : len;
- }
- }
- }
- p = g_status.line_buff;
- if (rc == OK) {
- if (block == BOX) {
- lend = ptoul( end );
- for (;ptoul( start ) <= (unsigned long)lend && rc == OK;) {
- g_status.copied = FALSE;
- load_box_buff( p, start, bc, ec, ' ' );
- *(p+last_c) = '\n';
- *(p+last_c+1) = CONTROL_Z;
- len = find_CONTROL_Z( p );
- if (fwrite( p, sizeof( char ), len, fp ) < (unsigned)len ||
- ceh.flag == ERROR)
- rc = ERROR;
- if (rc == OK) {
- start = find_next( start );
- if (start == NULL)
- start = end + 1;
- }
- }
- g_status.copied = FALSE;
- } else {
- number = ptoul( end ) - ptoul( start );
- len = 0x0800;
- start = nptos( start );
- while (number > 0x0800L && rc != ERROR) {
- _fmemcpy( full_screen_buffer, start, len );
- if (fwrite(full_screen_buffer,sizeof(char),len,fp)<(unsigned)len
- || ceh.flag == ERROR)
- rc = ERROR;
- if (rc != ERROR) {
- number -= 0x0800L;
- start += 0x0800;
- start = nptos( start );
- }
- }
- /*
- * now less than 2k is left, so finish off the write
- */
- if (rc != ERROR) {
- len = (int)number;
- _fmemcpy( full_screen_buffer, start, len );
- if (fwrite(full_screen_buffer,sizeof(char),len,fp) < (unsigned)len
- || ceh.flag == ERROR)
- rc = ERROR;
- }
- }
- if (ceh.flag != ERROR) {
- if (fclose( fp ) != 0)
- rc = ERROR;
- }
- }
- }
- return( rc );
- }
-
-
- /*
- * Name: hw_save
- * Purpose: To save text to a file, eliminating trailing space on the
- * way.
- * Date: November 11, 1989
- * Passed: name: name of disk file
- * start: first character in text buffer
- * end: last character (+1) in text buffer
- * block: type of block defined
- * Returns: OK, or ERROR if anything went wrong
- */
- int hw_save( char *name, text_ptr start, text_ptr end, int block )
- {
- char *lf = "wb";
- char *crlf = "w";
- register char *write_mode;
-
- write_mode = mode.crlf == LF ? lf : crlf;
- return( write_file( name, write_mode, start, end, block ) );
- }
-
-
- /*
- * Name: hw_append
- * Purpose: To append text to a file.
- * Date: November 11, 1989
- * Passed: name: name of disk file
- * start: first character in text buffer
- * end: last character (+1) in text buffer
- * block: type of defined block
- * Returns: OK, or ERROR if anything went wrong
- */
- int hw_append( char *name, text_ptr start, text_ptr end, int block )
- {
- char *lf = "ab";
- char *crlf = "a";
- register char *append_mode;
-
- append_mode = mode.crlf == LF ? lf : crlf;
- return( write_file( name, append_mode, start, end, block ) );
- }
-
-
- /*
- * Name: hw_load
- * Purpose: To load a file into the text buffer.
- * Date: November 11, 1989
- * Passed: name: name of disk file
- * start: first character in text buffer
- * limit: last available character in text buffer
- * end: last character of file in text buffer
- * line: line to display messages
- * Returns: OK, or ERROR if anything went wrong
- */
- int hw_load( char *name, text_ptr start, text_ptr limit, text_ptr *end,
- int line )
- {
- int fd; /* file being read */
- register int length; /* number of bytes actually read */
- register int rc;
- unsigned long ustart, ulimit;
- char buff[MAX_COLS+2];
- char line_buff[(MAX_COLS+2)*2]; /* buffer for char and attribute */
-
- /*
- * try reading the file
- */
- rc = OK;
- if ((fd = open( name, O_RDONLY )) == ERROR || ceh.flag == ERROR) {
- /*
- * file not found or error loading file
- */
- combine_strings( buff, main7a, name, main7b );
- save_screen_line( 0, line, line_buff );
- set_prompt( buff, line );
- getkey( );
- restore_screen_line( 0, line, line_buff );
- rc = ERROR;
- } else {
- /*
- * read the entire file, without going past end of buffer.
- * Note that this means a file that is within 1K of the limit
- * will not be accepted. length set to a number > 0 for first loop
- */
- limit = addltop( -1024, limit );
- ulimit = ptoul( limit );
- start = cpf( start );
- for (length=1; rc == OK && length > 0;) {
- ustart = ptoul( start );
- if (ustart >= ulimit ) {
- /*
- * file too big
- */
- combine_strings( buff, main8a, name, main8b );
- error( WARNING, line, buff );
- rc = WARNING;
- } else {
-
- /*
- * length = number of bytes read. since we only try to
- * read 2048 bytes at one time, we should have no problem
- * using the results from unsigned long arithmetic.
- */
- if ((length = read( fd, full_screen_buffer, 2048 )) == ERROR ||
- ceh.flag == ERROR) {
- combine_strings( buff, "error reading file '", name, "'" );
- error( WARNING, line, buff );
- rc = ERROR;
- } else {
- if (ustart + (unsigned long)length >= ulimit) {
- /*
- * file too big
- */
- combine_strings( buff, main10a, name, main10b );
- error( WARNING, line, buff );
- rc = WARNING;
- length = (int)(ulimit - ustart);
- }
- _fmemcpy( start, full_screen_buffer, length );
- start = addltop( length, start );
- }
- start = cpf( start );
- }
- }
- if (rc != ERROR) {
- if (*(start-1) != '\n')
- *start++ = '\n';
- }
- /*
- * close the file and report the final character in the buffer
- */
- close( fd );
- *end = start;
- }
- return( rc );
- }
-
-
- /*
- * Name: get_help
- * Purpose: save the screen and display key definitions
- * Date: June 5, 1991
- * Notes: This routine is dependent on the length of the strings in the
- * help screen. To make it easy to load in a new help screen,
- * the strings are assumed to be 80 characters long followed by
- * the '\0' character. It is assumed each that string contains
- * exactly 81 characters.
- */
- int get_help( WINDOW *arg_filler )
- {
- register char *help;
- register int line;
-
- xygoto( -1, -1 );
- save_screen( );
- help = help_screen[0];
- for (line=0; help != NULL; ) {
- s_output( help, line, 0, g_display.help_color );
- help = help_screen[++line];
- }
- line = getkey( );
- restore_screen( );
- return( OK );
- }
-
-
- /*
- * Name: save_screen
- * Purpose: save the contents of the screen to the screen buffer
- * Date: June 5, 1991
- */
- void save_screen( void )
- {
- _fmemcpy( full_screen_buffer, g_display.display_address, 4000 );
- }
-
-
- /*
- * Name: restore_screen
- * Purpose: restore the contents of the screen from screen buffer
- * Date: June 5, 1991
- */
- void restore_screen( void )
- {
- _fmemcpy( g_display.display_address, full_screen_buffer, 4000 );
- }
-
-
- /*
- * Name: show_credits
- * Purpose: display authors
- * Date: June 5, 1991
- */
- void show_credits( void )
- {
- register char *credit;
- int line;
-
- xygoto( -1, -1 );
- credit = credit_screen[0];
- for (line=0; credit != NULL; ) {
- s_output( credit, line+2, 11, g_display.text_color );
- credit = credit_screen[++line];
- }
- }
-
-
- /*
- * The next function was written by Tom Waters, twaters@nswc-wo.navy.mil, and
- * modified extensively by Frank Davis.
- *
- * "I use ANSI.SYS to redefine a few of my function keys and this causes a
- * problem when getch() is used in a program. For example, instead of returning
- * 321 for the F7, I get the redefined string inserted in the text. So, instead
- * of using getch(), I use the following function ..."
- *
- * Tom, thanks for the info and solution. I'm sure your function will be
- * be appreciated by everyone with ANSI key defs, Frank.
- */
-
- /*
- * Name: getkey
- * Purpose: use bios to get keyboard input (getch has trouble w/ ANSI
- * redefined keys)
- * Date: July 2, 1991
- * Modified:July 12, 1991, Frank Davis - accept ALT-xxx keyboard entry
- * September 10, 1991, Frank Davis - add support for Ctrl+Up, etc...
- * Passed: None
- * Notes: Uses the BIOS to read the next keyboard character. Service
- * 0 is keyboard read. Service 0x10 is the extended keyboard read.
- * Test for a bunch of special cases. Allow the user to enter any
- * ASCII or Extended ASCII code as a normal text characters,
- * exceptions are 10 and 26 (LF, EOF).
- *
- * Control @ is defined as 0. we need to do a special test
- * for this key, otherwise it's interpretted as an Alt key. It's
- * the only "regular" Control key that returns 0 in AL and the scan
- * byte in AH. The "regular" Control keys are those in the range
- * 0-31 and they return the Control character in AL and the scan
- * code in AH. All of the Alt + CHARACTER keys return 0 in AL and
- * the scan code in ah.
- *
- * This function was written for US keyboards. It may have to be
- * modified for other keyboards, eg. Belgium, Canada, Czech,
- * Slovak, Denmark, France, Germany, etc...
- *
- * if Ctrl-Break is pressed, it returns 0xffff as the key pressed.
- * let's set it to CONTROL_BREAK == 269 and do nothing.
- */
- int getkey( void )
- {
- unsigned key, num_lock, control, shift;
- register scan;
- register unsigned lo;
-
- /*
- * this code is used during testing to check the amount of memory
- * in the near heap.
- *
- * char buff[MAX_COLS];
- * ultoa( _memavl( ), buff, 10 );
- * s_output( "h= ", g_display.mode_line, 23, g_display.mode_color );
- * s_output( buff, g_display.mode_line, 25, g_display.mode_color );
- */
-
-
-
- /*
- * _bios_keybrd == int 16. It returns the scan code in ah, hi part of key,
- * and the ascii key code in al, lo part of key. If the character was
- * entered via ALT-xxx, the scan code, hi part of key, is 0.
- */
- if (mode.enh_kbd) {
- key = _bios_keybrd( 0x10 );
- lo = _bios_keybrd( 0x12 );
-
- /*
- * couple of special cases: 1) if user enters Alt-224 then the
- * hi byte == 0 and lo byte == 0xe0. we need to let this get
- * thru as an Extended ASCII char. 2) although we could make the
- * cursor pad keys do different functions than the numeric pad
- * cursor keys, let's set the 0xe0 in the lo byte to zero and forget
- * about support for those keys.
- */
- if ((key & 0x00ff) == 0x00e0 && (key & 0xff00) != 0)
- key = key & 0xff00;
- } else {
- key = _bios_keybrd( 0 );
- lo = _bios_keybrd( 2 );
- }
- num_lock = lo & 0x20;
- control = lo & 0x04;
- shift = lo & 0x03;
- scan = (key & 0xff00) >> 8;
- lo = key & 0X00FF;
-
- if (lo == 0) {
- /*
- * special case for Control @, which is defined as 0 or NULL.
- */
- if (scan == 3)
- lo = 430;
-
- /*
- * when first byte is 0, map it above 256, so that we can
- * let ALT-xxx keys map to their 'natural' place. In
- * otherwords, use 0-255 as normal text characters and
- * those >= 256 are function keys.
- */
- else
- lo = scan | 0x100;
-
- /*
- * now test for Control-Break. let's set this to do nothing in the
- * editor. manually map Control-Break to 269 - DO NOT assign
- * any function to 269.
- */
- } else if (key == 0xffff)
- lo = CONTROL_BREAK;
-
-
- /*
- * Pressing Control+BackSpace generates the 0x7f character. Instead of
- * 0x7f, make lo the ASCII backspace character. If anyone wants the
- * 0x7f character, then they can enter it via ALT+xxx.
- */
- if (scan == 14 && lo == 0x7f)
- lo = 8;
-
- /*
- * At the bottom of page 195 in MASM 6.0 ref manual, "..when the keypad
- * ENTER and / keys are read through the BIOS interrupt 16h, only E0h
- * is seen since the interrupt only gives one-byte scan codes."
- */
- else if (scan == 0xe0) {
- /*
- * plain Grey Enter
- */
- if (lo == 13 && !shift)
- lo = 285;
- /*
- * shift Grey Enter
- */
- else if (lo == 13)
- lo = 298;
- /*
- * control Grey Enter
- */
- else if (lo == 10)
- lo = 299;
- }
-
- /*
- * let's massage all of the control key combinations.
- */
- if (lo < 32) {
-
- /*
- * My machine at home is sorta weird. On every machine that I've
- * tested at the awffice, the ALT-xxx combination returns 0 for the
- * scan byte and xxx for the ASCII code. My machine returns 184 (decimal)
- * as the scan code?!?!? I added the next two lines for my machine at
- * home. I wonder if someone forgot to zero out ah for Alt keypad entry
- * when they wrote my bios?
- */
- if (scan > 0x80)
- scan = 0;
-
- /*
- * If user enters ALT+010 make this a return. LF is a special character
- * and needs to be handled by the editor.
- */
- if (scan == 0 && lo == 10)
- lo = 425;
-
- /*
- * Separate the ESC key from the ^[ key. The scan code for the ESC
- * key is 1. Map this to a different index into the key function
- * array just in case someone wants to define ESC or ^[ to different
- * functions. BTW, ESC and ^[ return the same ASCII code, 27.
- *
- */
- else if (scan == 1) {
- if (shift)
- lo = 259;
- else if (control)
- lo = 260;
- else
- lo = 258;
- }
-
- /*
- * Scan code for Enter = 28. Separate the various Enter keys.
- */
- else if (scan == 28) {
- if (shift)
- lo = 263;
- else if (control)
- lo = 264;
- else
- lo = 262;
- }
-
- /*
- * Scan code for Backspace = 14. Separate the various BackSpace keys.
- */
- else if (scan == 14) {
- if (shift)
- lo = 266;
- else if (control)
- lo = 267;
- else
- lo = 265;
- }
-
- /*
- * Scan code for Tab = 15. Separate the tab key.
- */
- else if (scan == 15) {
- lo = 268;
- }
-
- /*
- * if scan code is not 0, then a Control key was pressed. Map
- * those keys to the WordStar commands.
- */
- else if (scan > 0)
- lo += 430;
-
- /*
- * Do not allow control z to get thru. Code 256 is not assigned to
- * any function, see default.h for more info.
- */
- if (lo == 26)
- lo = 256;
-
- } else if (!num_lock) {
- switch (scan) {
- /*
- * scan code for grey - == 74. if num_lock is not toggled, assign it
- * to the scroll line up function.
- */
- case 74 :
- lo = 423;
- break;
-
- /*
- * scan code for grey + == 78. if num_lock is not toggled, assign it
- * to the scroll line down function. if shift grey + then stationary
- * scroll down.
- */
- case 78 :
- lo = 424;
- break;
- }
- }
-
-
- /*
- * let's set up for the Shift+Alt and Control+Alt keys.
- * With these key combinations, we can do the International keyboard
- * stuff, see the Microsoft MS DOS 5.0 manual pages 623-637.
- */
- if (lo > 256 && (shift || control)) {
-
- /*
- * add 55 to Ctrl+Left thru Ctrl+Home when the shift key is pressed.
- * this is not part of the International keyboard stuff, just a way
- * to assign the horizontal scroll left and right funcs to cursor keys.
- */
- if (shift) {
- if (lo >= 371 && lo <= 374)
- lo += 55;
-
- /*
- * if shift is down, map alt 1! thru alt =+ to shift alt 1! thru alt =+
- */
- else if (lo >= 376 && lo <= 387)
- lo += 86;
-
- /*
- * internation keyboard stuff
- */
- else if (lo >= 272 && lo <= 309)
- lo += 202;
- }
- }
-
- /*
- * the line feed is a special character that must be handled
- * by the editor.
- * don't let the eof character, 26, get thru either.
- */
- if (lo == 10)
- lo = 425;
- else if (lo == 26)
- lo = 256;
- return( lo );
- }
-
-
- /*
- * Name: getfunc
- * Purpose: get the function assigned to key c
- * Date: July 11, 1991
- * Passed: c: key just pressed
- * Notes: key codes less than 256 or 0x100 are not assigned a function.
- * The codes in the range 0-255 are ASCII and extended ASCII chars.
- */
- int getfunc( int c )
- {
- register int i = c;
-
- if (i <= 256)
- i = 0;
- else
- i = key_func[i-256];
- return( i );
- }
-
-
- /*
- * Name: record_on_off
- * Purpose: save keystrokes in keystroke buffer
- * Date: April 1, 1992
- * Passed: window: pointer to current window
- * Notes: -1 in .next field indicates the end of a recording
- * -1 in .key field indicates the initial, unassigned macro key
- * STROKE_LIMIT+1 in .next field indicates an unused space.
- */
- int record_on_off( WINDOW *window )
- {
- register int next;
- int prev;
- int line;
- int key;
- int func;
- char line_buff[(MAX_COLS+2)*2]; /* buffer for char and attribute */
-
- mode.record = !mode.record;
- if (mode.record == TRUE) {
- line = window->bottom_line;
- show_avail_strokes( );
- save_screen_line( 0, line, line_buff );
- /*
- * press key that will play back recording
- */
- set_prompt( main11, line );
-
- /*
- * get the candidate macro key and look up the function assigned to it.
- */
- key = getkey( );
- func = getfunc( key );
-
- /*
- * the key must be an unused, recognized function key or a function
- * key assigned to a previously defined macro. we also need room
- * in the macro structure.
- */
- if (key <= 256 || (func != 0 && func != PlayBack)) {
- /*
- * cannot assign a recording to this key
- */
- error( WARNING, line, main12 );
- mode.record = FALSE;
- } else if (g_status.stroke_count == 0) {
- /*
- * no more room in recording buffer
- */
- error( WARNING, line, main13 );
- mode.record = FALSE;
- } else {
-
- /*
- * everything is everything so far, just check for a prev macro
- */
- prev = OK;
- if (func == PlayBack) {
- /*
- * overwrite recording (y/n)?
- */
- set_prompt( main14, line );
- if (get_yn( ) == A_NO) {
- prev = ERROR;
- mode.record = FALSE;
- }
- }
- if (prev == OK) {
- g_status.recording_key = key;
- next = macro.first_stroke[key-256];
-
- /*
- * if key has already been assigned to a macro, clear macro def.
- */
- if (next != STROKE_LIMIT+1) {
- do {
- prev = next;
- next = macro.strokes[next].next;
- macro.strokes[prev].key = MAX_KEYS+1;
- macro.strokes[prev].next = STROKE_LIMIT+1;
- ++g_status.stroke_count;
- } while (next != -1);
- show_avail_strokes( );
- }
-
- /*
- * find the first open space and initialize
- */
- for (next=0; macro.strokes[next].next != STROKE_LIMIT+1;)
- next++;
- macro.first_stroke[key-256] = next;
- macro.strokes[next].key = -1;
- macro.strokes[next].next = -1;
- key_func[key-256] = PlayBack;
- /*
- * recording
- */
- s_output( main15, g_display.mode_line, 23,
- g_display.mode_color | 0x80 );
- }
- }
- restore_screen_line( 0, line, line_buff );
- }
-
- /*
- * the flashing "Recording" and the stroke count write over the modes.
- * when we get thru defining a macro, redisplay the modes.
- */
- if (mode.record == FALSE) {
- memset( line_buff, ' ', 36 );
- line_buff[36] = '\0';
- s_output( line_buff, g_display.mode_line, 23, g_display.mode_color );
- show_smarttab_mode( );
- show_indent_mode( );
- show_sync_mode( );
- show_search_case( );
- show_wordwrap_mode( );
-
- /*
- * let's look at the macro. if the first .key of the macro is
- * still -1, which is the initial unassigned key in a macro, reset
- * the macro so other keys may be assigned to this node.
- */
- key = g_status.recording_key;
- if (key != 0) {
- next = macro.first_stroke[key-256];
- if (macro.strokes[next].key == -1) {
- macro.strokes[next].key = MAX_KEYS+1;
- macro.strokes[next].next = STROKE_LIMIT+1;
- macro.first_stroke[key-256] = STROKE_LIMIT+1;
- if (getfunc( key ) == PlayBack)
- key_func[key-256] = 0;
- }
- }
- g_status.recording_key = 0;
- }
- return( OK );
- }
-
-
- /*
- * Name: record_keys
- * Purpose: save keystrokes in keystroke buffer
- * Date: April 1, 1992
- * Passed: line: line to display prompts
- * Notes: -1 in .next field indicates the end of a recording
- * STROKE_LIMIT+1 in .next field indicates an unused space.
- */
- void record_keys( int line )
- {
- register int next;
- register int prev;
- int key;
- int func;
-
- if (mode.record == TRUE) {
- if (g_status.stroke_count == 0)
- /*
- * no more room in recording buffer
- */
- error( WARNING, line, main13 );
- else {
- key = g_status.key_pressed;
- func = getfunc( key );
- if (func != RecordMacro && func != SaveMacro && func != LoadMacro &&
- func != ClearAllMacros) {
-
- /*
- * a -1 in the next field marks the end of the keystroke recording.
- */
- next = macro.first_stroke[g_status.recording_key - 256];
- if (macro.strokes[next].next != STROKE_LIMIT+1) {
- while (macro.strokes[next].next != -1)
- next = macro.strokes[next].next;
- }
- prev = next;
-
- /*
- * now find an open space to record the current key.
- */
- if (macro.strokes[next].key != -1) {
- for (; next < STROKE_LIMIT &&
- macro.strokes[next].next != STROKE_LIMIT+1;)
- next++;
- if (next == STROKE_LIMIT) {
- for (next=0; next < prev &&
- macro.strokes[next].next != STROKE_LIMIT+1;)
- next++;
- }
- }
- if (next == prev && macro.strokes[prev].key != -1)
- /*
- * no more room in recording buffer
- */
- error( WARNING, line, main13 );
- else {
- /*
- * next == prev if we are recording the initial macro node.
- */
- macro.strokes[prev].next = next;
- macro.strokes[next].next = -1;
- macro.strokes[next].key = key;
- g_status.stroke_count--;
- show_avail_strokes( );
- }
- }
- }
- }
- }
-
-
- /*
- * Name: show_avail_strokes
- * Purpose: show available free key strokes in lite bar at bottom of screen
- * Date: April 1, 1992
- */
- void show_avail_strokes( void )
- {
- char strokes[MAX_COLS];
-
- s_output( main18, g_display.mode_line, 34, g_display.mode_color );
- itoa( g_status.stroke_count, strokes, 10 );
- s_output( " ", g_display.mode_line, 52, g_display.mode_color );
- s_output( strokes, g_display.mode_line, 52, g_display.mode_color );
- }
-
-
- /*
- * Name: save_strokes
- * Purpose: save strokes to a file
- * Date: April 1, 1992
- * Passed: window: pointer to current window
- */
- int save_strokes( WINDOW *window )
- {
- FILE *fp; /* file to be written */
- char name[MAX_COLS+2]; /* file name */
- char line_buff[(MAX_COLS+1)*2]; /* buffer for char and attribute */
- register int rc;
- int prompt_line;
- int fattr;
-
- name[0] = '\0';
- prompt_line = window->bottom_line;
- save_screen_line( 0, prompt_line, line_buff );
- /*
- * name for macro file
- */
- if ((rc = get_name( main19, prompt_line, name,
- g_display.message_color )) == OK) {
-
- /*
- * make sure it is OK to overwrite any existing file
- */
- rc = get_fattr( name, &fattr );
- if (rc == OK) {
- /*
- * overwrite existing file
- */
- set_prompt( main20, prompt_line );
- if (get_yn( ) != A_YES || change_mode( name, prompt_line ) == ERROR)
- rc = ERROR;
- }
- if (rc != ERROR) {
- if ((fp = fopen( name, "wb" )) != NULL) {
- fwrite( ¯o, sizeof( MACRO ), 1, fp );
- fclose( fp );
- }
- }
- }
- restore_screen_line( 0, prompt_line, line_buff );
- return( OK );
- }
-
-
- /*
- * Name: load_strokes
- * Purpose: load strokes from a file
- * Date: April 1, 1992
- * Passed: window: pointer to current window
- * Notes: show the user a file pick list. I can never remember macro
- * file names or the directory in which they hide. might as well
- * give the user a file pick list.
- */
- int load_strokes( WINDOW *window )
- {
- register FILE *fp; /* file to be read */
- char dname[MAX_COLS]; /* directory search pattern */
- char stem[MAX_COLS]; /* directory stem */
- register int rc;
-
- dname[0] = '\0';
- /*
- * search path for macro file
- */
- if (get_name( main21, window->bottom_line, dname,
- g_display.message_color ) == OK) {
- if (validate_path( dname, stem ) == OK) {
- rc = list_and_pick( dname, stem, window );
-
- /*
- * if everything is everything, load in the file selected by user.
- */
- if (rc == OK) {
- if ((fp = fopen( dname, "rb" )) != NULL && ceh.flag != ERROR) {
- fread( ¯o, sizeof( MACRO ), 1, fp );
- fclose( fp );
- }
- if (ceh.flag == OK)
- connect_macros( );
- }
- } else
- /*
- * invalid path or file name
- */
- error( WARNING, window->bottom_line, main22 );
- }
- return( OK );
- }
-
-
- /*
- * Name: clear_macro
- * Purpose: reset all macro buffers, pointers, functions.
- * Date: April 1, 1992
- * Notes: reset the available macro stroke count. reset all fields in
- * macro structure. clear any keys assigned to macros in the
- * function assignment array.
- */
- int clear_macros( WINDOW *arg_filler )
- {
- register int i;
-
- g_status.stroke_count = STROKE_LIMIT;
- for (i=0; i<STROKE_LIMIT; i++) {
- macro.strokes[i].next = STROKE_LIMIT+1;
- macro.strokes[i].key = MAX_KEYS+1;
- }
- for (i=0; i<MAX_KEYS; i++) {
- macro.first_stroke[i] = STROKE_LIMIT+1;
- if (key_func[i] == PlayBack)
- key_func[i] = 0;
- }
- return( OK );
- }
-
-
- /*
- * Name: connect_macros
- * Purpose: hook up all (if any) macros to the function key definition table
- * Date: April 1, 1992
- * Notes: we need to connect all macro definitions to the key definition
- * table in the startup routine or when we read in a new macro
- * definition file. the predefined func assignments take
- * precedence over macro definitions.
- */
- void connect_macros( void )
- {
- register int i;
-
- /*
- * reset the key function assignment array. initially, no keys may be
- * assigned to a macro.
- */
- for (i=0; i<MAX_KEYS; i++)
- if (key_func[i] == PlayBack)
- key_func[i] = 0;
-
- /*
- * now, find out how many free keystrokes are in the macro structure.
- */
- g_status.stroke_count = 0;
- for (i=0; i<STROKE_LIMIT; i++)
- if (macro.strokes[i].next == STROKE_LIMIT+1)
- ++g_status.stroke_count;
-
- /*
- * go thru the first stroke list to see if any key has been assigned to
- * a macro and connect the macro def to the key. predefined function
- * assignments take precedence over macros.
- */
- for (i=0; i<MAX_KEYS; i++) {
- if (macro.first_stroke[i] != STROKE_LIMIT+1)
- if (key_func[i] == 0)
- key_func[i] = PlayBack;
- }
- }
-