// // Copyright (C) 1996 Ben Ross // // You may distribute under the terms of the GNU General Public // License as specified in the COPYING file. // // $Id: XeTextBase.C,v 1.13 2001/08/04 11:47:20 ben Exp $ #include #include #include #include #include #include #include #include #include #include #ifdef _SGI_SOURCE #include #endif #include #include #ifdef __GNUC__ // Manually instantiate templates for XeArray: #include template class XeArray; template class XeArray; #endif #if 0 class GExpose { public: GExpose(int line, int col) : line(line), col(col) {} XE_MINIALLOC(0); long line; long col; }; inline void XeTextBase::addGExpose(int line, int col) { GExpose* item = new GExpose(line, col); _gExposeQueue.append(item); } #endif #define LINE_MARGIN 40 #define TAB 4 #define XINSET 4 #define YINSET 4 #define NO_PLANEMASK 0 XeTextBase::XeTextBase(XeObject *parent, const char *name, const char* className) : XeWidget(parent, name, className) { _yinset = XINSET; _xinset = YINSET; _topLine = _leftColumn = 0; _tab = TAB; _lineMargin = LINE_MARGIN; _lineNumbers = FALSE; _focusInside = FALSE; _noPlanemask = NO_PLANEMASK; // default colours _lineNumColour = lookupColour(this, "blue"); _activeColour = lookupColour(this, "red"); _inactiveColour = lookupColour(this, "gray50"); memset(&_sel, 0, sizeof(SelStruct)); memset(_sepTable, 0, sizeof(uchar) * 32); _anchor = _sel; _word = _sel; setBreak('\n'); // $$$ XSetWindowAttributes attr; unsigned long valuemask; valuemask = CWBitGravity; attr.bit_gravity = NorthWestGravity; XChangeWindowAttributes(gDisplay, _window, valuemask, &attr); // default font char *fontname = "-b&h-lucidatypewriter-medium-r-normal-sans-12*"; setBackground(lookupColour(this, "grey85")); setFont(fontname); doShow(); // select necessary events XSelectInput(gDisplay, _window, (ButtonPressMask | ButtonReleaseMask | ButtonMotionMask | ExposureMask | KeyPressMask | FocusChangeMask | EnterWindowMask | LeaveWindowMask)); // Setup I-Beam (XTerm) cursor Cursor cursor = XCreateFontCursor(gDisplay, XC_xterm); XDefineCursor(gDisplay, _window, cursor); XFreeCursor(gDisplay, cursor); // Text and linestarts buffers: _text = new XeArray(512); _lineStarts = new XeArray; _lineStarts->insert(0, 0); } XeTextBase::~XeTextBase() { delete _text; delete _lineStarts; } void XeTextBase::getResources() { XeWidget::getResources(); XrmValue value; // American spelling of colour if (getResource("lineNumColor", "lineNumColor", &value)) setLineNumColour(lookupColour(this, value.addr)); if (getResource("activeColor", "activeColor", &value)) setActiveColour(lookupColour(this, value.addr)); if (getResource("inactiveColor", "inactiveColor", &value)) setInactiveColour(lookupColour(this, value.addr)); // English spelling (overrides) if (getResource("lineNumColour", "lineNumColour", &value)) setLineNumColour(lookupColour(this, value.addr)); if (getResource("activeColour", "activeColour", &value)) setActiveColour(lookupColour(this, value.addr)); if (getResource("inactiveColour", "inactiveColour", &value)) setInactiveColour(lookupColour(this, value.addr)); } // setFont needs to set some colours. void XeTextBase::setFont(const char* fontName) { XGCValues values; ulong valuemask; XeWidget::setFont(fontName); valuemask = GCForeground | GCFont; values.foreground = _lineNumColour; values.font = _font->fid; _lineGC = getGC(this, &values, valuemask); valuemask |= GCLineWidth | GCBackground; values.line_width = 2; values.foreground = _background; values.background = _textCol; values.font = _font->fid; _backGC = getGC(this, &values, valuemask); _lineMargin = cwidth() * 6; // 6 digit wide area for line numbers } void XeTextBase::setLineNumColour(ulong col) { _lineNumColour = col; } void XeTextBase::setActiveColour(ulong col) { _activeColour = col; } void XeTextBase::setInactiveColour(ulong col) { _inactiveColour = col; } // doShow - remake GCs void XeTextBase::doShow() { XeWidget::doShow(); if (!_neverMapped) return; XGCValues values; ulong valuemask; values.line_width = 2; values.foreground = _activeColour; valuemask = GCLineWidth | GCForeground; _activeGC = getGC(this, &values, valuemask); values.foreground = _inactiveColour; _inactiveGC = getGC(this, &values, valuemask); _caretGC = _inactiveGC; valuemask = GCForeground | GCFont; values.foreground = _lineNumColour; values.font = _font->fid; _lineGC = getGC(this, &values, valuemask); valuemask |= GCLineWidth | GCBackground; values.line_width = 2; values.foreground = _background; values.background = _textCol; values.font = _font->fid; _backGC = getGC(this, &values, valuemask); // This GC can be shared but I have to fix the selection code // so that it doesnt need to be clipped. // This should work unclipped now...(more testing needed) values.foreground = _textCol; values.background = _background; values.function = GXinvert; values.plane_mask = values.foreground ^ _background; valuemask = GCForeground | GCBackground | GCFunction | GCPlaneMask; _invertGC = getGC(this, &values, valuemask); } void XeTextBase::setBreakChars(const char* breakChars) { const char* p = breakChars; while (*p) { setBreak(*p); p++; } } // // Deal only with Expose, GraphicsExpose and FocusChange events // in this base class since all input-type events will be defined // in a derived class. // int XeTextBase::doEvent(XEvent *theEvent) { switch(theEvent->type) { case GraphicsExpose: case Expose: doExpose((XExposeEvent *)theEvent); break; default: return XeObject::doEvent(theEvent); } return 0; } void XeTextBase::takeFocus(bool focus) { if(focus) { _caretGC = _activeGC; } else { _caretGC = _inactiveGC; } _focusInside = focus; if(!selLength()) drawCaret(TRUE); } // // doExpose(): interprets the expose rect as a range of visible // line numbers and visible columns, and calls redrawLines() // with appropriate lines and columns. // void XeTextBase::doExpose(XExposeEvent *event) { int x1 = event->x - xinset(), xmax = textWidth(); int y1 = event->y - yinset(), ymax = textHeight(); int x2 = event->width + x1; int y2 = event->height + y1; x1 = (x1 < 0) ? 0 : x1; x1 = (x1 > xmax) ? xmax : x1; y1 = (y1 < 0) ? 0 : y1; y1 = (y1 > ymax) ? ymax : y1; x2 = (x2 < 0) ? 0 : x2; x2 = (x2 > xmax) ? xmax : x2; y2 = (y2 < 0) ? 0 : y2; y2 = (y2 > ymax) ? ymax : y2; int startline, endline; int startcol, endcol; startline = y1 / lheight(); endline = y2 / lheight(); startcol = x1 / cwidth(); endcol = x2 / cwidth(); redrawLines(startline, endline, startcol, endcol); if(_lineNumbers && event->x < _lineMargin) redrawLineNumbers(startline, endline); drawCaret(TRUE); } #if 0 void XeTextBase::doGraphicsExpose(XGraphicsExposeEvent* event) { GExpose* item = (GExpose*)_gExposeQueue.head(); if(event->type == GraphicsExpose) { if(event->count) { // don't remove queue item yet //printf("Processing count %d\n", event->count); return; } } _gExposeQueue.remove(); delete item; } #endif // // resizeEvent: virtual function called from XeObject::XeWidget to handle // window resizing. // void XeTextBase::resizeEvent(uint width, uint height) { _visCols = (width - xinset()) / cwidth(); _visLines = (height - yinset()) / lheight(); int rwidth, rheight; rheight = height - (yinset() + textHeight()); rwidth = width - (xinset() + textWidth()); if(rheight) XClearArea(gDisplay, _window, 0, height - rheight, width, rheight, False); if(rwidth) XClearArea(gDisplay, _window, width - rwidth, 0, rwidth, height, False); } void XeTextBase::resize(int cols, int lines) { XeObject::resize(cols * cwidth() + xinset() + _xinset, lines * lheight() + yinset() * 2); } #if 0 ulong XeTextBase::numLines() { ulong numlines; numlines = _lineStarts->getSize(); if(numlines - _topLine < (ulong)_visLines) { numlines += _visLines - (numlines - _topLine); } return numlines; } #endif std::string XeTextBase::getSelected(void) { long len = selLength(); std::string str; if (!len) return std::string(); return std::string(&(*_text)[_sel.selStart], len); } std::string XeTextBase::getLine(long line) { int len, index; if((ulong)line >= _lineStarts->getSize()) return std::string(); index = (*_lineStarts)[line]; if((ulong)index == _text->getSize()) return std::string(); if((ulong)line == _lineStarts->getSize() - 1) len = _text->getSize() - index + 1; else len = (*_lineStarts)[line + 1] - index; return std::string(&(*_text)[index], len - 1); } const char* XeTextBase::getLine(long line, int& length) { ulong index; if((ulong)line >= _lineStarts->getSize()) return NULL; index = (*_lineStarts)[line]; if((ulong)index == _text->getSize()) { length = 0; return NULL; } if((ulong)line == _lineStarts->getSize() - 1) length = _text->getSize() - index; else length = (*_lineStarts)[line + 1] - index - 1; return &(*_text)[index]; } void XeTextBase::setLineNumbers(bool state, bool update) { _lineNumbers = state; if(update) XClearArea(gDisplay, _window, 0, 0, _width, _height, True); } void XeTextBase::setTabSize(short tabSize, bool update) { _tab = tabSize; _sel.posStart = getPhysicalLength(_sel.lineStart, _sel.selStart); _sel.posEnd = getPhysicalLength(_sel.lineEnd, _sel.selEnd); _word.posStart = _sel.posStart; _word.posEnd = _sel.posEnd; if(_anchor.selStart == _sel.selStart) { _anchor.posStart = _sel.posStart; _anchor.posEnd = _sel.posEnd; } else { _anchor.posEnd = _sel.posStart; _anchor.posStart = _sel.posEnd; } if(update) { redrawLines(0, _visLines, 0, _visCols, TRUE); drawCaret(TRUE); } } // // getPhysicalLength(): Calculates the actual length in whole characters // (including spaces in tabs) of a line. If the to argument is used // then the line length is calculated only up to that index. // int XeTextBase::getPhysicalLength(long line, long to) { ulong i; char* p; int tabstops, spaces, len = 0; if(to < 0) to = _text->getSize(); i = (*_lineStarts)[line]; if (i >= _text->getSize()) return 0; p = &(*_text)[i]; while (i < (ulong)to && i < _text->getSize() && *p != '\n') { tabstops = len/_tab + 1; if (*p == '\t') { spaces = (tabstops * _tab) - len; len += spaces; } else { len++; } p++; i++; } return len; } // // getSelPos(): given a line number and a physical character position // in a line, getSelPos returns the text index of the caret position // and updates the physical position according to tab. // ulong XeTextBase::getSelPos(long line, long &pos) { int tabstops, spaces, len = 0; ulong selpos; char *p; selpos = (*_lineStarts)[line]; if(selpos >= _text->getSize()) { pos = 0; return selpos; } p = &(*_text)[selpos]; while (*p != '\n' && selpos < _text->getSize() && len < pos) { tabstops = len/_tab + 1; if(*p == '\t') { spaces = (tabstops * _tab) - len; if(len + spaces > pos) { pos = len; return selpos; } len += spaces; } else { len++; } selpos++; p++; } pos = len; return selpos; } bool XeTextBase::isBreak(uchar index) { if (isUnprintable(index)) // always break on unprintable characters return TRUE; short aindex = index / (sizeof(uchar) * 8); if(aindex > 31) return FALSE; if(_sepTable[aindex] & (uchar)(1 << (index % (sizeof(uchar) * 8)))) return TRUE; return FALSE; } void XeTextBase::setBreak(uchar index) { short aindex = index / (sizeof(uchar) * 8); if(aindex > 31) return; _sepTable[aindex] |= (uchar)(1 << (index % (sizeof(uchar) * 8))); } void XeTextBase::getNextPos(bool rev, SelStruct* sel) { long i = sel->selStart; if((ulong)i >= _text->getSize()) return; char* p = &(*_text)[i]; char first = *p; if(!rev) { while((ulong)i < _text->getSize()) { if(!checkChar(*p, 1, first, sel->selStart, sel->posStart, sel->lineStart)) break; p++; i++; } } else { p--; while(i >= 0) { if(!checkChar(*p, -1, first, sel->selStart, sel->posStart, sel->lineStart)) break; p--; i--; } } } void XeTextBase::getWord(SelStruct* sel) { long selpos = sel->selStart; long i = selpos; if((ulong)i >= _text->getSize()) return; char* p = &(*_text)[selpos]; char first = *p; // Count back to find the start of the word p--; while(i >= 0 && first != '\n') { if(!checkChar(*p, -1, first, sel->selStart, sel->posStart, sel->lineStart)) break; p--; i--; } p = &(*_text)[selpos]; i = selpos; while((ulong)i < _text->getSize()) { if(!checkChar(*p, 1, first, sel->selEnd, sel->posEnd, sel->lineEnd)) break; p++; i++; } } bool XeTextBase::checkChar(char c, short amt, char first, long& selpos, long& pos, long& line) { if(isWhitespace(first) || isBreak(first)) { if(isWhitespace(first)) { if(!isWhitespace(c)) return FALSE; selpos += amt; pos = getPhysicalLength(line, selpos); } else if(first == '\n') { if(amt > 0) { selpos++; line++; pos = 0; } return FALSE; } else { if(c != first || !isBreak(c) || c == '\n') return FALSE; selpos += amt; pos += amt; } } else { if(isWhitespace(c) || isBreak(c)) return FALSE; selpos += amt; pos += amt; } if(selpos < 0) selpos = 0; if(pos < 0) pos = 0; return TRUE; } // // updateLinestarts(): adds the amount given in amt to each element // in the _lineStarts array from the line fromline. It is // necessary to call this function every time a character or // range of characters are inserted or deleted. // This is a rather inefficient way of handing the linestarts // when editing large files at the beginning.. but I hope to // implement some better data structures later. // void XeTextBase::updateLinestarts(ulong fromline, int amt) { register ulong i; register ulong numlines = _lineStarts->getSize() - 1; if(fromline > numlines) return; register ulong* buf = &(*_lineStarts)[fromline]; for (i = fromline; i <= numlines; i++) *buf++ += amt; } #define clearArea(x, y, width, height) \ XClearArea(gDisplay, _window, x, y, width, height, False) void XeTextBase::redrawLine(int line, int leftCol, int rightCol, bool erase, bool ignoreSelection) { // $$$ Requires args to be appropriately clipped int recty = yinset() + lheight() * line; long absline = line + _topLine; long lcol = leftCol + _leftColumn; long rcol = rightCol + _leftColumn; if ((ulong)absline >= _lineStarts->getSize()) { if (erase) clearArea(xinset() + leftCol * cwidth(), recty, (rightCol - leftCol + 1) * cwidth(), lheight()); return; } char* p; ulong j; j = (*_lineStarts)[absline]; if (j >= _text->getSize()) { if (erase) clearArea(xinset() + leftCol * cwidth(), recty, (rightCol - leftCol + 1) * cwidth(), lheight()); return; } p = &(*_text)[j]; enum { BEGIN, TABS, TABS_SELECTED, NORMAL, NORMAL_SELECTED, UNPRINTABLE, UNPRINTABLE_SELECTED, END }; int x = 0; int y = recty + _font->ascent; long numChars = 0; int tabstops = 1; int state = BEGIN; char* chunk = NULL; int chunkLen = 0; for (;; p++,j++) { int newState; int charLen = 0; int width; bool selected = false; if (!ignoreSelection) selected = inSelection(j); if (*p == '\n' || j >= _text->getSize() || numChars > rcol) { newState = END; } else if (*p == '\t') { newState = selected ? TABS_SELECTED : TABS; charLen = (_tab * tabstops) - numChars; numChars += charLen; } else { numChars++; charLen = 1; if (isUnprintable(*p)) newState = selected ? UNPRINTABLE_SELECTED : UNPRINTABLE; else newState = selected ? NORMAL_SELECTED : NORMAL; } tabstops = numChars / _tab + 1; if (state == BEGIN) { if (newState == END) { // Nothing on line x = xinset() + leftCol * cwidth(); width = ((rightCol + 1) * cwidth()) - (x - xinset()); if (selected && *p == '\n') // End of line is selected XFillRectangle(gDisplay, _window, _textGC, x, recty, width, lheight()); else if (erase) clearArea(x, recty, width, lheight()); break; } if (numChars <= lcol) // keep incrementing numChars continue; // Include this byte in the first chunk x = xinset() + (numChars - charLen - _leftColumn) * cwidth(); chunkLen = charLen; chunk = p; state = newState; } else if (state == newState) { // state unchanged: chunkLen += charLen; state = newState; } else { // state != newState // Draw preceeding chunk: width = chunkLen * cwidth(); switch (state) { case TABS: case TABS_SELECTED: { // Draw foreground colour rectangle. // Clip x because it's possible for the beginning of a tab to // start before the leftmost column. Also clip width because it // may span over the right edge of the text area int clipx = (x < xinset()) ? xinset() : x; width -= clipx - x; x = clipx; if (width + x > xinset() + (rightCol + 1) * cwidth()) width -= (width + x) - (xinset() + (rightCol + 1) * cwidth()); if (state == TABS && erase) clearArea(x, recty, width, lheight()); else if (state == TABS_SELECTED) XFillRectangle(gDisplay, _window, _textGC, x, recty, width, lheight()); } break; case NORMAL: XDrawImageString(gDisplay, _window, _textGC, x, y, chunk, chunkLen); break; case NORMAL_SELECTED: XDrawImageString(gDisplay, _window, _backGC, x, y, chunk, chunkLen); break; case UNPRINTABLE: case UNPRINTABLE_SELECTED: for (int i = 0; i < chunkLen; i++) { int upx = i * cwidth() + x; if (state == UNPRINTABLE) { if (erase) XFillRectangle(gDisplay, _window, _backGC, upx, recty, cwidth(), lheight()); XFillRectangle(gDisplay, _window, _textGC, upx + 1, recty + 1, cwidth() - 2, lheight() - 2); } else { XFillRectangle(gDisplay, _window, _textGC, upx, recty, cwidth(), lheight()); XFillRectangle(gDisplay, _window, _backGC, upx + 1, recty + 1, cwidth() - 2, lheight() - 2); } } break; } x += width; if (newState == END) { // x may already be over this point (selected tab): if (x >= xinset() + (rightCol + 1) * cwidth()) break; width = ((rightCol + 1) * cwidth()) - (x - xinset()); if (selected && *p == '\n') // End of line is selected XFillRectangle(gDisplay, _window, _textGC, x, recty, width, lheight()); else if (erase) clearArea(x, recty, width, lheight()); break; // finished... } // Reset chunk start info: chunk = p; chunkLen = charLen; state = newState; } } } // // redrawLines(): startline and endline are local line numbers // where 0 is the top line in the visible window // void XeTextBase::redrawLines(int startline, int endline, int leftCol, int rightCol, bool erase, bool ignoreSelection) { // Clip args: if (startline < 0) { startline = 0; if (endline < 0) return; } else if (startline >= _visLines) { return; } if (endline >= _visLines) endline = _visLines - 1; if (leftCol < 0 || leftCol >= _visCols) leftCol = 0; if (rightCol < 0 || rightCol >= _visCols) rightCol = _visCols - 1; if (rightCol < leftCol) rightCol = leftCol; for (int i = startline; i <= endline; i++) { redrawLine(i, leftCol, rightCol, erase, ignoreSelection); } } void XeTextBase::redrawLineNumbers(int startline, int endline, bool erase) { char numbuf[20]; int linex, y, i, line; int lh = lheight(); if(startline < 0) startline = 0; if(endline >= _visLines) endline = _visLines - 1; y = yinset() + lh * startline + _font->ascent; for(i = startline; i <= endline; i++) { line = i + _topLine; if((ulong)line >= _lineStarts->getSize()) { y = yinset() + i * lh; XClearArea(gDisplay, _window, 0, y, _lineMargin, _height - y, False); break; } sprintf(numbuf, "%d", line + 1); linex = _lineMargin - cwidth() * strlen(numbuf); if (erase) XClearArea(gDisplay, _window, 0, y - _font->ascent, xinset(), lh, False); XDrawString(gDisplay, _window, _lineGC, linex, y, numbuf, strlen(numbuf)); y += lh; } } // // drawCaret(): draws the caret where text will be inserted. // a state of 1 means draw on and a state of 0 means clear. // an optional line can be given to drawCaret() otherwise // _sel.lineStart is used. // void XeTextBase::drawCaret(bool state, long altline, long altsel) { // don't bother doing anything if there is a selection. if(selLength()) return; int x, y; long line = (altline < 0) ? _sel.lineStart-_topLine : altline-_topLine; long sel = (altsel < 0) ? _sel.selStart : altsel; int col = _sel.posStart - _leftColumn; if(line < 0 || line >= _visLines || col < 0 || col >= _visCols) return; x = xinset() + cwidth() * col; y = yinset() + lheight() * line; if(state) { XDrawLine(gDisplay, _window, _caretGC, x+1, y, x+1, y + lheight() -1); } else { XDrawLine(gDisplay, _window, _backGC, x+1, y, x+1, y+ lheight() -1); } if(_text->getSize() && _text->getSize() > (ulong)sel) { char* c = &(*_text)[sel]; if (*c != '\n' && *c != '\t') { if (isUnprintable(*c)) // $$$ probably want a function to do this since redrawLine() knows // how to draw unprintables as well. XFillRectangle(gDisplay, _window, _textGC, x + 1, y + 1, cwidth() - 2, lheight() - 2); else XDrawString(gDisplay, _window, _textGC, x, y+_font->ascent, &(*_text)[sel], 1); } } } // // hiliteSelection(): is mainly used to unhilite the current selection. // It simply inverts the selection area but doesn't change any of the // variables in _sel. // void XeTextBase::hiliteSelection(bool erase) { if(!selLength()) return; if (_noPlanemask) { redrawLines(_sel.lineStart - _topLine, _sel.lineEnd - _topLine, 0 , _visCols, erase, erase); return; } XRectangle r; int ps = _sel.posStart - _leftColumn; int pe = _sel.posEnd - _leftColumn; ps = (ps < 0) ? 0 : ps; pe = (pe < 0) ? 0 : pe; pe = (pe > _visCols) ? (_visCols) : pe; r.height = lheight(); for(long i = _sel.lineStart; i <= _sel.lineEnd; i++) { if(i - _topLine < 0) continue; if((i - _topLine) >= _visLines) return; r.y = (i - _topLine) * r.height + yinset(); if(i == _sel.lineStart && i == _sel.lineEnd) { r.x = ps * cwidth() + xinset(); r.width = pe * cwidth() + xinset() - r.x; } else if(i == _sel.lineStart) { r.x = ps * cwidth() + xinset(); r.width = textWidth() - r.x + xinset(); } else if(i == _sel.lineEnd) { r.x = xinset(); r.width = pe * cwidth(); } else { r.x = xinset(); r.width = textWidth(); } XFillRectangle(gDisplay, _window, _invertGC, r.x, r.y, r.width, r.height); } } void XeTextBase::eraseLine(int line, int xpos) { int y, width, height; if(xpos < xinset()) xpos = xinset(); y = yinset() + lheight() * line; width = _width - xpos; height = lheight(); XClearArea(gDisplay, _window, xpos, y, width, height, False); } // // scrollVTo(): is a public function that other interface elements // can use to scroll the text to a particular global line. // The function intercepts GraphicsExpose events to process // them straight away. // void XeTextBase::scrollVTo(ulong line) { scrollV(_topLine - line); syncScroll(); } // // scrollHTo(): the horizontal equivalent of scrollVTo() // void XeTextBase::scrollHTo(long col) { scrollH(_leftColumn - col); syncScroll(); } // // scrollV(): scrolls up or down with the given number of lines. // a negative number means scroll up and positive means // scroll down. // bool XeTextBase::scrollV(int lines, bool override) { return scroll(lines, 0, override); } // // scrollH(): scrolls a number of columns left or right, a negative number // means scroll left and positive scroll right. // bool XeTextBase::scrollH(int cols) { return scroll(0, cols); } bool XeTextBase::scroll(int lines, int cols, bool override) { int x, y, ydest, xdest, width, height; int xamt, yamt; bool xreturn=TRUE, yreturn=TRUE; long numlines = (override) ? (_lineStarts->getSize() + _visLines - 1) : numLines(); if(!lines) yreturn = FALSE; else if(_topLine == 0 && lines > 0) yreturn = FALSE; else if((_topLine + _visLines) >= numlines && lines < 0) yreturn = FALSE; if(!cols) xreturn = FALSE; else if(_leftColumn == 0 && cols > 0) xreturn = FALSE; if(!xreturn && !yreturn) return FALSE; else if(!xreturn) cols = 0; else if(!yreturn) lines = 0; if(lines < 0 && (_topLine + _visLines - lines) > numlines) lines = _topLine + _visLines - numlines; if(lines > 0 && lines > _topLine) lines = _topLine; if(cols > _leftColumn) cols = _leftColumn; _topLine -= lines; _leftColumn -= cols; if(lines > _visLines || lines < -_visLines) { if(_lineNumbers) redrawLineNumbers(0, _visLines-1, TRUE); redrawLines(0, _visLines-1, 0, -1, TRUE); goto end; } else if(cols > _visCols || cols < -_visCols) { redrawLines(0, _visLines-1, 0, -1, TRUE); goto end; } xamt = cols * cwidth(); yamt = lines * lheight(); height = textHeight(); if(yamt < 0) { height += yamt; ydest = yinset(); y = ydest - yamt; } else { height -= yamt; y = yinset(); ydest = y + yamt; } // scroll line numbers separately if necessary. if(cols && lines && _lineNumbers) { x = 0; width = xinset(); XCopyArea(gDisplay, _window, _window, _textGC, x, y, width, height, x, ydest); } if(!cols) { x = 0; width = _width; XCopyArea(gDisplay, _window, _window, _textGC, x, y, width, height, x, ydest); } else { width = textWidth(); if(xamt < 0) { width += xamt; xdest = xinset(); x = xdest - xamt; } else { width -= xamt; x = xinset(); xdest = x + xamt; } XCopyArea(gDisplay, _window, _window, _textGC, x, y, width, height, xdest, ydest); } if(yamt < 0) { // need to update lines at bottom of window if(_lineNumbers) redrawLineNumbers(_visLines + lines, _visLines - 1, TRUE); redrawLines(_visLines + lines, _visLines - 1, 0, -1, TRUE); } else if(yamt > 0) { // need to update lines at top of window if(_lineNumbers) redrawLineNumbers(0, lines-1, TRUE); redrawLines(0, lines-1, 0, -1, TRUE); } if(xamt < 0) { // redraw columns on the right of the window redrawLines(0, _visLines - 1, _visCols + cols, _visCols - 1, TRUE); } else if(xamt > 0) { // redraw columns on the left of the window redrawLines(0, _visLines, 0, cols-1, TRUE); } end: drawCaret(TRUE); // Notify potential scroll bar of changes in total lines. if(lines && numLines() > _lineStarts->getSize()) callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); return TRUE; } void XeTextBase::syncScroll(void) { // // perform events that are on the queue for this window // immediately since GraphicsExpose events generated by // scrolling should be performed _now_ // The XSync call gaurantees that the GraphicsExpose events are // on the event queue by the time XCheckWindowEvent is called. XEvent event; XSync(gDisplay, False); while(XCheckWindowEvent(gDisplay, _window, 0xffffffff, &event)) if(event.type == GraphicsExpose) doExpose((XExposeEvent *)&event); } void XeTextBase::autoScroll(void) { // auto scroll vertically: if(_anchor.lineStart < _topLine || _anchor.lineStart - _topLine >= _visLines) { scrollV(_topLine - _anchor.lineStart + (_visLines - 1) / 2); } // auto scroll horizontally: if(_anchor.posStart < _leftColumn) { scrollH(_leftColumn - _anchor.posStart); } else if(_anchor.posStart - _leftColumn >= _visCols) { scrollH((_visCols - 1) - (_anchor.posStart-_leftColumn)); } drawCaret(TRUE); } void XeTextBase::setText(char *text, unsigned long len) { unsigned long i = 0; char *p; p = text; _text->replaceBuffer(text, len); _lineStarts->deleteRange(0, _lineStarts->getSize()-1); _lineStarts->insert(0, 0); while(i < len) { if(*p == '\n') { _lineStarts->insert(_lineStarts->getSize(), i + 1); } p++; i++; } } void XeTextBase::clearText(void) { if(!_text->getSize()) return; hiliteSelection(); _sel.selEnd = _sel.selStart; _anchor = _sel; _word = _sel; _lineStarts->deleteRange(0, _lineStarts->getSize()-1); _lineStarts->insert(0, 0); _text->deleteRange(0, _text->getSize() - 1); XClearArea(gDisplay, _window, 0, 0, _width, _height, False); setSelection(0,0,0,0,TRUE); } int XeTextBase::replaceSelection(char* s, long len, bool hiliteSel) { int range = selLength(); int x, y, width, height, ydest; int lh = lheight(); int pos, epos; long linesDeleted = 0, linesInserted = 0, currentLine; // If this object owns the X selection then clear it. if (_selOwner == this) clearXSelection(CurrentTime); // remove selection hilite. if(hiliteSel) hiliteSelection(); // special case for backspace character: if(s != NULL && *s == '\b' && len == 0 && !range) { if(_sel.selStart != 0) { drawCaret(FALSE); _sel.selEnd = _sel.selStart; _sel.selStart--; _sel.posEnd = _sel.posStart; if(_sel.posStart == 0) _sel.posStart = getPhysicalLength(--_sel.lineStart); else _sel.posStart = getPhysicalLength(_sel.lineStart, _sel.selStart); range = 1; } else { return -1; } } //int oldlen = getPhysicalLength(_sel.lineStart); epos = _sel.posEnd; pos = _sel.posStart; // first delete any selection. if(range) { _text->deleteRange(_sel.selStart, _sel.selEnd-1); if(_sel.lineStart != _sel.lineEnd) { linesDeleted = _sel.lineEnd - _sel.lineStart; _lineStarts->deleteRange(_sel.lineStart+1, _sel.lineEnd); } _sel.selEnd = _sel.selStart; _sel.posEnd = _sel.posStart; } else { drawCaret(FALSE); } currentLine = _sel.lineStart; // if replacing text, update data. if(len) { int i = 0; char* p = s; long line = _sel.lineStart; ulong base = _sel.selStart; while(i < len) { if(*p == '\n') _lineStarts->insert(++line, base + i + 1); p++; i++; } // now insert text _text->insert(_sel.selStart, s, len); // update variables. _sel.selEnd = _sel.selStart = _sel.selStart + len; currentLine = line; linesInserted = currentLine - _sel.lineStart; if(*(--p) == '\n') _sel.posStart = 0; else _sel.posStart = getPhysicalLength(currentLine, _sel.selStart); _sel.posEnd = _sel.posStart; } updateLinestarts(currentLine + 1, len - range); x = xinset() + cwidth() * (pos - _leftColumn); //eraseLine(_sel.lineStart - _topLine, x); redrawLines(_sel.lineStart - _topLine, _sel.lineStart - _topLine, pos - _leftColumn-1, _visCols, TRUE); // Only update the rest of the window if one or more lines // of text have been inserted or one or more have been deleted. // $$$ this needs to be looked at.. has a few redraw bugs/ineficiencies // when inserting/deleting text with a large number of lines if(linesInserted || linesDeleted) { int amt = linesInserted - linesDeleted; int theight = yinset() + textHeight(); x = xinset(); y = yinset() + (_sel.lineEnd + 1 - _topLine) * lh; ydest = y + (amt * lh); width = textWidth(); //_width - xinset(); height = theight; //_height - y; //XCopyArea(gDisplay, _window, _window, _vcopyGC, x, y, width, height, // x, ydest); if(amt > 0) { height = height - ydest; if(ydest < theight) { XCopyArea(gDisplay, _window, _window, _textGC, x, y, width, height, x, ydest); } //y = yinset() + (_sel.lineStart + 1 - _topLine) * lh; //XClearArea(gDisplay, _window, x, y, width, ydest-y, False); redrawLines(_sel.lineStart + 1 - _topLine, currentLine - _topLine, 0, -1, TRUE); } else { //int oydest=ydest; if(ydest < yinset()) { y += (yinset() - ydest); ydest = yinset(); } height -= y; if(y < theight) { XCopyArea(gDisplay, _window, _window, _textGC, x, y, width, height, x, ydest); } if(currentLine >= _sel.lineStart + 1) { //y = yinset() + (_sel.lineStart + 1 - _topLine) * lh; //XClearArea(gDisplay, _window, x, y, width, oydest - y, False); redrawLines(_sel.lineStart + 1 - _topLine, currentLine - _topLine, 0, -1, TRUE); } //y = theight + (amt * lh); //XClearArea(gDisplay, _window, x, y, width, theight - y, False); redrawLines(_visLines - 1 + amt, _visLines - 1, 0, -1, TRUE); } if(_lineNumbers) redrawLineNumbers(_sel.lineStart + 1 - _topLine, _visLines-1); } _sel.lineStart = _sel.lineEnd = currentLine; _anchor = _sel; _word = _sel; return 0; } void XeTextBase::getPosition(int x, int y, long& line, long& col) { int xmax = textWidth(); int ymax = textHeight() - 1; x -= xinset(); y -= yinset(); if(y < 0) y = 0; if(y > ymax) y = ymax; if(x < 0) x = 0; if(x > xmax) x = xmax; line = y / lheight() + _topLine; col = x / cwidth() + _leftColumn; } SelStruct XeTextBase::getSel(long line, long column, SelMode mode) { SelStruct sel; memset(&sel, 0, sizeof(SelStruct)); // Clamp possible line overflow if(line < 0) line = 0; if((ulong)line >= _lineStarts->getSize()) line = _lineStarts->getSize() - 1; if(column < 0) column = 0; sel.lineEnd = sel.lineStart = line; sel.selEnd = sel.selStart = getSelPos(line, column); sel.posEnd = sel.posStart = column; if(mode == SelWord) { getWord(&sel); } return sel; } // // setSelection(): This version of set selection clears the previous // selection and then hilites a new area between indicies start // and end and between the lines linestart and lineend. This // function would be used mainly by undo commands and search // routines. If lineend is omitted ....??? // void XeTextBase::setSelection(long start, long end, long linestart, long lineend, bool hiliteSel) { if(hiliteSel) // unhilite selection hiliteSelection(); // Do some bounds checking: if(start < 0) return; if(end < 0) return; if((ulong)start > _text->getSize()) return; if((ulong)end > _text->getSize()) return; if(linestart < 0) return; if((ulong)linestart >= _lineStarts->getSize()) return; if(lineend < 0) lineend = linestart; if((ulong)lineend >= _lineStarts->getSize()) return; // Hide the caret: if(hiliteSel) drawCaret(FALSE); // Setup selection struct: _sel.selStart = start; _sel.selEnd = end; _sel.lineStart = linestart; _sel.lineEnd = lineend; _sel.posStart = getPhysicalLength(linestart, start); if(start != end) _sel.posEnd = getPhysicalLength(lineend, end); else _sel.posEnd = _sel.posStart; if(hiliteSel) { drawCaret(TRUE); hiliteSelection(FALSE); // hilite selection } _anchor = _sel; _word = _sel; } void XeTextBase::printSel(SelStruct sel) { printf("selStart = %ld selEnd = %ld\n", sel.selStart, sel.selEnd); printf("lineStart = %ld lineEnd = %ld\n", sel.lineStart, sel.lineEnd); printf("posStart = %ld posEnd = %ld\n", sel.posStart, sel.posEnd); } // // setSelection(): This variant of setSelection clears the previous // selection and sets the selection to the position of line and // column. If mode is SelChar then the selection becomes a caret // at that point, if mode is SelWord or SelLine the selection is // expanded to the surrouding line or word. // The value of column is set the value of the actual column found // for the selection. // This function would usually be used by arrow movement events // or button click events to position the selection caret. // void XeTextBase::setSelection(long line, long column, SelMode mode, bool hiliteSel) { if(hiliteSel) { hiliteSelection(); // unhilite selection drawCaret(FALSE); } _sel = getSel(line, column, mode); if(hiliteSel) { drawCaret(TRUE); hiliteSelection(FALSE); // hilite selection } _anchor = _sel; _word = _sel; _swapAnchor = FALSE; if(selLength()) swapSel(_anchor); } // // stretchSelection(): Extends the selection around the _anchor by the given // selpos (text index) and line offsets. // void XeTextBase::stretchSelection(long seloffset, long lineoffset, SelMode mode, bool hiliteSel) { long selstart = _anchor.selStart + seloffset; long linestart = _anchor.lineStart + lineoffset; // Do some bounds checking: if(selstart < 0) return; if((ulong)selstart > _text->getSize()) return; if(linestart < 0) return; if((ulong)linestart >= _lineStarts->getSize()) return; extendSelection(linestart, getPhysicalLength(linestart, selstart), mode, FALSE, hiliteSel); } // // extendSelection(): extends the selection from its current point // to the given line and column. _anchor is used as the // anchor point of the selection. // void XeTextBase::extendSelection(long line, long column, SelMode mode, bool shiftSel, bool hiliteSel) { SelStruct newSel; // Clamp possible line overflow if(line < 0) line = 0; if((ulong)line >= _lineStarts->getSize()) line = _lineStarts->getSize() - 1; if(column < 0) column = 0; newSel.selStart = getSelPos(line, column); newSel.lineStart = line; newSel.posStart = column; SelStruct prevAnchor = _anchor; // used by _noPlanemask redraw method if(mode == SelWord) { if(newSel.selStart < _word.selStart && !_swapAnchor && !shiftSel) { if(_anchor.selStart > _word.selEnd) { swapSel(_word); extendSelection(_word, _anchor, hiliteSel); swapSel(_word); } _anchor = _word; _swapAnchor = TRUE; } else if(newSel.selStart >= _word.selEnd && _swapAnchor && !shiftSel) { if(_anchor.selStart < _word.selStart) { extendSelection(_word, _anchor, hiliteSel); } _anchor = _word; swapSel(_anchor); _swapAnchor = FALSE; } bool rev = (newSel.selStart < _anchor.selEnd); getNextPos(rev, &newSel); } if(!selLength() && newSel.selStart != _sel.selStart) drawCaret(FALSE); extendSelection(newSel, _anchor, hiliteSel); _sel = _anchor; if(_anchor.selStart > _anchor.selEnd) swapSel(_sel); if (_noPlanemask && hiliteSel) { // it's quicker to redraw this way if the hardware doesn't support // a planemask if(_anchor.lineStart < prevAnchor.lineStart) { redrawLines(_anchor.lineStart - _topLine, prevAnchor.lineStart - _topLine, 0, -1, TRUE); } else if (_anchor.lineStart > prevAnchor.lineStart) { redrawLines(prevAnchor.lineStart - _topLine, _anchor.lineStart - _topLine, 0, -1, TRUE); } else if (_anchor.lineStart - _topLine < _visLines) { redrawLines(_anchor.lineStart - _topLine, _anchor.lineStart - _topLine, 0, -1, TRUE); } } if(newSel.selStart == _anchor.selEnd) drawCaret(TRUE); } void XeTextBase::extendSelection(const SelStruct& newSel, SelStruct& anchor, bool hiliteSel) { long selStart, lineStart, posStart; selStart = anchor.selStart; lineStart = anchor.lineStart; posStart = anchor.posStart; anchor.selStart = newSel.selStart; anchor.lineStart = newSel.lineStart; anchor.posStart = newSel.posStart; if (!hiliteSel || _noPlanemask) return; XRectangle r; int c = newSel.posStart - _leftColumn; int w = posStart - _leftColumn; int lh = lheight(); int cw = cwidth(); int ldiff = 0, rdiff = 0; //ytemp = 0; if(c < 0) { ldiff = -c; c = 0; } if(c > _visCols) { rdiff = c - _visCols; c = _visCols; } if(w < 0) w = 0; r.height = lh; if(newSel.lineStart < lineStart) { // Hilite lineStart to left of posStart if(newSel.lineStart - _topLine < _visLines-1 && lineStart < _topLine + _visLines) { r.x = xinset(); r.y = yinset() + (lineStart - _topLine) * lh; r.width = w * cw; XFillRectangle(gDisplay, _window, _invertGC, r.x, r.y, r.width, lh); } // then hilite newSel.lineStart to the right of c if(newSel.lineStart >= _topLine) { r.x = xinset() + c * cw; r.y = yinset() + (newSel.lineStart - _topLine) * lh; r.width = textWidth() - r.x + xinset(); XFillRectangle(gDisplay, _window, _invertGC, r.x, r.y, r.width, lh); } if(lineStart - newSel.lineStart > 1) { r.x = xinset(); r.y = yinset() + (newSel.lineStart + 1 - _topLine) * lh; r.width = textWidth(); if(lineStart >= _topLine + _visLines) r.height = yinset() + textHeight() - r.y; else r.height = lh * (lineStart - newSel.lineStart - 1); XFillRectangle(gDisplay, _window, _invertGC, r.x, r.y, r.width, r.height); } } else if(newSel.lineStart > lineStart) { // first hilite lineStart to right of posStart if(lineStart >= _topLine) { r.x = xinset() + w * cw; r.y = yinset() + (lineStart - _topLine) * lh; r.width = textWidth() - r.x + xinset(); XFillRectangle(gDisplay, _window, _invertGC, r.x, r.y, r.width, lh); } // then hilite newSel.lineStart to left of c if(newSel.lineStart - _topLine < _visLines) { r.x = xinset(); r.y = yinset() + (newSel.lineStart - _topLine) * lh; r.width = c * cw; XFillRectangle(gDisplay, _window, _invertGC, r.x, r.y, r.width, lh); } if(newSel.lineStart - lineStart > 1) { r.x = xinset(); r.width = textWidth(); if(lineStart < _topLine) r.y = yinset(); else r.y = yinset() + (lineStart + 1 - _topLine) * lh; r.height = yinset() + (newSel.lineStart - _topLine) * lh - r.y; XFillRectangle(gDisplay, _window, _invertGC, r.x, r.y, r.width, r.height); } } else if(newSel.lineStart - _topLine < _visLines) { r.x = c * cw + xinset(); r.y = (newSel.lineStart - _topLine) * lh + yinset(); if(newSel.posStart > posStart) { r.width = (c - w) * cw; r.x = w * cw + xinset(); } else if(newSel.posStart < posStart) { r.width = (posStart - newSel.posStart - ldiff) * cw; } else { r.width = 0; } XFillRectangle(gDisplay, _window, _invertGC, r.x, r.y, r.width, r.height); } } // Selection Crap void XeTextBase::getSelectionData(char** data, int& len) { len = selLength(); if(_text->getSize()) *data = &(*_text)[_sel.selStart]; } // Default behavoir for receiving selection data void XeTextBase::selectionNotify(char* data, int len) { replaceSelection(data, len); autoScroll(); }