// // "$Id: Fl_Input_.cxx,v 1.21.2.11.2.15 2002/07/23 16:28:19 easysw Exp $" // // Common input widget routines for the Fast Light Tool Kit (FLTK). // // Copyright 1998-2002 by Bill Spitzak and others. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 // USA. // // Please report all bugs and problems to "fltk-bugs@fltk.org". // // 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 #include #include #include #include #include "flstring.h" #include #include #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); const char* lastspace = p; char* lastspace_out = o; int width_to_lastspace = 0; int word_count = 0; int word_wrap; if (input_type()==FL_SECRET_INPUT) { while (o= value_+size_ || isspace(*p))) { word_wrap = w() - Fl::box_dw(box()) - 2; width_to_lastspace += (int)fl_width(lastspace_out, o-lastspace_out); if (p > lastspace+1) { if (word_count && width_to_lastspace > word_wrap) { p = lastspace; o = lastspace_out; break; } word_count++; } lastspace = p; lastspace_out = o; } if (p >= value_+size_) break; int c = *p++ & 255; if (c < ' ' || c == 127) { if (c=='\n' && input_type()==FL_MULTILINE_INPUT) {p--; break;} if (c == '\t' && input_type()==FL_MULTILINE_INPUT) { for (c = (o-buf)%8; c<8 && o= 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() & FL_DAMAGE_ALL) return; // don't waste time if it won't be done if (damage() & FL_DAMAGE_EXPOSE) { if (p < mu_p) mu_p = p; } else { mu_p = p; } damage(FL_DAMAGE_EXPOSE); 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()); } void Fl_Input_::drawtext(int X, int Y, int W, int H) { int do_mu = !(damage()&FL_DAMAGE_ALL); 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(); 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; int newscroll = xscroll_; if (curx > newscroll+W-20) { // figure out scrolling so there is space after the cursor: newscroll = curx+20-W; // figure out the furthest left we ever want to scroll: int ex = int(expandpos(p, e, buf, 0))+2-W; // use minimum of both amounts: if (ex < newscroll) newscroll = ex; } else if (curx < newscroll+20) { newscroll = curx-20; } if (newscroll < 0) newscroll = 0; if (newscroll != xscroll_) { xscroll_ = newscroll; mu_p = 0; erase_cursor_only = 0; } } lines++; if (e >= value_+size_) break; p = e+1; } // adjust the scrolling: if (input_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 tc = active_r() ? textcolor() : fl_inactive(textcolor()); p = value(); // visit each line and draw it: int desc = height-fl_descent(); int xpos = X - xscroll_ + 1; 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) goto CONTINUE2; // this line is before the changes if (readonly()) erase_cursor_only = 0; // this isn't the most efficient way if (erase_cursor_only && p > pp) goto CONTINUE2; // this line is after // calculate area to erase: int r = X+W; int x; if (p >= pp) { x = X; if (erase_cursor_only) r = xpos+2; else if (readonly()) x -= 3; } else { x = xpos+(int)expandpos(p, pp, buf, 0); if (erase_cursor_only) r = x+2; else if (readonly()) x -= 3; } // clip to and erase it: fl_color(color()); fl_rectf(x, Y+ypos, r-x, height); fl_push_clip(x, Y+ypos, r-x, 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 = xpos; int offset1 = 0; if (pp > p) { fl_color(tc); x1 += int(expandpos(p, pp, buf, &offset1)); fl_draw(buf, offset1, xpos, Y+ypos+desc); } pp = value()+selend; int x2 = X+W; int offset2; if (pp <= e) x2 = xpos+int(expandpos(p, pp, buf, &offset2)); else offset2 = strlen(buf); fl_color(selection_color()); fl_rectf(x1, Y+ypos, x2-x1, height); fl_color(fl_contrast(textcolor(), selection_color())); fl_draw(buf+offset1, offset2-offset1, x1, Y+ypos+desc); if (pp < e) { fl_color(tc); fl_draw(buf+offset2, x2, Y+ypos+desc); } } else { // draw unselected text fl_color(tc); fl_draw(buf, xpos, Y+ypos+desc); } if (do_mu) fl_pop_clip(); CONTINUE2: // draw the cursor: if (Fl::focus() == this && selstart == selend && position() >= p-value() && position() <= e-value()) { fl_color(cursor_color()); if (readonly()) { fl_line(xpos+curx-3, Y+ypos+height-1, xpos+curx, Y+ypos+height-4, xpos+curx+3, Y+ypos+height-1); } else { fl_rectf(xpos+curx, Y+ypos, 2, height); } } CONTINUE: ypos += height; if (e >= value_+size_) break; if (*e == '\n' || *e == ' ') e++; p = e; } // for minimal update, erase all lines below last one if necessary: if (input_type()==FL_MULTILINE_INPUT && do_mu && yposcolor()); 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_::word_end(int i) const { if (input_type() == FL_SECRET_INPUT) return size(); //while (i < size() && !isword(index(i))) i++; while (i < size() && isword(index(i))) i++; return i; } int Fl_Input_::word_start(int i) const { if (input_type() == FL_SECRET_INPUT) return 0; // if (i >= size() || !isword(index(i))) // while (i > 0 && !isword(index(i-1))) i--; while (i > 0 && isword(index(i-1))) i--; return i; } int Fl_Input_::line_end(int i) const { if (input_type() != FL_MULTILINE_INPUT) return size(); if (wrap()) { // go to the start of the paragraph: int j = i; while (j > 0 && index(j-1) != '\n') j--; // now measure lines until we get past i, end of that line is real eol: setfont(); for (const char* p=value()+j; ;) { char buf[MAXBUF]; p = expand(p, buf); if (p-value() >= i) return p-value(); p++; } } else { while (i < size() && index(i) != '\n') i++; return i; } } int Fl_Input_::line_start(int i) const { if (input_type() != FL_MULTILINE_INPUT) return 0; int j = i; while (j > 0 && index(j-1) != '\n') j--; if (wrap()) { // now measure lines until we get past i, start of that line is real eol: setfont(); for (const char* p=value()+j; ;) { char buf[MAXBUF]; const char* e = expand(p, buf); if (e-value() >= i) return p-value(); p = e+1; } } else return j; } 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 = (input_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 >= value_+size_) break; p = e+1; } const char *l, *r, *t; double f0 = Fl::event_x()-X+xscroll_; for (l = p, r = e; l= newmark) { if (newpos == newmark) { if (newpos < size()) newpos++; else newmark--; } if (Fl::event_clicks() > 1) { newpos = line_end(newpos); newmark = line_start(newmark); } else { newpos = word_end(newpos); newmark = word_start(newmark); } } else { if (Fl::event_clicks() > 1) { newpos = line_start(newpos); newmark = line_end(newmark); } else { newpos = word_start(newpos); newmark = word_end(newmark); } } // if the multiple click does not increase the selection, revert // to single-click behavior: if (!drag && (mark() > position() ? (newmark >= position() && newpos <= mark()) : (newmark >= mark() && newpos <= position()))) { Fl::event_clicks(0); newmark = newpos = l-value(); } } 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) { if (p != position_) minimal_update(position_, p); if (m != mark_) minimal_update(mark_, m); } else { // new position is a cursor if (position_ == mark_) { // old position was just a cursor if (Fl::focus() == this && !(damage()&FL_DAMAGE_EXPOSE)) { 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) { // unlike before, i must be at the start of the line already! setfont(); char buf[MAXBUF]; const char* p = value()+i; const char* e = expand(p, buf); const char *l, *r, *t; for (l = p, r = e; l e) {b = mark(); e = position();} if (input_type() == FL_SECRET_INPUT) e = b; Fl::copy(value()+b, e-b, clipboard); 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 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) 0 && !isspace(index(b))) b--; // make sure we redraw the old selection or cursor: if (mark_ < b) b = mark_; if (position_ < b) b = position_; minimal_update(b); mark_ = position_ = undoat; 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) { memmove(buffer+b+ilen, buffer+b, size_-b+1); memcpy(buffer+b, undobuffer, ilen); size_ += ilen; b += ilen; } if (xlen) { undobuffersize(xlen); memcpy(undobuffer, buffer+b, xlen); memmove(buffer+b, buffer+b+xlen, size_-xlen-b+1); 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; } int Fl_Input_::copy_cuts() { // put the yank buffer into the X clipboard if (!yankcut || input_type()==FL_SECRET_INPUT) return 0; Fl::copy(undobuffer, yankcut, 1); 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_ENTER: case FL_LEAVE: return 1; 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()&FL_DAMAGE_EXPOSE)) {minimal_update(position_); erase_cursor_only = 1;} } else //if (Fl::selection_owner() != this) minimal_update(mark_, position_); case FL_HIDE: if (when() & FL_WHEN_RELEASE) maybe_do_callback(); return 1; case FL_PUSH: handle_mouse(X, Y, W, H, Fl::event_state(FL_SHIFT)); if (Fl::focus() != this) { Fl::focus(this); handle(FL_FOCUS); } return 1; case FL_DRAG: handle_mouse(X, Y, W, H, 1); return 1; case FL_RELEASE: copy(0); 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 (input_type() != FL_MULTILINE_INPUT) while (e > t && isspace(*(e-1))) e--; if (input_type() == FL_INT_INPUT) { while (isspace(*t) && t < e) t ++; const char *p = t; if (*p == '+' || *p == '-') p ++; if (strncmp(p, "0x", 2) == 0) { p += 2; while (isxdigit(*p) && p < e) p ++; } else { while (isdigit(*p) && p < e) p ++; } if (p < e) { fl_beep(FL_BEEP_ERROR); return 1; } else return replace(0, size(), t, e - t); } else if (input_type() == FL_FLOAT_INPUT) { while (isspace(*t) && t < e) t ++; const char *p = t; if (*p == '+' || *p == '-') p ++; while (isdigit(*p) && p < e) p ++; if (*p == '.') { p ++; while (isdigit(*p) && p < e) p ++; if (*p == 'e' || *p == 'E') { p ++; if (*p == '+' || *p == '-') p ++; while (isdigit(*p) && p < e) p ++; } } if (p < e) { fl_beep(FL_BEEP_ERROR); return 1; } else return replace(0, size(), t, e - t); } 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_DOWN_BOX); color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR); align(FL_ALIGN_LEFT); textsize_ = FL_NORMAL_SIZE; textfont_ = FL_HELVETICA; textcolor_ = FL_FOREGROUND_COLOR; cursor_color_ = FL_FOREGROUND_COLOR; // was FL_BLUE mark_ = position_ = size_ = 0; bufsize = 0; buffer = 0; value_ = ""; xscroll_ = yscroll_ = 0; maximum_size_ = 32767; } void Fl_Input_::put_in_buffer(int len) { if (value_ == buffer && bufsize > len) { buffer[size_] = 0; 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