/* Part of XPCE --- The SWI-Prolog GUI toolkit Author: Jan Wielemaker and Anjo Anjewierden E-mail: jan@swi.psy.uva.nl WWW: http://www.swi.psy.uva.nl/projects/xpce/ Copyright (c) 1985-2002, University of Amsterdam All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include static status initOffsetText(TextObj, int); static status initPositionText(TextObj); static status initAreaText(TextObj); static status recomputeText(TextObj t, Name what); static status get_char_pos_text(TextObj t, Int chr, int *X, int *Y); static status caretText(TextObj t, Int where); static status prepareEditText(TextObj t, Name selector); #define Wrapped(t) ((t)->wrap == NAME_wrap || \ (t)->wrap == NAME_wrapFixedWidth) #define Before(x, y) if ( valInt(x) > valInt(y) ) { Int _z = x; x=y; y=_z; } #define Before_i(x, y) if ( x > y ) { intptr_t _z = x; x=y; y=_z; } #define MakeSel(f, t) toInt(((valInt(t) & 0xffff) << 16) | \ (valInt(f) & 0xffff)) #define GetSel(s,f,t) { *(t) = ((valInt(s) >> 16) & 0xffff); \ *(f) = (valInt(s) & 0xffff); \ } /******************************** * CREATE * ********************************/ static status initialiseText(TextObj t, CharArray string, Name format, FontObj font) { if ( isDefault(string) ) string = CtoCharArray(""); initialiseGraphical(t, ZERO, ZERO, ZERO, ZERO); if ( notDefault(format) ) assign(t, format, format); if ( notDefault(font) ) assign(t, font, font); assign(t, underline, OFF); assign(t, string, string); assign(t, margin, toInt(100)); assign(t, wrap, NAME_extend); assign(t, position, newObject(ClassPoint, EAV)); assign(t, caret, getSizeCharArray(string)); assign(t, show_caret, OFF); assign(t, background, NIL); assign(t, x_offset, ZERO); assign(t, x_caret, ZERO); assign(t, y_caret, ZERO); assign(t, selection, NIL); return recomputeText(t, NAME_position); } /******************************** * COMPUTE * ********************************/ static status computeText(TextObj t) { if ( notNil(t->request_compute) ) { obtainClassVariablesObject(t); CHANGING_GRAPHICAL(t, if ( t->request_compute == NAME_position ) initPositionText(t); else if ( t->request_compute == NAME_area ) initAreaText(t); changedEntireImageGraphical(t)); assign(t, request_compute, NIL); } succeed; } static status recomputeText(TextObj t, Name what) { if ( notNil(t->selection) ) /* normalise the selection */ { int from, to; int size = t->string->data.s_size; GetSel(t->selection, &from, &to); if ( from > size || to > size ) { if ( from > size ) from = size; if ( from > size ) to = size; assign(t, selection, MakeSel(toInt(from), toInt(to))); } } if ( notNil(t->request_compute) && t->request_compute != what ) computeText(t); return requestComputeGraphical(t, what); } /******************************** * REDRAW * ********************************/ static status RedrawAreaText(TextObj t, Area a) { int x, y, w, h; initialiseDeviceGraphical(t, &x, &y, &w, &h); repaintText(t, x, y, w, h); if ( t->pen != ZERO ) { r_thickness(valInt(t->pen)); r_dash(t->texture); r_box(x, y, w, h, 0, NIL); } return RedrawAreaGraphical(t, a); } void str_format(PceString out, const PceString in, const int width, const FontObj font) { int x = 0; int last_is_layout = TRUE; if ( isstrA(in) ) /* 8-bit string */ { charA *s = in->s_textA; charA *e = &s[in->s_size]; charA *o = out->s_textA; charA *lb = NULL; /* last-break; */ for(;; s++) { *o++ = *s; if ( s == e ) { int n = o - out->s_textA - 1; assert(n <= out->s_size); out->s_size = n; return; } if ( !last_is_layout && isspace(*s) ) lb = o-1; last_is_layout = isspace(*s); if ( *s == '\n' ) x = 0; else x += c_width(*s, font); if ( x > width ) { if ( lb ) { o = lb; s = in->s_textA + (lb-out->s_textA); while( isspace(s[1]) ) s++, o++; /* map (*) --> \1\n */ *o++ = '\n'; lb = NULL; x = 0; } } } } else /* 16-bit string */ { charW *s = in->s_textW; charW *e = &s[in->s_size]; charW *o = out->s_textW; charW *lb = NULL; /* last-break; */ for(;; s++) { *o++ = *s; if ( s == e ) { out->s_size = o - out->s_textW - 1; return; } if ( !last_is_layout && iswspace(*s) ) lb = o-1; last_is_layout = iswspace(*s); if ( *s == '\n' ) x = 0; else x += c_width(*s, font); if ( x > width ) { if ( lb ) { o = lb; s = in->s_textW + (lb-out->s_textW); while( iswspace(s[1]) ) s++, o++; /* map (*) --> \1\n */ *o++ = '\n'; lb = NULL; x = 0; } } } } } void str_one_line(PceString to, PceString from) { int n; for(n=0; ns_size; n++) { unsigned int c = str_fetch(from, n); if ( c == '\n' ) c = 0xb6; /* Paragraph sign */ else if ( c == '\t' ) c = 0xbb; /* >> */ else if ( c == '\r' ) c = 0xab; /* << */ str_store(to, n, c); } to->s_size = from->s_size; } static void draw_caret(int x, int y, int w, int h, int active) { if ( active ) { int cx = x + w/2; r_fillpattern(BLACK_IMAGE, NAME_foreground); r_fill_triangle(cx, y, x, y+h, x+w, y+h); } else { ipoint pts[4]; int cx = x + w/2; int cy = y + h/2; int i = 0; pts[i].x = cx; pts[i].y = y; i++; pts[i].x = x; pts[i].y = cy; i++; pts[i].x = cx; pts[i].y = y+h; i++; pts[i].x = x+w; pts[i].y = cy; i++; r_fillpattern(GREY50_IMAGE, NAME_foreground); r_fill_polygon(pts, i); } } #ifndef OL_CURSOR_SIZE #define OL_CURSOR_SIZE 9 #endif status repaintText(TextObj t, int x, int y, int w, int h) { PceString s = &t->string->data; int b = valInt(t->border); int sf = 0, st = 0; int flags = 0; Style style = NIL; if ( notNil(t->background) ) { if ( isDefault(t->background) ) r_clear(x, y, w, h); else r_fill(x, y, w, h, t->background); } if ( t->underline == ON ) flags |= TXT_UNDERLINED; x += b; y += b; w -= 2*b; h -= 2*b; if ( t->wrap == NAME_clip ) d_clip(x, y, w, h); if ( notNil(t->selection) ) { GetSel(t->selection, &sf, &st); style = getClassVariableValueObject(t, NAME_selectionStyle); } if ( Wrapped(t) ) { LocalString(buf, s->s_iswide, s->s_size+1); DEBUG(NAME_text, Cprintf("RedrawAreaText(%s): \"%s\"\n", pp(t), s->s_textA)); str_format(buf, s, valInt(t->margin), t->font); if ( notNil(t->selection) ) str_selected_string(buf, t->font, sf, st, style, x+valInt(t->x_offset), y, w, h, t->format, NAME_top); else str_string(buf, t->font, x+valInt(t->x_offset), y, w, h, t->format, NAME_top, flags); } else { if ( t->wrap == NAME_clip ) { LocalString(buf, s->s_iswide, s->s_size+1); str_one_line(buf, s); if ( notNil(t->selection) ) { str_selected_string(buf, t->font, sf, st, style, x+valInt(t->x_offset), y, w, h, t->format, NAME_top); } else { str_string(buf, t->font, x+valInt(t->x_offset), y, w, h, t->format, NAME_top, flags); } } else { if ( notNil(t->selection) ) { str_selected_string(s, t->font, sf, st, style, x+valInt(t->x_offset), y, w, h, t->format, NAME_top); } else { str_string(s, t->font, x+valInt(t->x_offset), y, w, h, t->format, NAME_top, flags); } } } if ( t->wrap == NAME_clip ) d_clip_done(); if ( t->show_caret != OFF ) { int fh = valInt(getAscentFont(t->font)); int active = (t->show_caret == ON); Any colour = getClassVariableValueClass(ClassTextCursor, active ? NAME_colour : NAME_inactiveColour); Any old = r_colour(colour); draw_caret(valInt(t->x_caret) - OL_CURSOR_SIZE/2 + x - b, valInt(t->y_caret) + y + fh - b - 3, OL_CURSOR_SIZE, OL_CURSOR_SIZE, active); r_colour(old); } succeed; } /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - To avoid texts moving on type actions a position is maintained. If the string changes the area is recomputed relative to this position argument. If the text gets a `change area' request it will recompute the position attribute to be used in subsequent string changes. initAreaText() recomputes the area from the position after the string or font has been changed. initPositionText() recomputes the position from the area after the area has been changed from outside. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ static status initAreaText(TextObj t) { int tw, x, y, w, h; Point pos = t->position; PceString s = &t->string->data; int size = s->s_size; int b = valInt(t->border); if ( valInt(t->caret) < 0 ) assign(t, caret, ZERO); if ( valInt(t->caret) > size ) assign(t, caret, toInt(size)); if ( Wrapped(t) ) { LocalString(buf, s->s_iswide, s->s_size + MAX_WRAP_LINES); str_format(buf, s, valInt(t->margin), t->font); str_size(buf, t->font, &tw, &h); if ( t->wrap == NAME_wrapFixedWidth && tw < valInt(t->margin) ) tw = valInt(t->margin); } else { if ( t->wrap == NAME_clip ) { LocalString(buf, s->s_iswide, s->s_size + 1); str_one_line(buf, s); str_size(buf, t->font, &tw, &h); } else { str_size(s, t->font, &tw, &h); } } if ( t->wrap == NAME_clip ) w = valInt(t->area->w) - 2*b; else w = tw; if ( t->format == NAME_right ) { x = valInt(pos->x) - w; y = valInt(pos->y); } else if ( t->format == NAME_center ) { x = valInt(pos->x) - w/2; y = valInt(pos->y) - h/2; } else { x = valInt(pos->x); y = valInt(pos->y); } x -= b; y -= b; w += 2*b; h+= 2*b; assign(t->area, x, toInt(x)); assign(t->area, y, toInt(y)); assign(t->area, w, toInt(w)); assign(t->area, h, toInt(h)); return initOffsetText(t, tw); } static status initPositionText(TextObj t) { int tw, x, y, w, h; Point pos = t->position; PceString s = &t->string->data; int b = valInt(t->border); if ( Wrapped(t) ) { LocalString(buf, s->s_iswide, s->s_size + MAX_WRAP_LINES); str_format(buf, s, valInt(t->margin), t->font); str_size(buf, t->font, &tw, &h); if ( t->wrap == NAME_wrapFixedWidth && tw < valInt(t->margin) ) tw = valInt(t->margin); } else { if ( t->wrap == NAME_clip ) { LocalString(buf, s->s_iswide, s->s_size + 1); str_one_line(buf, s); str_size(buf, t->font, &tw, &h); } else { str_size(s, t->font, &tw, &h); } } if ( t->wrap == NAME_clip ) w = valInt(t->area->w) - 2*b; else w = tw; if ( equalName(t->format, NAME_left) ) { x = valInt(t->area->x); y = valInt(t->area->y) + b; } else if ( equalName(t->format, NAME_right) ) { x = valInt(t->area->x) + w; y = valInt(t->area->y) + b; } else { x = valInt(t->area->x) + w/2; y = valInt(t->area->y) + h/2; } x += b; y += b; /* was missing? */ w += 2*b; h += 2*b; assign(pos, x, toInt(x)); assign(pos, y, toInt(y)); assign(t->area, w, toInt(w)); assign(t->area, h, toInt(h)); return initOffsetText(t, tw); } static int initOffsetText(TextObj t, int tw) { if ( t->wrap != NAME_clip ) { int x, y; assign(t, x_offset, ZERO); get_char_pos_text(t, DEFAULT, &x, &y); assign(t, x_caret, toInt(x)); assign(t, y_caret, toInt(y)); } else { int x, y, w = valInt(t->area->w) - valInt(t->border), shift; int xoff; if ( tw <= w || t->caret == ZERO ) assign(t, x_offset, ZERO); else if ( t->caret == getSizeCharArray(t->string) ) assign(t, x_offset, toInt(w - tw)); xoff = valInt(t->x_offset); get_char_pos_text(t, DEFAULT, &x, &y); if ( x >= w ) shift = w - x; else if ( x < 0 ) shift = - x; else shift = 0; if ( shift ) { xoff += shift; x += shift; assign(t, x_offset, toInt(xoff)); } assign(t, x_caret, toInt(x)); assign(t, y_caret, toInt(y)); } succeed; } static status resizeText(TextObj t, Real xfactor, Real yfactor, Point origin) { float xf, yf; int ox = valInt(t->position->x); int oy = valInt(t->position->y); int nx, ny; init_resize_graphical(t, xfactor, yfactor, origin, &xf, &yf, &ox, &oy); if ( xf == 1.0 && yf == 1.0 ) succeed; nx = ox + rfloat((float) (valInt(t->position->x)-ox) * xf); ny = oy + rfloat((float) (valInt(t->position->y)-oy) * yf); assign(t->position, x, toInt(nx)); assign(t->position, y, toInt(ny)); return recomputeText(t, NAME_area); } /* Determine the position of a character in pixels. It returns the coordinate of the upper left corner of the character. By default the position of the caret is returned. ** Tue Nov 8 17:24:58 1988 jan@swivax.UUCP (Jan Wielemaker) */ static Point getCharacterPositionText(TextObj t, Int chr) { int x, y; get_char_pos_text(t, chr, &x, &y); answer(answerObject(ClassPoint, toInt(x), toInt(y), EAV)); } static void get_char_pos_helper(TextObj t, PceString s, int caret, int *cx, int *cy) { int b = valInt(t->border); int ch = valInt(getHeightFont(t->font)); int w = abs((int)valInt(t->area->w)); int lw, sl; if ( (sl = str_next_rindex(s, caret-1, '\n')) < 0 ) { sl = 0; } else { sl++; *cy += (str_lineno(s, sl)-1) * ch; } lw = str_width(s, sl, caret, t->font); w -= 2 * b; if ( t->format == NAME_left ) { *cx = lw; } else { int el; int rw; if ( (el = str_next_index(s, caret, '\n')) < 0 ) el = s->s_size; rw = str_width(s, caret, el, t->font); if ( t->format == NAME_center ) *cx = w/2 - (lw+rw)/2 + lw; else /* right */ *cx = w - rw; } } static status get_char_pos_text(TextObj t, Int chr, int *X, int *Y) { int caret = (isDefault(chr) ? valInt(t->caret) : valInt(chr)); int cx = 0; /* clang doesn't see this is not needed */ int cy = 0; PceString s = &t->string->data; int b = valInt(t->border); if ( Wrapped(t) ) { LocalString(buf, s->s_iswide, Wrapped(t) ? s->s_size + MAX_WRAP_LINES : 0); str_format(buf, s, valInt(t->margin), t->font); get_char_pos_helper(t, s, caret, &cx, &cy); } else if ( t->wrap == NAME_clip ) { LocalString(buf, s->s_iswide, s->s_size + 1); str_one_line(buf, s); get_char_pos_helper(t, s, caret, &cx, &cy); } *X = cx + valInt(t->x_offset) + b; *Y = cy + b; succeed; } Int get_pointed_text(TextObj t, int x, int y) { PceString s = &t->string->data; int ch = valInt(getHeightFont(t->font)); int b = valInt(t->border); int cw, w; int caret = 0, el; int line = (y-b) / ch; /* line for caret */ string buf; if ( s->s_size == 0 ) answer(ZERO); x -= b; if ( Wrapped(t) ) { str_init(&buf, s, alloca(str_allocsize(s))); str_format(&buf, s, valInt(t->margin), t->font); s = &buf; } /* Find the start of the line pointed at by pos. */ while(line-- > 0) { int c2; if ( (c2 = str_next_index(s, caret, '\n')) < 0 ) break; caret = c2+1; } if ( caret > s->s_size ) caret = s->s_size; if ( (el = str_next_index(s, caret, '\n')) < 0 ) el = s->s_size; /* caret = start of line, el = end of line */ if ( t->format == NAME_left ) w = 0; else { int lw = str_width(s, caret, el, t->font); if ( t->format == NAME_center ) w = (valInt(t->area->w) - lw)/2 - b; else w = valInt(t->area->w) - lw - 2*b; } w += valInt(t->x_offset); if ( caret < el-1 ) { for( cw = c_width(str_fetch(s, caret), t->font); x > w + cw/2; caret++, w += cw, cw = c_width(str_fetch(s, caret), t->font) ) { if ( caret >= el ) break; } } answer(toInt(caret)); } static Int getPointedText(TextObj t, Point pos) { int x = valInt(pos->x); int y = valInt(pos->y); return get_pointed_text(t, x, y); } /******************************** * ATTRIBUTES * ********************************/ static status backgroundText(TextObj t, Any bg) { if ( t->background != bg) { CHANGING_GRAPHICAL(t, assign(t, background, bg); changedEntireImageGraphical(t)); } succeed; } static status underlineText(TextObj t, BoolObj underline) { if ( t->underline != underline ) { CHANGING_GRAPHICAL(t, assign(t, underline, underline); changedEntireImageGraphical(t)); } succeed; } status transparentText(TextObj t, BoolObj val) { Any bg = (val == ON ? NIL : DEFAULT); return backgroundText(t, bg); } static BoolObj getTransparentText(TextObj t) { answer(isNil(t->background) ? ON : OFF); } status fontText(TextObj t, FontObj font) { if (t->font != font) { assign(t, font, font); recomputeText(t, NAME_area); } succeed; } static status formatText(TextObj t, Name format) { if (t->format != format) { assign(t, format, format); recomputeText(t, NAME_position); } succeed; } status borderText(TextObj t, Int border) { if (t->border != border) { assign(t, border, border); recomputeText(t, NAME_area); } succeed; } status stringText(TextObj t, CharArray s) { if ( t->string != s ) { prepareEditText(t, DEFAULT); valueString((StringObj) t->string, s); caretText(t, DEFAULT); recomputeText(t, NAME_area); } succeed; } status showCaretText(TextObj t, Any val) { if ( t->show_caret == val ) succeed; CHANGING_GRAPHICAL(t, assign(t, show_caret, val); changedEntireImageGraphical(t)); succeed; } /******************************* * SELECTION * *******************************/ static status selectionText(TextObj t, Int from, Int to) { int changed = FALSE; if ( from == to ) from = NIL; if ( isNil(from) ) { if ( notNil(t->selection) ) { assign(t, selection, NIL); changed++; } } else { int ofrom, oto; Int new; if ( notNil(t->selection) ) { GetSel(t->selection, &ofrom, &oto); } else ofrom = oto = 0; if ( isDefault(from) ) from = toInt(ofrom); if ( isDefault(to) ) to = toInt(oto); Before(from, to); new = MakeSel(from, to); if ( new != t->selection ) { assign(t, selection, MakeSel(from, to)); changed++; } } if ( changed ) changedEntireImageGraphical(t); succeed; } static Point getSelectionText(TextObj t) { if ( notNil(t->selection) ) { int from, to; GetSel(t->selection, &from, &to); answer(answerObject(ClassPoint, toInt(from), toInt(to), EAV)); } fail; } static StringObj getSelectedTextText(TextObj t) { if ( notNil(t->selection) ) { int from, to; GetSel(t->selection, &from, &to); answer(getSubString((StringObj)t->string, toInt(from), toInt(to))); } fail; } static status copyText(TextObj t) { StringObj s = getSelectedTextText(t); DisplayObj d = getDisplayGraphical((Graphical)t); if ( !d ) { if ( instanceOfObject(EVENT->value, ClassEvent) ) d = getDisplayEvent(EVENT->value); } if ( s && d ) return send(d, NAME_copy, s, EAV); fail; } static status deleteSelectionText(TextObj t) { if ( notNil(t->selection) ) { int from, to; GetSel(t->selection, &from, &to); prepareEditText(t, DEFAULT); deleteString((StringObj)t->string, toInt(from), toInt(to-from)); assign(t, selection, NIL); if ( valInt(t->caret) > from ) caretText(t, toInt(from)); recomputeText(t, NAME_area); } succeed; } static status cutText(TextObj t) { if ( send(t, NAME_copy, EAV) ) { int from, to; GetSel(t->selection, &from, &to); return deleteSelectionText(t); } fail; } /******************************* * GEOMETRY * *******************************/ static status geometryText(TextObj t, Int x, Int y, Int w, Int h) { Int ox = t->area->x; Int oy = t->area->y; Point p = t->position; Area a = t->area; if ( Wrapped(t) && notDefault(w) ) { assign(t, margin, w); CHANGING_GRAPHICAL(t, initAreaText(t); setArea(t->area, x, y, DEFAULT, DEFAULT)); } else { if ( t->wrap != NAME_clip ) w = (Int) DEFAULT; geometryGraphical(t, x, y, w, DEFAULT); } assign(p, x, toInt(valInt(p->x) + valInt(a->x) - valInt(ox))); assign(p, y, toInt(valInt(p->y) + valInt(a->y) - valInt(oy))); if ( notDefault(w) ) { int tw, h; if ( isDefault(t->font) ) obtainClassVariablesObject(t); /* resolve the font */ str_size(&t->string->data, t->font, &tw, &h); initOffsetText(t, tw); } succeed; } static status updateShowCaretText(TextObj t) { if ( t->show_caret != OFF ) { PceWindow sw = getWindowGraphical((Graphical)t); int active = (sw && sw->input_focus == ON); showCaretText(t, active ? (Any)ON : (Any)NAME_passive); } succeed; } static status eventText(TextObj t, EventObj ev) { if ( eventGraphical(t, ev) ) succeed; if ( isAEvent(ev, NAME_focus) ) { if ( isAEvent(ev, NAME_obtainKeyboardFocus) ) showCaretText(t, ON); else if ( isAEvent(ev, NAME_releaseKeyboardFocus) ) showCaretText(t, OFF); return updateShowCaretText(t); } if ( t->show_caret == ON && isAEvent(ev, NAME_keyboard) ) return send(t, NAME_typed, ev, EAV); fail; } static status typedText(TextObj t, EventId id) { return typedKeyBinding(KeyBindingText(), id, (Graphical) t); } static int start_of_line(PceString s, int n) { if ( n > 0 && str_fetch(s, n) == '\n' ) n--; n = str_next_rindex(s, n, '\n') + 1; /* returns -1 on not found! */ return n; } static int end_of_line(PceString s, int n) { if ( (n = str_next_index(s, n, '\n')) < 0 ) n = s->s_size; return n; } static int forward_word(PceString s, int i, int n) { while( n-- > 0 && i < s->s_size ) { while( i < s->s_size && !isalnum(str_fetch(s, i)) ) i++; while( i < s->s_size && isalnum(str_fetch(s, i)) ) i++; } return i; } static int backward_word(PceString s, int i, int n) { while( n-- > 0 && i > 0 ) { i--; while( i > 0 && !isalnum(str_fetch(s, i)) ) i--; while( i > 0 && isalnum(str_fetch(s, i-1)) ) i--; } return i; } /******************************** * INTERACTIVE EDIT COMMANDS * ********************************/ #define UArg(t) (isDefault(arg) ? 1 : valInt(arg)) static void deselectText(TextObj t) { if ( notNil(t->selection) ) selectionText(t, NIL, DEFAULT); } static status caretText(TextObj t, Int where) { int size = t->string->data.s_size; if ( isDefault(where) || valInt(where) >= size ) { where = toInt(size); } else if ( valInt(where) < 0 ) { where = ZERO; } assign(t, caret, where); if ( t->show_caret == ON ) recomputeText(t, NAME_area); succeed; } static status forwardCharText(TextObj t, Int arg) { deselectText(t); return caretText(t, add(t->caret, toInt(UArg(t)))); } static status backwardCharText(TextObj t, Int arg) { deselectText(t); return caretText(t, sub(t->caret, toInt(UArg(t)))); } static status nextLineText(TextObj t, Int arg, Int column) { int cx, cy; int fw, fh; deselectText(t); fw = valInt(getExFont(t->font)); fh = valInt(getHeightFont(t->font)); get_char_pos_text(t, DEFAULT, &cx, &cy); cy += UArg(t) * fh + fh/2; cx = (isDefault(column) ? cx + fw/2 : valInt(column)); return caretText(t, get_pointed_text(t, cx, cy)); } static status previousLineText(TextObj t, Int arg, Int column) { deselectText(t); return nextLineText(t, toInt(-UArg(t)), column); } static Int getColumnText(TextObj t) { int cx, cy; int fw; fw = valInt(getExFont(t->font)); get_char_pos_text(t, DEFAULT, &cx, &cy); answer(toInt(cx + fw/2)); } static status endOfLineText(TextObj t, Int arg) { PceString s = &t->string->data; int caret = valInt(t->caret); int n; deselectText(t); caret = end_of_line(s, caret); for(n = UArg(t)-1; caret < t->string->data.s_size && n > 0; n--) { caret++; caret = end_of_line(s, caret); } return caretText(t, toInt(caret)); } static status beginningOfLineText(TextObj t, Int arg) { PceString s = &t->string->data; int caret = valInt(t->caret); int n; deselectText(t); caret = start_of_line(s, caret); for(n = UArg(t)-1; caret > 0 && n > 0; n--) { caret--; caret = start_of_line(s, caret); } return caretText(t, toInt(caret)); } static status forwardWordText(TextObj t, Int arg) { int caret = valInt(t->caret); deselectText(t); caret = forward_word(&t->string->data, caret, UArg(t)); return caretText(t, toInt(caret)); } static status backwardWordText(TextObj t, Int arg) { int caret = valInt(t->caret); deselectText(t); caret = backward_word(&t->string->data, caret, UArg(t)); return caretText(t, toInt(caret)); } /******************************* * EDIT COMMANDS * *******************************/ static void prepareInsertText(TextObj t) { if ( !instanceOfObject(t->string, ClassString) ) assign(t, string, newObject(ClassString, name_procent_s, t->string, EAV)); if ( getClassVariableValueObject(t, NAME_insertDeletesSelection) == ON ) deleteSelectionText(t); } static status prepareEditText(TextObj t, Name selector) { if ( notDefault(selector) && !getSendMethodClass(ClassString, selector) ) fail; if ( !instanceOfObject(t->string, ClassString) ) assign(t, string, newObject(ClassString, name_procent_s, t->string, EAV)); selectionText(t, NIL, DEFAULT); succeed; } status pasteText(TextObj t, Name which) { CharArray str; Any selection; DisplayObj d = CurrentDisplay(t); if ( d && (selection=get(d, NAME_paste, which, EAV)) && (str=checkType(selection, TypeCharArray, NIL)) ) { prepareInsertText(t); insertString((StringObj) t->string, t->caret, str); caretText(t, add(t->caret, getSizeCharArray(str))); doneObject(str); return recomputeText(t, NAME_area); } fail; } static status backwardDeleteCharText(TextObj t, Int arg) { int caret = valInt(t->caret); int len = UArg(t); int from = (len > 0 ? caret - len : caret); int size = t->string->data.s_size; deselectText(t); len = abs(len); if ( from < 0 ) { len += from; from = 0; } if ( from + len > size ) len = size - from; if ( len > 0 ) { caretText(t, toInt(from)); prepareEditText(t, DEFAULT); deleteString((StringObj) t->string, toInt(from), toInt(len)); return recomputeText(t, NAME_area); } succeed; } static status deleteCharText(TextObj t, Int arg) { return backwardDeleteCharText(t, toInt(-UArg(t))); } static status cutOrDeleteCharText(TextObj t, Int arg) { if ( notNil(t->selection) && isDefault(arg) ) return cutText(t); else return deleteCharText(t, arg); } static status cutOrBackwardDeleteCharText(TextObj t, Int arg) { if ( notNil(t->selection) && isDefault(arg) ) return cutText(t); else return backwardDeleteCharText(t, arg); } static status killLineText(TextObj t, Int arg) { PceString s = &t->string->data; int caret = valInt(t->caret); int end, n; deselectText(t); if ( isDefault(arg) && str_fetch(s, caret) == '\n' ) return deleteCharText(t, DEFAULT); end = end_of_line(s, caret); if ( notDefault(arg) ) for( n=UArg(t); end < s->s_size && n > 0; n--, end++ ) end = end_of_line(s, end); prepareEditText(t, DEFAULT); deleteString((StringObj) t->string, t->caret, toInt(end-caret)); return recomputeText(t, NAME_area); } static status clearText(TextObj t) { deselectText(t); prepareEditText(t, DEFAULT); deleteString((StringObj) t->string, ZERO, DEFAULT); caretText(t, ZERO); return recomputeText(t, NAME_area); } static status insertText(TextObj t, Int where, CharArray str) { if ( isDefault(where) ) where = t->caret; prepareEditText(t, DEFAULT); insertString((StringObj)t->string, where, str); caretText(t, add(where, getSizeCharArray(str))); return recomputeText(t, NAME_area); } static status insertSelfText(TextObj t, Int times, Int chr) { wint_t c; int tms; if ( isDefault(times) ) times = ONE; tms = valInt(times); if ( isDefault(chr) ) { EventObj ev = EVENT->value; if ( instanceOfObject(ev, ClassEvent) && isAEvent(ev, NAME_printable) ) c = valInt(getIdEvent(ev)); else return errorPce(t, NAME_noCharacter); } else c = valInt(chr); prepareInsertText(t); { LocalString(buf, c > 0xff, tms); int i; for(i=0; is_size = i; str_insert_string((StringObj) t->string, t->caret, buf); caretText(t, add(t->caret, times)); return recomputeText(t, NAME_area); } } static status newlineText(TextObj t, Int arg) { return insertSelfText(t, arg, toInt('\n')); } static status openLineText(TextObj t, Int arg) { int tms = UArg(t); if ( tms > 0 ) { PceString nl = str_nl(&t->string->data); LocalString(buf, t->string->data.s_iswide, nl->s_size * tms); int i; for(i=0; is_size, nl, 0, nl->s_size); buf->s_size = nl->s_size * tms; prepareInsertText(t); str_insert_string((StringObj) t->string, t->caret, buf); recomputeText(t, NAME_area); } succeed; } static status gosmacsTransposeText(TextObj t) { int caret = valInt(t->caret); if ( caret >= 2 ) { wint_t tmp; PceString s; deselectText(t); prepareEditText(t, DEFAULT); s = &((StringObj)t->string)->data; tmp = str_fetch(s, caret-2); str_store(s, caret-2, str_fetch(s, caret-1)); str_store(s, caret-1, tmp); return recomputeText(t, NAME_area); } fail; } static status transposeCharsText(TextObj t) { int caret = valInt(t->caret); if ( caret >= 1 ) { wint_t tmp; PceString s; deselectText(t); prepareEditText(t, DEFAULT); s = &((StringObj)t->string)->data; tmp = str_fetch(s, caret-1); str_store(s, caret-1, str_fetch(s, caret)); str_store(s, caret, tmp); return recomputeText(t, NAME_area); } fail; } static status killWordText(TextObj t, Int arg) { int caret = valInt(t->caret); deselectText(t); prepareEditText(t, DEFAULT); caret = forward_word(&t->string->data, caret, UArg(t)); deleteString((StringObj) t->string, t->caret, sub(toInt(caret), t->caret)); return recomputeText(t, NAME_area); } static status backwardKillWordText(TextObj t, Int arg) { Int caret = t->caret; deselectText(t); prepareEditText(t, DEFAULT); caret = toInt(backward_word(&t->string->data, valInt(caret), UArg(t))); deleteString((StringObj) t->string, caret, sub(t->caret, caret)); caretText(t, caret); return recomputeText(t, NAME_area); } static status formatCenterText(TextObj t) { deselectText(t); return formatText(t, NAME_center); } static status formatLeftText(TextObj t) { deselectText(t); return formatText(t, NAME_left); } static status formatRightText(TextObj t) { deselectText(t); return formatText(t, NAME_right); } /******************************** * HANDLING LONG TEXT * ********************************/ status lengthText(TextObj t, Int l) { int fw, len; if ( isDefault(t->font) ) obtainClassVariablesObject(t); fw = valInt(getExFont(t->font)); len = (valInt(l)+1) * fw; return marginText(t, toInt(len), NAME_clip); } status marginText(TextObj t, Int width, Name wrap) { int changed = FALSE; if ( isNil(width) ) { width = toInt(100); /* initial default */ wrap = NAME_extend; } else if ( isDefault(wrap) ) { wrap = NAME_wrap; } if ( t->wrap != wrap ) { assign(t, wrap, wrap); changed++; } assign(t, margin, width); if ( Wrapped(t) ) changed++; else if ( wrap == NAME_clip ) setGraphical(t, DEFAULT, DEFAULT, width, DEFAULT); if ( changed ) recomputeText(t, NAME_area); succeed; } /******************************** * LOAD-STORE * ********************************/ static status loadText(TextObj t, IOSTREAM *fd, ClassDef def) { TRY(loadSlotsObject(t, fd, def)); if ( restoreVersion <= 6 && t->pen != ZERO ) assign(t, pen, ZERO); if ( isNil(t->wrap) ) assign(t, wrap, NAME_extend); if ( isNil(t->margin) ) assign(t, margin, toInt(100)); if ( isNil(t->border) ) assign(t, border, ZERO); if ( isNil(t->underline) ) assign(t, underline, OFF); succeed; } static status convertOldSlotText(TextObj t, Name slot, Any value) { if ( slot == NAME_transparent && isNil(t->background) ) assign(t, background, (value == ON ? NIL : DEFAULT)); succeed; } /******************************* * DELEGATION * *******************************/ static status catchAllText(TextObj t, Name sel, int argc, Any* argv) { if ( qadSendv(t->string, NAME_hasSendMethod, 1, (Any *)&sel) || prepareEditText(t, sel) ) { status rval; if ( (rval = vm_send(t->string, sel, NULL, argc, argv)) ) recomputeText(t, NAME_area); return rval; } return errorPce(t, NAME_noBehaviour, CtoName("->"), sel); } static Any getCatchAllText(TextObj t, Name sel, int argc, Any *argv) { if ( qadSendv(t->string, NAME_hasGetMethod, 1, (Any *)&sel) ) answer(vm_get(t->string, sel, NULL, argc, argv)); errorPce(t, NAME_noBehaviour, CtoName("<-"), sel); fail; } static status hasSendMethodText(TextObj t, Name sel) { if ( hasSendMethodObject(t, sel) || hasSendMethodObject(t->string, sel) || getSendMethodClass(ClassString, sel) ) succeed; fail; } static status hasGetMethodText(TextObj t, Name sel) { if ( hasGetMethodObject(t, sel) || hasGetMethodObject(t->string, sel) ) succeed; fail; } /******************************* * CLASS DECLARATION * *******************************/ /* Type declarations */ static char *T_insert[] = { "at=[int]", "text=char_array" }; static char *T_resize[] = { "factor_x=real", "factor_y=[real]", "origin=[point]" }; static char *T_margin[] = { "int*", "[{wrap,wrap_fixed_width,clip}]" }; static char *T_linesADintD_columnADintD[] = { "lines=[int]", "column=[int]" }; static char *T_convertOldSlot[] = { "slot=name", "value=unchecked" }; static char *T_initialise[] = { "string=[char_array]", "format=[{left,center,right}]", "font=[font]" }; static char *T_insertSelf[] = { "times=[int]", "character=[char]" }; static char *T_geometry[] = { "x=[int]", "y=[int]", "width=[int]", "height=[int]" }; static char *T_selection[] = { "from=[int]*", "to=[int]" }; static char *T_catchAll[] = { "selector=name", "argument=unchecked ..." }; /* Instance Variables */ static vardecl var_text[] = { IV(NAME_string, "char_array", IV_GET, NAME_storage, "Represented string (may contain newlines)"), SV(NAME_font, "font", IV_GET|IV_STORE, fontText, NAME_appearance, "Font used to draw the string"), SV(NAME_format, "{left,center,right}", IV_GET|IV_STORE, formatText, NAME_appearance, "Left, center or right alignment"), IV(NAME_margin, "int", IV_GET, NAME_appearance, "Margin for <->wrap equals wrap"), SV(NAME_underline, "bool", IV_GET|IV_STORE, underlineText, NAME_appearance, "Underlined text?"), IV(NAME_position, "point", IV_NONE, NAME_internal, "Avoid `walking' with alignment"), IV(NAME_caret, "int", IV_GET, NAME_caret, "Index (0-based) of caret"), SV(NAME_showCaret, "bool|{passive}", IV_GET|IV_STORE, showCaretText, NAME_appearance, "If not @off, show the caret"), SV(NAME_background, "[colour|pixmap]*", IV_GET|IV_STORE, backgroundText, NAME_appearance, "@nil: transparent; @default: cleared"), SV(NAME_border, "0..", IV_GET|IV_STORE, borderText, NAME_appearance, "Border around actual text"), IV(NAME_wrap, "{extend,wrap,wrap_fixed_width,clip}", IV_GET, NAME_appearance, "How long text is handled"), IV(NAME_xOffset, "int", IV_NONE, NAME_internal, "Horizontal scroll when nonzero length"), IV(NAME_xCaret, "int", IV_NONE, NAME_internal, "X-position of caret"), IV(NAME_yCaret, "int", IV_NONE, NAME_internal, "Y-position of caret"), IV(NAME_Selection, "int*", IV_NONE, NAME_internal, "Selected text") }; /* Send Methods */ static senddecl send_text[] = { SM(NAME_event, 1, "event", eventText, DEFAULT, "Handle focus and keyboard events"), SM(NAME_geometry, 4, T_geometry, geometryText, DEFAULT, "Only move text"), SM(NAME_initialise, 3, T_initialise, initialiseText, DEFAULT, "Create from string, format and font"), SM(NAME_resize, 3, T_resize, resizeText, DEFAULT, "Resize text with specified factor"), SM(NAME_string, 1, "char_array", stringText, NAME_storage, "Represented string"), SM(NAME_formatCenter, 0, NULL, formatCenterText, NAME_appearance, "Set center alignment"), SM(NAME_formatLeft, 0, NULL, formatLeftText, NAME_appearance, "Set left alignment"), SM(NAME_formatRight, 0, NULL, formatRightText, NAME_appearance, "Set right alignment"), SM(NAME_margin, 2, T_margin, marginText, NAME_appearance, "Determine how long text is handled"), SM(NAME_length, 1, "int", lengthText, NAME_area, "(compatibility)"), SM(NAME_prefix, 0, NULL, succeedObject, NAME_binding, "Multi-key prefix (see class key_binding)"), SM(NAME_backwardChar, 1, "times=[int]", backwardCharText, NAME_caret, "Move caret characters backward (\\C-f)"), SM(NAME_backwardWord, 1, "times=[int]", backwardWordText, NAME_caret, "Move caret words backward (\\eb)"), SM(NAME_beginningOfLine, 1, "times=[int]", beginningOfLineText, NAME_caret, "Move caret to start of line (\\C-a)"), SM(NAME_caret, 1, "[int]", caretText, NAME_caret, "Move caret to 0-based index"), SM(NAME_endOfLine, 1, "times=[int]", endOfLineText, NAME_caret, "Move caret to end of line (\\C-e)"), SM(NAME_forwardChar, 1, "times=[int]", forwardCharText, NAME_caret, "Move caret characters forwards (\\C-f)"), SM(NAME_forwardWord, 1, "times=[int]", forwardWordText, NAME_caret, "Move caret words forward (\\ef)"), SM(NAME_nextLine, 2, T_linesADintD_columnADintD, nextLineText, NAME_caret, "Move caret lines down (\\C-n)"), SM(NAME_previousLine, 2, T_linesADintD_columnADintD, previousLineText, NAME_caret, "Move caret lines up (\\C-n)"), SM(NAME_convertOldSlot, 2, T_convertOldSlot, convertOldSlotText, NAME_compatibility, "Convert <-transparent to <-background"), SM(NAME_transparent, 1, "bool", transparentText, NAME_compatibility, "Defines <-background"), SM(NAME_backwardDeleteChar, 1, "times=[int]", backwardDeleteCharText, NAME_delete, "Delete chars backward from caret (DEL)"), SM(NAME_backwardKillWord, 1, "times=[int]", backwardKillWordText, NAME_delete, "Deletes words backward from caret (\\eDEL)"), SM(NAME_clear, 0, NULL, clearText, NAME_delete, "Wipe out all text (\\C-u)"), SM(NAME_deleteChar, 1, "times=[int]", deleteCharText, NAME_delete, "Delete characters forwards (\\C-d)"), SM(NAME_cutOrDeleteChar, 1, "times=[int]", cutOrDeleteCharText, NAME_delete, "Cut or delete characters forwards (DEL)"), SM(NAME_cutOrBackwardDeleteChar, 1, "times=[int]", cutOrBackwardDeleteCharText, NAME_delete, "Cut or delete characters backward (BS)"), SM(NAME_killLine, 1, "times=[int]", killLineText, NAME_delete, "Delete lines from caret \\C-k)"), SM(NAME_killWord, 1, "times=[int]", killWordText, NAME_delete, "Deletes words forward from caret (\\ed)"), SM(NAME_typed, 1, "event|event_id", typedText, NAME_event, "Handle a keystroke"), SM(NAME_insert, 2, T_insert, insertText, NAME_insert, "Insert text at position [<-caret]"), SM(NAME_insertSelf, 2, T_insertSelf, insertSelfText, NAME_insert, "Insert n-times char at caret"), SM(NAME_newline, 1, "times=[int]", newlineText, NAME_insert, "Insert newlines (RET, LFD)"), SM(NAME_openLine, 1, "times=[int]", openLineText, NAME_insert, "Insert newlines after caret (\\C-o)"), SM(NAME_DrawPostScript, 1, "{head,body}", drawPostScriptText, NAME_postscript, "Create PostScript"), SM(NAME_compute, 0, NULL, computeText, NAME_repaint, "Recompute area/offset"), SM(NAME_paste, 1, "which=[{primary,clipboard}]", pasteText, NAME_selection, "Paste primary selection or clipboard"), SM(NAME_gosmacsTranspose, 0, NULL, gosmacsTransposeText, NAME_transpose, "Transpose two char_array before caret"), SM(NAME_transposeChars, 0, NULL, transposeCharsText, NAME_transpose, "Transpose two char_array around caret"), SM(NAME_selection, 2, T_selection, selectionText, NAME_selection, "Make [from, to) the selection"), SM(NAME_copy, 0, NULL, copyText, NAME_selection, "Copy selection (\\C-c)"), SM(NAME_cut, 0, NULL, cutText, NAME_selection, "Copy and delete selection"), SM(NAME_cutOrDeleteChar, 1, "times=[int]", cutOrDeleteCharText, NAME_delete, "Delete characters forwards (DEL)"), SM(NAME_catchAll, 2, T_catchAll, catchAllText, NAME_delegate, "Delegate to <-string"), SM(NAME_hasSendMethod, 1, "name", hasSendMethodText, DEFAULT, "Test if text or <-string defines method"), SM(NAME_hasGetMethod, 1, "name", hasGetMethodText, DEFAULT, "Test if text or <-string defines method") }; /* Get Methods */ static getdecl get_text[] = { GM(NAME_characterPosition, 1, "point", "index=[int]", getCharacterPositionText, NAME_calculate, "Convert index to position of character"), GM(NAME_column, 0, "pixels=int", NULL, getColumnText, NAME_caret, "Current X-location of caret (pixels)"), GM(NAME_upDownColumn, 0, "pixels=int", NULL, getColumnText, NAME_caret, "Current X-location of caret (pixels)"), GM(NAME_transparent, 0, "bool", NULL, getTransparentText, NAME_compatibility, "Map <-background"), GM(NAME_pointed, 1, "index=int", "at=point", getPointedText, NAME_event, "Convert position to character index"), GM(NAME_selectedText, 0, "string", NULL, getSelectedTextText, NAME_selection, "New string with contents of selection"), GM(NAME_selection, 0, "point", NULL, getSelectionText, NAME_selection, "New point with start and end of selection"), GM(NAME_catchAll, 2, "unchecked", T_catchAll, getCatchAllText, NAME_delegate, "Delegate to <-string") }; /* Resources */ static classvardecl rc_text[] = { RC(NAME_pen, RC_REFINE, "0", NULL), RC(NAME_border, "0..", "0", "Space around the actual text"), RC(NAME_font, "font", "normal", "Default font"), RC(NAME_format, "name", "left", "Default adjustment: {left,center,right}"), RC(NAME_selectionStyle, "style", UXWIN("style(colour := white, background := black)", "@_select_style"), "Style for <-selection"), RC(NAME_insertDeletesSelection, "bool", "@on", "->insert_self and ->paste delete the selection"), RC(NAME_keyBinding, "string", "", "`Key = selector' binding list") }; /* Class Declaration */ static Name text_termnames[] = { NAME_string, NAME_format, NAME_font }; ClassDecl(text_decls, var_text, send_text, get_text, rc_text, 3, text_termnames, "$Rev$"); status makeClassText(Class class) { declareClass(class, &text_decls); setRedrawFunctionClass(class, RedrawAreaText); setLoadStoreFunctionClass(class, loadText, NULL); succeed; }