/*-------------------------------------------------------------------+
| |
| Copyright (c) 1996, SAS Institute Inc. |
| Unpublished - All Rights Reserved |
| S A S / C S A M P L E |
| |
| NAME: WDHYPER |
| LANGUAGE: C |
| PURPOSE: Demonstrates how a hypertext window can be |
| incorporated into a windowing application. |
| See SAS/C Full-Screen Support Library User's Guide, |
| Second Edition, Chapters 5, 6, and 7. |
| INSTALLATION: Before compiling, you will need to modify the |
| filename value in the browse_init function. |
| To quickly find it, search for the string "fopen". |
| SYSTEM NOTES: Proper execution will require that the SAS/C |
| transient library, release 5.00H or higher, be |
| available at runtime. |
| INPUT/OUTPUT: A series of windows including PF values, browsing |
| the source code for this very program, playing |
| a simple number guessing game, and viewing a |
| hypertext file are demonstrated. |
| |
| MVS - |
| COMPILE/LINK: SUBMIT prefix.SAMPLE.AUX(WDHYPER) |
| where "prefix" is the installation defined high-level|
| qualifier for the SAS/C product. |
| EXECUTE: execute under TSO, see below |
| TSO - |
| COMPILE: LC370 CLIST |
| LINK: CLK370 CLIST |
| EXECUTE: CALL your.load.lib(WDHYPER) |
| CMS - |
| COMPILE: GLOBAL MACLIB LC370 L$FSSL |
| LC370 WDHYPER (EXTNAME |
| LINK: GLOBAL TXTLIB LC370BAS LC370STD L$FSSL |
| CLINK WDHYPER (GENMOD |
| EXECUTE: WDHYPER |
+-------------------------------------------------------------------*/
#eject
/* Some useful macro's to make field manipulation understandable */
#define GAME_RESULT_LINE 6
#define GAME_RESULT_GUESSES_LEFT 10
#define GAME_RESULT_LAST_GUESS 24
#define GAME_RESULT_FIELD 36
#define GAME_GUESS_LINE 9
#define GAME_GUESS_FIELD 24
/* Header files */
#include <wdlib.h> /* Window interface header */
#include <stdio.h> /* Standard I/O header */
#include <stdlib.h> /* Standard library header */
#include <time.h> /* Time functions header */
/* A very few special purpose external variables */
char title_string(|44|); /* Title and time string */
char game_result_message(|36|); /* Win or lose message */
int game_number; /* The object of the game */
char err_buf(|136|); /* FSSL error msg buffer */
/* Prototypes for application functions */
void fs_error_warn(char *fn, int rc, int line);
int wd_error_warn(char *, WDWI *, int parm1, void *parm2);
int event_hd(WDWI *wdwi, int event, int parm1, void *parm2);
int gamepop_hd(WDWI *wdwi, int event, int parm1, void *parm2);
int browse_init(WDWI *);
void pfbars_init(WDWI *);
void game_init(WDWI *);
void game_play(WDWI *, WDINPUT *);
void time_update(WDWI *);
int line_count(void);
/* Common open list structure used to open the BROWSER */
/* window, the GAME window and the HYPER window. */
static WDOLST olst_common = {
"COMMON ", /* Window name */
0, 0, /* Physical screen origin */
0, 0, /* Number of rows, number of cols */
3, 6, /* Minimum window size */
NULL, /* Border, title, cmdline color */
/* Window flags */
WDW_BORDER+WDW_TITLE+
WDW_ERROR_EVT+WDW_WARN_EVT,
/* Border, title, cmdline attr. */
BRIGHT+REVERSE_VIDEO,
0, 0, /* Initial cursor row, col */
&event_hd, /* Event handler function ptr */
100, /* Event handler priority 16-239 */
NULL, /* Title text */
NULL, /* Cmdline text */
NULL, /* Border chars */
NULL}; /* Address of "user data" field */
/* The window structure used to open the GAMEPOP popup */
/* window. */
static WDOLST olst_gamepop = {
"GAMEPOP ", /* Window name */
15,18, /* Physical screen origin */
3, 45, /* Number of rows, number of cols */
3, 45, /* Minimum window size */
YELLOW, /* Border, title, cmdline color */
/* Window flags */
WDW_BORDER+WDW_TITLE+
WDW_ERROR_EVT+WDW_WARN_EVT,
/* Border, title, cmdline attr. */
BRIGHT,
0, 0, /* Initial cursor row, col */
&gamepop_hd,/* Event handler function ptr */
239, /* Event handler priority 16-239 */
/* Title text */
"SAS/C Sample Game Results",
NULL, /* Cmdline text */
NULL, /* Border chars */
NULL}; /* Address of "user data" field */
/* The main function begins here */
void main() {
struct FS_TERMATTR *term_attr; /* Terminal attribute structure ptr */
WDWI *browser, *game, *pfbars, /* Pointers to the 4 primary windows*/
*hyper;
WDOLST *olst_pfbars, *olst_hyper; /* Pointer to open list structures */
int rc;
char *init_string; /* Initial search string for HYPER */
double mask; /* Event handling mask for HYPER */
memset(&mask,0xFF,8);
term_attr = wdinit(err_buf); /* Begin Window interface operations*/
if (term_attr == NULL) {
fs_error_warn("wdinit",-1 ,(__LINE__-3));
}
/* Modify the common open list structure with the */
/* specifics of the the BROWSER window and then */
/* open the window. */
strcpy(olst_common.name, "BROWSER ");
olst_common.beg_row = 0;
olst_common.beg_col = 0;
olst_common.height = term_attr->prim_row - 4;
olst_common.width = term_attr->prim_col;
olst_common.color = MAGENTA;
olst_common.title = "SAS/C Sample Browser";
browser = wdopen(&olst_common, &rc);
if (browser == NULL) {
fs_error_warn("wdopen", rc ,(__LINE__-3));
}
/* Modify the common open list structure with the */
/* specifics of the the GAME window and then open */
/* the window. */
strcpy(olst_common.name, "GAME ");
olst_common.beg_row = 0;
olst_common.beg_col = 15;
olst_common.height = term_attr->prim_row - 4;
olst_common.width = term_attr->prim_col - 30;
olst_common.color = BLUE;
olst_common.title = "SAS/C Sample Game";
game = wdopen(&olst_common, &rc);
if (game == NULL) {
fs_error_warn("wdopen", rc ,(__LINE__-3));
}
/* Use the wdrtolst() function to make a copy of */
/* the common open list structure, modify it with */
/* the specifics of the PFBARS window, then open */
/* the window. */
olst_pfbars = wdrtolst(game);
strcpy(olst_pfbars->name, "PFBARS ");
olst_pfbars->beg_row = term_attr->prim_row - 4;
olst_pfbars->beg_col = 0;
olst_pfbars->height = 4;
olst_pfbars->width = term_attr->prim_col;
olst_pfbars->color = RED;
olst_pfbars->title = "PF Key Assignments";
pfbars = wdopen(olst_pfbars, &rc);
if (pfbars == NULL) {
fs_error_warn("wdopen", rc ,(__LINE__-3));
}
free(olst_pfbars); /* Once the window is open, the WDOLST */
/* structure is no longer required. */
/* Use the wdrtolst() function to make a copy of */
/* the common open list structure, modify it with */
/* the specifics of the HYPER window, then open */
/* the window. */
olst_hyper = wdrtolst(game);
strcpy(olst_hyper->name, "HYPER ");
olst_hyper->beg_row = 0;
olst_hyper->beg_col = 0;
olst_hyper->height = term_attr->prim_row - 4;
olst_hyper->width = term_attr->prim_col;
olst_hyper->color = GREEN;
olst_hyper->title = "SAS/C Sample Hypertext Document Viewer";
hyper = wdhyopn(olst_hyper, "dsn:SASC.WDHYPOUT.HYP",
NULL, mask, NULL, &rc);
if (hyper == NULL) {
fs_error_warn("wdopen", rc ,(__LINE__-3));
}
free(olst_hyper); /* Once the window is open, the WDOLST */
/* structure is no longer required. */
/* Present the windows in the desired order(BROWSER on */
/* top, GAME behind it, PFBARS behind game, and HYPER */
/* at the bottom. */
wdsettop(hyper);
wdsettop(pfbars);
wdsettop(game);
wdsettop(browser);
/* Call the Window interface dispatcher. All further */
/* activity will be controlled by her. */
wdwait();
/* The flow of control will only reach this logical */
/* point when all windows have been terminated. */
wdterm();
} /* end of the main() function */
#eject
/* This is the handler for all display manager events */
/* (except those events associatted with the GAMEPOP */
/* popup window, which has its own seperate handler). */
int event_hd(WDWI *wdwi, int event, int parm1, void *parm2) {
WDWI *pfbars; /* Pointer to the PFBARS window */
WDWI *game; /* Pointer to the GAME window */
WDWI *browser; /* Pointer to the BROWSER window */
WDWI *hyper; /* Pointer to the HYPER window */
/* Get the pointer values from the dispatcher. */
pfbars = wdinqwd("PFBARS ");
game = wdinqwd("GAME ");
browser = wdinqwd("BROWSER ");
hyper = wdinqwd("HYPER ");
/* Update the time and date on all events except */
/* initialization and termination events. At */
/* these two events, the PFBARS window may not */
/* always be available. */
if (event != WEVT_INIT && event != WEVT_TERM) time_update(pfbars);
/* The action taken by the handler depends on what kind of */
/* event has occurred. */
switch (event)
{
case WEVT_PFK: /* A PF Key was depressed, */
switch (parm1) /* which one was it? */
{
case PF_7: /* Scroll all eligible subwindows up a full page */
case PF_19: /* if the active window is the BROWSER window. */
if (strcmp(wdwi->name,"BROWSER ") == 0)
wdscroll(wdwi, WD_SCROLL_ALL, 0, WD_UP, WSCR_PAGE);
break;
case PF_8: /* Scroll all eligible subwindows down a full page */
case PF_20: /* if the active window is the BROWSER window. */
if (strcmp(wdwi->name,"BROWSER ") == 0)
wdscroll(wdwi, WD_SCROLL_ALL, 0, WD_DOWN, WSCR_PAGE);
break;
case PF_10: /* Scroll all eligible subwindows left a full page */
case PF_22:
wdscroll(wdwi, WD_SCROLL_ALL, 0, WD_LEFT, WSCR_PAGE);
break;
case PF_11: /* Scroll all eligible subwindows right a full page */
case PF_23:
wdscroll(wdwi, WD_SCROLL_ALL, 0, WD_RIGHT, WSCR_PAGE);
break;
case PF_3: /* Terminate the window interface environment */
case PF_15:
return(WRET_TERM);
break;
case PF_9: /* Select a new "top" window */
case PF_21:
wdsetnxt(); /* Put the next window on top... */
/* if that window happens to be PFBARS.... */
if (strcmp(wdinqtop()->name,"PFBARS ") == 0)
{
/* if PFBARS is not overlapping ANY other window...*/
/* put some other window on top. (Because the */
/* PFBARS window doesn't DO anything! It just pre- */
/* sents information about what the PF Keys do.) */
if (wdovlap(pfbars,game) == 0 &&
wdovlap(pfbars,browser) == 0 &&
wdovlap(pfbars,hyper) == 0) wdsetnxt();
}
break;
case PF_2: /* Move the current window according to subsequent */
case PF_14: /* cursor position. */
wdmove(WD_ANY_KEY, 0);
break;
case PF_4: /* Change the size of the current window according */
case PF_16: /* to subsequent cursor position. */
wdgrow(WD_ANY_KEY, 0);
break;
case PF_5: /* Zoom the current window to the physical maximum */
case PF_17: /* -OR- Unzoom the current window to original size */
wdzoom(wdwi, WD_ZOOM_TOGGLE);
break;
default: /* No action is defined for these PF Keys */
case PF_1:
case PF_6:
case PF_12:
case PF_13:
case PF_18:
case PF_24:
break;
} /* end of switch(parm1) */
return (WRET_PASSTHRU);
break;
case WEVT_WARN: /* A Window interface warning has been detected, */
/* Call the routine that prints the message. */
return(wd_error_warn("WEVT_WARN", wdwi, parm1, parm2));
break;
case WEVT_ERROR: /* A Window interface error has been detected, */
/* Call the routine that prints the message. */
return(wd_error_warn("WEVT_ERROR", wdwi, parm1, parm2));
break;
case WEVT_INIT: /* A new window has just opened up! Go paint it */
/* up with the appropriate stuff! */
if (strcmp(wdwi->name,"BROWSER ") == 0) return(browse_init(wdwi));
if (strcmp(wdwi->name,"PFBARS ") == 0) pfbars_init(wdwi);
if (strcmp(wdwi->name,"GAME ") == 0) game_init(wdwi);
return(WRET_PASSTHRU);
break;
case WEVT_ENTER: /* The operator pressed enter while the cursor */
/* was in some window. If it was the GAME */
/* window, play the game; otherwise do nothing.*/
if (strcmp(wdwi->name,"GAME ") == 0) game_play(wdwi,parm2);
return (WRET_PASSTHRU);
break;
default: /* For all other events, do nothing and return */
return (WRET_PASSTHRU);
break;
} /* end of switch(event) */
} /* end of event_hd() */
#eject
/* This function is called by the event handler to initialize the */
/* BROWSER window. */
int browse_init(WDWI *browser) {
WDSW *sub_browser; /* Pointer to the browsing subwindow */
char buf(|80|); /* A buffer to read the records into */
int program_size; /* Number of source lines in this pgm */
FILE *fp;
int i, rc;
/* Call the line_count function to determine the number of source */
/* lines in this program. That number will be used to determine */
/* the size of the subwindow in which the browsing takes place. */
program_size = line_count();
/* Define a large subwindow in which to place records for browsing */
/* This particular technique is not put forward as "best" or even */
/* as particularly effecient, but it does illustrate the use of */
/* subwindows and subwindow scrolling nicely. */
sub_browser = wddfsw(browser, 0, 0, program_size,
browser->use_width, 0 ,0 ," ", PROTECTED, WHITE, ' ', WD_VERT_DATA,
&rc);
/* IMPORTANT!!!!! You will need to modify the filename to whatever */
/* your local installer choose. A nice feature of this sample is */
/* that it browses its own source code; allowing you to following */
/* the bouncing ball! Also, the actual file name is operating */
/* system dependent; which is no problem since SAS/C automatically */
/* defines CMS as a symbol when compiling on CMS and OSVS when */
/* compiling on an OS-derivative operating system. */
#ifdef CMS
fp = fopen("cms:LCSAMPLE MACLIB * MEMBER WDHYPER","rb");
#else
fp = fopen("dsn:SASC.SAMPLE(WDHYPER)","rb");
#endif
if (fp == NULL) {
printf("Unable to open file for browsing\n");
return(WEVT_TERM);
}
/* Read the records and fill up the subwindow */
for(i=0; i < program_size ; i++) {
rc = fread(buf,80,1,fp);
if (feof(fp)) break;
if (rc == 0 ) {
printf("Unsuccessful fread during subwindow load\n");
return(WRET_FAILED);
}
/* Place the record into the subwindow */
wdupdsw(sub_browser, UPD_TEXT, i, 0, buf);
} /* end of for(...) */
} /* end of browse_init() */
/* This function is called by the event handler to initialize the */
/* PFBARS window. */
void pfbars_init(WDWI *pfbars) {
char *pf_string1 = " PF1 PF2 PF3 PF4 PF5 PF6 PF7 "
"PF8 PF9 PF10 PF11 PF12";
char *pf_string2 = " NULL Move End Grow Zoom NULL Up "
"Down Next LEFT RIGHT NULL";
/* Cause the current time and date to be placed in the PFBARS title */
time_update(pfbars);
/* Paint in the PF Key numbers and values */
wdupdln(pfbars, DEF_FIELD, 0, 0, pf_string1, PROTECTED, RED,
USE_TEXT_LEN, 0);
wdupdln(pfbars, DEF_FIELD, 1, 0, pf_string2, PROTECTED, RED,
USE_TEXT_LEN, 0);
} /* end of pfbars_init() */
/* This function is called by pfbars_init() and event_hd() to */
/* keep the time and date in the PFBARS title up-to-date. */
void time_update(WDWI *pfbars) {
char *time_string;
time_t now;
time(&now); /* Get the current Greenwich Mean Time */
time_string = ctime(&now); /* and convert it to something readable */
sprintf(title_string,"PF Key Assignments--%.24s",time_string);
/* Update the title field in the PFBARS window */
wdupdln(pfbars, UPD_TEXT, WD_TITLE, 0, title_string,
0,0, USE_TEXT_LEN, BLANK);
} /* end of time_update() */
/* This function is called by the event handler to initialize the */
/* GAME window. */
void game_init(WDWI *game) {
time_t now;
/* Use the current "seconds after the minute" to seed the psuedo- */
/* random number generator. Then generate a psuedorandom number. */
time(&now);
srand(gmtime(&now)->tm_sec);
for(;;) {
game_number = rand();
/* Ensure that the number is between 1 and 1000, inclusive */
if (game_number > 0 && game_number <= 1000) break;
} /* end of for(...) */
/* Paint in the game instructions and initial field values */
/* Notice that when defining a field, all of the parameters*/
/* to wdupdln() are required. */
wdupdln(game,DEF_FIELD, 0, 5,
"I have randomly chosen a number between", PROTECTED, BLUE,
USE_TEXT_LEN, 0);
wdupdln(game,DEF_FIELD, 1, 5,
"1 and 1000. I'll give you 20 guesses.", PROTECTED, BLUE,
USE_TEXT_LEN, 0);
wdupdln(game,DEF_FIELD, 2, 5,
"Each time I'll tell you if you are too", PROTECTED, BLUE,
USE_TEXT_LEN, 0);
wdupdln(game,DEF_FIELD, 3, 5,
"high or too low. ", PROTECTED, BLUE, USE_TEXT_LEN, 0);
/* Line 4 is blank */
wdupdln(game,DEF_FIELD, 5, 5,
"Guesses Left Last Guess Result", PROTECTED, BLUE,
USE_TEXT_LEN, 0);
wdupdln(game,DEF_FIELD, GAME_RESULT_LINE, GAME_RESULT_GUESSES_LEFT,
"20", PROTECTED, GREEN, USE_TEXT_LEN, 0);
wdupdln(game,DEF_FIELD, GAME_RESULT_LINE, GAME_RESULT_LAST_GUESS,
"0000", PROTECTED, GREEN, USE_TEXT_LEN, 0);
wdupdln(game,DEF_FIELD, GAME_RESULT_LINE, GAME_RESULT_FIELD,
"Good Luck", PROTECTED, GREEN, USE_TEXT_LEN, 0);
/* Line 7 is blank */
wdupdln(game,DEF_FIELD, 8, 21,
"Next Guess", PROTECTED, BLUE, USE_TEXT_LEN, 0);
wdupdln(game,DEF_FIELD, GAME_GUESS_LINE, GAME_GUESS_FIELD,
"0000", BRIGHT+NUMERIC, CYAN, USE_TEXT_LEN, 0);
wdsetlcr(game,GAME_GUESS_LINE,GAME_GUESS_FIELD);
} /* end of game_init() */
/* This function is called by the event handler to actually play */
/* the game when the enter key is pressed. */
void game_play(WDWI *game, WDINPUT *input) {
int guess_value;
char *text_ptr;
int text_max_len;
char guess_left_text(|3|) = "\0\0\0";
int guess_left_value;
char guess_result_text(|10|);
char previous_guess_text(|5|) = "\0\0\0\0\0";
/* Determine if the player has correctly guessed the number.*/
/* If the player actually entered a guess, use the WD_INPUT */
/* structure to access the guess value. If the player did */
/* not "modify" the guess field, then the program is forced */
/* to "reuse" the previous guess. The previous guess is */
/* recovered by directly accessing the data in the window */
/* via the wdgetln() function. */
if (input->first_inp_field != NULL)
guess_value = atoi(input->first_inp_field->inp_data);
else
{
wdgetln(game, GET_TEXT, GAME_GUESS_LINE, GAME_GUESS_FIELD,
&text_ptr, NULL, NULL, &text_max_len, NULL);
guess_value = atoi(text_ptr);
}
if (guess_value == game_number) {
strcpy(game_result_message,"You won! Hit any PF to continue.");
/* Cause a popup window to be opened and displayed. Note */
/* that the GAMEPOP window has a unique event handler. */
/* This is not required, but it does lead to a clearer */
/* programming style. */
wdpopup(&olst_gamepop,0);
/* Cause another initialization event to happen for the */
/* GAME window. This is how the game is reset for further*/
/* play. (The event will be handled asynchronously.) */
wdpostev(game,WEVT_INIT,NULL,NULL,WD_WINDOW_PRIORITY,NULL);
return;
}
/* Retrieve the number of guesses remaining directly from the */
/* data in the window. Of course, this same data could have */
/* been kept in a program variable, but this is a sample pgm! */
wdgetln(game, GET_TEXT, GAME_RESULT_LINE, GAME_RESULT_GUESSES_LEFT,
&text_ptr, NULL, NULL, &text_max_len, NULL);
guess_left_value = atoi(text_ptr);
/* Deterimine if the player has run out of guesses */
if (guess_left_value == 1) {
strcpy(game_result_message,"You lost. Hit any PF to continue.");
/* Process the popup window in the same manner as above. */
wdpopup(&olst_gamepop,0);
wdpostev(game,WEVT_INIT,NULL,NULL,WD_WINDOW_PRIORITY,NULL);
return;
}
/* Having neither won nor lost; prepare for another round */
/* by decrementing the number of remaining guesses, saving */
/* the previous guess, and determining whether the player */
/* guessed high or low. */
sprintf(guess_left_text,"%-2d\0",--guess_left_value);
wdupdln(game,UPD_TEXT, GAME_RESULT_LINE, GAME_RESULT_GUESSES_LEFT,
guess_left_text, 0, 0, USE_TEXT_LEN, 0);
sprintf(previous_guess_text,"%-4d\0",guess_value);
wdupdln(game,UPD_TEXT, GAME_RESULT_LINE, GAME_RESULT_LAST_GUESS,
previous_guess_text, 0, 0, USE_TEXT_LEN, 0);
if (guess_value > game_number) strcpy(guess_result_text,"Too big");
else strcpy(guess_result_text,"Too small");
wdupdln(game,UPD_TEXT, GAME_RESULT_LINE, GAME_RESULT_FIELD,
guess_result_text, 0, 0, USE_TEXT_LEN, 0);
wdsetlcr(game,GAME_GUESS_LINE,GAME_GUESS_FIELD);
} /* end of game_play() */
/* This is the unique event handler for the GAMEPOP popup window */
/* A unique handler is NOT required for a window, but this one */
/* demonstrates that it is a possible style consideration. */
int gamepop_hd(WDWI *wdwi, int event, int parm1, void *parm2) {
switch (event) {
case WEVT_INIT: /* Display the result of the game */
wdalarm(); /* Sound the horn, in glory or shame */
wdupdln(wdwi,DEF_FIELD, 0, 0, game_result_message, PROTECTED,
YELLOW, USE_TEXT_LEN, 0);
return(WRET_PASSTHRU);
break;
case WEVT_PFK: /* Close the popup window on any PF key */
return(WRET_TERM);
break;
case WEVT_WARN: /* A Window interface warning has been detected, */
/* Call the routine that prints the message. */
return(wd_error_warn("WEVT_WARN", wdwi, parm1, parm2));
break;
case WEVT_ERROR: /* A Window interface error has been detected, */
/* Call the routine that prints the message. */
return(wd_error_warn("WEVT_ERROR", wdwi, parm1, parm2));
break;
default: return(WRET_PASSTHRU); break;
} /* end of switch(event) */
} /* end of gamepop_hd() */
#eject
/* This function is called by both the common event handler and */
/* the unique GAMEPOP handler. It's job is to emit Window */
/* interface warnings and errors. */
int wd_error_warn(char *type, WDWI *wdwi, int parm1, void *parm2) {
char window_name(|8|);
if (wdwi) memcpy(window_name,wdwi->name,8);
else strcpy(window_name,"NULL ");
printf("%s rc = %d for window = %.8s warn_text = %.80s\n",
type, parm1, window_name, (char *) parm2);
return(parm1); /* reflect back error code */
} /* end of wd_error_warn() */
/* This function issues FSSL warnings and errors */
void fs_error_warn(char *fn, int rc, int line) {
if (!rc) return;
if (rc > 0)
printf("%s warning rc = %d at line %d\n", fn, rc, line);
else
printf("%s error rc = %d at line %d\n", fn, rc, line);
printf("error text %s\n\n", err_buf);
} /* end of fs_error_warn() */
/* This is an unusual function. It makes use of the __LINE__ */
/* ANSI macro which always contains the number of the current */
/* source line. Since this function is at the bottom of the */
/* source program, the actual size of the source can be dyn- */
/* amically determined at runtime. */
int line_count(void) {
return(__LINE__ + 1);
} /* end of line_count() and the end of all source code */
|