#include <stdio.h>
#include <xterm.h>
#include <error.h>
#include <data.h>
#include <xcharmouse.h>
#include <xterm_io.h>
#if OPT_WIDE_CHARS
#include <fontutils.h>
#include <menu.h>
#endif
#include <assert.h>
#include <signal.h>
#define getMinRow(screen) ((xw->flags & ORIGIN) ? (screen)->top_marg : 0)
#define getMaxRow(screen) ((xw->flags & ORIGIN) ? (screen)->bot_marg : (screen)->max_row)
#define getMinCol(screen) 0
#define getMaxCol(screen) ((screen)->max_col)
ScrnBuf
Allocate(int nrow, int ncol, Char ** addr)
{
ScrnBuf base;
Char *tmp;
int i, j, k;
size_t entries = MAX_PTRS * nrow;
size_t length = BUF_PTRS * nrow * ncol;
if ((base = TypeCallocN(ScrnPtr, entries)) == 0)
SysError(ERROR_SCALLOC);
if ((tmp = TypeCallocN(Char, length)) == 0)
SysError(ERROR_SCALLOC2);
*addr = tmp;
for (i = k = 0; i < nrow; i++) {
base[k] = 0;
k += BUF_HEAD;
for (j = BUF_HEAD; j < MAX_PTRS; j++) {
base[k++] = tmp;
tmp += ncol;
}
}
return (base);
}
static int
Reallocate(XtermWidget xw,
ScrnBuf * sbuf,
Char ** sbufaddr,
int nrow,
int ncol,
int oldrow,
int oldcol)
{
ScrnBuf base;
Char *tmp;
int i, j, k, minrows;
size_t mincols;
Char *oldbuf;
int move_down = 0, move_up = 0;
size_t entries = MAX_PTRS * nrow;
size_t length = BUF_PTRS * nrow * ncol;
if (sbuf == NULL || *sbuf == NULL) {
return 0;
}
oldbuf = *sbufaddr;
if (nrow < oldrow
&& xw->misc.resizeGravity == SouthWestGravity) {
move_up = (oldrow - nrow)
- (xw->screen.max_row - xw->screen.cur_row);
if (move_up < 0)
move_up = 0;
memmove(*sbuf, *sbuf + (move_up * MAX_PTRS),
MAX_PTRS * (oldrow - move_up) * sizeof((*sbuf)[0]));
}
*sbuf = TypeRealloc(ScrnPtr, entries, *sbuf);
if (*sbuf == 0)
SysError(ERROR_RESIZE);
base = *sbuf;
if ((tmp = TypeCallocN(Char, length)) == 0)
SysError(ERROR_SREALLOC);
*sbufaddr = tmp;
minrows = (oldrow < nrow) ? oldrow : nrow;
mincols = (oldcol < ncol) ? oldcol : ncol;
if (nrow > oldrow
&& xw->misc.resizeGravity == SouthWestGravity) {
move_down = Min(nrow - oldrow, xw->screen.savedlines);
tmp += (ncol * move_down * BUF_PTRS);
}
for (i = k = 0; i < minrows; i++) {
k += BUF_HEAD;
for (j = BUF_HEAD; j < MAX_PTRS; j++) {
memcpy(tmp, base[k++], mincols);
tmp += ncol;
}
}
for (i = k = 0, tmp = *sbufaddr; i < nrow; i++) {
for (j = 0; j < BUF_HEAD; j++)
base[k++] = 0;
for (j = BUF_HEAD; j < MAX_PTRS; j++) {
base[k++] = tmp;
tmp += ncol;
}
}
free(oldbuf);
return move_down ? move_down : -move_up;
}
#if OPT_WIDE_CHARS
#if 0
static void
dump_screen(const char *tag,
XtermWidget xw,
ScrnBuf sbuf,
Char * sbufaddr,
unsigned nrow,
unsigned ncol)
{
unsigned y, x;
TRACE(("DUMP %s, ptrs %d\n", tag, xw->num_ptrs));
TRACE((" sbuf %p\n", sbuf));
TRACE((" sbufaddr %p\n", sbufaddr));
TRACE((" nrow %d\n", nrow));
TRACE((" ncol %d\n", ncol));
for (y = 0; y < nrow; ++y) {
ScrnPtr ptr = BUF_CHARS(sbuf, y);
TRACE(("%3d:%p:", y, ptr));
for (x = 0; x < ncol; ++x) {
Char c = ptr[x];
if (c == 0)
c = '~';
TRACE(("%c", c));
}
TRACE(("\n"));
}
}
#else
#define dump_screen(tag, xw, sbuf, sbufaddr, nrow, ncol)
#endif
static void
ReallocateBufOffsets(XtermWidget xw,
ScrnBuf * sbuf,
Char ** sbufaddr,
unsigned nrow,
unsigned ncol,
size_t new_max_offsets)
{
unsigned i;
int j, k;
ScrnBuf base;
Char *oldbuf, *tmp;
size_t entries, length;
int old_max_ptrs = MAX_PTRS;
assert(nrow != 0);
assert(ncol != 0);
assert(new_max_offsets != 0);
dump_screen("before", xw, *sbuf, *sbufaddr, nrow, ncol);
xw->num_ptrs = new_max_offsets;
entries = MAX_PTRS * nrow;
length = BUF_PTRS * nrow * ncol;
oldbuf = *sbufaddr;
*sbuf = TypeRealloc(ScrnPtr, entries, *sbuf);
if (*sbuf == 0)
SysError(ERROR_RESIZE);
base = *sbuf;
if ((tmp = TypeCallocN(Char, length)) == 0)
SysError(ERROR_SREALLOC);
*sbufaddr = tmp;
for (i = k = 0; i < nrow; i++) {
k += BUF_HEAD;
for (j = BUF_HEAD; j < old_max_ptrs; j++) {
memcpy(tmp, base[k++], ncol);
tmp += ncol;
}
tmp += ncol * (new_max_offsets - old_max_ptrs);
}
for (i = k = 0, tmp = *sbufaddr; i < nrow; i++) {
for (j = 0; j < BUF_HEAD; j++)
base[k++] = 0;
for (j = BUF_HEAD; j < MAX_PTRS; j++) {
base[k++] = tmp;
tmp += ncol;
}
}
free(oldbuf);
dump_screen("after", xw, *sbuf, *sbufaddr, nrow, ncol);
xw->num_ptrs = old_max_ptrs;
}
void
ChangeToWide(XtermWidget xw)
{
TScreen *screen = &(xw->screen);
unsigned new_bufoffset = OFF_FINAL + (screen->max_combining * 2);
int savelines = screen->scrollWidget ? screen->savelines : 0;
if (screen->wide_chars)
return;
TRACE(("ChangeToWide\n"));
if (xtermLoadWideFonts(xw, True)) {
if (savelines < 0)
savelines = 0;
if (screen->alternate)
SwitchBufPtrs(screen);
ReallocateBufOffsets(xw,
&screen->allbuf, &screen->sbuf_address,
(unsigned) (MaxRows(screen) + savelines),
(unsigned) MaxCols(screen),
new_bufoffset);
if (screen->altbuf) {
ReallocateBufOffsets(xw,
&screen->altbuf, &screen->abuf_address,
(unsigned) MaxRows(screen),
(unsigned) MaxCols(screen),
new_bufoffset);
}
screen->wide_chars = True;
xw->num_ptrs = new_bufoffset;
screen->visbuf = &screen->allbuf[MAX_PTRS * savelines];
if (screen->alternate)
SwitchBufPtrs(screen);
update_font_utf8_mode();
SetVTFont(xw, screen->menu_font_number, True, NULL);
}
TRACE(("...ChangeToWide\n"));
}
#endif
void
ClearCells(XtermWidget xw, int flags, unsigned len, int row, int col)
{
if (len != 0) {
TScreen *screen = &(xw->screen);
flags |= TERM_COLOR_FLAGS(xw);
memset(SCRN_BUF_CHARS(screen, row) + col, ' ', len);
memset(SCRN_BUF_ATTRS(screen, row) + col, flags, len);
if_OPT_EXT_COLORS(screen, {
memset(SCRN_BUF_FGRND(screen, row) + col,
xw->sgr_foreground, len);
memset(SCRN_BUF_BGRND(screen, row) + col,
xw->cur_background, len);
});
if_OPT_ISO_TRADITIONAL_COLORS(screen, {
memset(SCRN_BUF_COLOR(screen, row) + col,
(int) xtermColorPair(xw), len);
});
if_OPT_DEC_CHRSET({
memset(SCRN_BUF_CSETS(screen, row) + col,
curXtermChrSet(xw, row), len);
});
if_OPT_WIDE_CHARS(screen, {
int off;
for (off = OFF_WIDEC; off < MAX_PTRS; ++off) {
memset(SCREEN_PTR(screen, row, off) + col, 0, len);
}
});
}
}
void
ScrnClearCells(XtermWidget xw, int row, int col, unsigned len)
{
#if OPT_WIDE_CHARS
TScreen *screen = &(xw->screen);
#endif
int flags = 0;
if_OPT_WIDE_CHARS(screen, {
int kl;
int kr;
if (DamagedCells(screen, len, &kl, &kr, INX2ROW(screen, row), col)
&& kr >= kl) {
ClearCells(xw, flags, (unsigned) (kr - kl + 1), row, kl);
}
});
ClearCells(xw, flags, len, row, col);
}
void
ScrnDisownSelection(XtermWidget xw)
{
if (ScrnHaveSelection(&(xw->screen))) {
if (xw->screen.keepSelection) {
UnhiliteSelection(xw);
} else {
DisownSelection(xw);
}
}
}
void
ScrnWriteText(XtermWidget xw,
PAIRED_CHARS(Char * str, Char * str2),
unsigned flags,
unsigned cur_fg_bg,
unsigned length)
{
TScreen *screen = &(xw->screen);
#if OPT_ISO_COLORS
#if OPT_EXT_COLORS
Char *fbf = 0;
Char *fbb = 0;
#else
Char *fb = 0;
#endif
#endif
#if OPT_DEC_CHRSET
Char *cb = 0;
#endif
Char *attrs;
int avail = MaxCols(screen) - screen->cur_col;
Char *chars;
#if OPT_WIDE_CHARS
Char starcol1, starcol2;
#endif
unsigned real_width = visual_width(PAIRED_CHARS(str, str2), length);
(void) cur_fg_bg;
if (avail <= 0)
return;
if (length > (unsigned) avail)
length = avail;
if (length == 0 || real_width == 0)
return;
chars = SCRN_BUF_CHARS(screen, screen->cur_row) + screen->cur_col;
attrs = SCRN_BUF_ATTRS(screen, screen->cur_row) + screen->cur_col;
if_OPT_EXT_COLORS(screen, {
fbf = SCRN_BUF_FGRND(screen, screen->cur_row) + screen->cur_col;
fbb = SCRN_BUF_BGRND(screen, screen->cur_row) + screen->cur_col;
});
if_OPT_ISO_TRADITIONAL_COLORS(screen, {
fb = SCRN_BUF_COLOR(screen, screen->cur_row) + screen->cur_col;
});
if_OPT_DEC_CHRSET({
cb = SCRN_BUF_CSETS(screen, screen->cur_row) + screen->cur_col;
});
#if OPT_WIDE_CHARS
starcol1 = *chars;
starcol2 = chars[length - 1];
#endif
if (flags & INVISIBLE) {
memset(chars, ' ', length);
} else {
memcpy(chars, str, length);
}
#if OPT_BLINK_TEXT
if ((flags & BLINK) && !(screen->blink_as_bold)) {
ScrnSetBlinked(screen, screen->cur_row);
}
#endif
#define ERROR_1 0x20
#define ERROR_2 0x00
if_OPT_WIDE_CHARS(screen, {
Char *char2;
if (real_width != length) {
Char *char1 = chars;
char2 = SCRN_BUF_WIDEC(screen, screen->cur_row);
char2 += screen->cur_col;
if (screen->cur_col && starcol1 == HIDDEN_LO && *char2 == HIDDEN_HI
&& iswide(PACK_PAIR(char1, char2, -1))) {
char1[-1] = ERROR_1;
char2[-1] = ERROR_2;
}
while (length) {
int ch = PACK_PAIR(str, str2, 0);
*char1 = *str;
char1++;
str++;
if (str2) {
*char2 = *str2;
str2++;
} else
*char2 = 0;
char2++;
length--;
if (iswide(ch)) {
*char1 = HIDDEN_LO;
*char2 = HIDDEN_HI;
char1++;
char2++;
}
}
if (*char1 == HIDDEN_LO
&& *char2 == HIDDEN_HI
&& char1[-1] == HIDDEN_LO
&& char2[-1] == HIDDEN_HI) {
*char1 = ERROR_1;
*char2 = ERROR_2;
}
} else {
if ((char2 = SCRN_BUF_WIDEC(screen, screen->cur_row)) != 0) {
char2 += screen->cur_col;
if (screen->cur_col && starcol1 == HIDDEN_LO && *char2 == HIDDEN_HI
&& iswide(PACK_PAIR(chars, char2, -1))) {
chars[-1] = ERROR_1;
char2[-1] = ERROR_2;
}
if (chars[length] == HIDDEN_LO && char2[length] == HIDDEN_HI &&
iswide(PACK_PAIR(chars, char2, length - 1))) {
chars[length] = ERROR_1;
char2[length] = ERROR_2;
}
if ((flags & INVISIBLE) || (str2 == 0))
memset(char2, 0, length);
else
memcpy(char2, str2, length);
}
}
});
flags &= ATTRIBUTES;
flags |= CHARDRAWN;
memset(attrs, (Char) flags, real_width);
if_OPT_WIDE_CHARS(screen, {
int off;
for (off = OFF_FINAL; off < MAX_PTRS; ++off) {
memset(SCREEN_PTR(screen,
screen->cur_row,
off) + screen->cur_col,
0, real_width);
}
});
if_OPT_EXT_COLORS(screen, {
memset(fbf, (int) ExtractForeground(cur_fg_bg), real_width);
memset(fbb, (int) ExtractBackground(cur_fg_bg), real_width);
});
if_OPT_ISO_TRADITIONAL_COLORS(screen, {
memset(fb, (int) cur_fg_bg, real_width);
});
if_OPT_DEC_CHRSET({
memset(cb, curXtermChrSet(xw, screen->cur_row), real_width);
});
if_OPT_WIDE_CHARS(screen, {
screen->last_written_col = screen->cur_col + real_width - 1;
screen->last_written_row = screen->cur_row;
});
if_OPT_XMC_GLITCH(screen, {
Resolve_XMC(xw);
});
}
static void
ScrnClearLines(XtermWidget xw, ScrnBuf sb, int where, unsigned n, unsigned size)
{
TScreen *screen = &(xw->screen);
int i, j;
size_t len = ScrnPointers(screen, n);
int last = (n * MAX_PTRS);
TRACE(("ScrnClearLines(where %d, n %d, size %d)\n", where, n, size));
assert(n != 0);
assert(size != 0);
memcpy((char *) screen->save_ptr,
(char *) &sb[MAX_PTRS * where],
len);
if (TERM_COLOR_FLAGS(xw)) {
int flags = TERM_COLOR_FLAGS(xw);
for (i = 0; i < last; i += MAX_PTRS) {
for (j = 0; j < MAX_PTRS; j++) {
if (j < BUF_HEAD)
screen->save_ptr[i + j] = 0;
else if (j == OFF_ATTRS)
memset(screen->save_ptr[i + j], flags, size);
#if OPT_ISO_COLORS
#if OPT_EXT_COLORS
else if (j == OFF_FGRND)
memset(screen->save_ptr[i + j], xw->sgr_foreground, size);
else if (j == OFF_BGRND)
memset(screen->save_ptr[i + j], xw->cur_background, size);
#else
else if (j == OFF_COLOR)
memset(screen->save_ptr[i + j], (int)
xtermColorPair(xw), size);
#endif
#endif
else
bzero(screen->save_ptr[i + j], size);
}
}
} else {
for (i = 0; i < last; i += MAX_PTRS) {
for (j = 0; j < BUF_HEAD; j++)
screen->save_ptr[i + j] = 0;
for (j = BUF_HEAD; j < MAX_PTRS; j++)
bzero(screen->save_ptr[i + j], size);
}
}
}
size_t
ScrnPointers(TScreen * screen, size_t len)
{
len *= MAX_PTRS;
if (len > screen->save_len) {
if (screen->save_len)
screen->save_ptr = TypeRealloc(ScrnPtr, len, screen->save_ptr);
else
screen->save_ptr = TypeMallocN(ScrnPtr, len);
screen->save_len = len;
if (screen->save_ptr == 0)
SysError(ERROR_SAVE_PTR);
}
return len * sizeof(ScrnPtr);
}
void
ScrnInsertLine(XtermWidget xw, ScrnBuf sb, int last, int where,
unsigned n, unsigned size)
{
TScreen *screen = &(xw->screen);
size_t len = ScrnPointers(screen, n);
assert(where >= 0);
assert(last >= (int) n);
assert(last >= where);
assert(n != 0);
assert(size != 0);
assert(MAX_PTRS > 0);
ScrnClearLines(xw, sb, (last -= n - 1), n, size);
assert(last >= where);
memmove((char *) &sb[MAX_PTRS * (where + n)],
(char *) &sb[MAX_PTRS * where],
MAX_PTRS * sizeof(char *) * (last - where));
memcpy((char *) &sb[MAX_PTRS * where],
(char *) screen->save_ptr,
len);
}
void
ScrnDeleteLine(XtermWidget xw, ScrnBuf sb, int last, int where,
unsigned n, unsigned size)
{
TScreen *screen = &(xw->screen);
assert(where >= 0);
assert(last >= where + (int) n - 1);
assert(n != 0);
assert(size != 0);
assert(MAX_PTRS > 0);
ScrnClearLines(xw, sb, where, n, size);
memmove((char *) &sb[MAX_PTRS * where],
(char *) &sb[MAX_PTRS * (where + n)],
MAX_PTRS * sizeof(char *) * ((last -= n - 1) - where));
memcpy((char *) &sb[MAX_PTRS * last],
(char *) screen->save_ptr,
MAX_PTRS * sizeof(char *) * n);
}
void
ScrnInsertChar(XtermWidget xw, unsigned n)
{
#define Target (data + col + n)
#define Source (data + col)
TScreen *screen = &(xw->screen);
ScrnBuf sb = screen->visbuf;
int last = MaxCols(screen);
int row = screen->cur_row;
int col = screen->cur_col;
Char *data;
size_t nbytes;
if (last <= (int) (col + n)) {
if (last <= col)
return;
n = last - col;
}
nbytes = (last - (col + n));
assert(screen->cur_col >= 0);
assert(screen->cur_row >= 0);
assert(n > 0);
assert(last > (int) n);
if_OPT_WIDE_CHARS(screen, {
int xx = INX2ROW(screen, screen->cur_row);
int kl;
int kr = screen->cur_col;
if (DamagedCells(screen, n, &kl, (int *) 0, xx, kr) && kr > kl) {
ClearCells(xw, 0, (unsigned) (kr - kl + 1), row, kl);
}
kr = screen->max_col - n + 1;
if (DamagedCells(screen, n, &kl, (int *) 0, xx, kr) && kr > kl) {
ClearCells(xw, 0, (unsigned) (kr - kl + 1), row, kl);
}
});
data = BUF_CHARS(sb, row);
memmove(Target, Source, nbytes);
data = BUF_ATTRS(sb, row);
memmove(Target, Source, nbytes);
if_OPT_EXT_COLORS(screen, {
data = BUF_FGRND(sb, row);
memmove(Target, Source, nbytes);
data = BUF_BGRND(sb, row);
memmove(Target, Source, nbytes);
});
if_OPT_ISO_TRADITIONAL_COLORS(screen, {
data = BUF_COLOR(sb, row);
memmove(Target, Source, nbytes);
});
if_OPT_DEC_CHRSET({
data = BUF_CSETS(sb, row);
memmove(Target, Source, nbytes);
});
if_OPT_WIDE_CHARS(screen, {
int off;
for (off = OFF_WIDEC; off < MAX_PTRS; ++off) {
data = BUFFER_PTR(sb, row, off);
memmove(Target, Source, nbytes);
}
});
ClearCells(xw, CHARDRAWN, n, row, col);
#undef Source
#undef Target
}
void
ScrnDeleteChar(XtermWidget xw, unsigned n)
{
#define Target (data + col)
#define Source (data + col + n)
TScreen *screen = &(xw->screen);
ScrnBuf sb = screen->visbuf;
int last = MaxCols(screen);
int row = screen->cur_row;
int col = screen->cur_col;
Char *data;
size_t nbytes;
if (last <= (int) (col + n)) {
if (last <= col)
return;
n = last - col;
}
nbytes = (last - (col + n));
assert(screen->cur_col >= 0);
assert(screen->cur_row >= 0);
assert(n > 0);
assert(last > (int) n);
if_OPT_WIDE_CHARS(screen, {
int kl;
int kr;
if (DamagedCells(screen, n, &kl, &kr,
INX2ROW(screen, screen->cur_row),
screen->cur_col))
ClearCells(xw, 0, (unsigned) (kr - kl + 1), row, kl);
});
data = BUF_CHARS(sb, row);
memmove(Target, Source, nbytes);
data = BUF_ATTRS(sb, row);
memmove(Target, Source, nbytes);
if_OPT_EXT_COLORS(screen, {
data = BUF_FGRND(sb, row);
memmove(Target, Source, nbytes);
data = BUF_BGRND(sb, row);
memmove(Target, Source, nbytes);
});
if_OPT_ISO_TRADITIONAL_COLORS(screen, {
data = BUF_COLOR(sb, row);
memmove(Target, Source, nbytes);
});
if_OPT_DEC_CHRSET({
data = BUF_CSETS(sb, row);
memmove(Target, Source, nbytes);
});
if_OPT_WIDE_CHARS(screen, {
int off;
for (off = OFF_WIDEC; off < MAX_PTRS; ++off) {
data = BUFFER_PTR(sb, row, off);
memmove(Target, Source, nbytes);
}
});
ClearCells(xw, 0, n, row, (int) (last - n));
ScrnClrWrapped(screen, row);
#undef Source
#undef Target
}
void
ScrnRefresh(XtermWidget xw,
int toprow,
int leftcol,
int nrows,
int ncols,
Bool force)
{
TScreen *screen = &(xw->screen);
int y = toprow * FontHeight(screen) + screen->border;
int row;
int maxrow = toprow + nrows - 1;
int scrollamt = screen->scroll_amt;
int max = screen->max_row;
int gc_changes = 0;
#ifdef __CYGWIN__
static char first_time = 1;
#endif
static int recurse = 0;
TRACE(("ScrnRefresh (%d,%d) - (%d,%d)%s\n",
toprow, leftcol,
nrows, ncols,
force ? " force" : ""));
if (screen->cursorp.col >= leftcol
&& screen->cursorp.col <= (leftcol + ncols - 1)
&& screen->cursorp.row >= ROW2INX(screen, toprow)
&& screen->cursorp.row <= ROW2INX(screen, maxrow))
screen->cursor_state = OFF;
for (row = toprow; row <= maxrow; y += FontHeight(screen), row++) {
#if OPT_ISO_COLORS
#if OPT_EXT_COLORS
Char *fbf = 0;
Char *fbb = 0;
#define ColorOf(col) (unsigned) ((fbf[col] << 8) | fbb[col])
#else
Char *fb = 0;
#define ColorOf(col) (unsigned) (fb[col])
#endif
#endif
#if OPT_DEC_CHRSET
Char *cb = 0;
#endif
#if OPT_WIDE_CHARS
int wideness = 0;
Char *widec = 0;
#define WIDEC_PTR(cell) widec ? &widec[cell] : 0
#define BLANK_CEL(cell) ((chars[cell] == ' ') && (widec == 0 || widec[cell] == 0))
#else
#define BLANK_CEL(cell) (chars[cell] == ' ')
#endif
Char cs = 0;
Char *chars;
Char *attrs;
int col = leftcol;
int maxcol = leftcol + ncols - 1;
int hi_col = maxcol;
int lastind;
unsigned flags;
unsigned test;
unsigned fg_bg = 0, fg = 0, bg = 0;
int x;
GC gc;
Bool hilite;
(void) fg;
(void) bg;
if (row < screen->top_marg || row > screen->bot_marg)
lastind = row;
else
lastind = row - scrollamt;
TRACE(("ScrnRefresh row=%d lastind=%d/%d\n", row, lastind, max));
if (lastind < 0 || lastind > max)
continue;
chars = SCRN_BUF_CHARS(screen, ROW2INX(screen, lastind));
attrs = SCRN_BUF_ATTRS(screen, ROW2INX(screen, lastind));
if_OPT_DEC_CHRSET({
cb = SCRN_BUF_CSETS(screen, ROW2INX(screen, lastind));
});
if_OPT_WIDE_CHARS(screen, {
widec = SCRN_BUF_WIDEC(screen, ROW2INX(screen, lastind));
});
if_OPT_WIDE_CHARS(screen, {
if (recurse < 3) {
if (leftcol > 0 &&
(PACK_PAIR(chars, widec, leftcol)) == HIDDEN_CHAR &&
iswide(PACK_PAIR(chars, widec, leftcol - 1))) {
leftcol--;
ncols++;
col = leftcol;
}
} else {
fprintf(stderr, "This should not happen. Why is it so?\n");
}
});
if (row < screen->startH.row || row > screen->endH.row ||
(row == screen->startH.row && maxcol < screen->startH.col) ||
(row == screen->endH.row && col >= screen->endH.col)) {
#if OPT_DEC_CHRSET
if (CSET_DOUBLE(*cb)) {
col /= 2;
maxcol /= 2;
}
#endif
if (!force) {
while (col <= maxcol && (attrs[col] & ~BOLD) == 0 &&
BLANK_CEL(col))
col++;
while (col <= maxcol && (attrs[maxcol] & ~BOLD) == 0 &&
BLANK_CEL(maxcol))
maxcol--;
}
#if OPT_DEC_CHRSET
if (CSET_DOUBLE(*cb)) {
col *= 2;
maxcol *= 2;
}
#endif
hilite = False;
} else {
if (row == screen->startH.row && col < screen->startH.col) {
recurse++;
ScrnRefresh(xw, row, col, 1, screen->startH.col - col,
force);
col = screen->startH.col;
}
if (row == screen->endH.row && maxcol >= screen->endH.col) {
recurse++;
ScrnRefresh(xw, row, screen->endH.col, 1,
maxcol - screen->endH.col + 1, force);
maxcol = screen->endH.col - 1;
}
if (screen->highlight_selection
&& screen->send_mouse_pos != VT200_HIGHLIGHT_MOUSE) {
hi_col = screen->max_col;
while (hi_col > 0 && !(attrs[hi_col] & CHARDRAWN))
hi_col--;
}
hilite = True;
}
if (col > maxcol)
continue;
if_OPT_DEC_CHRSET({
if (CSET_DOUBLE(*cb)) {
col /= 2;
maxcol /= 2;
}
cs = cb[col];
});
flags = attrs[col];
#if OPT_WIDE_CHARS
if (widec)
wideness = iswide(PACK_PAIR(chars, widec, col));
else
wideness = 0;
#endif
if_OPT_EXT_COLORS(screen, {
fbf = SCRN_BUF_FGRND(screen, ROW2INX(screen, lastind));
fbb = SCRN_BUF_BGRND(screen, ROW2INX(screen, lastind));
fg_bg = ColorOf(col);
fg = extract_fg(xw, fg_bg, flags);
bg = extract_bg(xw, fg_bg, flags);
});
if_OPT_ISO_TRADITIONAL_COLORS(screen, {
fb = SCRN_BUF_COLOR(screen, ROW2INX(screen, lastind));
fg_bg = ColorOf(col);
fg = extract_fg(xw, fg_bg, flags);
bg = extract_bg(xw, fg_bg, flags);
});
gc = updatedXtermGC(xw, flags, fg_bg, hilite);
gc_changes |= (flags & (FG_COLOR | BG_COLOR));
x = CurCursorX(screen, ROW2INX(screen, row), col);
lastind = col;
for (; col <= maxcol; col++) {
if ((attrs[col] != flags)
|| (hilite && (col > hi_col))
#if OPT_ISO_COLORS
|| ((flags & FG_COLOR)
&& (extract_fg(xw, ColorOf(col), attrs[col]) != fg))
|| ((flags & BG_COLOR)
&& (extract_bg(xw, ColorOf(col), attrs[col]) != bg))
#endif
#if OPT_WIDE_CHARS
|| (widec
&& ((iswide(PACK_PAIR(chars, widec, col))) != wideness)
&& !((PACK_PAIR(chars, widec, col)) == HIDDEN_CHAR))
#endif
#if OPT_DEC_CHRSET
|| (cb[col] != cs)
#endif
) {
assert(col >= lastind);
TRACE(("ScrnRefresh looping drawXtermText %d..%d:%s\n",
lastind, col,
visibleChars(PAIRED_CHARS(&chars[lastind],
WIDEC_PTR(lastind)),
(unsigned) (col - lastind))));
test = flags;
checkVeryBoldColors(test, fg);
x = drawXtermText(xw, test & DRAWX_MASK, gc, x, y,
cs,
PAIRED_CHARS(&chars[lastind], WIDEC_PTR(lastind)),
(unsigned) (col - lastind), 0);
if_OPT_WIDE_CHARS(screen, {
int i;
int off;
for (off = OFF_FINAL; off < MAX_PTRS; off += 2) {
Char *com_lo = BUFFER_PTR(screen->visbuf,
ROW2INX(screen, row),
off + 0);
Char *com_hi = BUFFER_PTR(screen->visbuf,
ROW2INX(screen, row),
off + 1);
for (i = lastind; i < col; i++) {
int my_x = CurCursorX(screen,
ROW2INX(screen, row),
i);
int base = PACK_PAIR(chars, widec, i);
int combo = PACK_PAIR(com_lo, com_hi, i);
if (iswide(base))
my_x = CurCursorX(screen,
ROW2INX(screen, row),
i - 1);
if (combo != 0)
drawXtermText(xw,
(test & DRAWX_MASK)
| NOBACKGROUND,
gc, my_x, y, cs,
PAIRED_CHARS(com_lo + i,
com_hi + i),
1, iswide(base));
}
}
});
resetXtermGC(xw, flags, hilite);
lastind = col;
if (hilite && (col > hi_col))
hilite = False;
flags = attrs[col];
if_OPT_EXT_COLORS(screen, {
fg_bg = ColorOf(col);
fg = extract_fg(xw, fg_bg, flags);
bg = extract_bg(xw, fg_bg, flags);
});
if_OPT_ISO_TRADITIONAL_COLORS(screen, {
fg_bg = ColorOf(col);
fg = extract_fg(xw, fg_bg, flags);
bg = extract_bg(xw, fg_bg, flags);
});
if_OPT_DEC_CHRSET({
cs = cb[col];
});
#if OPT_WIDE_CHARS
if (widec)
wideness = iswide(PACK_PAIR(chars, widec, col));
#endif
gc = updatedXtermGC(xw, flags, fg_bg, hilite);
gc_changes |= (flags & (FG_COLOR | BG_COLOR));
}
if (chars[col] == 0) {
#if OPT_WIDE_CHARS
if (widec == 0 || widec[col] == 0)
#endif
chars[col] = ' ';
}
}
assert(col >= lastind);
TRACE(("ScrnRefresh calling drawXtermText %d..%d:%s\n",
lastind, col,
visibleChars(PAIRED_CHARS(&chars[lastind], WIDEC_PTR(lastind)),
(unsigned) (col - lastind))));
test = flags;
checkVeryBoldColors(test, fg);
drawXtermText(xw, test & DRAWX_MASK, gc, x, y,
cs,
PAIRED_CHARS(&chars[lastind], WIDEC_PTR(lastind)),
(unsigned) (col - lastind), 0);
if_OPT_WIDE_CHARS(screen, {
int i;
int off;
for (off = OFF_FINAL; off < MAX_PTRS; off += 2) {
Char *com_lo = BUFFER_PTR(screen->visbuf,
ROW2INX(screen, row),
off + 0);
Char *com_hi = BUFFER_PTR(screen->visbuf,
ROW2INX(screen, row),
off + 1);
for (i = lastind; i < col; i++) {
int my_x = CurCursorX(screen,
ROW2INX(screen, row),
i);
int base = PACK_PAIR(chars, widec, i);
int combo = PACK_PAIR(com_lo, com_hi, i);
if (iswide(base))
my_x = CurCursorX(screen,
ROW2INX(screen, row),
i - 1);
if (combo != 0)
drawXtermText(xw,
(test & DRAWX_MASK)
| NOBACKGROUND,
gc, my_x, y, cs,
PAIRED_CHARS(com_lo + i,
com_hi + i),
1, iswide(base));
}
}
});
resetXtermGC(xw, flags, hilite);
}
if_OPT_ISO_COLORS(screen, {
if (gc_changes & FG_COLOR)
SGR_Foreground(xw, xw->cur_foreground);
if (gc_changes & BG_COLOR)
SGR_Background(xw, xw->cur_background);
});
#if defined(__CYGWIN__) && defined(TIOCSWINSZ)
if (first_time == 1) {
TTYSIZE_STRUCT ts;
first_time = 0;
TTYSIZE_ROWS(ts) = nrows;
TTYSIZE_COLS(ts) = ncols;
ts.ws_xpixel = xw->core.width;
ts.ws_ypixel = xw->core.height;
SET_TTYSIZE(screen->respond, ts);
}
#endif
recurse--;
}
void
ScrnUpdate(XtermWidget xw,
int toprow,
int leftcol,
int nrows,
int ncols,
Bool force)
{
TScreen *screen = &(xw->screen);
if (ScrnHaveSelection(screen)
&& (toprow <= screen->endH.row)
&& (toprow + nrows - 1 >= screen->startH.row)) {
ScrnDisownSelection(xw);
}
ScrnRefresh(xw, toprow, leftcol, nrows, ncols, force);
}
void
ClearBufRows(XtermWidget xw,
int first,
int last)
{
TScreen *screen = &(xw->screen);
unsigned len = MaxCols(screen);
int row;
TRACE(("ClearBufRows %d..%d\n", first, last));
for (row = first; row <= last; row++) {
if_OPT_DEC_CHRSET({
SCRN_ROW_CSET(screen, row) = CSET_SWL;
});
ScrnClrWrapped(screen, row);
ClearCells(xw, 0, len, row, 0);
}
}
int
ScreenResize(XtermWidget xw,
int width,
int height,
unsigned *flags)
{
TScreen *screen = &(xw->screen);
int code, rows, cols;
int border = 2 * screen->border;
int move_down_by = 0;
#ifdef TTYSIZE_STRUCT
TTYSIZE_STRUCT ts;
#endif
Window tw = VWindow(screen);
TRACE(("ScreenResize %dx%d border %d font %dx%d\n",
height, width, border,
FontHeight(screen), FontWidth(screen)));
assert(width > 0);
assert(height > 0);
if (screen->is_running) {
if (width >= FullWidth(screen)) {
XClearArea(screen->display, tw,
FullWidth(screen), 0,
0, (unsigned) height,
False);
}
if (height >= FullHeight(screen)) {
XClearArea(screen->display, tw,
0, FullHeight(screen),
(unsigned) width, 0,
False);
}
}
TRACE(("...computing rows/cols: %.2f %.2f\n",
(double) (height - border) / FontHeight(screen),
(double) (width - border - ScrollbarWidth(screen)) / FontWidth(screen)));
rows = (height - border) / FontHeight(screen);
cols = (width - border - ScrollbarWidth(screen)) / FontWidth(screen);
if (rows < 1)
rows = 1;
if (cols < 1)
cols = 1;
if (MaxRows(screen) != rows || MaxCols(screen) != cols) {
int savelines = (screen->scrollWidget
? screen->savelines
: 0);
int delta_rows = rows - MaxRows(screen);
TRACE(("...ScreenResize chars %dx%d\n", rows, cols));
if (screen->is_running) {
if (screen->cursor_state)
HideCursor();
if (screen->alternate
&& xw->misc.resizeGravity == SouthWestGravity)
SwitchBufPtrs(screen);
if (screen->altbuf)
(void) Reallocate(xw,
&screen->altbuf,
&screen->abuf_address,
rows,
cols,
MaxRows(screen),
MaxCols(screen));
move_down_by = Reallocate(xw,
&screen->allbuf,
&screen->sbuf_address,
rows + savelines, cols,
MaxRows(screen) + savelines,
MaxCols(screen));
screen->visbuf = &screen->allbuf[MAX_PTRS * savelines];
}
AdjustSavedCursor(xw, move_down_by);
set_max_row(screen, screen->max_row + delta_rows);
set_max_col(screen, cols - 1);
if (screen->is_running) {
if (xw->misc.resizeGravity == SouthWestGravity) {
screen->savedlines -= move_down_by;
if (screen->savedlines < 0)
screen->savedlines = 0;
if (screen->savedlines > screen->savelines)
screen->savedlines = screen->savelines;
if (screen->topline < -screen->savedlines)
screen->topline = -screen->savedlines;
set_cur_row(screen, screen->cur_row + move_down_by);
screen->cursorp.row += move_down_by;
ScrollSelection(screen, move_down_by, True);
if (screen->alternate)
SwitchBufPtrs(screen);
}
}
set_tb_margins(screen, 0, screen->max_row);
*flags &= ~ORIGIN;
if (screen->cur_row > screen->max_row)
set_cur_row(screen, screen->max_row);
if (screen->cur_col > screen->max_col)
set_cur_col(screen, screen->max_col);
screen->fullVwin.height = height - border;
screen->fullVwin.width = width - border - screen->fullVwin.sb_info.width;
} else if (FullHeight(screen) == height && FullWidth(screen) == width)
return (0);
screen->fullVwin.fullheight = height;
screen->fullVwin.fullwidth = width;
ResizeScrollBar(xw);
ResizeSelection(screen, rows, cols);
#ifndef NO_ACTIVE_ICON
if (screen->iconVwin.window) {
XWindowChanges changes;
screen->iconVwin.width =
MaxCols(screen) * screen->iconVwin.f_width;
screen->iconVwin.height =
MaxRows(screen) * screen->iconVwin.f_height;
changes.width = screen->iconVwin.fullwidth =
screen->iconVwin.width + 2 * xw->misc.icon_border_width;
changes.height = screen->iconVwin.fullheight =
screen->iconVwin.height + 2 * xw->misc.icon_border_width;
changes.border_width = xw->misc.icon_border_width;
TRACE(("resizing icon window %dx%d\n", changes.height, changes.width));
XConfigureWindow(XtDisplay(xw), screen->iconVwin.window,
CWWidth | CWHeight | CWBorderWidth, &changes);
}
#endif
#ifdef TTYSIZE_STRUCT
TTYSIZE_ROWS(ts) = rows;
TTYSIZE_COLS(ts) = cols;
#ifdef USE_STRUCT_WINSIZE
ts.ws_xpixel = width;
ts.ws_ypixel = height;
#endif
code = SET_TTYSIZE(screen->respond, ts);
TRACE(("return %d from SET_TTYSIZE %dx%d\n", code, rows, cols));
(void) code;
#if defined(SIGWINCH) && defined(USE_STRUCT_TTYSIZE)
if (screen->pid > 1) {
int pgrp;
TRACE(("getting process-group\n"));
if (ioctl(screen->respond, TIOCGPGRP, &pgrp) != -1) {
TRACE(("sending SIGWINCH to process group %d\n", pgrp));
kill_process_group(pgrp, SIGWINCH);
}
}
#endif
#else
TRACE(("ScreenResize cannot do anything to pty\n"));
#endif
return (0);
}
Bool
non_blank_line(TScreen * screen,
int row,
int col,
int len)
{
ScrnBuf sb = screen->visbuf;
int i;
Char *ptr = BUF_CHARS(sb, row);
for (i = col; i < len; i++) {
if (ptr[i])
return True;
}
if_OPT_WIDE_CHARS(screen, {
if ((ptr = BUF_WIDEC(sb, row)) != 0) {
for (i = col; i < len; i++) {
if (ptr[i])
return True;
}
}
});
return False;
}
#define minRectRow(screen) (getMinRow(screen) + 1)
#define minRectCol(screen) (getMinCol(screen) + 1)
#define maxRectRow(screen) (getMaxRow(screen) + 1)
#define maxRectCol(screen) (getMaxCol(screen) + 1)
static int
limitedParseRow(XtermWidget xw, TScreen * screen, int row)
{
int min_row = minRectRow(screen);
int max_row = maxRectRow(screen);
if (row < min_row)
row = min_row;
else if (row > max_row)
row = max_row;
return row;
}
static int
limitedParseCol(XtermWidget xw, TScreen * screen, int col)
{
int min_col = minRectCol(screen);
int max_col = maxRectCol(screen);
(void) xw;
if (col < min_col)
col = min_col;
else if (col > max_col)
col = max_col;
return col;
}
#define LimitedParse(num, func, dft) \
func(xw, screen, (nparams > num) ? params[num] : dft)
void
xtermParseRect(XtermWidget xw, int nparams, int *params, XTermRect * target)
{
TScreen *screen = &(xw->screen);
memset(target, 0, sizeof(*target));
target->top = LimitedParse(0, limitedParseRow, minRectRow(screen));
target->left = LimitedParse(1, limitedParseCol, minRectCol(screen));
target->bottom = LimitedParse(2, limitedParseRow, maxRectRow(screen));
target->right = LimitedParse(3, limitedParseCol, maxRectCol(screen));
TRACE(("parsed rectangle %d,%d %d,%d\n",
target->top,
target->left,
target->bottom,
target->right));
}
static Bool
validRect(XtermWidget xw, XTermRect * target)
{
TScreen *screen = &(xw->screen);
TRACE(("comparing against screensize %dx%d\n",
maxRectRow(screen),
maxRectCol(screen)));
return (target != 0
&& target->top >= minRectRow(screen)
&& target->left >= minRectCol(screen)
&& target->top <= target->bottom
&& target->left <= target->right
&& target->top <= maxRectRow(screen)
&& target->right <= maxRectCol(screen));
}
void
ScrnFillRectangle(XtermWidget xw,
XTermRect * target,
int value,
unsigned flags,
Bool keepColors)
{
TScreen *screen = &(xw->screen);
TRACE(("filling rectangle with '%c' flags %#x\n", value, flags));
if (validRect(xw, target)) {
unsigned left = target->left - 1;
unsigned size = target->right - left;
Char attrs = flags;
int row, col;
attrs &= ATTRIBUTES;
attrs |= CHARDRAWN;
for (row = target->bottom - 1; row >= (target->top - 1); row--) {
TRACE(("filling %d [%d..%d]\n", row, left, left + size));
for (col = left; col < target->right; ++col) {
Char temp = SCRN_BUF_ATTRS(screen, row)[col];
if (!keepColors) {
temp &= ~(FG_COLOR | BG_COLOR);
}
temp = attrs | (temp & (FG_COLOR | BG_COLOR | PROTECTED));
temp |= CHARDRAWN;
SCRN_BUF_ATTRS(screen, row)[col] = temp;
#if OPT_ISO_COLORS
if (attrs & (FG_COLOR | BG_COLOR)) {
if_OPT_EXT_COLORS(screen, {
SCRN_BUF_FGRND(screen, row)[col] = xw->sgr_foreground;
SCRN_BUF_BGRND(screen, row)[col] = xw->cur_background;
});
if_OPT_ISO_TRADITIONAL_COLORS(screen, {
SCRN_BUF_COLOR(screen, row)[col] = xtermColorPair(xw);
});
}
#endif
}
memset(SCRN_BUF_CHARS(screen, row) + left, (Char) value, size);
if_OPT_WIDE_CHARS(screen, {
int off;
for (off = OFF_WIDEC; off < MAX_PTRS; ++off) {
memset(SCREEN_PTR(screen, row, off) + left, 0, size);
}
})
}
ScrnUpdate(xw,
target->top - 1,
target->left - 1,
(target->bottom - target->top) + 1,
(target->right - target->left) + 1,
False);
}
}
#if OPT_DEC_RECTOPS
void
ScrnCopyRectangle(XtermWidget xw, XTermRect * source, int nparam, int *params)
{
TScreen *screen = &(xw->screen);
TRACE(("copying rectangle\n"));
if (validRect(xw, source)) {
XTermRect target;
xtermParseRect(xw,
((nparam > 3) ? 2 : (nparam - 1)),
params,
&target);
if (validRect(xw, &target)) {
unsigned high = (source->bottom - source->top) + 1;
unsigned wide = (source->right - source->left) + 1;
unsigned size = (high * wide * MAX_PTRS);
int row, col, n, j;
Char *cells = TypeMallocN(Char, size);
if (cells == 0)
return;
TRACE(("OK - make copy %dx%d\n", high, wide));
target.bottom = target.top + (high - 1);
target.right = target.left + (wide - 1);
for (row = source->top - 1; row < source->bottom; ++row) {
for (col = source->left - 1; col < source->right; ++col) {
n = (((1 + row - source->top) * wide)
+ (1 + col - source->left)) * MAX_PTRS;
for (j = OFF_ATTRS; j < MAX_PTRS; ++j)
cells[n + j] = SCREEN_PTR(screen, row, j)[col];
}
}
for (row = target.top - 1; row < target.bottom; ++row) {
for (col = target.left - 1; col < target.right; ++col) {
if (row >= getMinRow(screen)
&& row <= getMaxRow(screen)
&& col >= getMinCol(screen)
&& col <= getMaxCol(screen)) {
n = (((1 + row - target.top) * wide)
+ (1 + col - target.left)) * MAX_PTRS;
for (j = OFF_ATTRS; j < MAX_PTRS; ++j)
SCREEN_PTR(screen, row, j)[col] = cells[n + j];
SCRN_BUF_ATTRS(screen, row)[col] |= CHARDRAWN;
}
}
}
free(cells);
ScrnUpdate(xw,
(target.top - 1),
(target.left - 1),
(target.bottom - target.top) + 1,
((target.right - target.left) + 1),
False);
}
}
}
void
ScrnMarkRectangle(XtermWidget xw,
XTermRect * target,
Bool reverse,
int nparam,
int *params)
{
TScreen *screen = &(xw->screen);
Bool exact = (screen->cur_decsace == 2);
TRACE(("%s %s\n",
reverse ? "reversing" : "marking",
(exact
? "rectangle"
: "region")));
if (validRect(xw, target)) {
int top = target->top - 1;
int bottom = target->bottom - 1;
int row, col;
int n;
for (row = top; row <= bottom; ++row) {
int left = ((exact || (row == top))
? (target->left - 1)
: getMinCol(screen));
int right = ((exact || (row == bottom))
? (target->right - 1)
: getMaxCol(screen));
TRACE(("marking %d [%d..%d]\n", row, left, right));
for (col = left; col <= right; ++col) {
unsigned flags = SCRN_BUF_ATTRS(screen, row)[col];
for (n = 0; n < nparam; ++n) {
#if OPT_TRACE
if (row == top && col == left)
TRACE(("attr param[%d] %d\n", n + 1, params[n]));
#endif
if (reverse) {
switch (params[n]) {
case 1:
flags ^= BOLD;
break;
case 4:
flags ^= UNDERLINE;
break;
case 5:
flags ^= BLINK;
break;
case 7:
flags ^= INVERSE;
break;
case 8:
flags ^= INVISIBLE;
break;
}
} else {
switch (params[n]) {
case 0:
flags &= ~SGR_MASK;
break;
case 1:
flags |= BOLD;
break;
case 4:
flags |= UNDERLINE;
break;
case 5:
flags |= BLINK;
break;
case 7:
flags |= INVERSE;
break;
case 8:
flags |= INVISIBLE;
break;
case 22:
flags &= ~BOLD;
break;
case 24:
flags &= ~UNDERLINE;
break;
case 25:
flags &= ~BLINK;
break;
case 27:
flags &= ~INVERSE;
break;
case 28:
flags &= ~INVISIBLE;
break;
}
}
}
#if OPT_TRACE
if (row == top && col == left)
TRACE(("first mask-change is %#x\n",
SCRN_BUF_ATTRS(screen, row)[col] ^ flags));
#endif
SCRN_BUF_ATTRS(screen, row)[col] = flags;
}
}
ScrnRefresh(xw,
(target->top - 1),
(exact ? (target->left - 1) : getMinCol(screen)),
(target->bottom - target->top) + 1,
(exact
? ((target->right - target->left) + 1)
: (getMaxCol(screen) - getMinCol(screen) + 1)),
False);
}
}
void
ScrnWipeRectangle(XtermWidget xw,
XTermRect * target)
{
TScreen *screen = &(xw->screen);
TRACE(("wiping rectangle\n"));
if (validRect(xw, target)) {
int top = target->top - 1;
int bottom = target->bottom - 1;
int row, col;
for (row = top; row <= bottom; ++row) {
int left = (target->left - 1);
int right = (target->right - 1);
TRACE(("wiping %d [%d..%d]\n", row, left, right));
for (col = left; col <= right; ++col) {
if (!((screen->protected_mode == DEC_PROTECT)
&& (SCRN_BUF_ATTRS(screen, row)[col] & PROTECTED))) {
SCRN_BUF_ATTRS(screen, row)[col] |= CHARDRAWN;
SCRN_BUF_CHARS(screen, row)[col] = ' ';
if_OPT_WIDE_CHARS(screen, {
int off;
for (off = OFF_WIDEC; off < MAX_PTRS; ++off) {
memset(SCREEN_PTR(screen, row, off) + col, 0, 1);
}
})
}
}
}
ScrnUpdate(xw,
(target->top - 1),
(target->left - 1),
(target->bottom - target->top) + 1,
((target->right - target->left) + 1),
False);
}
}
#endif