/* file: annot.c G. Moody 1 May 1990
Last revised: 24 April 2020
Annotation list handling and display functions for WAVE
-------------------------------------------------------------------------------
WAVE: Waveform analyzer, viewer, and editor
Copyright (C) 1990-2010 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
#include
#include
void set_frame_title();
struct ap *get_ap()
{
struct ap *a;
if ((a = (struct ap *)malloc(sizeof(struct ap))) == 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 annotations\n", 0,
NOTICE_BUTTON_YES, "Continue", 0);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
}
return (a);
}
int annotations; /* non-zero if there are annotations to be shown */
time_t tupdate; /* time of last update to annotation file */
/* Annot_init() (re)opens annotation file(s) for the current record, and
reads the annotations into memory. The function returns 0 if no annotations
can be read, 1 if some annotations were read but memory was exhausted, or 2
if all of the annotations were read successfully. On return, ap_start and
annp both point to the first (earliest) annotation, ap_end points to the
last one, and attached and scope_annp are reset to NULL. */
annot_init()
{
char *p;
struct ap *a;
/* If any annotation editing has been performed, bring the output file
up-to-date. */
if (post_changes() == 0)
return (0); /* do nothing if the update failed (the user may be
able to recover by changing permissions or
clearing file space as needed) */
/* Reset pointers. */
attached = scope_annp = NULL;
/* Free any memory that was previously allocated for annotations.
This might take a while ... */
if (frame) xv_set(frame, FRAME_BUSY, TRUE, NULL);
while (ap_end) {
a = ap_end->previous;
if (ap_end->this.aux) free(ap_end->this.aux);
free(ap_end);
ap_end = a;
}
/* Check that the annotator name, if any, is legal. */
if (nann > 0 && badname(af.name)) {
char ts[ANLMAX+3];
int dummy = (int)sprintf(ts, "`%s'", af.name);
#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,
"The annotator name:",
ts,
"cannot be used. Press `Continue', then",
"select an annotator name containing only",
"letters, digits, tildes, and underscores.", 0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
af.name = NULL;
annotator[0] = '\0';
set_annot_item("");
set_frame_title();
return (annotations = 0);
}
/* Reset the frame title. */
set_frame_title();
/* Set time of last update to current time. */
tupdate = time((time_t *)NULL);
/* Return 0 if no annotations are requested or available. */
if (getgvmode() & WFDB_HIGHRES) setafreq(freq);
else setafreq(0.);
if (nann < 1 || annopen(record, &af, 1) < 0) {
ap_start = annp = scope_annp = NULL;
if (frame) xv_set(frame, FRAME_BUSY, FALSE, NULL);
return (annotations = 0);
}
if (getgvmode() & WFDB_HIGHRES) setafreq(freq);
else setafreq(0.);
if ((ap_start = annp = scope_annp = a = get_ap()) == NULL ||
getann(0, &(a->this))) {
(void)annopen(record, NULL, 0);
if (frame) xv_set(frame, FRAME_BUSY, FALSE, NULL);
return (annotations = 0);
}
/* Read annotations into memory, constructing a doubly-linked list on the
fly. */
do {
a->next = NULL;
a->previous = ap_end;
if (ap_end) ap_end->next = a;
ap_end = a;
/* Copy the aux string, if any (since the aux pointer returned by
getann points to static memory that may be overwritten). Return 1
if we run out of memory. */
if (a->this.aux) {
if ((p = (char *)calloc(*(a->this.aux)+2, 1)) == NULL) {
if (frame)
xv_set(frame, FRAME_BUSY, FALSE, NULL);
if (frame) {
#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 aux string\n", 0,
NOTICE_BUTTON_YES, "Continue", 0);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
}
return (annotations = 1);
}
memcpy(p, a->this.aux, *(a->this.aux)+1);
a->this.aux = p;
}
} while ((a = get_ap()) && getann(0, &(a->this)) == 0);
/* Return 1 if we ran out of memory while reading the annotation file. */
if (a == NULL) {
if (frame) xv_set(frame, FRAME_BUSY, FALSE, NULL);
return (annotations = 1);
}
/* Return 2 if the entire annotation file has been read successfully. */
else {
free(a); /* release last (unneeded) ap structure */
if (frame) xv_set(frame, FRAME_BUSY, FALSE, NULL);
return (annotations = 2);
}
}
/* next_match() returns the time of the next annotation (i.e., the one later
than and closest to those currently displayed) that matches the template
annotation. The mask specifies which fields must match. */
WFDB_Time next_match(template, mask)
struct WFDB_ann *template;
int mask;
{
if (annotations && annp) {
/* annp might be NULL if the annotation list is empty, or if the
last annotation occurs before display_start_time; in either
case, next_match() returns -1. */
do {
if (mask&M_ANNTYP) {
if (template->anntyp) {
if (template->anntyp != annp->this.anntyp)
continue;
}
else if ((annp->this.anntyp & 0x80) == 0)
continue;
}
if ((mask&M_SUBTYP) && template->subtyp != annp->this.subtyp)
continue;
if ((mask&M_CHAN) && template->chan != annp->this.chan )
continue;
if ((mask&M_NUM) && template->num != annp->this.num )
continue;
if ((mask&M_AUX) && (annp->this.aux == NULL ||
strstr(annp->this.aux+1, template->aux+1) == NULL))
continue;
if ((mask&M_MAP2)&&template->anntyp!=map2(annp->this.anntyp))
continue;
if (annp->this.time < display_start_time + nsamp)
continue;
return (annp->this.time);
} while (annp->next, annp = annp->next);
}
return (-1L);
}
/* previous_match() returns the time of the previous annotation (i.e., the one
earlier than and closest to those currently displayed) that matches the
template annotation. The mask specifies which fields must match. */
WFDB_Time previous_match(template, mask)
struct WFDB_ann *template;
int mask;
{
if (annotations) {
/* annp might be NULL if the annotation list is empty, or if the
last annotation occurs before display_start_time. In the first
case, previous_match() returns -1; in the second case, it begins
its search with the last annotation in the list. */
if (annp == NULL) {
if (ap_end && ap_end->this.time < display_start_time)
annp = ap_end;
else
return (-1L);
}
do {
if (mask&M_ANNTYP) {
if (template->anntyp) {
if (template->anntyp != annp->this.anntyp)
continue;
}
else if ((annp->this.anntyp & 0x80) == 0)
continue;
}
if ((mask&M_SUBTYP) && template->subtyp != annp->this.subtyp)
continue;
if ((mask&M_CHAN) && template->chan != annp->this.chan )
continue;
if ((mask&M_NUM) && template->num != annp->this.num )
continue;
if ((mask&M_AUX) && (annp->this.aux == NULL ||
strstr(annp->this.aux+1, template->aux+1) == NULL))
continue;
if ((mask&M_MAP2)&&template->anntyp!=map2(annp->this.anntyp))
continue;
if (annp->this.time > display_start_time)
continue;
return (annp->this.time);
} while (annp->previous, annp = annp->previous);
}
return (-1L);
}
/* Show_annotations() displays annotations between times left and left+dt at
appropriate x-locations in the ECG display area. */
void show_annotations(left, dt)
WFDB_Time left;
int dt;
{
char buf[5], *p;
int n, s, x, y, ytop, xs = -1, ys;
WFDB_Time t, right = left + dt;
if (annotations == 0) return;
/* Find the first annotation to be displayed. */
(void)locate_annotation(left, -128); /* -128 is the lowest chan value */
if (annp == NULL) return;
/* Display all of the annotations in the window. */
while (annp->this.time < right) {
x = (int)((annp->this.time - left)*tscale);
if (annp->this.anntyp & 0x80) {
y = ytop = abase; p = ".";
}
else switch (annp->this.anntyp) {
case NOTQRS:
y = ytop = abase; p = "."; break;
case NOISE:
y = ytop = abase - linesp;
if (annp->this.subtyp == -1) { p = "U"; break; }
/* The existing scheme is good for up to 4 signals; it can be
easily extended to 8 or 12 signals using the chan and num
fields, or to an arbitrary number of signals using `aux'. */
for (s = 0; s < nsig && s < 4; s++) {
if (annp->this.subtyp & (0x10 << s))
buf[s] = 'u'; /* signal s is unreadable */
else if (annp->this.subtyp & (0x01 << s))
buf[s] = 'n'; /* signal s is noisy */
else
buf[s] = 'c'; /* signal s is clean */
}
buf[s] = '\0';
p = buf; break;
case STCH:
case TCH:
case NOTE:
y = ytop = abase - linesp;
if (!show_aux && annp->this.aux) p = annp->this.aux+1;
else p = annstr(annp->this.anntyp);
break;
case LINK:
y = ytop = abase - linesp;
if (!show_aux && annp->this.aux) {
char *p1 = annp->this.aux + 1, *p2 = p1 + *(p1-1);
p = p1;
while (p1 < p2) {
if (*p1 == ' ' || *p1 == '\t') {
p = p1 + 1;
break;
}
p1++;
}
}
break;
case RHYTHM:
y = ytop = abase + linesp;
if (!show_aux && annp->this.aux) p = annp->this.aux+1;
else p = annstr(annp->this.anntyp);
break;
case INDEX_MARK:
y = ytop = abase - linesp;
p = ":";
break;
case BEGIN_ANALYSIS:
y = ytop = abase - linesp;
p = "<";
break;
case END_ANALYSIS:
y = ytop = abase - linesp;
p = ">";
break;
case REF_MARK:
y = ytop = abase - linesp;
p = ";";
break;
default:
y = ytop = abase; p = annstr(annp->this.anntyp); break;
}
if (ann_mode == 2 && y == abase) {
int yy = y + annp->this.num*vscalea;
if (xs >= 0)
XDrawLine(display, osb, draw_ann, xs, ys, x, yy);
xs = x;
ys = yy;
}
else {
if (ann_mode == 1 && (unsigned)annp->this.chan < nsig) {
if (sig_mode == 0)
y = ytop +=
base[(unsigned)annp->this.chan] - abase + mmy(2);
else {
int i;
for (i = 0; i < siglistlen; i++)
if (annp->this.chan == siglist[i]) {
y = ytop += base[i] - abase + mmy(2);
break;
}
}
}
n = strlen(p);
if (n > 3 && !overlap && annp->next &&
(annp->next)->this.time < right) {
int maxwidth;
maxwidth = (int)(((annp->next)->this.time-annp->this.time)*
tscale)
- XTextWidth(font, " ", 1);
while (n > 3 && XTextWidth(font, p, n) > maxwidth)
n--;
}
XDrawString(display, osb,
annp->this.anntyp == LINK ? draw_sig : draw_ann,
x, y, p, n);
if (annp->this.anntyp == LINK) {
int xx = x + XTextWidth(font, p, n), yy = y + linesp/4;
XDrawLine(display, osb, draw_sig, x, yy, xx, yy);
}
if (show_subtype) {
sprintf(buf, "%d", annp->this.subtyp); p = buf; y += linesp;
XDrawString(display, osb, draw_ann, x, y, p, strlen(p));
}
if (show_chan) {
sprintf(buf, "%d", annp->this.chan); p = buf; y += linesp;
XDrawString(display, osb, draw_ann, x, y, p, strlen(p));
}
if (show_num) {
sprintf(buf, "%d", annp->this.num); p = buf; y += linesp;
XDrawString(display, osb, draw_ann, x, y, p, strlen(p));
}
if (show_aux && annp->this.aux != NULL) {
p = annp->this.aux + 1; y += linesp;
XDrawString(display, osb, draw_ann, x, y, p, strlen(p));
}
}
if (show_marker && annp->this.anntyp != NOTQRS) {
XSegment marker[2];
marker[0].x1 = marker[0].x2 = marker[1].x1 = marker[1].x2 = x;
if (ann_mode == 1 && (unsigned)annp->this.chan < nsig) {
unsigned int c = (unsigned)annp->this.chan;
if (sig_mode == 1) {
int i;
for (i = 0; i < siglistlen; i++)
if (c == siglist[i]) {
c = i;
break;
}
if (i == siglistlen) {
marker[0].y1 = 0;
marker[1].y2 = canvas_height;
}
else {
marker[0].y1 = (c == 0) ? 0 : (base[c-1] + base[c])/2;
marker[1].y2 = (c == nsig-1) ? canvas_height :
(base[c+1] + base[c])/2;
}
}
else {
marker[0].y1 = (c == 0) ? 0 : (base[c-1] + base[c])/2;
marker[1].y2 = (c == nsig-1) ? canvas_height :
(base[c+1] + base[c])/2;
}
}
else {
marker[0].y1 = 0;
marker[1].y2 = canvas_height;
}
marker[0].y2 = ytop - linesp;
marker[1].y1 = y + mmy(2);
XDrawSegments(display, osb, draw_ann, marker, 2);
}
if (annp->next == NULL) break;
annp = annp->next;
}
}
void clear_annotation_display()
{
if (ann_mode == 1 || (use_overlays && show_marker)) {
XFillRectangle(display, osb, clear_ann,
0, 0, canvas_width+mmx(10), canvas_height);
if (!use_overlays)
do_disp();
}
else
XFillRectangle(display, osb, clear_ann,
0, abase-mmy(8), canvas_width+mmx(10), mmy(13));
}
/* This function locates an annotation at time t, attached to signal s, in the
annotation list. If there is no such annotation, it returns NULL, and annp
points to the annotation that follows t (annp is NULL if no annotations
follow t). If there is an annotation at time t, the function sets annp to
point to the first such annotation, and returns the value of annp. (More
than one annotation may exist at time t; if so, on return, annp points to
the one with the lowest `chan' field than is no less than s.)
*/
struct ap *locate_annotation(t, s)
WFDB_Time t;
int s;
{
/* First, find out which of ap_start, annp, and ap_end is nearest t. */
if (annp == NULL) {
if (ap_start == NULL) return (NULL);
annp = ap_start;
}
if (annp->this.time < t) {
if (ap_end == NULL || ap_end->this.time < t)
/* no annotations follow t */
return (annp = NULL);
if (t - annp->this.time > ap_end->this.time - t)
annp = ap_end; /* closer to end than to previous position */
}
else {
if (t < ap_start->this.time) { /* no annotations precede t */
annp = ap_start;
return (NULL);
}
if (t - ap_start->this.time < annp->this.time - t)
annp = ap_start;
}
/* Traverse the list to find the annotation at time t and signal s, or its
successor. */
while (annp->this.time >= t && annp->previous)
annp = annp->previous;
while ((annp && annp->this.time < t) ||
(annp->this.time == t && annp->this.chan < s))
annp = annp->next;
if (annp == NULL || annp->this.time != t || annp->this.chan != s)
return (NULL);
else
return (annp);
}
int changes; /* number of edits since last save */
void check_post_update()
{
if (++changes >= 20 || time((time_t *)NULL) > tupdate+60)
(void)post_changes();
if (changes < 2)
set_frame_title();
}
/* This function deletes the annotation at time t and attached to signal s,
if there is one. It leaves behind a "phantom" annotation, which is
displayed by this program as a "."; this makes it relatively simple to
replace the deleted annotation at the same point if it was deleted in error.
"Phantom" annotations are not written to the output annotation file.
If an attempt is made to delete a "phantom" annotation, the effect is to
undelete it (i.e., its original state is restored).
If there is a marker (INDEX_MARK, BEGIN_ANALYSIS, or END_ANALYSIS) at t,
this function deletes the marker without leaving a "phantom" annotation.
*/
void delete_annotation(t, s)
WFDB_Time t;
int s;
{
if (locate_annotation(t, s)) {
if (annp->this.anntyp <= ACMAX) { /* not a marker */
if (accept_edit == 0) {
#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,
"You may not edit annotations unless you first",
"enable editing from the `Edit' menu.", 0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
annp->this.anntyp ^= 0x80; /* MSB of anntyp is "phantom"
marker */
check_post_update();
}
else { /* marker to be deleted */
struct ap *a = annp;
switch (a->this.anntyp) {
case BEGIN_ANALYSIS:
begin_analysis_time = -1L; reset_start(); break;
case END_ANALYSIS:
end_analysis_time = -1L; reset_stop(); break;
default: break;
}
/* Remove the annotation from the list by reconstructing the
links around it, free its memory allocation, and reset annp. */
if (a->previous) (a->previous)->next = a->next;
else ap_start = a->next;
if (a->next) {
annp = a->next;
(a->next)->previous = a->previous;
}
else annp = ap_end = a->previous;
if (a->this.aux) free(a->this.aux);
free(a);
}
}
}
/* This function moves the annotation pointed to by `a' to a new time `t'.
Note that the links from `a' must be valid or NULL when this function is
called. */
void move_annotation(a, t)
struct ap *a;
WFDB_Time t;
{
if (a->this.anntyp <= ACMAX && accept_edit == 0) {
#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,
"You may not edit annotations unless you first",
"enable editing from the `Edit' menu.", 0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
/* Remove the annotation from the list by reconstructing the links
around it. */
if (a->previous) (a->previous)->next = a->next;
else ap_start = a->next;
if (a->next) (a->next)->previous = a->previous;
else ap_end = a->previous;
/* Adjust the time. */
a->this.time = t;
/* Reinsert the annotation into the list. (Make sure that annp doesn't
point to `a' first.) */
annp = a->next;
(void)insert_annotation(a);
}
/* This function is used by insert_annotation (below) to insert the annotation
pointed to by `a' into the annotation list before the annotation pointed to
by `annp'. */
static void do_insertion(a)
struct ap *a;
{
if (annp == NULL) { /* append to end of annotation list */
a->next = NULL;
a->previous = ap_end;
if (ap_end == NULL) { /* annotation list is empty */
ap_start = annp = ap_end = a; annotations = 1; }
else {
ap_end->next = a;
annp = ap_end = a;
}
}
else { /* insert before annp */
a->next = annp;
a->previous = annp->previous;
annp->previous = a;
if (a->previous) (a->previous)->next = a;
else ap_start = a;
}
}
/* This function inserts the annotation pointed to by `a' into the annotation
list. If there is already an annotation at time a->time, with the same
`chan' field, it is overwritten by the inserted annotation. */
void insert_annotation(a)
struct ap *a;
{
if (accept_edit == 0 && a->this.anntyp <= ACMAX) {
#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,
"You may not edit annotations unless you first",
"enable editing from the `Edit' menu.", 0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
if (locate_annotation(a->this.time, a->this.chan)) {
/* annp points to an existing annotation that must be replaced. */
if (accept_edit || annp->this.anntyp > ACMAX) {
/* overwrite annotation or marker at annp */
if (annp->this.aux) free(annp->this.aux);
annp->this = a->this;
}
else {
/* If we get here, we must be inserting a marker, not a real
annotation. In this case, we can't overwrite the real
annotation, so we change the marker so that it points to
the next sample. */
a->this.time++;
insert_annotation(a);
return;
}
}
else
do_insertion(a);
if (a->this.anntyp <= ACMAX)
check_post_update();
else {
switch (a->this.anntyp) {
case BEGIN_ANALYSIS:
if (begin_analysis_time >= 0L &&
begin_analysis_time != a->this.time)
delete_annotation(begin_analysis_time, -1);
begin_analysis_time = a->this.time;
a->this.chan = -1;
reset_start();
break;
case END_ANALYSIS:
if (end_analysis_time >= 0L &&
end_analysis_time != a->this.time)
delete_annotation(end_analysis_time, -1);
end_analysis_time = a->this.time;
a->this.chan = -1;
reset_stop();
break;
case REF_MARK:
if (ref_mark_time >= 0L &&
ref_mark_time != a->this.time)
delete_annotation(ref_mark_time, -1);
ref_mark_time = a->this.time;
a->this.chan = -1;
reset_ref();
break;
default:
break;
}
}
}
/* This function changes all annotations between begin_analysis_time and
end_analysis_time to that specified by the annotation template, after asking
for confirmation if doing so would affect annotations not currently
visible. If the annotation template's anntyp is NOTQRS, then previously
deleted annotations within the range are undeleted, and all other
annotations within the range are deleted. Any markers within the range are
unaffected.
*/
void change_annotations()
{
struct ap *a;
if (accept_edit == 0) {
#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,
"You may not edit annotations unless you first",
"enable editing from the `Edit' menu.", 0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
if (begin_analysis_time == -1L || end_analysis_time == -1L ||
begin_analysis_time >= end_analysis_time) {
#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,
"You must specify a range by inserting `<' and `>'",
"markers (or by setting the Start and End times",
"in the Analyze panel).", 0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
if (ann_template.anntyp == BEGIN_ANALYSIS ||
ann_template.anntyp == END_ANALYSIS) {
#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,
"Select a different annotation type.", 0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
if (begin_analysis_time < display_start_time ||
end_analysis_time > display_start_time + nsamp) {
int result;
#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,
"The range of this action is not limited to visible",
"annotations. Press `Cancel' to continue without",
"changing any annotations, or `Confirm' to make",
"changes anyway.", 0,
NOTICE_BUTTON_YES, "Cancel",
NOTICE_BUTTON_NO, "Confirm", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
if (result == NOTICE_YES) return;
}
(void)locate_annotation(begin_analysis_time, -128);
a = annp;
while (a && a->this.time < end_analysis_time) {
if (a->this.anntyp <= ACMAX) { /* leave markers alone */
if (ann_template.anntyp == NOTQRS)
a->this.anntyp ^= 0x80; /* see delete_annotation() */
else {
WFDB_Time t = a->this.time;
a->this = ann_template;
a->this.time = t;
}
++changes;
}
a = a->next;
}
check_post_update();
set_frame_title();
clear_annotation_display();
show_annotations(display_start_time, nsamp);
}
/* This function writes the current contents of the annotation list to an
annotation file, if any changes have been made since the previous call
(or since the annotation list was initialized). The name of the output
file is the same as that of the input file, but it is always written to
the current directory (even if explicit path information was provided in
the input annotator name). If no annotator name was specified, the name
by which this program was invoked (normally `wave') is used for this
purpose.
This function attempts to ensure that the input file is not overwritten.
If the current directory already contains a file with the name to be used
for the output file, the existing file is first renamed by appending a "~"
to the annotator name (unless this was done during a previous invocation
of this function and the record and annotator names have not been changed
since). Only one level of backup is preserved, so you will overwrite the
original annotation file if it is in the current directory and you open the
same annotator more than once.
If the "save" operation succeeds, the function returns 1; if it fails for
any reason, it returns 0.
*/
int post_changes()
{
int result;
struct ap *a;
if (changes <= 0) return (1);
/* If there was no annotator name specified, use the name by which this
program was invoked for this purpose. */
if (af.name == NULL) {
af.name = pname;
strcpy(annotator, pname);
set_annot_item(pname);
}
if (savebackup) {
char afname[ANLMAX+RNLMAX+2], afbackup[ANLMAX+RNLMAX+2];
FILE *tfile;
/* Generate a name for the updated annotation file. */
sprintf(afname, "%s.%s", record, af.name);
/* If the file already exists in the current directory, rename it. */
if (tfile = fopen(afname, "r")) {
fclose(tfile); /* yes -- try to do so */
/* Generate a name for a backup file by appending a `~'. */
sprintf(afbackup, "%s~", afname);
if (rename(afname, afbackup)) {
#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 changes cannot be saved unless you remove,",
"or obtain permission to rename, the file named",
afname,
"in the current directory.",
"",
"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 {
finish_log();
xv_destroy_safe(frame);
exit(1);
}
}
}
savebackup = 0;
}
af.stat = (af.stat == WFDB_AHA_READ) ? WFDB_AHA_WRITE : WFDB_WRITE;
if (getgvmode() & WFDB_HIGHRES) setafreq(freq);
else setafreq(0.);
if (annopen(record, &af, 1)) {
/* An error from annopen is most likely to result from not being able
to create the output file. Warn the user and try again later. */
#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 changes cannot be saved until you obtain",
"write permission in the current directory.",
"",
"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 {
finish_log();
xv_destroy_safe(frame);
exit(1);
}
}
af.stat = (af.stat == WFDB_AHA_WRITE) ? WFDB_AHA_READ : WFDB_READ;
a = ap_start;
/* Write the annotation list to the output file. This might take a while
.... */
xv_set(frame, FRAME_BUSY, TRUE, NULL);
while (a) {
if (isann(a->this.anntyp) && putann(0, &(a->this))) {
/* An error from putann is most likely to be the result of file
space exhaustion. Warn the user and try again later. */
#ifdef NOTICE
Xv_notice notice;
#endif
xv_set(frame, FRAME_BUSY, FALSE, NULL);
#ifdef 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 changes cannot be saved until additional",
"file space is made available.",
"",
"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 {
finish_log();
xv_destroy_safe(frame);
exit(1);
}
}
a = a->next;
}
if (getgvmode() & WFDB_HIGHRES) setafreq(freq);
else setafreq(0.);
(void)annopen(record, NULL, 0); /* force flush and close of output */
changes = 0;
xv_set(frame, FRAME_BUSY, FALSE, NULL);
/* Set time of last update to current time. */
tupdate = time((time_t *)NULL);
return (1);
}
/* Reset the base frame title. */
void set_frame_title()
{
static char frame_title[7+RNLMAX+1+ANLMAX+2+DSLMAX+1];
/* 7 for "Record ", RNLMAX for record name, 1 for " " or "(",
ANLMAX for annotator name, 2 for " " or ") ",
DSLMAX for description from log file, 1 for null */
if (annotator[0]) {
if (changes)
sprintf(frame_title, "WAVE %s Record %s(%s) ", WAVEVERSION,
record, annotator);
else
sprintf(frame_title, "WAVE %s Record %s %s ", WAVEVERSION,
record, annotator);
}
else
sprintf(frame_title, "Record %s ", record);
if (description[0] != '\0')
strcat(frame_title, description);
xv_set(frame, FRAME_LABEL, frame_title, NULL);
}
/* Reset the base frame footer. */
void set_frame_footer()
{
/* Keep this in sync with grid choice menu in modepan.c! */
static char *grid_desc[7] = { "",
"Grid intervals: 0.2 sec",
"Grid intervals: 0.5 mV",
"Grid intervals: 0.2 sec x 0.5 mV",
"Grid intervals: 0.04 sec x 0.1 mV",
"Grid intervals: 1 min x 0.5 mV",
"Grid intervals: 1 min x 0.1 mV" };
xv_set(frame, FRAME_RIGHT_FOOTER, grid_desc[grid_mode], NULL);
if (attached && (attached->this).aux &&
display_start_time <= (attached->this).time &&
(attached->this).time < display_start_time + nsamp)
xv_set(frame, FRAME_LEFT_FOOTER, attached->this.aux + 1, NULL);
else if (time_mode) {
int tm_save = time_mode;
time_mode = 0;
xv_set(frame, FRAME_LEFT_FOOTER, wtimstr(display_start_time), NULL);
time_mode = tm_save;
}
else {
char *p = timstr(-display_start_time);
if (*p == '[') {
time_mode = 1;
xv_set(frame, FRAME_LEFT_FOOTER, wtimstr(display_start_time), NULL);
time_mode = 0;
}
else
xv_set(frame, FRAME_LEFT_FOOTER, "", NULL);
}
}
/* Return 1 if p[] would not be a legal annotator name, 0 otherwise. */
int badname(p)
char *p;
{
char *pb;
if (p == NULL || *p == '\0')
return (1); /* empty name is illegal */
for (pb = p + strlen(p) - 1; pb >= p; pb--) {
if (('a' <= *pb && *pb <= 'z') || ('A' <= *pb && *pb <= 'Z') ||
('0' <= *pb && *pb <= '9') || *pb == '~' || *pb == '_')
continue; /* legal character */
else if (*pb == '/')
return (0); /* we don't need to check directory names */
else
return (1); /* illegal character */
}
return (0);
}
int read_anntab()
{
char *atfname, buf[256], *p1, *p2, *s1, *s2, *getenv(), *strtok();
int a;
FILE *atfile;
if ((atfname =
defaults_get_string("wave.anntab","Wave.Anntab",getenv("ANNTAB"))) &&
(atfile = fopen(atfname, "r"))) {
while (fgets(buf, 256, atfile)) {
p1 = strtok(buf, " \t");
if (*p1 == '#') continue;
a = atoi(p1);
if (0 < a && a <= ACMAX && (p1 = strtok((char *)NULL, " \t"))) {
p2 = p1 + strlen(p1) + 1;
if ((s1 = (char *)malloc(((unsigned)(strlen(p1) + 1)))) == NULL ||
(*p2 &&
(s2 = (char *)malloc(((unsigned)(strlen(p2)+1)))) == NULL)) {
wfdb_error("read_anntab: insufficient memory\n");
return (-1);
}
(void)strcpy(s1, p1);
(void)setannstr(a, s1);
if (*p2) {
(void)strcpy(s2, p2);
(void)setanndesc(a, s2);
}
else
(void)setanndesc(a, (char*)NULL);
}
}
(void)fclose(atfile);
return (0);
}
else
return (-1);
}
int write_anntab()
{
char *atfname;
FILE *atfile;
int a;
if ((atfname = getenv("ANNTAB")) &&
(atfile = fopen(atfname, "w"))) {
for (a = 1; a <= ACMAX; a++)
if (anndesc(a))
(void)fprintf(atfile, "%d %s %s\n", a, annstr(a), anndesc(a));
return (0);
}
else
return (-1);
}