// // Copyright (C) 1996 Ben Ross // // You may distribute under the terms of the GNU General Public // License as specified in the COPYING file. // // $Id: XeText.C,v 1.12 2002/01/06 04:39:39 ben Exp $ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _SGI_SOURCE #include #include #endif #include #define CUT_BUFFER "XE_CUT_BUFFER" // Cut buffer atom name // These defines affect the speed of auto-scrolling. #define STIMEOUT 30000 // microsecond interval between scrolling #define VSTOL 1.5 // Vertical accelleration divisor #define HSTOL 3 // Horizontal accelleration divisor #define MAX_UNDOS 100 // // $$$ todo?: make Undo a KrLink and use the KrLink to make the // _undoList and _redoList instead of using XeSpList // class Undo { private: Undo(XeText* text, char undoType, int dataLen, char aRev = 0) : type(undoType), anchorRev(aRev), len(dataLen) { selSwapped = text->_sel.selEnd != text->_anchor.selEnd; caretPos = text->_caretPos; save = FALSE; } char type; char save : 1; char anchorRev : 1; char selSwapped : 1; long len; long caretPos; friend class XeText; }; // $$$ add word wrap mode, to preserve that word-wrap state? // nah, probably implement word wrap directly in XeText::replaceSelection() struct TextData { long numLines; }; #define SOFT_TAB_SIZE 4 XeText::XeText(XeObject *parent, const char *name, const char* className) : XeTextBase(parent, name, className) { _autoIndent = TRUE; _scrollAmt = 2; _caretPos = 0; _selMode = SelChar; _readOnly = None; _xcutBuffer = NULL; _doingPaste = FALSE; _redoPos = 0; _saveEvent = _changeEvent = NULL; _softTab = SOFT_TAB_SIZE; _stimeout = STIMEOUT; _vstol = VSTOL; _hstol = HSTOL; _numUndos = 0; _maxUndos = MAX_UNDOS; //_borderHilite = BlackPixel(gDisplay, gScreenNum); _borderHilite = lookupColour(this, "grey40"); //XSetWindowBorderWidth(gDisplay, _window, 2); XSetWindowBorder(gDisplay, _window, _background); // // Get Atom for setting and getting cut buffer property. // _cutBuffer = XInternAtom(gDisplay, CUT_BUFFER, False); } XeText::~XeText(void) { // clean up lists. std::slist::iterator iter = _redoList.begin(); while(iter != _redoList.end()) { delete *iter; iter++; } iter = _undoList.begin(); while(iter != _undoList.end()) { delete *iter; iter++; } } void XeText::getResources(void) { // get X resources from Xrm database XeTextBase::getResources(); int ops = XeTextKeys::getNumOps(); XrmValue value; // Read in key map resources: for (int i=0; i < ops; i++) { const char* name = XeTextKeys::_mappings[i].name; KeySym key; uint mods; if (getResource(name, name, &value)) { parseKeyString(value.addr, value.size-1, key, mods); if (key == NoSymbol) { key = XeTextKeys::_mappings[i].defaultKey; mods = XeTextKeys::_mappings[i].defaultModifiers; } } else { key = XeTextKeys::_mappings[i].defaultKey; mods = XeTextKeys::_mappings[i].defaultModifiers; } _keyTable.add((void*)(XeTextKeys::_mappings[i].op), key, mods); } } void XeText::setReadOnly(bool ro) { if (ro && _readOnly == None) { // setting to read-only mode _readOnly = _activeGC; _activeGC = _inactiveGC; } else if (!ro && _readOnly != None) { // setting to read/write mode _activeGC = _readOnly; _readOnly = None; } else return; // no effect _caretGC = _activeGC; drawCaret(TRUE); } int XeText::doEvent(XEvent* theEvent) { switch(theEvent->type) { case KeyPress: doKey((XKeyEvent *)theEvent); break; case ButtonPress: case ButtonRelease: doButton((XButtonEvent *)theEvent); break; case MotionNotify: doMotion((XMotionEvent *)theEvent); break; default: XeTextBase::doEvent(theEvent); break; } return 0; } void XeText::doKey(XKeyEvent* event) { int bufsize = 20; char buffer[20], c; KeySym key; uint state = event->state; KeyFunc keyFunc; XLookupString(event, buffer, bufsize, &key, NULL); if ((keyFunc = (KeyFunc)_keyTable.lookup(key, state)) != NULL) { // these functions don't affect cursor position: if (keyFunc == (KeyFunc)XeTextKeys::page_up || keyFunc == (KeyFunc)XeTextKeys::page_down || keyFunc == (KeyFunc)XeTextKeys::home || keyFunc == (KeyFunc)XeTextKeys::end) { keyFunc(this); callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); return; } //autoScroll(); keyFunc(this); // $$$ perhaps this stuff should be handled by the // setSelection* routines if (selLength() && _selOwner != this) setXSelection(event->time); else if (!selLength() && _selOwner == this) clearXSelection(event->time); if (_anchor.lineStart < _topLine) scrollV(_scrollAmt); else if (_anchor.lineStart - _topLine >= _visLines) scrollV(-_scrollAmt); autoScroll(); callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); return; } if(_readOnly) return; state = ControlMask | Mod1Mask; if (event->state & state) return; if(key == XK_BackSpace) { c = '\b'; if(replaceSelection(&c, 0) < 0) { callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); return; } } else if(key == XK_Delete) { if (!selLength()) XeTextKeys::right(this); c = '\b'; if(replaceSelection(&c, 0) < 0) { callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); return; } } else if((key==XK_Return) || (key==XK_KP_Enter) || (key==XK_Linefeed)) { doNewLine(); } else if((key >= XK_KP_Multiply && key <= XK_KP_9) || (key >= XK_space && key <= XK_ydiaeresis)) { // handle normal key replaceSelection(&buffer[0], 1); } else if(key == XK_Tab) { doTab(); } else { return; } _caretPos = _sel.posStart; autoScroll(); callCallbacks(XE_CB_TEXTKEY, NULL); } void XeText::doNewLine(void) { // // Implements auto-indent // if (_autoIndent) { ulong index = (*_lineStarts)[_sel.lineStart]; if (index >= _text->getSize()) goto noindent; char* p = &(*_text)[index]; char* s = p; long len = 0; while (*p != '\n' && index < _text->getSize()) { if (!isWhitespace(*p)) break; if (index == (ulong)_sel.selStart) // Thank uuuu Brian! break; len++; index++; p++; } if (len) { char* t = (char*)alloca(len + 1); *t = '\n'; memcpy(t + 1, s, len); replaceSelection(t, len + 1); return; } } noindent: char c = '\n'; replaceSelection(&c, 1); } void XeText::doTab(void) { char c; if (_softTab >= _tab) { c = '\t'; replaceSelection(&c, 1); return; } // try out soft tabbing int pos = _sel.posStart; int spos = pos; int phtab_pos, nhtab_pos; int pstab_pos, nstab_pos; if (!(pos % _tab)) { phtab_pos = pos - _tab; nhtab_pos = pos + _tab; } else { phtab_pos = pos / _tab * _tab; nhtab_pos = phtab_pos + _tab; } // $$$ pstab_pos probably wont be needed pstab_pos = pos / _softTab * _softTab; if (!(pos % _softTab)) nstab_pos = pos + _softTab; else nstab_pos = pstab_pos + _softTab; // count previous spaces char* p = NULL; if (_text->getSize() && _sel.selStart) p = &(*_text)[_sel.selStart - 1]; while (spos > 0) { if (*p != 32) break; if (spos == phtab_pos) break; spos--; p--; } int numSpaces = _sel.posStart - spos; int tabLen = nstab_pos - pos; if (nhtab_pos == nstab_pos) { if (numSpaces) { // $$$ don't consider undo at the moment. // // replace previous numSpaces with a '\t' if (selLength()) { c = '\b'; replaceSelection(&c, 0); } stretchSelection(-numSpaces, 0, SelChar, FALSE); c = '\t'; replaceSelection(&c, 1, FALSE); } else { // insert single tab. c = '\t'; replaceSelection(&c, 1); } } else { // insert nstab_pos - pos spaces. char* cp = (char*)alloca(tabLen); memset(cp, 32, tabLen); replaceSelection(cp, tabLen); } } #define MAX_OPEN_BRACES 50 #define MAX_BRACE_PAIRS 10 inline bool XeText::openBrace(long index, long line, Index* table, int& count) { if (count >= MAX_OPEN_BRACES - 1) // give up completely return false; if (count >= 0) { table[count].pos = index; table[count].line = line; } count++; return true; } inline bool XeText::closeBrace(long index, long line, Index* table, int& count) { count--; if (index < _sel.selStart) // brace set closes before sel start, ignore return true; if (count < 0) // unmatched closing brace in range $$$ give up? return false; // brace set opens after sel start if (table[count].pos >= _sel.selStart) return true; setSelection(table[count].pos, index + 1, table[count].line, line); autoScroll(); return false; } void XeText::braceMatchSelect() { // $$$ new technique: // scan from beginnning of text, build up a table for // each type of brace which is indexed by the number of unclosed // brace sets with the value the index into _text // quotes can be dealt with reliably when scanning from the start // of the text. // $$$ make this a config file param ... // Note: adding <> might be useful sometimes, but the existence // of lone <'s and >'s in mathematical expressions stuffs it up ... const char* braceChars = "(){}[]"; int numBracePairs = strlen(braceChars) / 2; Index* braceTable = (Index*)alloca(sizeof(Index) * MAX_OPEN_BRACES * numBracePairs); memset(braceTable, 0, sizeof(Index) * MAX_OPEN_BRACES * numBracePairs); int* braceCount = (int*)alloca(sizeof(int) * numBracePairs); memset(braceCount, 0, sizeof(int) * numBracePairs); enum { // quote/comment state NONE, DOUBLE, DOUBLE_BS, SINGLE, SINGLE_BS, SLASH, CPP_COMMENT, // C++ comment C_COMMENT, C_COMMENT_END }; // 7 inputs: // ' " \ / * \n (other) // 9 states from enum above: char stateTable[7][9] = { // input = ' { SINGLE, DOUBLE, DOUBLE, NONE, SINGLE, SINGLE, CPP_COMMENT, C_COMMENT, C_COMMENT }, // input = " { DOUBLE, NONE, DOUBLE, SINGLE, SINGLE, DOUBLE, CPP_COMMENT, C_COMMENT, C_COMMENT }, // input = '\' { NONE, DOUBLE_BS, DOUBLE, SINGLE_BS, SINGLE, NONE, CPP_COMMENT, C_COMMENT, C_COMMENT }, // input = / { SLASH, DOUBLE, DOUBLE, SINGLE, SINGLE, CPP_COMMENT, CPP_COMMENT, C_COMMENT, NONE }, // input = * { NONE, DOUBLE, DOUBLE, SINGLE, SINGLE, C_COMMENT, CPP_COMMENT, C_COMMENT_END, C_COMMENT_END }, // input = \n { NONE, DOUBLE, DOUBLE_BS, SINGLE, SINGLE_BS, NONE, NONE, C_COMMENT, C_COMMENT }, // input = other { NONE, DOUBLE, DOUBLE, SINGLE, SINGLE, NONE, CPP_COMMENT, C_COMMENT, C_COMMENT }, }; int state = NONE; int input, pair; long index = 0; long line = 0; char* p = &(*_text)[0]; for (; (ulong)index < _text->getSize(); index++, p++) { char ch = *p; // convert character into a state table index: switch (ch) { case '\'': input = 0; break; case '"': input = 1; break; case '\\': input = 2; break; case '/': input = 3; break; case '*': input = 4; break; case '\n': input = 5; line++; break; default: input = 6; break; } // Look up new state: state = stateTable[input][state]; // Keep going, ignoring anything quoted or commented if (state != NONE) continue; for (pair = 0; pair < numBracePairs; pair++) { if (ch == braceChars[pair*2]) { if (!openBrace(index, line, &braceTable[pair * MAX_OPEN_BRACES], braceCount[pair])) return; } else if (ch == braceChars[pair*2 + 1]) { if (!closeBrace(index, line, &braceTable[pair * MAX_OPEN_BRACES], braceCount[pair])) return; } } } } // // Copy selection then delete it. // void XeText::cutLine() { // Don't do anything if more than one line is selected if (_sel.lineStart != _sel.lineEnd) return; long len; long lstart, lend; // Line numbers before long pstart, pend; // Positions in line before long astart, aend; // Absolute positions before long n_lstart, n_lend; // Line numbers after long n_pstart, n_pend; // Positions in line after long n_astart, n_aend; // Absolute positions after /* * Initialize before-vars * */ lstart= _sel.lineStart; // Start line number lend = _sel.lineEnd; // The same pstart= _sel.posStart; // Cursor position in line pend = _sel.posEnd; // The same astart= _sel.selStart; // Absolut index of cursor (in text) aend = _sel.selEnd; // The same len = getPhysicalLength(lstart); // Length of current line /* * Compute after-vars * */ n_astart = astart - pstart ; // Absolut index of 1st character of the current line n_aend = n_astart + len ; // ................ last ............................ n_aend = n_aend + 1 ; // We want the final '\n' too n_pstart = pstart ; // Must be the same for replaceSelection() n_pend = pstart ; // to work : it's a way to get n_lstart = lstart ; // this function to cut properly n_lend = lstart + 1 ; // the current line if ( (ulong)(lstart) == ( _lineStarts->getSize() - 1 ) ) // If we are on last line { // we wan' t delete a // $$$ really need to return here n_lend -= 1 ; // non-existing line n_aend -= 1 ; } #if XEDEBUGALTY == 1 printf("\n%s\n", "--- cutting line -----"); printf("%s%d\n","_sel.posStart :",(int)(pstart) ); printf("%s%d\n","_sel.posEnd :",(int)(pend) ); printf("%s%d\n","_sel.lineStart:",(int)(lstart) ); printf("%s%d\n","_sel.lineEnd :",(int)(lend) ); printf("%s%d\n","_sel.selStart :",(int)(astart) ); printf("%s%d\n","_sel.selEnd :",(int)(aend) ); printf("%s\n", "........................."); printf("%s%d\n","line length :",(int)(len) ); printf("%s%d\n","_sel.posStart :",(int)(n_pstart) ); printf("%s%d\n","_sel.posEnd :",(int)(n_pend) ); printf("%s%d\n","_sel.lineStart:",(int)(n_lstart) ); printf("%s%d\n","_sel.lineEnd :",(int)(n_lend) ); printf("%s%d\n","_sel.selStart :",(int)(n_astart) ); printf("%s%d\n","_sel.selEnd :",(int)(n_aend) ); #endif /* * Let's do it ! * */ // $$$ this doesn't actually copy the selection setSelection(n_astart, n_aend, n_lstart, n_lend, FALSE); // Put our computed values replaceSelection(NULL, 0L, FALSE); // Go ! autoScroll(); // Go ! callCallbacks(XE_CB_TEXTKEY, NULL); // What's this ? } void XeText::doButton(XButtonEvent *event) { long col, line; getPosition(event->x, event->y, line, col); if(event->type == ButtonPress && event->state & ShiftMask) { if(event->button == Button1) { _selMode = SelChar; _shiftSel = TRUE; // vital that this is set to true. extendSelection(line, col); } else if(event->button == Button2) { _selMode = SelWord; _shiftSel = TRUE; // vital that this is set to true. extendSelection(line, col, SelWord, TRUE); } } else if(event->type == ButtonPress && event->state & Mod1Mask) { if(event->button == Button2) { if(_selOwner == this && selLength()) { int len = selLength(); _xcutBuffer = new char[len + 1]; memcpy(_xcutBuffer, &(*_text)[_sel.selStart], len); _xcutBuffer[len] = 0; } setSelection(line, col); _doingPaste = TRUE; } } else if(event->type == ButtonPress) { switch(event->button) { case Button1: setSelection(line, col); //_caretPos = _sel.posStart; // Upon suggestion of Brian: _caretPos = col; _selMode = SelChar; _shiftSel = FALSE; break; case Button2: setSelection(line, col, SelWord); _caretPos = col; _selMode = SelWord; _shiftSel = FALSE; break; case Button3: _lastLine = line - _topLine; _lastCol = col - _leftColumn; break; } } else if(event->type == ButtonRelease) { _shiftSel = FALSE; if(_doingPaste && event->button == Button2) { xpaste(); _doingPaste = FALSE; } else if(event->button != Button3) { if(selLength()) { //XSetWindowBorder(gDisplay, _window, _borderHilite); setXSelection(event->time); } else if(_selOwner == this) { selectionClear(); clearXSelection(event->time); } } } } void XeText::doMotion(XMotionEvent* event) { if(event->state & Button3Mask) { dragAround(event); return; } if(event->state & Button2Mask && _doingPaste) { long line, col; getPosition(event->x, event->y, line, col); XeTextBase::setSelection(line, col); autoMotionScroll(event, ABSOLUTE); return; } if((event->state & Button1Mask && _selMode == SelChar) || (event->state & Button2Mask && _selMode == SelWord)) { dragSelect(event); autoMotionScroll(event); } } void XeText::dragAround(XMotionEvent *event) { XEvent current, ahead; XMotionEvent *theEvent = event; while(XEventsQueued(gDisplay, QueuedAfterReading) > 0) { XPeekEvent(gDisplay, &ahead); if(ahead.type != MotionNotify) break; if(ahead.xmotion.window != _window) break; XNextEvent(gDisplay, ¤t); theEvent = (XMotionEvent*)¤t; } int x = theEvent->x - xinset(); int y = theEvent->y - yinset(); int newline = y / lheight(); int newcol = x / cwidth(); int ydiff = newline - _lastLine; int xdiff = newcol - _lastCol; _lastLine = newline; _lastCol = newcol; //if(x < 0 || x > textWidth() || if (y < 0 || y > textHeight()) { // $$$ need to be able to limit an axis (horizontal or vertical) autoMotionScroll(theEvent, NONE, Forward); } else if(xdiff || ydiff) { scroll(ydiff, xdiff, TRUE); syncScroll(); if(ydiff) callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); } } void XeText::dragSelect(XMotionEvent *event) { // This is to prevent flicker when the text is at the // top and y is less than yinset() if(event->y < yinset()) return; long line, col; getPosition(event->x, event->y, line, col); XeTextBase::extendSelection(line, col, _selMode, _shiftSel); } void XeText::autoMotionScroll(XMotionEvent* event, ScrollMode mode, ScrollDirection dir) { // Here's a bit 'o a hack until timeout callbacks or // until Krypton! Take over the main event loop with select // so auto-scrolling happens in defined time increments. XEvent theEvent; int xfd = ConnectionNumber(gDisplay); fd_set rset; timeval time; long line, col, mask = 0xffffffff; int amt=0, offset; ScrollSide side; if (mode == EXTEND || mode == ABSOLUTE) dir = Reverse; if(event->x < xinset()) { side = Left; offset = xinset() - event->x; } else if(event->x > xinset() + textWidth()) { side = Right; offset = event->x - xinset() - textWidth(); } else if(event->y < yinset()) { side = Top; offset = yinset() - event->y; } else if(event->y > yinset() + textHeight()) { side = Bottom; offset = event->y - yinset() - textHeight(); } else { return; } getPosition(event->x, event->y, line, col); if(side == Top || side == Bottom) amt = int((offset / lheight()) / _vstol + 1); else if(side == Left || side == Right) amt = int((offset / cwidth()) / _hstol + 1); if (dir == Forward) amt = -amt; while(1) { // scroll according to which side the mouse dragged // off the window. // $$$ should call scrollExtendSelection() before scrollH()/scrollV(). // This doesn't work at the moment as extendSelection() doesn't // deal with extending beyond the visible area at the moment. switch(side) { case Left: col -= amt; if(!scrollH(amt)) return; scrollExtendSelection(line, col, mode); break; case Right: col += amt; if(!scrollH(-amt)) return; scrollExtendSelection(line, col, mode); break; case Top: line -= amt; if(!scrollV(amt)) { if(_sel.selStart > 0 && mode == EXTEND) XeTextBase::extendSelection(0, 0); return; } scrollExtendSelection(line, col, mode); break; case Bottom: line += amt; if(!scrollV(-amt)) return; scrollExtendSelection(line, col, mode); break; } if (side == Top || side == Bottom) callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); //XFlush(gDisplay); XSync(gDisplay, FALSE); while (XCheckMaskEvent(gDisplay, mask, &theEvent)) switch (scrollEvent(theEvent, amt, offset, line, col, dir, side)) { case 1: goto mfinish; case 2: return; } time.tv_sec = 0; time.tv_usec = _stimeout; // timeout microseconds while(time.tv_sec + time.tv_usec > 0) { FD_ZERO(&rset); FD_SET(xfd, &rset); #ifdef linux select(FD_SETSIZE, &rset, NULL, NULL, &time); #else portaselect(FD_SETSIZE, &rset, NULL, NULL, &time); #endif if(FD_ISSET(xfd, &rset)) { while (XCheckMaskEvent(gDisplay, mask, &theEvent)) { switch (scrollEvent(theEvent, amt, offset, line, col, dir, side)) { case 1: goto mfinish; case 2: return; } } } gApp->doUpdate(); XFlush(gDisplay); } } mfinish: if (mode == NONE) { _lastLine = (theEvent.xmotion.y - yinset()) / lheight(); _lastCol = (theEvent.xmotion.x - xinset()) / cwidth(); } } void XeText::scrollExtendSelection(long line, long column, ScrollMode mode) { switch (mode) { case EXTEND: XeTextBase::extendSelection(line, column, _selMode, _shiftSel); break; case ABSOLUTE: XeTextBase::setSelection(line, column); break; default: ; } } int XeText::scrollEvent(XEvent& theEvent, int& amt, int& offset, long& line, long& col, ScrollDirection dir, ScrollSide side) { if(theEvent.type == ButtonRelease) { if(selLength()) setXSelection(theEvent.xbutton.time); return 2; } else if(theEvent.type == GraphicsExpose) { doExpose((XExposeEvent*)&theEvent); } else if(theEvent.type == MotionNotify) { getPosition(theEvent.xmotion.x, theEvent.xmotion.y, line, col); if(side == Left) { if(theEvent.xmotion.x >= xinset()) return 1; offset = xinset() - theEvent.xmotion.x; amt = int((offset / cwidth()) / _hstol + 1); } else if(side == Right) { if(theEvent.xmotion.x <= xinset() + textWidth()) return 1; offset = theEvent.xmotion.x - xinset() - textWidth(); amt = int((offset / cwidth()) / _hstol + 1); } else if(side == Top) { if(theEvent.xmotion.y >= yinset()) return 1; offset = yinset() - theEvent.xmotion.y; amt = int((offset / lheight()) / _vstol + 1); } else if(side == Bottom) { if(theEvent.xmotion.y <= yinset() + textHeight()) return 1; offset = theEvent.xmotion.y - yinset() - textHeight(); amt = int((offset / lheight()) / _vstol + 1); } if (dir == Forward) amt = -amt; } return 0; } void XeText::clear(void) { if(_readOnly) return; if(selLength()) { replaceSelection(NULL, 0L); autoScroll(); callCallbacks(XE_CB_TEXTKEY, NULL); } } // // Copy selection into property on root window // void XeText::copy(void) { int range = selLength(); if(!range) return; char* data = &(*_text)[_sel.selStart]; // set cut buffer property on root window. XChangeProperty(gDisplay, RootWindow(gDisplay, gScreenNum), _cutBuffer, XA_STRING, 8, PropModeReplace, (uchar*)data, range); } // // Copy selection then delete it. // void XeText::cut(void) { if(_readOnly) return; if(!selLength()) return; copy(); replaceSelection(NULL, 0L); autoScroll(); callCallbacks(XE_CB_TEXTKEY, NULL); } // // Paste data from cut buffer property on root window if any. // void XeText::paste(void) { if(_readOnly) return; int result, fmt_return; ulong length, bytes_after_return; uchar* data_return; Atom type_return; result = XGetWindowProperty(gDisplay, RootWindow(gDisplay, gScreenNum), _cutBuffer, 0, LONG_MAX, False, XA_STRING, &type_return, &fmt_return, &length, &bytes_after_return, &data_return); if(result == Success && length != 0) { replaceSelection((char*)data_return, length); XFree(data_return); } autoScroll(); callCallbacks(XE_CB_TEXTKEY, NULL); } // // Stuff to deal with X-selection and crap: // void XeText::xpaste(void) { if(_readOnly) return; if(_xcutBuffer) { replaceSelection(_xcutBuffer, strlen(_xcutBuffer)); autoScroll(); callCallbacks(XE_CB_TEXTKEY, NULL); delete[] _xcutBuffer; _xcutBuffer = NULL; clearXSelection(CurrentTime); return; } // Don't enable pasting into ourself if(_selOwner == (XeObject*)this) return; if(_selOwner) { char* data; int len; _selOwner->getSelectionData(&data, len); if(len) { replaceSelection(data, len); autoScroll(); callCallbacks(XE_CB_TEXTKEY, NULL); } } else { getRemoteSelection(CurrentTime); } } void XeText::selectionNotify(char* data, int len) { replaceSelection(data, len); autoScroll(); callCallbacks(XE_CB_TEXTKEY, NULL); } void XeText::selectionClear(void) { //XSetWindowBorder(gDisplay, _window, _background); } void XeText::selectAll(void) { setSelection(0, _text->getSize(), 0, _lineStarts->getSize() - 1, TRUE); if (selLength() && _selOwner != this) setXSelection(CurrentTime); } // $$$ with the intrdoduction of undo stuff, the text shifting will // become somewhat more complicated since it will need to be saved as // a single undo event. void XeText::shiftText(int) { if(!selLength()) return; // Extend selection to fit on full lines: if(_anchor.selStart > _sel.selStart) { if(_sel.posEnd) extendSelection(_sel.lineEnd+1, 0); swapSel(_anchor); extendSelection(_sel.lineStart, 0); } else { extendSelection(_sel.lineStart, 0); swapSel(_anchor); if(_sel.posEnd) extendSelection(_sel.lineEnd+1, 0); } swapSel(_anchor); // Next shift text in desired direction: (right for now) } void XeText::reset(void) { clearRedoBuf(); std::slist::iterator iter = _undoList.begin(); while(iter != _undoList.end()) { delete *iter; iter++; } _undoBuf.remove(); _undoList.clear(); _numUndos = 0; long line = (ulong(_sel.lineStart) >= _lineStarts->getSize()) ? _lineStarts->getSize() - 1 : _sel.lineStart; long sel = (*_lineStarts)[line]; XeTextBase::setSelection(sel, sel, line, line, FALSE); redrawLines(0, _visLines, 0, _visCols, TRUE); redrawLineNumbers(0, _visLines, TRUE); drawCaret(TRUE); callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); } // // This section deals with undo/redo events: // void XeText::saveEvent(void) { clearRedoBuf(); std::slist::iterator iter = _undoList.begin(); while(iter != _undoList.end()) { if((*iter)->type <= InsertSelection) break; iter++; } _saveEvent = (iter != _undoList.end()) ? *iter : NULL; _changeEvent = NULL; if(_saveEvent) _saveEvent->save = TRUE; } void XeText::changeEvent(void) { _changeEvent = (!_undoList.empty()) ? _undoList.front() : NULL; } int XeText::undo(void) { if (_undoList.empty()) return 0; Undo* event = _undoList.front(); _caretPos = event->caretPos; switch(event->type) { case Select: { Undo currState(this, event->type, sizeof(SelStruct)); SelStruct undoSel; _undoBuf.get(&undoSel, sizeof(SelStruct), _redoPos); _undoBuf.replace(&_sel, sizeof(SelStruct), _redoPos); _redoPos += sizeof(SelStruct); XeTextBase::setSelection(undoSel.selStart, undoSel.selEnd, undoSel.lineStart, undoSel.lineEnd); if(event->selSwapped) swapSel(_anchor); *event = currState; } break; case SelectExtend: { if (_sel.selStart == _sel.selEnd) drawCaret(FALSE); Undo currState(this, event->type, sizeof(SelStruct), event->anchorRev); SelStruct undoSel; _undoBuf.get(&undoSel, sizeof(SelStruct), _redoPos); _undoBuf.replace(&_anchor, sizeof(SelStruct), _redoPos); _redoPos += sizeof(SelStruct); if(event->anchorRev) swapSel(undoSel); // // $$$ clipping bugs in extend selection when extending to a position // that is off the visible area (not previously delt with!) // undoSel.posStart = getPhysicalLength(undoSel.lineStart, undoSel.selStart); undoSel.posEnd = getPhysicalLength(undoSel.lineEnd, undoSel.selEnd); XeTextBase::extendSelection(undoSel, _anchor, TRUE); _sel = _anchor = undoSel; if(event->anchorRev) { swapSel(_anchor); if(!event->selSwapped) swapSel(_sel); } else if(event->selSwapped) { swapSel(_sel); } *event = currState; } break; case Delete: { char* c = (char*)_undoBuf.get(1, _redoPos + sizeof(TextData)); _redoPos += event->len; XeTextBase::replaceSelection(c, 1); } break; case DeleteSelection: { SelStruct sel = _sel; insertBytes(event->len); _redoPos += event->len; XeTextBase::setSelection(sel.selStart, _sel.selStart, sel.lineStart, _sel.lineStart); if(event->selSwapped) swapSel(_anchor); } break; case Insert: { long len = event->len - sizeof(TextData); long lines = getNumLines(); _redoPos += event->len; drawCaret(FALSE); XeTextBase::stretchSelection(-len, -lines, SelChar, FALSE); XeTextBase::replaceSelection(NULL, 0, FALSE); } break; case InsertSelection: { long len = event->len - sizeof(TextData); long lines = getNumLines(); _redoPos += event->len; drawCaret(FALSE); XeTextBase::stretchSelection(-len, -lines, SelChar, FALSE); _redoList.push_front(event); _undoList.pop_front(); SelStruct sel = _sel; event = _undoList.front(); if(_redoList.front() == _saveEvent) _saveEvent = event; if(_redoList.front() == _changeEvent) _changeEvent = event; event->save = _redoList.front()->save; insertBytes(event->len, FALSE); _redoPos += event->len; XeTextBase::setSelection(sel.selStart, _sel.selStart, sel.lineStart, _sel.lineStart); if(event->selSwapped) swapSel(_anchor); } break; } _redoList.push_front(event); _undoList.pop_front(); _numUndos--; autoScroll(); callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); if(event == _changeEvent) return 1; if(event == _saveEvent) return 2; return 0; } int XeText::redo(void) { if (_redoList.empty()) return 0; Undo* event = _redoList.front(); _caretPos = event->caretPos; switch(event->type) { case Select: { Undo currState(this, event->type, sizeof(SelStruct)); SelStruct undoSel; _redoPos -= sizeof(SelStruct); _undoBuf.get(&undoSel, sizeof(SelStruct), _redoPos); _undoBuf.replace(&_sel, sizeof(SelStruct), _redoPos); XeTextBase::setSelection(undoSel.selStart, undoSel.selEnd, undoSel.lineStart, undoSel.lineEnd); if(event->selSwapped) swapSel(_anchor); *event = currState; } break; case SelectExtend: { if (_sel.selStart == _sel.selEnd) drawCaret(FALSE); Undo currState(this, event->type, sizeof(SelStruct), event->anchorRev); SelStruct undoSel; _redoPos -= sizeof(SelStruct); _undoBuf.get(&undoSel, sizeof(SelStruct), _redoPos); _undoBuf.replace(&_anchor, sizeof(SelStruct), _redoPos); if(event->anchorRev) swapSel(_anchor); undoSel.posStart = getPhysicalLength(undoSel.lineStart, undoSel.selStart); undoSel.posEnd = getPhysicalLength(undoSel.lineEnd, undoSel.selEnd); XeTextBase::extendSelection(undoSel, _anchor, TRUE); _sel = _anchor = undoSel; if(event->selSwapped) swapSel(_sel); *event = currState; } break; case Delete: { char c = '\b'; _redoPos -= event->len; XeTextBase::replaceSelection(&c, 0); } break; case DeleteSelection: { _redoPos -= event->len; XeTextBase::replaceSelection(NULL, 0); } break; case Insert: { _redoPos -= event->len; insertBytes(event->len); } break; case InsertSelection: { _redoPos -= event->len; _undoList.push_front(event); _redoList.pop_front(); assert(!_redoList.empty()); event = _redoList.front(); if (_undoList.front() == _saveEvent) _saveEvent = event; if (_undoList.front() == _changeEvent) _changeEvent = event; _redoPos -= event->len; insertBytes(event->len); } break; } _undoList.push_front(event); _redoList.pop_front(); _numUndos++; autoScroll(); callCallbacks(XE_CB_LINETOTAL_CHANGED, NULL); if(event == _saveEvent) return 1; if(event == _changeEvent) return 2; return 0; } // // Overridden XeTextBase functions for saving of events into undo buffer: // void XeText::setSelection(long line, long column, SelMode mode, bool hiliteSel) { if (previousEvent() != Select || (previousEvent() == Select && selLength() != 0) || mode != SelChar) addSelEvent(Select); XeTextBase::setSelection(line, column, mode, hiliteSel); } void XeText::setSelection(long start, long end, long linestart, long lineend, bool hiliteSel) { if (previousEvent() != Select || (previousEvent() == Select && selLength() != 0) || start != end || (linestart != lineend && lineend != -1)) addSelEvent(Select); XeTextBase::setSelection(start, end, linestart, lineend, hiliteSel); } void XeText::extendSelection(long line, long column, SelMode mode, bool shiftSel, bool hiliteSel) { bool anchorReversed = FALSE; if(_shiftSel) { SelStruct sel = getSel(line, column); if(sel.selStart < _anchor.selEnd && _anchor.selEnd < _anchor.selStart) anchorReversed = TRUE; else if(sel.selStart > _anchor.selEnd && _anchor.selEnd > _anchor.selStart) anchorReversed = TRUE; } if (previousEvent() != SelectExtend || anchorReversed) addSelEvent(SelectExtend, anchorReversed); if(anchorReversed) swapSel(_anchor); XeTextBase::extendSelection(line, column, mode, shiftSel, hiliteSel); } void XeText::stretchSelection(long seloffset, long lineoffset, SelMode mode, bool hiliteSel) { if (previousEvent() != SelectExtend) addSelEvent(SelectExtend); XeTextBase::stretchSelection(seloffset, lineoffset, mode, hiliteSel); } int XeText::replaceSelection(char *s, long len, bool hiliteSel) { // Add a TextUndo with type Insert, Delete or 2 InsertDelete // events depending on what is being inserted and what is currently // selected. clearRedoBuf(); long currLine = _sel.lineStart; Undo* event = NULL; TextData data; data.numLines = _sel.lineEnd - _sel.lineStart; if(s == NULL || (s != NULL && len == 0 && *s == '\b')) { if(selLength()) { // DeleteSelection event truncUndoList(); event = new Undo(this, DeleteSelection, selLength() + sizeof(TextData)); _undoBuf.insert(&(*_text)[_sel.selStart], selLength(), 0); _undoBuf.insert(&data, sizeof(TextData), 0); } else { // Delete Event if(_sel.selStart > 0) { truncUndoList(); event = new Undo(this, Delete, sizeof(TextData) + 1); _undoBuf.insert(&(*_text)[_sel.selStart - 1], 1, 0); _undoBuf.insert(&data, sizeof(TextData), 0); } } } else { assert(len > 0); currLine = _sel.lineStart; if(selLength()) { // InsertSelection event pair truncUndoList(); event = new Undo(this, InsertSelection, selLength() + sizeof(TextData)); _undoBuf.insert(&(*_text)[_sel.selStart], selLength(), 0); _undoBuf.insert(&data, sizeof(TextData), 0); _undoList.push_front(event); // second event saves whats inserted (like Insert) event = new Undo(this, InsertSelection, sizeof(TextData) + len); _undoBuf.insert(s, len, 0); } else { truncUndoList(); // Insert event // fill in numLines after calling replaceSelection() event = new Undo(this, Insert, sizeof(TextData) + len); _undoBuf.insert(s, len, 0); } } if(event) _undoList.push_front(event); int val = XeTextBase::replaceSelection(s, len, hiliteSel); if(event && (event->type == Insert || event->type == InsertSelection)) { data.numLines = _sel.lineStart - currLine; _undoBuf.insert(&data, sizeof(TextData), 0); } return val; } // // Miscellaneous functions used by the undo stuff: // void XeText::truncUndoList(void) { // $$$ this wont work if _maxUndos == 0 assert(_maxUndos > 0); if(_numUndos >= _maxUndos) { Undo* undo = _undoList.back(); _undoBuf.remove(KRBUF_END, _undoBuf.length() - undo->len); delete undo; _undoList.pop_back(); if (!_undoList.empty()) { undo = _undoList.back(); if (undo && undo->type == InsertSelection) { _undoBuf.remove(KRBUF_END, _undoBuf.length() - undo->len); delete undo; _undoList.pop_back(); } } } else { _numUndos++; } } long XeText::getNumLines(void) { TextData data; _undoBuf.get(&data, sizeof(TextData), _redoPos); return data.numLines; } void XeText::insertBytes(int len, bool hiliteSel) { len -= sizeof(TextData); int getlen = len; int pos = _redoPos + sizeof(TextData); char* buffer; _undoBuf.getPartial(getlen, pos); buffer = (getlen < len) ? (char*)alloca(len) : 0; XeTextBase::replaceSelection((char*)_undoBuf.get(buffer, len, pos, FALSE), len, hiliteSel); } void XeText::clearRedoBuf(void) { std::slist::iterator iter = _redoList.begin(); while(iter != _redoList.end()) { delete *iter; iter++; } _undoBuf.remove(_redoPos); _redoList.clear(); _redoPos = 0; } int XeText::previousEvent() { Undo* undoHead = (!_undoList.empty()) ? _undoList.front() : NULL; if (undoHead) return undoHead->type; return -1; } void XeText::addSelEvent(char type, bool anchorRev) { clearRedoBuf(); truncUndoList(); Undo* event = new Undo(this, type, sizeof(SelStruct), anchorRev); SelStruct data; data = (type == SelectExtend) ? _anchor : _sel; _undoBuf.insert(&data, sizeof(SelStruct), 0); _undoList.push_front(event); }