/* file: logpan.c G. Moody 1 May 1990 Last revised: 22 June 1999 Log panel functions for WAVE ------------------------------------------------------------------------------- WAVE: Waveform analyzer, viewer, and editor Copyright (C) 1999 George B. Moody This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . You may contact the author by e-mail (wfdb@physionet.org) or postal mail (MIT Room E25-505A, Cambridge, MA 02139 USA). For updates to this software, please visit PhysioNet (http://www.physionet.org/). _______________________________________________________________________________ */ #include "wave.h" #include "xvwave.h" #include #include /* for */ #include #include #include #include #define LLLMAX (RNLMAX+40+DSLMAX) /* max length of line in log file */ Frame log_frame; /* A WAVE log file contains one-line entries. Each entry specifies a record and a time or time interval in that record, and contains an associated text string, which (typically) contains a user-entered description of features of the signals at that time. The format is: where is the record name (if empty, use the record name given in the previous entry, or the name of the currently displayed record if this is the first entry) is either the start and end times separated by a hyphen, or the time of the center of the region of interest, in strtim format is the text of the log entry, which cannot begin with whitespace but which may contain embedded whitespace (this field may be empty) and is a sequence of one or more space or tab characters. This format is compatible with `pschart' and `psfd' script file format. The log is kept internally as a doubly-linked list of log_entry structures. */ struct log_entry { struct log_entry *prev, *next; char *record, *time_spec, *text; } *first_entry, *current_entry, *last_entry; /* This function selects an annotation at a specified time. If no such annotation exists, it moves the log marker (an index mark pseudo-annotation) to the specified time and selects the marker. */ void set_marker(t) long t; { static struct ap *log_marker; if (locate_annotation(t, 0)) attached = annp; else if (log_marker) { move_annotation(log_marker, t); attached = log_marker; } else if (log_marker = get_ap()) { log_marker->this.time = t; log_marker->this.anntyp = INDEX_MARK; insert_annotation(log_marker); attached = log_marker; } } /* add_entry allocates memory for a log_entry structure, fills it in using the input arguments, and inserts it in the linked list at the location after the current entry. (If current_entry is NULL, the new entry is inserted at the head of the list.) Upon successful (non-zero) return, current_entry points to the newly inserted entry, and first_entry and last_entry may have been updated; if memory cannot be allocated, add_entry returns 0, and the log entry pointers are left unchanged. */ int add_entry(recp, timep, textp) char *recp, *timep, *textp; /* record name, time specification, and text */ { struct log_entry *new_entry; char *p; long t0; /* Allocate memory for structure and strings. */ if ((new_entry = (struct log_entry *)malloc(sizeof(struct log_entry))) == NULL || (new_entry->record = (char *)malloc(strlen(recp)+1)) == NULL || (new_entry->time_spec = (char *)malloc(strlen(timep)+1)) == NULL || (textp && (new_entry->text = (char *)malloc(strlen(textp)+1)) == NULL)) { #ifdef NOTICE Xv_notice notice = xv_create((Frame)frame, NOTICE, XV_SHOW, TRUE, #else (void)notice_prompt((Frame)frame, (Event *)NULL, #endif NOTICE_MESSAGE_STRINGS, "Error in allocating memory for log\n", 0, NOTICE_BUTTON_YES, "Continue", 0); #ifdef NOTICE xv_destroy_safe(notice); #endif if (new_entry) { if (new_entry->time_spec) free(new_entry->time_spec); if (new_entry->record) free(new_entry->record); free(new_entry); } return (0); } /* Fill in the log_entry structure. */ strcpy(new_entry->record, recp); strcpy(new_entry->time_spec, timep); if (textp) strcpy(new_entry->text, textp); else new_entry->text = NULL; /* Insert the new entry into the linked list. */ if (current_entry) { if (current_entry->next) /* insert into middle of list */ (current_entry->next)->prev = new_entry; else /* append at tail */ last_entry = new_entry; new_entry->next = current_entry->next; new_entry->prev = current_entry; current_entry->next = new_entry; } else if (first_entry) { /* insert at head of list */ new_entry->next = first_entry; new_entry->prev = NULL; first_entry->prev = new_entry; first_entry = new_entry; } else { /* list is empty, initialize it */ new_entry->next = new_entry->prev = NULL; first_entry = last_entry = new_entry; } current_entry = new_entry; p = strchr(timep, '-'); if (p) *p = '\0'; return (1); } /* delete_entry removes the current entry from the linked list, and resets the current_entry pointer so that it points to the next entry in the linked list if there is one, or to the previous entry otherwise. */ void delete_entry() { struct log_entry *p; if (current_entry) { if (current_entry->prev) (current_entry->prev)->next = current_entry->next; else /* deleting entry at head */ first_entry = current_entry->next; if (p = current_entry->next) (current_entry->next)->prev = current_entry->prev; else /* deleting entry at tail */ p = last_entry = current_entry->prev; free(current_entry->record); free(current_entry->time_spec); if (current_entry->text) free(current_entry->text); free(current_entry); current_entry = p; } } /* read_log appends the contents of the log file named by its argument to the linked list. It returns 1 if completely successful, 0 if the file cannot be read or if not all properly formatted entries can be stored. */ int read_log(logfname) char *logfname; { char buf[LLLMAX+1], *p, *recp = record, *timep, *textp = NULL, *strtok(); FILE *logfile; int ignore; long t0, t1; if ((logfile = fopen(logfname, "r")) == NULL) return (0); while (fgets(buf, LLLMAX, logfile)) { /* read and parse an entry */ /* Check that entry is properly formatted -- if not, skip it. */ for (p = buf, ignore = 0; *p; p++) { if (!isprint(*p) && !isspace(*p)) { ignore = 1; break; } } if (ignore) continue; if (buf[0] != ' ' && buf[0] != '\t') { recp = strtok(buf, " \t"); /* first token is record name */ timep = strtok(NULL, " \t\n");/* second token is time spec */ } else /* record name missing, use previous value */ timep = strtok(buf, " \t"); /* first token is time spec */ if (recp == NULL || timep == NULL) continue; /* skip improperly formatted entries */ textp = strtok(NULL, "\n"); /* remainder of line is text */ if (add_entry(recp, timep, textp) == 0) { fclose(logfile); return (0); } } fclose(logfile); return (1); } /* write_log copies the contents of the linked list to the log file named by its argument. It returns 1 if successful, 0 otherwise. */ static int log_changes, save_log_backup; int write_log(logfname) char *logfname; { int result; struct log_entry *p; FILE *logfile; /* Do we need to back up ? */ if (save_log_backup) { char backfname[LNLMAX+2]; sprintf(backfname, "%s~", logfname); if (rename(logfname, backfname)) { #ifdef NOTICE Xv_notice notice = xv_create((Frame)frame, NOTICE, XV_SHOW, TRUE, NOTICE_STATUS, &result, #else result = notice_prompt((Frame)frame, (Event *)NULL, #endif NOTICE_MESSAGE_STRINGS, "Your log cannot be saved unless you remove the file named", backfname, "", "You may attempt to correct this problem from", "another window after pressing `Continue', or", "you may exit immediately and discard your", "changes by pressing `Exit'.", 0, NOTICE_BUTTON_YES, "Continue", NOTICE_BUTTON_NO, "Exit", NULL); #ifdef NOTICE xv_destroy_safe(notice); #endif if (result == NOTICE_YES) return (0); else if (post_changes()) { xv_destroy_safe(frame); exit(1); } else return (0); } save_log_backup = 0; } if ((logfile = fopen(logfname, "w")) == NULL) { #ifdef NOTICE Xv_notice notice = xv_create((Frame)frame, NOTICE, XV_SHOW, TRUE, NOTICE_STATUS, &result, #else result = notice_prompt((Frame)frame, (Event *)NULL, #endif NOTICE_MESSAGE_STRINGS, "Your log cannot be saved until you obtain", "write permission for", logfname, "", "You may attempt to correct this problem from", "another window after pressing `Continue', or", "you may exit immediately and discard your", "changes by pressing `Exit'.", 0, NOTICE_BUTTON_YES, "Continue", NOTICE_BUTTON_NO, "Exit", NULL); #ifdef NOTICE xv_destroy_safe(notice); #endif if (result == NOTICE_YES) return (0); else if (post_changes()) { xv_destroy_safe(frame); exit(1); } else return (0); } for (p = first_entry; p; p = p->next) { fprintf(logfile, "%s %s", p->record, p->time_spec); if (p->text) fprintf(logfile, " %s", p->text); fprintf(logfile, "\n"); } log_changes = 0; fclose(logfile); return (1); } Panel_item log_name_item, log_text_item, load_button, add_button, replace_button, delete_button, edit_button, first_button, rreview_button, prev_button, pause_button, next_button, review_button, last_button; struct itimerval timer; Panel log_panel; void show_current_entry() { char *p; int record_changed = 0; long t0; if (current_entry) { if (current_entry->text) strncpy(description, current_entry->text, DSLMAX); else description[0] = '\0'; if (strcmp(record, current_entry->record)) { set_record_item(current_entry->record); record_changed = 1; } p = strchr(current_entry->time_spec, '-'); if (p) *p = '\0'; if ((t0 = strtim(current_entry->time_spec)) < 0L) t0 = -t0; if (p) /* start and end times are given */ *p = '-'; else { /* only one time given -- place mark at that time */ set_marker(t0); if ((t0 -= nsamp/2) < 0L) t0 = 0L; } if (!record_changed) { set_frame_title(); (void)find_display_list(t0); } set_start_time(timstr(t0)); set_end_time(timstr(t0 + nsamp)); xv_set(log_text_item, PANEL_VALUE, description, NULL); disp_proc(log_text_item, (Event *)NULL); if (attached) box((int)((attached->this.time - display_start_time)*tscale), (ann_mode==1 && (unsigned)attached->this.chan < nsig) ? (int)(base[(unsigned)attached->this.chan] + mmy(2)) : abase, 1); } } Notify_value show_next_entry() { char *p; long t0; if (current_entry->next) current_entry = current_entry->next; else current_entry = first_entry; show_current_entry(); if (current_entry->next && strcmp(current_entry->next->record, current_entry->record) == 0) { p = strchr(current_entry->next->time_spec, '-'); if (p) *p = '\0'; if ((t0 = strtim(current_entry->next->time_spec)) < 0L) t0 = -t0; if (p) *p = '-'; /* t1 = strtim(p+1); */ else if ((t0 -= nsamp/2) < 0L) t0 = 0L; (void)find_display_list(t0); } return (NOTIFY_DONE); } Notify_value show_prev_entry() { char *p; long t0; if (current_entry->prev) current_entry = current_entry->prev; else current_entry = last_entry; show_current_entry(); if (current_entry->prev && strcmp(current_entry->prev->record, current_entry->record) == 0) { p = strchr(current_entry->prev->time_spec, '-'); if (p) *p = '\0'; if ((t0 = strtim(current_entry->prev->time_spec)) < 0L) t0 = -t0; if (p) *p = '-'; /* t1 = strtim(p+1); */ else if ((t0 -= nsamp/2) < 0L) t0 = 0L; (void)find_display_list(t0); } return (NOTIFY_DONE); } int review_delay; int review_in_progress; void log_review(direction) int direction; { review_in_progress = direction; timer.it_value.tv_sec = timer.it_interval.tv_sec = review_delay; if (direction == 1) notify_set_itimer_func(log_frame, show_next_entry, ITIMER_REAL, &timer, NULL); else notify_set_itimer_func(log_frame, show_prev_entry, ITIMER_REAL, &timer, NULL); } void pause_review() { review_in_progress = 0; notify_set_itimer_func(log_frame,NOTIFY_FUNC_NULL,ITIMER_REAL,NULL,NULL); } /* Handle enabling/disabling log navigation buttons for non-review modes. */ void set_buttons() { xv_set(pause_button, PANEL_INACTIVE, TRUE, 0); if (log_file_name) { xv_set(load_button, PANEL_INACTIVE, FALSE, 0); xv_set(add_button, PANEL_INACTIVE, FALSE, 0); xv_set(edit_button, PANEL_INACTIVE, FALSE, 0); } if (current_entry) { xv_set(replace_button, PANEL_INACTIVE, FALSE, 0); xv_set(delete_button, PANEL_INACTIVE, FALSE, 0); xv_set(first_button, PANEL_INACTIVE, FALSE, 0); xv_set(last_button, PANEL_INACTIVE, FALSE, 0); xv_set(next_button, PANEL_INACTIVE, current_entry->next ? FALSE : TRUE, 0); xv_set(prev_button, PANEL_INACTIVE, current_entry->prev ? FALSE : TRUE, 0); xv_set(review_button, PANEL_INACTIVE, (current_entry->next || current_entry->prev) ? FALSE : TRUE, 0); xv_set(rreview_button, PANEL_INACTIVE, (current_entry->next || current_entry->prev) ? FALSE : TRUE, 0); } else { xv_set(replace_button, PANEL_INACTIVE, TRUE, 0); xv_set(delete_button, PANEL_INACTIVE, TRUE, 0); xv_set(first_button, PANEL_INACTIVE, TRUE, 0); xv_set(rreview_button, PANEL_INACTIVE, TRUE, 0); xv_set(prev_button, PANEL_INACTIVE, TRUE, 0); xv_set(next_button, PANEL_INACTIVE, TRUE, 0); xv_set(review_button, PANEL_INACTIVE, TRUE, 0); xv_set(last_button, PANEL_INACTIVE, TRUE, 0); } } void disable_buttons() { xv_set(load_button, PANEL_INACTIVE, TRUE, 0); xv_set(add_button, PANEL_INACTIVE, TRUE, 0); xv_set(replace_button, PANEL_INACTIVE, TRUE, 0); xv_set(delete_button, PANEL_INACTIVE, TRUE, 0); xv_set(edit_button, PANEL_INACTIVE, TRUE, 0); xv_set(first_button, PANEL_INACTIVE, TRUE, 0); xv_set(rreview_button, PANEL_INACTIVE, TRUE, 0); xv_set(prev_button, PANEL_INACTIVE, TRUE, 0); xv_set(pause_button, PANEL_INACTIVE, TRUE, 0); xv_set(next_button, PANEL_INACTIVE, TRUE, 0); xv_set(review_button, PANEL_INACTIVE, TRUE, 0); xv_set(last_button, PANEL_INACTIVE, TRUE, 0); } /* Edit the log file. */ void edit_log_file() { char *edit_command, *editor, *getenv(); int clen, elen, result; if ((editor = getenv("EDITOR")) == NULL) editor = defaults_get_string("wave.texteditor", "Wave.TextEditor", EDITOR); elen = strlen(editor); edit_command = malloc(elen + strlen(log_file_name) + 3); if (edit_command) { sprintf(edit_command, "%s %s\n", editor, log_file_name); analyze_proc(); do_command(edit_command); free(edit_command); } } /* Handle selections in the log window. */ static void log_select(item, event) Panel_item item; Event *event; { int client_data = (int)xv_get(item, PANEL_CLIENT_DATA); char timestring[25]; switch (client_data) { case 'a': /* add an entry */ if (attached && display_start_time < attached->this.time && attached->this.time < display_start_time + nsamp) strcpy(timestring, mstimstr(attached->this.time)); else { strcpy(timestring, strtok(timstr(display_start_time), " ")); strcat(timestring, "-"); strcat(timestring, strtok(timstr(display_start_time+nsamp), " ")); } if (add_entry(record, timestring, xv_get(log_text_item,PANEL_VALUE))) { if (++log_changes > 10) write_log(log_file_name); set_buttons(); } break; case 'd': /* delete the current entry */ delete_entry(); if (++log_changes > 10) write_log(log_file_name); set_buttons(); show_current_entry(); break; case 'e': /* edit the log file */ if (log_file_name) { disable_buttons(); if (log_changes > 0) write_log(log_file_name); edit_log_file(); /* Clear out the old entries. */ for (current_entry = first_entry; current_entry; ) delete_entry(); /* Reinitialize from the new log file. */ if (read_log(log_file_name)) save_log_backup = 1; log_changes = 0; current_entry = first_entry; set_buttons(); } break; case 'f': /* specify log file name */ /* Do nothing unless the name has been changed. */ if (strncmp(log_file_name, (char *)xv_get(log_name_item,PANEL_VALUE), LNLMAX)) { /* If edits have been made, write the current log to the old file. If the write fails, reset the file name (after write_log has alerted the user). */ if (log_changes && write_log(log_file_name) == 0) xv_set(log_name_item, PANEL_VALUE, log_file_name, 0); else { /* Clear out the old entries. */ for (current_entry = first_entry; current_entry; ) delete_entry(); strncpy(log_file_name, (char *)xv_get(log_name_item, PANEL_VALUE), LNLMAX); /* Reinitialize from the new log file. */ if (read_log(log_file_name)) save_log_backup = 1; log_changes = 0; current_entry = first_entry; } set_buttons(); show_current_entry(); } break; case 'l': /* force reload of log file */ /* If edits have been made, write the current log to the old file. If the write fails, reset the file name (after write_log has alerted the user). */ if (log_changes) { char backfname[LNLMAX+2]; sprintf(backfname, "%s~", log_file_name); save_log_backup = 0; write_log(backfname); } /* Clear out the old entries. */ for (current_entry = first_entry; current_entry; ) delete_entry(); strncpy(log_file_name, (char *)xv_get(log_name_item, PANEL_VALUE), LNLMAX); /* Reinitialize from the new log file. */ if (read_log(log_file_name)) save_log_backup = 1; log_changes = 0; current_entry = first_entry; set_buttons(); show_current_entry(); break; case 'p': /* pause review */ pause_review(); set_buttons(); break; case 'r': /* replace description field of current entry */ if (current_entry) { char *newtext = (char *)xv_get(log_text_item, PANEL_VALUE); char *newtextp; if (strcmp(newtext, current_entry->text) && (newtextp = (char *)malloc(strlen(newtext)+1))) { strcpy(newtextp, newtext); free(current_entry->text); current_entry->text = newtextp; if (++log_changes > 10) write_log(log_file_name); } } break; case 'A': /* show first entry */ if (first_entry) { current_entry = first_entry; set_buttons(); show_current_entry(); } break; case '<': /* show previous entry */ if (current_entry->prev) { current_entry = current_entry->prev; set_buttons(); show_current_entry(); } break; case '>': /* show next entry */ if (current_entry->next) { current_entry = current_entry->next; set_buttons(); show_current_entry(); } break; case 'Z': /* show last entry */ if (last_entry) { current_entry = last_entry; set_buttons(); show_current_entry(); } break; case '+': /* review log entries */ case '-': /* review log entries in reverse order */ disable_buttons(); xv_set(pause_button, PANEL_INACTIVE, FALSE, 0); log_review(client_data == '+' ? 1 : -1); break; } } static void adjust_delay(item, value) Panel_item item; int value; { review_delay = value; if (review_in_progress) log_review(review_in_progress); } /* Set up log window. */ static void create_log_popup() { int dx; Icon icon; icon = xv_create(XV_NULL, ICON, ICON_IMAGE, icon_image, ICON_LABEL, "Log", NULL); log_frame = xv_create(frame, FRAME_CMD, FRAME_LABEL, "WAVE log", FRAME_ICON, icon, 0); log_panel = xv_get(log_frame, FRAME_CMD_PANEL); log_name_item = xv_create(log_panel, PANEL_TEXT, XV_X, xv_col(log_panel, 0), XV_Y, xv_row(log_panel, 0), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "File: ", XV_HELP_DATA, "wave:file.log.file", PANEL_VALUE_DISPLAY_LENGTH, 60, PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'f', 0); load_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, "Load", XV_HELP_DATA, "wave:file.log.load", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'l', PANEL_INACTIVE, TRUE, 0); log_text_item = xv_create(log_panel, PANEL_TEXT, XV_X, xv_col(log_panel, 0), XV_Y, xv_row(log_panel, 1), PANEL_DISPLAY_LEVEL, PANEL_CURRENT, PANEL_LABEL_STRING, "Description: ", XV_HELP_DATA, "wave:file.log.description", PANEL_VALUE_DISPLAY_LENGTH, 50, PANEL_CLIENT_DATA, (caddr_t) '!', /* used by disp_proc, see annot.c */ 0); xv_create(log_panel, PANEL_SLIDER, XV_HELP_DATA, "wave:file.log.review_delay", PANEL_LABEL_STRING, "Delay:", PANEL_DIRECTION, PANEL_HORIZONTAL, PANEL_VALUE, 5, PANEL_MAX_VALUE, 10, PANEL_MIN_VALUE, 1, PANEL_SHOW_RANGE, TRUE, PANEL_SHOW_VALUE, FALSE, PANEL_NOTIFY_PROC, adjust_delay, NULL); add_button = xv_create(log_panel, PANEL_BUTTON, XV_X, xv_col(log_panel, 0), XV_Y, xv_row(log_panel, 3), PANEL_LABEL_STRING, "Add", XV_HELP_DATA, "wave:file.log.add", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'a', PANEL_INACTIVE, TRUE, 0); replace_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, "Replace", XV_HELP_DATA, "wave:file.log.replace", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'r', PANEL_INACTIVE, TRUE, 0); delete_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, "Delete", XV_HELP_DATA, "wave:file.log.delete", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'd', PANEL_INACTIVE, TRUE, 0); edit_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, "Edit", XV_HELP_DATA, "wave:file.log.edit", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'e', PANEL_INACTIVE, TRUE, 0); dx = xv_get(log_panel, PANEL_ITEM_X_GAP); xv_set(log_panel, PANEL_ITEM_X_GAP, 4*dx, 0); first_button = xv_create(log_panel, PANEL_BUTTON, XV_X, xv_col(log_panel, 0), XV_Y, xv_row(log_panel, 4), PANEL_LABEL_STRING, "|<", XV_HELP_DATA, "wave:file.log.|<", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'A', PANEL_INACTIVE, TRUE, 0); xv_set(log_panel, PANEL_ITEM_X_GAP, dx, 0); rreview_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, "<<", XV_HELP_DATA, "wave:file.log.<<", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) '-', PANEL_INACTIVE, TRUE, 0); prev_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, "<", XV_HELP_DATA, "wave:file.log.<", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) '<', PANEL_INACTIVE, TRUE, 0); pause_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, "Pause", XV_HELP_DATA, "wave:file.log.pause", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'p', PANEL_INACTIVE, TRUE, 0); next_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, ">", XV_HELP_DATA, "wave:file.log.>", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) '>', PANEL_INACTIVE, TRUE, 0); review_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, ">>", XV_HELP_DATA, "wave:file.log.>>", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) '+', PANEL_INACTIVE, TRUE, 0); last_button = xv_create(log_panel, PANEL_BUTTON, PANEL_LABEL_STRING, ">|", XV_HELP_DATA, "wave:file.log.>|", PANEL_NOTIFY_PROC, log_select, PANEL_CLIENT_DATA, (caddr_t) 'Z', PANEL_INACTIVE, TRUE, 0); window_fit(log_panel); window_fit(log_frame); xv_set(log_frame, FRAME_CMD_PUSHPIN_IN, TRUE, 0); } int log_popup_active = -1; /* Make the log popup window appear. */ void show_log() { if (log_popup_active < 0) create_log_popup(); wmgr_top(log_frame); xv_set(log_frame, WIN_MAP, TRUE, 0); log_popup_active = 1; } /* Update and close the log file. */ void finish_log() { if (log_changes > 0) write_log(log_file_name); } /* Enter demonstration mode. */ void start_demo() { char *filename, *p, *title, *getenv(); int c, r, x, y; Frame text_frame; Textsw textsw; Textsw_status status; extern void mode_undo(); if (filename = malloc(strlen(helpdir) + strlen("wave/demo.txt") + 2)) { if ((title = getenv("DEMOTITLE")) == NULL) title = "Demonstration of WAVE"; if ((p = getenv("DEMOX")) == NULL) x = 10; else x = atoi(p); if ((p = getenv("DEMOY")) == NULL) y = 700; else y = atoi(p); if ((p = getenv("DEMOCOLS")) == NULL) c = 80; else c = atoi(p); if ((p = getenv("DEMOROWS")) == NULL) r = 20; else r = atoi(p); text_frame = xv_create(frame, FRAME, XV_LABEL, title, XV_X, x, XV_Y, y, WIN_COLUMNS, c, WIN_ROWS, r, 0); textsw = (Textsw)xv_create(text_frame, TEXTSW, NULL); sprintf(filename, "%s/wave/demo.txt", helpdir); xv_set(textsw, TEXTSW_STATUS, &status, TEXTSW_FILE, filename, TEXTSW_FIRST, 0, TEXTSW_READ_ONLY, TRUE, NULL); if (status == TEXTSW_STATUS_OKAY) xv_set(text_frame, WIN_MAP, TRUE, 0); free(filename); } create_log_popup(); log_popup_active = 0; xv_set(log_name_item, PANEL_VALUE, log_file_name, NULL); show_mode(); ghflag = gvflag = visible = 1; show_signame = 16; mode_undo(); dismiss_mode(); if (read_log(log_file_name)) { xv_set(pause_button, PANEL_INACTIVE, FALSE, 0); log_review((Panel_item)NULL, (Event *)NULL); } }