diff options
Diffstat (limited to 'src/Fl_Input_.cxx')
| -rw-r--r-- | src/Fl_Input_.cxx | 704 |
1 files changed, 704 insertions, 0 deletions
diff --git a/src/Fl_Input_.cxx b/src/Fl_Input_.cxx new file mode 100644 index 000000000..a04d3cef2 --- /dev/null +++ b/src/Fl_Input_.cxx @@ -0,0 +1,704 @@ +// Fl_Input_.C + +// This is the base class for Fl_Input. You can use it directly +// if you are one of those people who like to define their own +// set of editing keys. It may also be useful for adding scrollbars +// to the input field. + +#include <FL/Fl.H> +#include <FL/Fl_Input_.H> +#include <FL/fl_draw.H> +#include <math.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> + +#define MAXBUF 1024 + +//////////////////////////////////////////////////////////////// + +// Copy string p..e to the buffer, replacing characters with ^X and \nnn +// as necessary. Truncate if necessary so the resulting string and +// null terminator fits in a buffer of size n. Return new end pointer. +const char* Fl_Input_::expand(const char* p, char* buf) const { + char* o = buf; + char* e = buf+(MAXBUF-4); + if (type()==FL_SECRET_INPUT) { + while (o<e && p < value_+size_) {*o++ = '*'; p++;} + } else while (o<e) { + if (p >= value_+size_) break; + int c = *p++ & 255; + if (c < ' ' || c == 127) { + if (c=='\n' && type()==FL_MULTILINE_INPUT) {p--; break;} + if (c == '\t' && type()==FL_MULTILINE_INPUT) { + for (c = (o-buf)%8; c<8 && o<e; c++) *o++ = ' '; + } else { + *o++ = '^'; + *o++ = c ^ 0x40; + } + } else if (c >= 128 && c < 0xA0) { + *o++ = '\\'; + *o++ = (c>>6)+'0'; + *o++ = ((c>>3)&7)+'0'; + *o++ = (c&7)+'0'; + } else if (c == 0xA0) { // nbsp + *o++ = ' '; + } else { + *o++ = c; + } + } + *o = 0; + return p; +} + +// After filling in such a buffer, find the width to e +double Fl_Input_::expandpos( + const char* p, // real string + const char* e, // pointer into real string + const char* buf, // conversion of real string by expand() + int* returnn // return offset into buf here +) const { + int n = 0; + if (type()==FL_SECRET_INPUT) n = e-p; + else while (p<e) { + int c = *p++ & 255; + if (c < ' ' || c == 127) { + if (c == '\t' && type()==FL_MULTILINE_INPUT) n += 8-(n%8); + else n += 2; + } else if (c >= 128 && c < 0xA0) { + n += 4; + } else { + n++; + } + } + if (returnn) *returnn = n; + return fl_width(buf, n); +} + +//////////////////////////////////////////////////////////////// + +// minimal update: +// Characters from mu_p to end of widget are redrawn. +// If erase_cursor_only, small part at mu_p is redrawn. +// Right now minimal update just keeps unchanged characters from +// being erased, so they don't blink. + +void Fl_Input_::minimal_update(int p) { + if (damage() & 128) return; // don't waste time if it won't be done + if (damage() & 2) { + if (p < mu_p) mu_p = p; + } else { + mu_p = p; + } + damage(2); + erase_cursor_only = 0; +} + +void Fl_Input_::minimal_update(int p, int q) { + if (q < p) p = q; + minimal_update(p); +} + +//////////////////////////////////////////////////////////////// + +static double up_down_pos; +static int was_up_down; + +void Fl_Input_::setfont() const { + fl_font(textfont(), textsize(), default_font(), default_size()); +} + +void Fl_Input_::drawtext(int X, int Y, int W, int H) { + + int do_mu = !(damage()&128); + if (Fl::focus()!=this && !size()) { + if (do_mu) { // we have to erase it if cursor was there + fl_color(color()); + fl_rectf(X, Y, W, H); + } + return; + } + + int selstart, selend; + if (Fl::focus()!=this && Fl::selection_owner()!=this && Fl::pushed()!=this) + selstart = selend = 0; + else if (position() <= mark()) { + selstart = position(); selend = mark(); + } else { + selend = position(); selstart = mark(); + } + + setfont(); + +#if 0 // patch to do auto-wrap written by Ian West + if ((type()==FL_MULTILINE_INPUT) && (value_==buffer) && (bufsize>=size_)) { + int wwidth = W-10; + int strtofln=0,lastsp=0,idx=0,lastbr=0; + while(idx <= size_){ + if((buffer[idx] <= ' ') || (idx == size_)) { + if(buffer[idx] == '\n') lastbr=idx; + buffer[idx]=' '; + int twidth=(int)fl_width(&buffer[strtofln],idx-strtofln); + if ((twidth >= wwidth) && (lastsp > strtofln)) { +// printf(stderr,"Line break, lastsp=%d, idx=%d, strtofln=%d, lastbr=%d\n",lastsp,idx,strtofln,lastbr); + buffer[lastsp]='\n'; + if (lastsp != lastbr) { + if (lastsp < mu_p){ + mu_p=lastsp; + erase_cursor_only = 0; + } + } + strtofln=lastsp+1; + } else { + lastsp=idx; + } + } + idx++; + } +// fprintf(stderr,"Line length %d %d %d\n",(int)fl_width(buffer),size_, mu_p); +// if(xscroll_ > 0) {xscroll_=0; mu_p=0;} + buffer[size_] = 0; + } +#endif + + const char *p, *e; + char buf[MAXBUF]; + + // count how many lines and put the last one into the buffer: + // And figure out where the cursor is: + int height = fl_height(); + int lines; + int curx, cury; + for (p=value(), curx=cury=lines=0; ;) { + e = expand(p, buf); + if (position() >= p-value() && position() <= e-value()) { + curx = int(expandpos(p, value()+position(), buf, 0)+.5); + if (Fl::focus()==this && !was_up_down) up_down_pos = curx; + cury = lines*height; + if (Fl::focus()==this) { + int fullw = int(expandpos(p, e, buf, 0)); + if (curx > xscroll_+W-20) { + xscroll_ = curx+20-W; + if (xscroll_ > fullw-W+2) xscroll_ = fullw-W+2; + mu_p = 0; erase_cursor_only = 0; + } + if (curx < xscroll_+20 && xscroll_) { + if (fullw > W-2) xscroll_ = curx-20; + else xscroll_ = 0; + mu_p = 0; erase_cursor_only = 0; + } + if (xscroll_ < 0) xscroll_ = 0; + } + } + lines++; + if (e >= value_+size_) break; + if (*e == '\n') e++; + p = e; + } + + // adjust the scrolling: + if (type()==FL_MULTILINE_INPUT) { + int newy = yscroll_; + if (cury < newy) newy = cury; + if (cury > newy+H-height) newy = cury-H+height; + if (newy < -1) newy = -1; + if (newy != yscroll_) {yscroll_ = newy; mu_p = 0; erase_cursor_only = 0;} + } else { + yscroll_ = -(H-height)/2; + } + + fl_clip(X, Y, W, H); + Fl_Color color = active_r() ? textcolor() : inactive(textcolor()); + + p = value(); + // visit each line and draw it: + int desc = height-fl_descent(); + int ypos = -yscroll_; + for (; ypos < H;) { + + // re-expand line unless it is the last one calculated above: + if (lines>1) e = expand(p, buf); + + if (ypos <= -height) goto CONTINUE; // clipped off top + + if (do_mu) { // for minimal update: + const char* pp = value()+mu_p; // pointer to where minimal update starts + if (e >= pp && (!erase_cursor_only || p <= pp)) { // we must erase this + // calculate area to erase: + int x1 = -xscroll_; + if (p < pp) x1 += int(expandpos(p, pp, buf, 0)); + // erase it: + fl_color(this->color()); + fl_rectf(X+x1, Y+ypos, erase_cursor_only?2:W-x1, height); + // it now draws entire line over it + // this should not draw letters to left of erased area, but + // that is nyi. + } + } + + // Draw selection area if required: + if (selstart < selend && selstart <= e-value() && selend > p-value()) { + const char* pp = value()+selstart; + int x1 = -xscroll_; + int offset1 = 0; + if (pp > p) { + fl_color(color); + x1 += int(expandpos(p, pp, buf, &offset1)); + fl_draw(buf, offset1, X-xscroll_, Y+ypos+desc); + } + pp = value()+selend; + int x2 = W; + int offset2; + if (pp <= e) x2 = int(expandpos(p, pp, buf, &offset2))-xscroll_; + else offset2 = strlen(buf); + fl_color(selection_color()); + fl_rectf(X+int(x1+.5), Y+ypos, int(x2-x1), height); + fl_color(contrast(textcolor(), selection_color())); + fl_draw(buf+offset1, offset2-offset1, X+x1, Y+ypos+desc); + if (pp < e) { + fl_color(color); + fl_draw(buf+offset2, X+x2, Y+ypos+desc); + } + } else { + // draw the cursor: + if (Fl::focus() == this && selstart == selend && + position() >= p-value() && position() <= e-value()) { + fl_color(cursor_color()); + fl_rectf(X+curx-xscroll_, Y+ypos, 2, height); + } + fl_color(color); + fl_draw(buf, X-xscroll_, Y+ypos+desc); + } + CONTINUE: + ypos += height; + if (e >= value_+size_) break; + if (*e == '\n') e++; + p = e; + } + + // for minimal update, erase all lines below last one if necessary: + if (type()==FL_MULTILINE_INPUT && do_mu && ypos<H + && (!erase_cursor_only || p <= value()+mu_p)) { + if (ypos < 0) ypos = 0; + fl_color(this->color()); + fl_rectf(X, Y+ypos, W, H-ypos); + } + + fl_pop_clip(); +} + +static int isword(char c) { + return (c&128 || isalnum(c) || strchr("#%&-/@\\_~", c)); +} + +int Fl_Input_::wordboundary(int i) const { + if (i<=0 || i>=size()) return 1; + return isword(index(i-1)) != isword(index(i)); +} + +int Fl_Input_::lineboundary(int i) const { + if (i<=0 || i>=size()) return 1; + if (type() != FL_MULTILINE_INPUT) return 0; + return index(i-1) == '\n' || index(i) == '\n'; +} + +void Fl_Input_::handle_mouse(int X, int Y, int /*W*/, int /*H*/, int drag) { + was_up_down = 0; + if (!size()) return; + setfont(); + + const char *p, *e; + char buf[MAXBUF]; + + int theline = (type()==FL_MULTILINE_INPUT) ? + (Fl::event_y()-Y+yscroll_)/fl_height() : 0; + + int newpos = 0; + for (p=value();; ) { + e = expand(p, buf); + theline--; if (theline < 0) break; + if (*e == '\n') e++; + p = e; + if (e >= value_+size_) break; + } + const char *l, *r, *t; + for (l = p, r = e; l<r; ) { + double f; + t = l+(r-l+1)/2; + f = X-xscroll_+expandpos(p, t, buf, 0); + if (f <= Fl::event_x()) l = t; + else r = t-1; + } + newpos = l-value(); + + int newmark = drag ? mark() : newpos; + if (Fl::event_clicks()) { + if (newpos >= newmark) { + if (newpos == newmark) { + if (newpos < size()) newpos++; + else newmark--; + } + if (Fl::event_clicks()>1) { + while (!lineboundary(newpos)) newpos++; + while (!lineboundary(newmark)) newmark--; + } else { + while (!wordboundary(newpos)) newpos++; + while (!wordboundary(newmark)) newmark--; + } + } else { + if (Fl::event_clicks()>1) { + while (!lineboundary(newpos)) newpos--; + } else { + while (!wordboundary(newpos)) newpos--; + } + } + } + position(newpos, newmark); +} + +int Fl_Input_::position(int p, int m) { + was_up_down = 0; + if (p<0) p = 0; + if (p>size()) p = size(); + if (m<0) m = 0; + if (m>size()) m = size(); + if (p == position_ && m == mark_) return 0; + if (Fl::selection_owner() == this) Fl::selection_owner(0); + if (p != m) { + // new position is a selection + if (Fl::focus()==this || Fl::pushed()==this) { + if (p != position_) minimal_update(position_, p); + if (m != mark_) minimal_update(mark_, m); + } + } else if (Fl::focus() == this) { + // new position is a cursor + if (position_ == mark_) { + // old position was just a cursor + if (!(damage()&2)) { + minimal_update(position_); erase_cursor_only = 1; + } + } else { // old position was a selection + minimal_update(position_, mark_); + } + } + position_ = p; + mark_ = m; + return 1; +} + +int Fl_Input_::up_down_position(int i, int keepmark) { + while (i > 0 && index(i-1) != '\n') i--; // go to start of line + double oldwid = 0.0; + setfont(); + while (index(i) && index(i)!='\n') { + double tt = oldwid + fl_width(index(i)); + if ((oldwid+tt)/2 >= up_down_pos) break; + oldwid = tt; + i++; + } + int j = position(i, keepmark ? mark_ : i); + was_up_down = 1; + return j; +} + +int Fl_Input_::copy() { + if (mark() != position()) { + int b, e; if (position() < mark()) { + b = position(); e = mark(); + } else { + e = position(); b = mark(); + } + if (type()!=FL_SECRET_INPUT) Fl::selection(*this, value()+b, e-b); + return 1; + } + return 0; +} + +#define MAXFLOATSIZE 40 + +static char* undobuffer; +static int undobufferlength; +static Fl_Input_* undowidget; +static int undoat; // points after insertion +static int undocut; // number of characters deleted there +static int undoinsert; // number of characters inserted +static int yankcut; // length of valid contents of buffer, even if undocut=0 + +static void undobuffersize(int n) { + if (n > undobufferlength) { + if (undobuffer) { + do {undobufferlength *= 2;} while (undobufferlength < n); + undobuffer = (char*)realloc(undobuffer, undobufferlength); + } else { + undobufferlength = n+9; + undobuffer = (char*)malloc(undobufferlength); + } + } +} + +// all changes go through here, delete characters b-e and insert text: +int Fl_Input_::replace(int b, int e, const char* text, int ilen) { + + was_up_down = 0; + + if (b<0) b = 0; + if (e<0) e = 0; + if (b>size_) b = size_; + if (e>size_) e = size_; + if (e<b) {int t=b; b=e; e=t;} + if (text && !ilen) ilen = strlen(text); + if (e<=b && !ilen) return 0; // don't clobber undo for a null operation + if (size_+ilen-(e-b) > maximum_size_) { + ilen = maximum_size_-size_+(e-b); + if (ilen < 0) ilen = 0; + } + + put_in_buffer(size_+ilen); + + if (e>b) { + if (undowidget == this && b == undoat) { + undobuffersize(undocut+(e-b)); + memcpy(undobuffer+undocut, value_+b, e-b); + undocut += e-b; + } else if (undowidget == this && e == undoat && !undoinsert) { + undobuffersize(undocut+(e-b)); + memmove(undobuffer+(e-b), undobuffer, undocut); + memcpy(undobuffer, value_+b, e-b); + undocut += e-b; + } else if (undowidget == this && e == undoat && (e-b)<undoinsert) { + undoinsert -= e-b; + } else { + undobuffersize(e-b); + memmove(undobuffer, value_+b, e-b); + undocut = e-b; + undoinsert = 0; + } + memcpy(buffer+b, buffer+e, size_-b+1); + size_ -= e-b; + undowidget = this; + undoat = b; + if (type() == FL_SECRET_INPUT) yankcut = 0; else yankcut = undocut; + } + + if (ilen) { + size_ += ilen; + if (undowidget == this && b == undoat) + undoinsert += ilen; + else { + undocut = 0; + undoinsert = ilen; + } + int i; + for (i=size_; i>b; i--) buffer[i] = buffer[i-ilen]; + for (i=0; i<ilen; i++) buffer[b+i] = text[i]; + } + undowidget = this; + mark_ = position_ = undoat = b+ilen; + + minimal_update(b); + if (when()&FL_WHEN_CHANGED) do_callback(); else set_changed(); + return 1; +} + +int Fl_Input_::undo() { + was_up_down = 0; + if (undowidget != this || !undocut && !undoinsert) return 0; + + int ilen = undocut; + int xlen = undoinsert; + int b = undoat-xlen; + int b1 = b; + + put_in_buffer(size_+ilen); + + if (ilen) { + size_ += ilen; + int i; + for (i=size_; i>b; i--) buffer[i] = buffer[i-ilen]; + for (i=0; i<ilen; i++) buffer[b++] = undobuffer[i]; + } + + if (xlen) { + undobuffersize(xlen); + memcpy(undobuffer, buffer+b, xlen); + memmove(buffer+b, buffer+b+xlen, size_-xlen-b); + size_ -= xlen; + } + + undocut = xlen; + if (xlen) yankcut = xlen; + undoinsert = ilen; + undoat = b; + mark_ = b /* -ilen */; + position_ = b; + + minimal_update(b1); + if (when()&FL_WHEN_CHANGED) do_callback(); else set_changed(); + return 1; +} + +#if 0 +int Fl_Input_::yank() { + // fake yank by trying to get it out of undobuffer + if (!yankcut) return 0; + return change(position(), position(), undobuffer, yankcut); +} +#endif + +int Fl_Input_::copy_cuts() { + // put the yank buffer into the X clipboard + if (!yankcut) return 0; + Fl::selection(*this, undobuffer, yankcut); + return 1; +} + +void Fl_Input_::maybe_do_callback() { + if (changed() || (when()&FL_WHEN_NOT_CHANGED)) { + clear_changed(); do_callback();} +} + +int Fl_Input_::handletext(int event, int X, int Y, int W, int H) { + switch (event) { + + case FL_FOCUS: + if (mark_ == position_) { + minimal_update(size()+1); + } else if (Fl::selection_owner() != this) + minimal_update(mark_, position_); + return 1; + + case FL_UNFOCUS: + if (mark_ == position_) { + if (!(damage()&2)) {minimal_update(position_); erase_cursor_only = 1;} + } else if (Fl::selection_owner() != this) { + minimal_update(mark_, position_); + } + if (when() & FL_WHEN_RELEASE) maybe_do_callback(); + return 1; + + case FL_PUSH: + handle_mouse(X, Y, W, H, Fl::event_state(FL_SHIFT)); + return 1; + + case FL_DRAG: + handle_mouse(X, Y, W, H, 1); + return 1; + + case FL_RELEASE: +// handle_mouse(X, Y, W, H, 1); + copy(); + return 1; + + case FL_SELECTIONCLEAR: + minimal_update(mark_, position_); + return 1; + + case FL_PASTE: { + // strip trailing control characters and spaces before pasting: + const char* t = Fl::event_text(); + const char* e = t+Fl::event_length(); + if (type()!=FL_MULTILINE_INPUT) while (e > t && *(uchar*)(e-1) <= ' ') e--; + return replace(position(), mark(), t, e-t);} + + default: + return 0; + } +} + +/*------------------------------*/ + +Fl_Input_::Fl_Input_(int x, int y, int w, int h, const char* l) +: Fl_Widget(x, y, w, h, l) { + box(FL_NO_BOX); + color(FL_WHITE, FL_SELECTION_COLOR); + align(FL_ALIGN_LEFT); + textsize_ = FL_NORMAL_SIZE; + textfont_ = FL_HELVETICA; + textcolor_ = FL_BLACK; + cursor_color_ = FL_BLACK; // was FL_BLUE + mark_ = position_ = size_ = 0; + bufsize = 0; + value_ = ""; + xscroll_ = yscroll_ = 0; + maximum_size_ = 32767; +} + +void Fl_Input_::put_in_buffer(int len) { + if (value_ == buffer && bufsize > len) return; + if (!bufsize) { + if (len > size_) len += 9; // let a few characters insert before realloc + bufsize = len+1; + buffer = (char*)malloc(bufsize); + } else if (bufsize <= len) { + // we may need to move old value in case it points into buffer: + int moveit = (value_ >= buffer && value_ < buffer+bufsize); + // enlarge current buffer + if (len > size_) { + do {bufsize *= 2;} while (bufsize <= len); + } else { + bufsize = len+1; + } + char* nbuffer = (char*)realloc(buffer, bufsize); + if (moveit) value_ += (nbuffer-buffer); + buffer = nbuffer; + } + memmove(buffer, value_, size_); buffer[size_] = 0; + value_ = buffer; +} + +int Fl_Input_::static_value(const char* str, int len) { + clear_changed(); + if (undowidget == this) undowidget = 0; + if (str == value_ && len == size_) return 0; + if (len) { // non-empty new value: + if (xscroll_ || yscroll_) { + xscroll_ = yscroll_ = 0; + minimal_update(0); + } else { + int i = 0; + // find first different character: + if (value_) { + for (; i<size_ && i<len && str[i]==value_[i]; i++); + if (i==size_ && i==len) return 0; + } + minimal_update(i); + } + value_ = str; + size_ = len; + } else { // empty new value: + if (!size_) return 0; // both old and new are empty. + size_ = 0; + value_ = ""; + xscroll_ = yscroll_ = 0; + minimal_update(0); + } + position(size(), 0); + return 1; +} + +int Fl_Input_::static_value(const char* str) { + return static_value(str, str ? strlen(str) : 0); +} + +int Fl_Input_::value(const char* str, int len) { + int r = static_value(str, len); + if (len) put_in_buffer(len); + return r; +} + +int Fl_Input_::value(const char* str) { + return value(str, str ? strlen(str) : 0); +} + +void Fl_Input_::resize(int X, int Y, int W, int H) { + if (W != w()) xscroll_ = 0; + if (H != h()) yscroll_ = 0; + Fl_Widget::resize(X, Y, W, H); +} + +Fl_Input_::~Fl_Input_() { + if (undowidget == this) undowidget = 0; + if (bufsize) free((void*)buffer); +} + +// end of Fl_Input_.C |
