WFDB Software Package 10.7.0
(42,567 bytes)
/* file: edit.c G. Moody 1 May 1990
Last revised: 27 April 2020
Annotation-editing 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 <http://www.gnu.org/licenses/>.
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 <unistd.h> /* getcwd */
#include <wfdb/ecgmap.h>
#include <xview/notice.h>
#include <xview/win_input.h>
WFDB_Time level_time;
Frame level_frame;
Panel level_panel;
Panel_item level_mode_item, level_time_item;
static char level_time_string[36];
int bar_on, bar_x, bar_y;
int level_mode, level_popup_active = -1;
int selected = -1;
void reset_ref()
{
(void)isigsettime(ref_mark_time);
(void)getvec(vref);
}
void recreate_level_popup()
{
int stat = level_popup_active;
void show_level_popup();
if (stat >= 0 && xv_destroy_safe(level_frame) == XV_OK) {
level_popup_active = -1;
show_level_popup(stat);
}
}
void set_level_mode(item, event)
Panel_item item;
Event *event;
{
void show_level_popup();
level_mode = (int)xv_get(level_mode_item, PANEL_VALUE);
show_level_popup(TRUE);
}
void show_level_popup(stat)
int stat;
{
int i, invalid_data;
void create_level_popup();
switch (level_mode) {
case 0:
sprintf(level_time_string, "Time: %s", mstimstr(-level_time));
break;
case 1:
if (level_time >= ref_mark_time)
sprintf(level_time_string, "Interval: %s",
mstimstr(level_time - ref_mark_time));
else
sprintf(level_time_string, "Interval: -%s",
mstimstr(ref_mark_time - level_time));
break;
case 2:
sprintf(level_time_string, "Sample number: %"WFDB_Pd_TIME,
level_time);
break;
case 3:
sprintf(level_time_string, "Interval: %"WFDB_Pd_TIME" samples",
level_time - ref_mark_time);
break;
}
invalid_data = (isigsettime(level_time) < 0 || getvec(level_v) < 0);
for (i = 0; i < nsig; i++) {
sprintf(level_name_string[i], "%s: ", signame[i]);
if (invalid_data || level_v[i] == WFDB_INVALID_SAMPLE) {
sprintf(level_value_string[i], " ");
sprintf(level_units_string[i], " ");
}
else switch (level_mode) {
default:
case 0: /* physical units (absolute) */
sprintf(level_value_string[i], "%8.3lf", aduphys(i, level_v[i]));
sprintf(level_units_string[i], "%s%s", sigunits[i],
calibrated[i] ? "" : " *");
break;
case 1: /* physical units (relative) */
sprintf(level_value_string[i], "%8.3lf",
aduphys(i, level_v[i]) - aduphys(i, vref[i]));
sprintf(level_units_string[i], "%s%s", sigunits[i],
calibrated[i] ? "" : " *");
break;
case 2: /* raw units (absolute) */
sprintf(level_value_string[i], "%6d", level_v[i]);
sprintf(level_units_string[i], "adu");
break;
case 3: /* raw units (relative) */
sprintf(level_value_string[i], "%6d", level_v[i] - vref[i]);
sprintf(level_units_string[i], "adu");
break;
}
}
if (level_popup_active < 0) create_level_popup();
else
xv_set(level_time_item,
PANEL_LABEL_STRING, level_time_string, 0);
for (i = 0; i < nsig; i++) {
xv_set(level_name[i],
PANEL_LABEL_STRING, level_name_string[i], 0);
xv_set(level_value[i],
PANEL_LABEL_STRING, level_value_string[i], 0);
xv_set(level_units[i],
PANEL_LABEL_STRING, level_units_string[i], 0);
}
xv_set(level_frame, WIN_MAP, stat, 0);
level_popup_active = stat;
}
static void dismiss_level_popup()
{
if (level_popup_active > 0) {
xv_set(level_frame, WIN_MAP, FALSE, 0);
level_popup_active = 0;
}
}
void create_level_popup()
{
int i;
Icon icon;
if (level_popup_active >= 0) return;
icon = xv_create(XV_NULL, ICON,
ICON_IMAGE, icon_image,
ICON_LABEL, "Levels",
NULL);
level_frame = xv_create(frame, FRAME,
XV_LABEL, "Levels",
FRAME_ICON, icon, 0);
level_panel = xv_create(level_frame, PANEL,
WIN_ROW_GAP, 12,
WIN_COLUMN_GAP, 70,
0);
level_mode_item = xv_create(level_panel, PANEL_CHOICE,
PANEL_DISPLAY_LEVEL, PANEL_CURRENT,
PANEL_LABEL_STRING, "Show: ",
XV_HELP_DATA, "wave:level.show",
PANEL_CHOICE_STRINGS,
"physical units (absolute)",
"physical units (relative)",
"raw units (absolute)",
"raw units (relative)", NULL,
PANEL_VALUE, 0,
PANEL_NOTIFY_PROC, set_level_mode,
PANEL_CLIENT_DATA, (caddr_t) 's',
0);
level_time_item = xv_create(level_panel, PANEL_MESSAGE,
XV_X, xv_col(level_panel, 0),
XV_Y, xv_row(level_panel, 1),
PANEL_LABEL_STRING, level_time_string,
PANEL_LABEL_BOLD, TRUE,
XV_HELP_DATA, "wave:level.time",
0);
for (i = 0; i < nsig; i++) {
level_name[i] = xv_create(level_panel, PANEL_MESSAGE,
XV_X, xv_col(level_panel, 0),
XV_Y, xv_row(level_panel, i+2),
PANEL_LABEL_STRING, level_name_string[i],
PANEL_LABEL_BOLD, TRUE,
PANEL_VALUE_DISPLAY_LENGTH, 10,
XV_HELP_DATA, "wave:level.signame",
0);
level_value[i] = xv_create(level_panel, PANEL_MESSAGE,
XV_X, xv_col(level_panel, 1),
XV_Y, xv_row(level_panel, i+2),
PANEL_LABEL_STRING, level_value_string[i],
PANEL_VALUE_DISPLAY_LENGTH, 10,
XV_HELP_DATA, "wave:level.value",
0);
level_units[i] = xv_create(level_panel, PANEL_MESSAGE,
XV_X, xv_col(level_panel, 2),
XV_Y, xv_row(level_panel, i+2),
PANEL_LABEL_STRING, level_units_string[i],
PANEL_VALUE_DISPLAY_LENGTH, 10,
XV_HELP_DATA, "wave:level.units",
0);
}
xv_create(level_panel, PANEL_BUTTON,
XV_X, xv_col(level_panel, 1),
XV_Y, xv_row(level_panel, nsig+3),
PANEL_LABEL_STRING, "Dismiss",
XV_HELP_DATA, "wave:level.dismiss",
PANEL_NOTIFY_PROC, dismiss_level_popup,
0);
window_fit(level_panel);
window_fit(level_frame);
level_popup_active = 0;
}
void bar(x, y, do_bar)
int x, y, do_bar;
{
int ya = y - mmy(8) - 1, yb = y + mmy(5) + 1;
static int level_on;
static XSegment bar[2];
/* Erase any other bar and levels. */
if (bar_on) {
XDrawSegments(display, osb, clear_crs, bar, 2);
bar_on = 0;
}
if (level_on) {
XDrawSegments(display, osb, clear_crs, level, level_on);
level_on = 0;
}
if (do_bar && 0 <= x && x < canvas_width) {
bar[0].x1 = bar[0].x2 = bar[1].x1 = bar[1].x2 = bar_x = x;
bar[0].y1 = 0; bar[0].y2 = ya; bar[1].y1 = yb; bar[1].y2=canvas_height;
XDrawSegments(display, osb, draw_crs, bar, 2);
bar_on = 1;
bar_y = y;
if (show_level) {
int i, n;
n = sig_mode ? siglistlen : nsig;
for (i = 0; i < n; i++) {
level[level_on].x1 = 0;
level[level_on].x2 = canvas_width;
level[level_on].y1 = level[level_on].y2 = sigy(i, x);
level_on++;
}
if (level_on) {
XDrawSegments(display, osb, draw_crs, level, level_on);
level_time = display_start_time + x/tscale;
show_level_popup(TRUE);
}
}
}
}
int box_on, box_left, box_xc, box_yc, box_right, box_top, box_bottom;
void box(x, y, do_box)
int x, do_box;
{
static XPoint box[5];
/* Clear any other box. */
if (box_on) {
XDrawLines(display, osb, clear_crs, box, 5, CoordModeOrigin);
box_on = 0;
}
if (do_box && 0 <= x && x < canvas_width) {
box_xc = x; box_yc = y;
box[0].x = box[1].x = box[4].x = box_left = x - mmx(1.5);
box[2].x = box[3].x = box_right = x + mmx(2.5);
box[0].y = box[3].y = box[4].y = box_bottom = y - mmy(7.5);
box[1].y = box[2].y = box_top = y + mmy(4.5);
XDrawLines(display, osb, draw_crs, box, 5, CoordModeOrigin);
box_on = 1;
}
}
/* This function redraws the box and bars, if any, after the ECG window has
been damaged. Do not call it except from the repaint procedure. */
void restore_cursor()
{
if (bar_on) {
bar_on = 0;
bar(bar_x, bar_y, 1);
}
if (box_on) {
box_on = 0;
box(box_xc, box_yc, 1);
}
}
static int in_box(x, y)
int x, y;
{
return (box_on && box_left <= x && x <= box_right &&
box_bottom <= y && y <= box_top);
}
static void attach_ann(a)
struct ap *a;
{
int y;
void set_frame_footer();
attached = a;
if (ann_mode == 1 && (unsigned)a->this.chan < nsig) {
if (sig_mode == 0)
y = base[(unsigned)a->this.chan] + mmy(2);
else {
int i;
y = abase;
for (i = 0; i < siglistlen; i++)
if (a->this.chan == siglist[i]) {
y = base[i] + mmy(2);
break;
}
}
}
else
y = abase;
box((int)((a->this.time - display_start_time)*tscale), y, 1);
set_frame_footer();
}
static void detach_ann()
{
void set_frame_footer();
attached = NULL;
box(0, 0, 0);
set_frame_footer();
}
#define ANNTEMPSTACKSIZE 16
static struct WFDB_ann ann_stack[ANNTEMPSTACKSIZE];
static int ann_stack_index;
static int safestrcmp(a, b)
char *a, *b;
{
if (a == NULL) return (b != NULL);
else if (b == NULL) return (-1);
else return (strcmp(a, b));
}
static void save_ann_template()
{
int i;
/* Search for ann_template in ann_stack. We don't bother checking the
last entry in ann_stack since we'll push ann_template onto the top
of the stack anyway. */
for (i = 0; i < ANNTEMPSTACKSIZE-1; i++) {
if (ann_stack[i].anntyp == ann_template.anntyp &&
ann_stack[i].subtyp == ann_template.subtyp &&
/* note: chan fields do not have to match */
ann_stack[i].num == ann_template.num &&
safestrcmp(ann_stack[i].aux, ann_template.aux) == 0)
break; /* ann_template is already in the stack */
}
/* Make room for ann_template at the top of the stack by discarding
the copy of ann_template we found in the stack, or the least recently
used template if we didn't find ann_template in the stack. */
for ( ; i > 0; i--)
ann_stack[i] = ann_stack[i-1];
ann_stack[ann_stack_index = 0] = ann_template;
}
static void set_ann_template(a)
struct WFDB_ann *a;
{
if (ann_template.anntyp != a->anntyp ||
ann_template.subtyp != a->subtyp ||
ann_template.num != a->num ||
safestrcmp(ann_template.aux, a->aux) != 0) {
ann_template = *a;
set_anntyp(ann_template.anntyp);
set_ann_aux(ann_template.aux);
set_ann_subtyp(ann_template.subtyp);
set_ann_chan(ann_template.chan);
set_ann_num(ann_template.num);
}
}
static void set_next_ann_template()
{
if (ann_stack_index > 0)
set_ann_template(&ann_stack[--ann_stack_index]);
}
static void set_prev_ann_template()
{
if (ann_stack_index < ANNTEMPSTACKSIZE-1)
set_ann_template(&ann_stack[++ann_stack_index]);
}
/* Parse the aux string of a LINK annotation to obtain the URL of the external
data, and open the URL in a browser window. */
static void parse_and_open_url(s)
char *s; /* aux string -- first byte is count of bytes to follow */
{
char *p1, *p2, *p3;
int use_path = 1;
if (s == NULL || *s == 0 || *(s+1) == ' ' || *(s+1) == '\t')
return; /* no link defined, do nothing */
p1 = p2 = s + 1; /* first data byte */
p3 = p1 + *s; /* first byte after valid data */
/* First, scan the string to see if it includes a protocol prefix
(typically `http:' or `ftp:'), a tag suffix (`#' followed by a string
specifying a location within a file), or a label (a string following
a space, not a part of the URL but intended to be displayed by WAVE). */
while (p2 < p3) {
if (*p2 == ':')
/* The URL appears to include a protocol prefix -- pass the string
to the browser as is except for removal of the label, if any. */
use_path = 0;
else if (*p2 == ' ' || *p2 == '\t')
/* There is a label in the string -- we don't need to scan further.
p2 marks the end of the URL. */
break;
else if (*p2 == '#' && use_path)
/* The URL includes a tag, and it's also incomplete. In this case,
we need to find the file specified by the portion of the string
up to p2, then furnish the complete pathname to the browser,
appending the tag again (but still removing the label, if any).
We don't need to scan further just yet. */
break;
p2++;
}
strncpy(url, p1, p2-p1);
url[p2-p1] = '\0';
if (use_path == 0 || url[0] == '/') {
/* Here, we have either a full URL with a protocol prefix (if use_path
is 0) or an absolute pathname of a local file, possibly with a tag
suffix. In either case, the URL can be passed to the browser as is.
*/
open_url();
return;
}
/* In this case, the string specifies a relative pathname (possibly
referring to a file in another directory in the WFDB path). First,
let's try to find the file using wfdbfile to search the WFDB path. */
if (p1 = wfdbfile(url, NULL)) { /* Success: wfdbfile has found the file! */
if (*p1 != '/') {
/* We still have only a relative pathname, and we need an absolute
pathname (the browser may not accept a relative path, and may
have a different working directory than WAVE in any case). */
static char *cwd;
if (cwd == NULL) cwd = getcwd(NULL, 256);
if (cwd && strlen(cwd) + strlen(p1) < sizeof(url) - 1)
sprintf(url, "%s/%s", cwd, p1);
else {
strncpy(url, p1, sizeof(url)-1);
url[strlen(url)] = '\0';
}
}
else
strcpy(url, p1);
/* We should now have a null-terminated absolute pathname in url. */
}
/* We still need to reattach the tag suffix, if any, to the URL. */
if (*p2 == '#') {
p1 = url + strlen(url);
while (p1 < url+sizeof(url)-1 && p2 < p3 &&
*p2 != ' ' && *p2 != '\t' && *p2 != '\0')
*p1++ = *p2++;
*p1 = '\0';
}
/* We should now have a complete URL (unless wfdbfile or getcwd failed
above, in which case we'll let the browser try to find it anyway). */
open_url();
}
/* Handle events in the ECG display window. */
void window_event_proc(window, event, arg)
Xv_Window window;
Event *event;
Notify_arg arg;
{
int e, i, x, y;
WFDB_Time t, tt;
struct ap *a;
static int left_down, middle_down, right_down, redrawing, dragged, warped;
void delete_annotation(), move_annotation();
e = (int)event_id(event);
x = (int)event_x(event);
y = (int)event_y(event);
t = display_start_time + x/tscale;
if (atimeres > 1) /* drop to next lower frame boundary if reading a
multi-frequency record in WFDB_HIGHRES mode (see
init.c) */
t -= (t % atimeres);
/* If there is an attached (selected) annotation, detach it if it is no
longer on-screen (the user may have used a main control panel button
to move to another location in the record). */
if (attached && (attached->this.time < display_start_time ||
attached->this.time >= display_start_time + nsamp))
detach_ann();
if (event_action(event) == ACTION_HELP)
xv_help_show(window, "wave:canvas", event);
/* Handle simple keyboard events. */
else if (event_is_ascii(event)) {
if (event_is_up(event)) return;
if (e == '.') ann_template.anntyp = NOTQRS;
else if (e == ':') ann_template.anntyp = INDEX_MARK;
else if (e == '<') ann_template.anntyp = BEGIN_ANALYSIS;
else if (e == '>') ann_template.anntyp = END_ANALYSIS;
else if (e == ';') ann_template.anntyp = REF_MARK;
else if (e == '\r' && attached && attached->this.anntyp == LINK)
parse_and_open_url(attached->this.aux);
else if (e == '+' && event_ctrl_is_down(event)) {
/* Increase size of selected signal, if any */
if (0 <= selected && selected < nsig)
vmag[selected] *= 1.1;
/* or of all signals, otherwise */
else
for (i = 0; i < nsig; i++)
vmag[i] *= 1.1;
vscale[0] = 0.0;
calibrate();
disp_proc(XV_NULL, (Event *) '.');
}
else if (e == '-' && event_ctrl_is_down(event)) {
/* Decrease size of selected signal, if any */
if (0 <= selected && selected < nsig)
vmag[selected] /= 1.1;
/* or of all signals, otherwise */
else
for (i = 0; i < nsig; i++)
vmag[i] /= 1.1;
vscale[0] = 0.0;
calibrate();
disp_proc(XV_NULL, (Event *) '.');
}
else if (e == '*' && event_ctrl_is_down(event)) {
/* Invert selected signal, if any */
if (0 <= selected && selected < nsig)
vmag[selected] *= -1.0;
/* or all signals, otherwise */
else
for (i = 0; i < nsig; i++)
vmag[i] *= -1.0;
vscale[0] = 0.0;
calibrate();
disp_proc(XV_NULL, (Event *) '.');
}
else if (e == ')' && event_ctrl_is_down(event)) {
/* Show more context, less detail (zoom out) */
tmag /= 1.01;
clear_cache();
if (display_start_time < 0)
display_start_time = -display_start_time;
display_start_time -= (nsamp + 100)/200;
if (display_start_time < 0) display_start_time = 0;
calibrate();
disp_proc(XV_NULL, (Event *) '^');
}
else if (e == '(' && event_ctrl_is_down(event)) {
/* Show less context, more detail (zoom in) */
tmag *= 1.01;
clear_cache();
if (display_start_time < 0)
display_start_time = -display_start_time;
display_start_time += (nsamp + 99)/198;
calibrate();
disp_proc(XV_NULL, (Event *) '^');
}
else if (e == '=' && event_ctrl_is_down(event)) {
/* Reset size of selected signal, if any */
if (0 <= selected && selected < nsig)
vmag[selected] = 1.0;
/* or of all signals, otherwise */
else
for (i = 0; i < nsig; i++)
vmag[i] = 1.0;
/* Reset time scale */
tmag = 1.0;
vscale[0] = 0.0;
if (display_start_time < 0)
display_start_time = -display_start_time;
display_start_time += nsamp/2;
calibrate();
display_start_time -= nsamp/2;
if (display_start_time < 0)
display_start_time = 0;
disp_proc(XV_NULL, (Event *) '^');
}
else {
static char es[2];
es[0] = e;
if ((i = strann(es)) != NOTQRS) ann_template.anntyp = i;
}
if (ann_popup_active < 0) show_ann_template();
if (ann_template.anntyp != -1)
set_anntyp(ann_template.anntyp);
}
/* Lock out mouse and other events while display is being updated. */
else if (redrawing) return;
/* Handle mouse and other events. */
else switch (e) {
case LOC_WINENTER: /* This doesn't seem to do anything useful. */
win_set_kbd_focus(window, xv_get(window, XV_XID));
break;
case KEY_LEFT(6): /* <Copy>: copy selected annotation into
Annotation Template */
case KEY_TOP(6): /* <F6>: same as <Copy> */
if (attached) {
set_ann_template(&(attached->this));
save_ann_template();
}
break;
case KEY_LEFT(9): /* <Find>: search */
case KEY_TOP(9): /* <F9>: same as <Find> */
if (event_is_down(event)) {
if (event_shift_is_down(event)) {
if (event_ctrl_is_down(event)) /* <ctrl><shift><Find>: home */
disp_proc(XV_NULL, (Event *) 'h');
else /* <shift><Find>: end */
disp_proc(XV_NULL, (Event *) 'e');
}
else if (event_ctrl_is_down(event)) /* <ctrl>+<Find>: backward */
disp_proc(XV_NULL, (Event *) '[');
else /* <Find>: forward */
disp_proc(XV_NULL, (Event *) ']');
}
selected = -1;
break;
/* <F10> = +half-screen <shift><F10> = -half_screen
<ctrl><F10> = +full-screen <shift><ctrl><F10> = -full-screen
*/
case KEY_LEFT(10):
case KEY_TOP(10):
if (event_is_down(event)) {
if (event_shift_is_down(event)) {
if (event_ctrl_is_down(event))
disp_proc(XV_NULL, (Event *) '<');
else
disp_proc(XV_NULL, (Event *) '(');
}
else {
if (event_ctrl_is_down(event))
disp_proc(XV_NULL, (Event *) '>');
else
disp_proc(XV_NULL, (Event *) ')');
}
}
if (event_is_down(event)) {
}
selected = -1;
break;
case KEY_RIGHT(7): /* home:
Ignore key release events.
Invoke disp_proc to move to the beginning of
the record. */
if (event_is_down(event))
disp_proc(XV_NULL, (Event *) 'h'); /* strange but correct! */
selected = -1;
break;
case KEY_RIGHT(13): /* end:
Ignore key release events.
Invoke disp_proc to move to the end of
the record. */
if (event_is_down(event))
disp_proc(XV_NULL, (Event *) 'e');
selected = -1;
break;
case KEY_RIGHT(9): /* page-up:
Ignore key release events.
Invoke disp_proc to move to the previous
frame. */
if (event_is_down(event)) {
if (event_ctrl_is_down(event))
disp_proc(XV_NULL, (Event *) '<');
else
disp_proc(XV_NULL, (Event *) '(');
}
selected = -1;
break;
case KEY_RIGHT(15): /* page-down:
Ignore key release events.
Invoke disp_proc to move to the next
frame. */
if (event_is_down(event)) {
if (event_ctrl_is_down(event))
disp_proc(XV_NULL, (Event *) '>');
else
disp_proc(XV_NULL, (Event *) ')');
}
selected = -1;
break;
case KEY_RIGHT(8): /* up-arrow:
Ignore key release events.
Do nothing unless in multi-edit mode and
an annotation with chan > 0 is attached.
If there is another annotation with the
same time as the attached annotation, but
on the previous signal, attach that
annotation. (`Previous signal' means with
a `chan' field one less than that of the
attached annotation.)
Otherwise, if <control> is not down, move
the attached annotation to the previous
signal.
Otherwise, if <control> is down, copy the
attached annotation to the previous signal,
and attach the copy. */
if (event_is_down(event) && ann_mode == 1 && attached &&
annp->this.chan > 0) {
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
break;
}
if (annp->previous &&
(annp->previous)->this.time == annp->this.time &&
(annp->previous)->this.chan == annp->this.chan - 1)
attach_ann(annp->previous);
else {
struct ap *a;
if (event_ctrl_is_down(event) && (a = get_ap())) {
a->this = annp->this;
a->this.chan--;
if (a->this.aux) {
char *p;
if ((p = (char *)calloc(*(a->this.aux)+2,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,
"This annotation cannot be inserted",
"because there is insufficient memory.",
0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
memcpy(p, a->this.aux, *(a->this.aux)+1);
a->this.aux = p;
}
insert_annotation(a);
set_ann_template(&(a->this));
save_ann_template();
attach_ann(a);
}
else {
annp->this.chan--;
check_post_update();
}
box(0,0,0);
bar(0,0,0);
clear_annotation_display();
show_annotations(display_start_time, nsamp);
box_on = dragged = 0;
attach_ann(attached);
}
annp = attached;
x = (attached->this.time - display_start_time)*tscale;
if (sig_mode == 0)
y = base[(unsigned)attached->this.chan] + mmy(2);
else {
int i;
y = abase;
for (i = 0; i < siglistlen; i++)
if (attached->this.chan == siglist[i]) {
y = base[i] + mmy(2);
break;
}
}
warped = 1;
xv_set(window, WIN_MOUSE_XY, (short)x, (short)y, NULL);
}
break;
case KEY_RIGHT(14): /* down-arrow:
Ignore key release events.
Do nothing unless in multi-edit mode and an
annotation with chan < nsig-1 is attached.
If there is another annotation with the
same time as the attached annotation, but
on the next signal, attach that annotation.
(`Next signal' means with a `chan' field one
more than that of the attached annotation.)
Otherwise, if <control> is not down, move
the attached annotation to the next signal.
Otherwise, if <control> is down, copy the
attached annotation to the next signal, and
attach the copy. */
if (event_is_down(event) && ann_mode == 1 && attached &&
annp->this.chan < nsig-1) {
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
break;
}
if (annp->next &&
(annp->next)->this.time == annp->this.time &&
(annp->next)->this.chan == annp->this.chan + 1)
attach_ann(annp->next);
else {
struct ap *a;
if (event_ctrl_is_down(event) && (a = get_ap())) {
a->this = annp->this;
a->this.chan++;
if (a->this.aux) {
char *p;
if ((p = (char *)calloc(*(a->this.aux)+2,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,
"This annotation cannot be inserted",
"because there is insufficient memory.",
0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
memcpy(p, a->this.aux, *(a->this.aux)+1);
a->this.aux = p;
}
insert_annotation(a);
set_ann_template(&(a->this));
save_ann_template();
attach_ann(a);
}
else {
annp->this.chan++;
check_post_update();
}
box(0,0,0);
bar(0,0,0);
clear_annotation_display();
show_annotations(display_start_time, nsamp);
box_on = dragged = 0;
attach_ann(attached);
}
annp = attached;
x = (attached->this.time - display_start_time)*tscale;
if (sig_mode == 0)
y = base[(unsigned)attached->this.chan] + mmy(2);
else {
int i;
y = abase;
for (i = 0; i < siglistlen; i++)
if (attached->this.chan == siglist[i]) {
y = base[i] + mmy(2);
break;
}
}
warped = 1;
xv_set(window, WIN_MOUSE_XY, (short)x, (short)y, NULL);
}
break;
case KEY_RIGHT(10): /* left-arrow: simulate left mouse button */
case MS_LEFT:
if (event_is_down(event)) {
/* The left button was pressed:
1. If the <Shift> key is depressed, select the signal nearest
the pointer and return. (A selected signal is highlighted.)
2. If the <Control> key is depressed, select the signal
nearest the pointer, insert it into the signal list, and
return.
3. If the <Meta> key is depressed, select the signal nearest
the pointer, delete its first occurrence (if any) in the
signal list, and return.
4. If annotation editing is disabled and if this instance of
WAVE has a sync button, signal other WAVE processes to
recenter their signal windows at the time indicated by
the mouse, and return.
5. If the middle button is down, switch the annotation
template to the previous entry in the annotation template
buffer.
6. Make the annotation template popup visible.
7. If the middle or right button is down, or if there are no
annotations left of the pointer, return.
8. If annotations are shown attached to signals, and the
pointer is in a selection box, attach the previous
annotation.
9. If annotations are shown attached to signals, and the
pointer is not in a selection box, attach the closest
annotation to the left of the pointer.
10. Otherwise, find the previous group of simultaneous
annotations and attach the first annotation of that group.
11. Recenter the display around the attached annotation, if
it is not currently displayed.
12. Draw marker bars above and below the attached annotation.
*/
if (event_shift_is_down(event) ||
event_ctrl_is_down(event) ||
event_meta_is_down(event)) {
int d, dmin = -1, i, imin = -1, n;
n = sig_mode ? siglistlen : nsig;
for (i = 0; i < n; i++) {
d = y - base[i];
if (d < 0) d = -d;
if (dmin < 0 || d < dmin) { imin = i; dmin = d; }
}
if (imin >= 0) {
set_signal_choice(imin);
if (selected == imin) selected = -1;
else selected = imin;
if (event_ctrl_is_down(event)) add_signal_choice();
if (event_meta_is_down(event)) delete_signal_choice();
}
break;
}
dragged = 0;
if (accept_edit == 0 && wave_ppid) {
char buf[80];
sprintf(buf, "wave-remote -pid %d -f '%s'\n", wave_ppid,
mstimstr(-t));
system(buf);
break;
}
if (middle_down) set_prev_ann_template();
show_ann_template();
if (middle_down || right_down) break;
left_down = 1;
(void)locate_annotation(t, -128); /* -128 is lowest chan value */
if (annp) {
if (annp->previous) annp = annp->previous;
else break;
}
else if (ap_end) annp = ap_end;
else break;
redrawing = 1;
if (ann_mode == 1) {
if (attached && in_box(x, y)) {
if (attached->previous) annp = attached->previous;
else annp = attached;
}
else {
double d, dx, dy, dmin = -1.0;
struct ap *a = annp;
while (a->next && annp->this.time == (a->next)->this.time)
a = a->next;
while (a && a->this.time >= display_start_time) {
dx = x - (a->this.time - display_start_time)*tscale;
if (sig_mode == 0)
dy = y - (base[a->this.chan] + mmy(2));
else {
int i;
dy = y - abase;
for (i = 0; i < siglistlen; i++)
if (a->this.chan == siglist[i]) {
dy = y - (base[i] + mmy(2));
break;
}
}
d = dx*dx + dy*dy;
if (dmin < 0. || d < dmin) {
dmin = d;
annp = a;
}
a = a->previous;
}
}
}
if (annp->this.time < display_start_time) {
struct ap *a = annp;
XFillRectangle(display, osb, clear_all,
0, 0, canvas_width+mmx(10), canvas_height);
if ((tt = annp->this.time - (WFDB_Time)((nsamp-freq)/2)) < 0L)
display_start_time = 0L;
else
display_start_time = strtim(timstr(tt));
do_disp();
left_down = bar_on = box_on = 0;
annp = a;
}
/* Attach the annotation, move the pointer to it, and draw the
marker bars. */
attach_ann(annp);
x = (annp->this.time - display_start_time)*tscale;
if (ann_mode == 1 && (unsigned)annp->this.chan < nsig) {
if (sig_mode == 0)
y = base[(unsigned)annp->this.chan] + mmy(2);
else {
int i;
y = abase;
for (i = 0; i < siglistlen; i++)
if (annp->this.chan == siglist[i]) {
y = base[i] + mmy(2);
break;
}
}
}
else
y = abase;
warped = 1;
xv_set(window, WIN_MOUSE_XY, (short)x, (short)y, NULL);
bar(x, y, 1);
redrawing = 0;
}
else {
/* The left button was released:
1. If the initial button press occurred while mouse events
were locked out, ignore this event.
2. If there is an attached annotation, and the pointer has
been dragged outside the box, move the annotation to the
pointer (keeping it attached), and redraw the annotations.
3. Erase the marker bars.
*/
if (!left_down) break;
left_down = 0;
if (attached && dragged && !in_box(x, y)) {
move_annotation(attached, t);
box(0,0,0);
bar(0,0,0);
clear_annotation_display();
show_annotations(display_start_time, nsamp);
box_on = dragged = 0;
attach_ann(attached);
}
bar(x, 0, 0);
}
break;
case KEY_LEFT(2):
case KEY_TOP(2):
case KEY_RIGHT(11): /* <5> on numeric keypad: simulate middle
mouse button */
case MS_MIDDLE:
if (event_is_down(event)) {
/* The middle button was pressed:
1. If the left or right button is down, ignore this event.
2. Draw marker bars above and below the pointer.
3. If annotation editing is disabled or if the <Control> key
is depressed, and if this instance of WAVE has a sync
button, signal other WAVE processes to recenter their
signal windows at the time indicated by the mouse, and
return.
4. If there is an attached annotation, and the pointer is
outside the box, detach the annotation (erase the box).
*/
if (left_down || right_down || ann_template.anntyp < 0) break;
middle_down = 1;
bar(x, y /* ? */, 1);
if ((accept_edit == 0 || event_ctrl_is_down(event)) && wave_ppid) {
char buf[80];
sprintf(buf, "wave-remote -pid %d -f '%s'\n", wave_ppid,
mstimstr(-t));
system(buf);
}
else if (attached && !in_box(x, y))
detach_ann();
break;
}
else {
/* The middle button was released:
1. If the initial button press occurred while mouse events
were locked out, or while the left or right buttons were
down, ignore this event.
2. If ann_mode is 1 (i.e., if annotations are attached
to signals), set the `chan' field of the current template
annotation according to the y-coordinate of the pointer.
3. If there is an attached annotation, and the pointer is
inside the box, change it or delete it (according to the
current template annotation).
4. Otherwise, if the current template annotation has a valid
annotation type, insert and attach it.
5. Redraw the annotation display and clear the marker bars.
*/
if (!middle_down) break;
middle_down = 0;
if (ann_mode == 1) {
int d, dmin = -1, i, imin = -1, n;
n = sig_mode ? siglistlen : nsig;
for (i = 0; i < n; i++) {
d = y - base[i];
if (d < 0) d = -d;
if (dmin < 0 || d < dmin) { imin = i; dmin = d; }
}
if (imin >= 0) {
if (sig_mode) imin = siglist[imin];
set_ann_chan(ann_template.chan = imin);
}
}
if (attached && in_box(x, y)) {
if (ann_template.anntyp == NOTQRS) {
save_ann_template();
delete_annotation(attached->this.time,attached->this.chan);
a = NULL;
}
else if (a = get_ap()) {
a->this = ann_template;
a->this.time = attached->this.time;
}
}
else if (ann_template.anntyp != NOTQRS && (a = get_ap())) {
a->this = ann_template;
a->this.time = t;
}
else
a = NULL;
if (a) {
/* There is an annotation to be inserted. Copy the aux string,
if any (since the template aux pointer points to static
memory that can be changed at any time by the user). */
if (a->this.aux) {
char *p;
if ((p = (char *)calloc(*(a->this.aux)+2, 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,
"This annotation cannot be inserted",
"because there is insufficient memory.",
0,
NOTICE_BUTTON_YES, "Continue", NULL);
#ifdef NOTICE
xv_destroy_safe(notice);
#endif
return;
}
memcpy(p, a->this.aux, *(a->this.aux)+1);
a->this.aux = p;
}
insert_annotation(a);
set_ann_template(&(a->this));
save_ann_template();
}
box(0,0,0);
bar(0,0,0);
clear_annotation_display();
show_annotations(display_start_time, nsamp);
box_on = 0;
bar(x,0,0);
}
break;
case KEY_RIGHT(12): /* right-arrow: simulate right mouse button */
case MS_RIGHT:
if (event_is_down(event)) {
/* The right button was pressed:
1. If the middle button is down, switch the annotation
template to the next entry in the annotation template
stack, and make the annotation template popup visible.
2. If the left or middle button is down, or if there are no
annotations right of the pointer, ignore this event.
3. If annotations are shown attached to signals, and the
pointer is not in a selection box, attach the closest
annotation to the right of the pointer.
4. Otherwise, attach the next annotation.
5. Recenter the display around the attached annotation, if
it is not currently displayed.
6. Draw marker bars above and below the attached annotation.
*/
if (middle_down) {
set_next_ann_template();
show_ann_template();
}
if (left_down || middle_down) break;
dragged = 0;
right_down = 1;
if (attached && in_box(x, y)) annp = attached->next;
else (void)locate_annotation(t, -128);
if (annp == NULL) break;
redrawing = 1;
if (ann_mode == 1 && (!attached || !in_box(x, y))) {
double d, dx, dy, dmin = -1.0;
struct ap *a = annp;
while (a && a->this.time < display_start_time + nsamp) {
dx = x - (a->this.time - display_start_time)*tscale;
if (sig_mode == 0)
dy = y - (base[a->this.chan] + mmy(2));
else {
int i;
dy = y - abase;
for (i = 0; i < siglistlen; i++)
if (a->this.chan == siglist[i]) {
dy = y - (base[i] + mmy(2));
break;
}
}
d = dx*dx + dy*dy;
if (dmin < 0. || d < dmin) {
dmin = d;
annp = a;
}
a = a->next;
}
}
if (annp->this.time >= display_start_time + nsamp) {
struct ap *a = annp;
XFillRectangle(display, osb, clear_all,
0, 0, canvas_width+mmx(10), canvas_height);
tt = annp->this.time - (WFDB_Time)((nsamp-freq)/2);
display_start_time = strtim(timstr(tt));
do_disp();
right_down = bar_on = box_on = 0;
annp = a;
}
/* Attach the annotation, move the pointer to it, and draw the
marker bars. */
attach_ann(annp);
x = (annp->this.time - display_start_time)*tscale;
if (ann_mode == 1 && (unsigned)annp->this.chan < nsig) {
if (sig_mode == 0)
y = base[(unsigned)annp->this.chan] + mmy(2);
else {
int i;
y = abase;
for (i = 0; i < siglistlen; i++)
if (annp->this.chan == siglist[i]) {
y = base[i] + mmy(2);
break;
}
}
}
else
y = abase;
warped = 1;
xv_set(window, WIN_MOUSE_XY, (short)x, (short)y, NULL);
bar(x, y, 1);
redrawing = 0;
}
else {
/* The right button was released:
1. If the initial button press occurred while mouse events
were locked out, ignore this event.
2. If there is an attached annotation, and the pointer has
been dragged outside the box, move the annotation to the
pointer (keeping it attached), and redraw the annotations.
3. Erase the marker bars.
*/
if (!right_down) break;
right_down = 0;
if (attached && dragged && !in_box(x, y)) {
move_annotation(attached, t);
box(0,0,0);
bar(0,0,0);
clear_annotation_display();
show_annotations(display_start_time, nsamp);
box_on = dragged = 0;
attach_ann(attached);
}
bar(x,0,0);
}
break;
case KEY_LEFT(3):
case KEY_TOP(3):
case KEY_RIGHT(4): /* <=> on numeric keypad: simulate drag left */
{
static int count = 1;
if (event_is_down(event)) {
if ((x -= count) < 0) x = 0;
if (count < 100) count++;
}
else
count = 1;
}
warped = 1;
if (middle_down) bar(x, y, 1);
xv_set(window, WIN_MOUSE_XY, (short)x, (short)y, NULL);
break;
case KEY_LEFT(4):
case KEY_TOP(4):
case KEY_RIGHT(6): /* <*> on numeric keypad: simulate drag right*/
{
static int count = 1;
if (event_is_down(event)) {
if ((x += count) >= canvas_width) x = canvas_width - 1;
if (count < 100) count++;
}
else
count = 1;
}
warped = 1;
xv_set(window, WIN_MOUSE_XY, (short)x, (short)y, NULL);
if (middle_down) bar(x, y, 1);
break;
case LOC_DRAG:
/* The mouse moved while one or more buttons were depressed:
1. If the initial button press occurred while mouse events were
locked out, or if the current pointer abscissa matches the
marker bar position, ignore this event.
2. If the pointer was warped since the previous drag event, ignore
this event.
3. If there is an attached annotation, and the pointer is inside
the box, move the marker bars to the box center abscissa unless
they are there already.
4. If ann_mode is 1, compare the signal number of the
nearest signal with the `chan' field of the template
annotation. If these are unequal, reset the `chan' field to
match, and redraw the marker bars to surround the selected
signal.
5. Otherwise, move the marker bars to the current pointer
abscissa.
*/
if ((!middle_down && !left_down && !right_down) || x == bar_x)
break;
else if (warped) {
warped = 0;
break;
}
else if (attached && in_box(x, y)) {
if (bar_x != box_xc) bar(box_xc, box_yc, 1);
}
else if (ann_mode == 1) {
int d, dmin = -1, i, ii, imin = -1, n;
n = sig_mode ? siglistlen : nsig;
for (i = 0; i < n; i++) {
d = y - base[i];
if (d < 0) d = -d;
if (dmin < 0 || d < dmin) { imin = i; dmin = d; }
}
ii = (imin >= 0 && sig_mode) ? siglist[imin] : imin;
if (imin >= 0 && ann_template.chan != ii) {
set_ann_chan(ann_template.chan = ii);
if (attached) attached->this.chan = ii;
}
bar(x, imin >= 0 ? base[(unsigned)imin] + mmy(2) : abase, 1);
}
else
bar(x, abase, 1);
dragged = 1;
break;
default:
#ifdef DEBUG
fprintf(stderr, "event %d at sample %ld (%s)\n", e, t, timstr(t));
#endif
break;
}
XClearWindow(display, xid);
}