ref: bee35b5750748cf101a6924543e46ec3f9c783ba
dir: /DoConfig/fltk/src/Fl_Input_.cxx/
// // "$Id$" // // Common input widget routines for the Fast Light Tool Kit (FLTK). // // Copyright 1998-2011 by Bill Spitzak and others. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this // file is missing or damaged, see the license at: // // http://www.fltk.org/COPYING.php // // Please report all bugs and problems on the following page: // // http://www.fltk.org/str.php // #include <FL/Fl.H> #include <FL/Fl_Input_.H> #include <FL/Fl_Window.H> #include <FL/fl_draw.H> #include <FL/fl_ask.H> #include <math.h> #include <FL/fl_utf8.h> #include "flstring.h" #include <stdlib.h> #include <ctype.h> #define MAXBUF 1024 #if defined(USE_X11) && !USE_XFT const int secret_char = '*'; // asterisk to hide secret input #else const int secret_char = 0x2022; // bullet to hide secret input #endif static int l_secret; extern void fl_draw(const char*, int, float, float); //////////////////////////////////////////////////////////////// /** \internal Converts a given text segment into the text that will be rendered on screen. This copies the text from \p p to \p buf, replacing characters with <tt>^X</tt> and <tt>\\nnn</tt> as necessary. The destination buffer is limited to \c MAXBUF (currently at 1024). All following text is truncated. \param [in] p pointer to source buffer \param [in] buf pointer to destination buffer \return pointer to the end of the destination buffer */ 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; // const char *pe = p + strlen(p); if (input_type()==FL_SECRET_INPUT) { while (o<e && p < value_+size_) { if (fl_utf8len((char)p[0]) >= 1) { l_secret = fl_utf8encode(secret_char, o); o += l_secret; } p++; } } else while (o<e) { if (wrap() && (p >= value_+size_ || isspace(*p & 255))) { word_wrap = w() - Fl::box_dw(box()) - 2; width_to_lastspace += (int)fl_width(lastspace_out, (int) (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 = fl_utf_nb_char((uchar*)buf, (int) (o-buf))%8; c<8 && o<e; c++) { *o++ = ' '; } } else { *o++ = '^'; *o++ = c ^ 0x40; } } else { *o++ = c; } } *o = 0; return p; } /** \internal Calculates the width in pixels of part of a text buffer. This call takes a string, usually created by expand, and calculates the width of the string when rendered with the given font. \param [in] p pointer to the start of the original string \param [in] e pointer to the end of the original string \param [in] buf pointer to the buffer as returned by expand() \return width of string in pixels */ 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; int chr = 0; int l; if (input_type()==FL_SECRET_INPUT) { while (p<e) { l = fl_utf8len((char)p[0]); if (l >= 1) n += l_secret; p += l; } } else while (p<e) { int c = *p & 255; if (c < ' ' || c == 127) { if (c == '\t' && input_type()==FL_MULTILINE_INPUT) { n += 8-(chr%8); chr += 7-(chr%8); } else n += 2; } else { n++; } chr += fl_utf8len((char)p[0]) >= 1; p++; } if (returnn) *returnn = n; return fl_width(buf, n); } //////////////////////////////////////////////////////////////// /** \internal Marks a range of characters for update. This call marks all characters from \p to the end of the text buffer for update. At least these characters will be redrawn in the next update cycle. Characters from \p mu_p to end of widget are redrawn. If \p erase_cursor_only, small part at \p mu_p is redrawn. Right now minimal update just keeps unchanged characters from being erased, so they don't blink. \param [in] p start of update range */ 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; } /** \internal Marks a range of characters for update. This call marks a text range for update. At least all characters from \p p to \p q will be redrawn in the next update cycle. \param [in] p start of update range \param [in] q end of update range */ void Fl_Input_::minimal_update(int p, int q) { if (q < p) p = q; minimal_update(p); } //////////////////////////////////////////////////////////////// /* Horizontal cursor position in pixels while moving up or down. */ double Fl_Input_::up_down_pos = 0; /* Flag to remember last cursor move. */ int Fl_Input_::was_up_down = 0; /** Sets the current font and font size. */ void Fl_Input_::setfont() const { fl_font(textfont(), textsize()); } /** Draws the text in the passed bounding box. If <tt>damage() & FL_DAMAGE_ALL</tt> is true, this assumes the area has already been erased to color(). Otherwise it does minimal update and erases the area itself. \param X, Y, W, H area that must be redrawn */ 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 draw_box(box(), X-Fl::box_dx(box()), Y-Fl::box_dy(box()), W+Fl::box_dw(box()), H+Fl::box_dh(box()), color()); } 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 threshold = height/2; 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-threshold) { // figure out scrolling so there is space after the cursor: newscroll = curx+threshold-W; // figure out the furthest left we ever want to scroll: int ex = int(expandpos(p, e, buf, 0))+4-W; // use minimum of both amounts: if (ex < newscroll) newscroll = ex; } else if (curx < newscroll+threshold) { newscroll = curx-threshold; } 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_push_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(); float xpos = (float)(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: float r = (float)(X+W); float xx; if (p >= pp) { xx = (float)X; if (erase_cursor_only) r = xpos+2; else if (readonly()) xx -= 3; } else { xx = xpos + (float)expandpos(p, pp, buf, 0); if (erase_cursor_only) r = xx+2; else if (readonly()) xx -= 3; } // clip to and erase it: fl_push_clip((int)xx-1-height/8, Y+ypos, (int)(r-xx+2+height/4), height); draw_box(box(), X-Fl::box_dx(box()), Y-Fl::box_dy(box()), W+Fl::box_dw(box()), H+Fl::box_dh(box()), color()); // 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; float x1 = xpos; int offset1 = 0; if (pp > p) { fl_color(tc); x1 += (float)expandpos(p, pp, buf, &offset1); fl_draw(buf, offset1, xpos, (float)(Y+ypos+desc)); } pp = value()+selend; float x2 = (float)(X+W); int offset2; if (pp <= e) x2 = xpos + (float)expandpos(p, pp, buf, &offset2); else offset2 = (int) strlen(buf); #ifdef __APPLE__ // Mac OS: underline marked ( = selected + Fl::compose_state != 0) text if (Fl::compose_state) { fl_color(textcolor()); } else #endif { fl_color(selection_color()); fl_rectf((int)(x1+0.5), Y+ypos, (int)(x2-x1+0.5), height); fl_color(fl_contrast(textcolor(), selection_color())); } fl_draw(buf+offset1, offset2-offset1, x1, (float)(Y+ypos+desc)); #ifdef __APPLE__ // Mac OS: underline marked ( = selected + Fl::compose_state != 0) text if (Fl::compose_state) { fl_color( fl_color_average(textcolor(), color(), 0.6) ); float width = fl_width(buf+offset1, offset2-offset1); fl_line(x1, Y+ypos+height-1, x1+width, Y+ypos+height-1); } #endif if (pp < e) { fl_color(tc); fl_draw(buf+offset2, (int) strlen(buf+offset2), x2, (float)(Y+ypos+desc)); } } else { // draw unselected text fl_color(tc); fl_draw(buf, (int) strlen(buf), xpos, (float)(Y+ypos+desc)); } if (do_mu) fl_pop_clip(); CONTINUE2: // draw the cursor: if (Fl::focus() == this && ( #ifdef __APPLE__ Fl::compose_state || #endif selstart == selend) && position() >= p-value() && position() <= e-value()) { fl_color(cursor_color()); // cursor position may need to be recomputed (see STR #2486) curx = int(expandpos(p, value()+position(), buf, 0)+.5); if (readonly()) { fl_line((int)(xpos+curx-2.5f), Y+ypos+height-1, (int)(xpos+curx+0.5f), Y+ypos+height-4, (int)(xpos+curx+3.5f), Y+ypos+height-1); } else { fl_rectf((int)(xpos+curx+0.5), Y+ypos, 2, height); } #ifdef __APPLE__ Fl::insertion_point_location(xpos+curx, Y+ypos+height, height); #endif } 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 && ypos<H && (!erase_cursor_only || p <= value()+mu_p)) { if (ypos < 0) ypos = 0; fl_push_clip(X, Y+ypos, W, H-ypos); draw_box(box(), X-Fl::box_dx(box()), Y-Fl::box_dy(box()), W+Fl::box_dw(box()), H+Fl::box_dh(box()), color()); fl_pop_clip(); } fl_pop_clip(); if (Fl::focus() == this) { fl_set_spot(textfont(), textsize(), (int)xpos+curx, Y+ypos-fl_descent(), W, H, window()); } } /** \internal Simple function that determines if a character could be part of a word. \todo This function is not UTF-8-aware. */ static int isword(char c) { return (c&128 || isalnum(c) || strchr("#%-@_~", c)); } /** Finds the end of a word. Returns the index after the last byte of a word. If the index is already at the end of a word, it will find the end of the following word, so if you call it repeatedly you will move forwards to the end of the text. Note that this is inconsistent with line_end(). \param [in] i starting index for the search \return end of the word */ 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; } /** Finds the start of a word. Returns the index of the first byte of a word. If the index is already at the beginning of a word, it will find the beginning of the previous word, so if you call it repeatedly you will move backwards to the beginning of the text. Note that this is inconsistent with line_start(). \param [in] i starting index for the search \return start of the word, or previous word */ int Fl_Input_::word_start(int i) const { if (input_type() == FL_SECRET_INPUT) return 0; while (i > 0 && !isword(index(i-1))) i--; while (i > 0 && isword(index(i-1))) i--; return i; } /** Finds the end of a line. This call calculates the end of a line based on the given index \p i. \param [in] i starting index for the search \return end of the line */ 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); int k = (int) (p-value()); if (k >= i) return k; p++; } } else { while (i < size() && index(i) != '\n') i++; return i; } } /** Finds the start of a line. This call calculates the start of a line based on the given index \p i. \param [in] i starting index for the search \return start of the line */ 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 ((int) (e-value()) >= i) return (int) (p-value()); p = e+1; } } else return j; } static int strict_word_start(const char *s, int i, int itype) { if (itype == FL_SECRET_INPUT) return 0; while (i > 0 && !isspace(s[i-1])) i--; return i; } static int strict_word_end(const char *s, int len, int i, int itype) { if (itype == FL_SECRET_INPUT) return len; while (i < len && !isspace(s[i])) i++; return i; } /** Handles mouse clicks and mouse moves. \todo Add comment and parameters */ 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<r; ) { double f; int cw = fl_utf8len((char)l[0]); if (cw < 1) cw = 1; t = l+cw; f = X-xscroll_+expandpos(p, t, buf, 0); if (f <= Fl::event_x()) {l = t; f0 = Fl::event_x()-f;} else r = t-cw; } if (l < e) { // see if closer to character on right: double f1; int cw = fl_utf8len((char)l[0]); if (cw > 0) { f1 = X-xscroll_+expandpos(p, l + cw, buf, 0) - Fl::event_x(); if (f1 < f0) l = l+cw; } } newpos = (int) (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) { newpos = line_end(newpos); newmark = line_start(newmark); } else { newpos = strict_word_end(value(), size(), newpos, input_type()); newmark = strict_word_start(value(), newmark, input_type()); } } else { if (Fl::event_clicks() > 1) { newpos = line_start(newpos); newmark = line_end(newmark); } else { newpos = strict_word_start(value(), newpos, input_type()); newmark = strict_word_end(value(), size(), newmark, input_type()); } } // 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 = (int) (l-value()); } } position(newpos, newmark); } /** Sets the index for the cursor and mark. The input widget maintains two pointers into the string. The \e position (\c p) is where the cursor is. The \e mark (\c m) is the other end of the selected text. If they are equal then there is no selection. Changing this does not affect the clipboard (use copy() to do that). Changing these values causes a redraw(). The new values are bounds checked. \param p index for the cursor position \param m index for the mark \return 0 if no positions changed \see position(int), position(), mark(int) */ int Fl_Input_::position(int p, int m) { int is_same = 0; 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 == m) is_same = 1; while (p < position_ && p > 0 && (size() - p) > 0 && (fl_utf8len((char)(value() + p)[0]) < 1)) { p--; } int ul = fl_utf8len((char)(value() + p)[0]); while (p < size() && p > position_ && ul < 0) { p++; ul = fl_utf8len((char)(value() + p)[0]); } while (m < mark_ && m > 0 && (size() - m) > 0 && (fl_utf8len((char)(value() + m)[0]) < 1)) { m--; } ul = fl_utf8len((char)(value() + m)[0]); while (m < size() && m > mark_ && ul < 0) { m++; ul = fl_utf8len((char)(value() + m)[0]); } if (is_same) m = p; 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; } /** Moves the cursor to the column given by \p up_down_pos. This function is helpful when implementing up and down cursor movement. It moves the cursor from the beginning of a line to the column indicated by the global variable \p up_down_pos in pixel units. \param [in] i index into the beginning of a line of text \param [in] keepmark if set, move only the cursor, but not the mark \return index to new cursor position */ 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<r; ) { t = l+(r-l+1)/2; int f = (int)expandpos(p, t, buf, 0); if (f <= up_down_pos) l = t; else r = t-1; } int j = (int) (l-value()); j = position(j, keepmark ? mark_ : j); was_up_down = 1; return j; } /** Put the current selection into the clipboard. This function copies the current selection between mark() and position() into the specified \c clipboard. This does not replace the old clipboard contents if position() and mark() are equal. Clipboard 0 maps to the current text selection and clipboard 1 maps to the cut/paste clipboard. \param clipboard the clipboard destination 0 or 1 \return 0 if no text is selected, 1 if the selection was copied \see Fl::copy(const char *, int, int) */ int Fl_Input_::copy(int clipboard) { int b = position(); int e = mark(); if (b != e) { if (b > 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); } } } /** Deletes text from \p b to \p e and inserts the new string \p text. All changes to the text buffer go through this function. It deletes the region between \p b and \p e (either one may be less or equal to the other), and then inserts the string \p text at that point and moves the mark() and position() to the end of the insertion. Does the callback if <tt>when() & FL_WHEN_CHANGED</tt> and there is a change. Set \p b and \p e equal to not delete anything. Set \p text to \c NULL to not insert anything. \p ilen can be zero or <tt>strlen(text)</tt>, which saves a tiny bit of time if you happen to already know the length of the insertion, or can be used to insert a portion of a string. If \p ilen is zero, <tt>strlen(text)</tt> is used instead. \p b and \p e are clamped to the <tt>0..size()</tt> range, so it is safe to pass any values. \p b, \p e, and \p ilen are used as numbers of bytes (not characters), where \p b and \p e count from 0 to size() (end of buffer). If \p b and/or \p e don't point to a valid UTF-8 character boundary, they are adjusted to the previous (\p b) or the next (\p e) valid UTF-8 character boundary, resp.. If the current number of characters in the buffer minus deleted characters plus inserted characters in \p text would overflow the number of allowed characters (maximum_size()), then only the first characters of the string are inserted, so that maximum_size() is not exceeded. cut() and insert() are just inline functions that call replace(). \param [in] b beginning index of text to be deleted \param [in] e ending index of text to be deleted and insertion position \param [in] text string that will be inserted \param [in] ilen length of \p text or 0 for \c nul terminated strings \return 0 if nothing changed \note If \p text does not point to a valid UTF-8 character or includes invalid UTF-8 sequences, the text is inserted nevertheless (counting invalid UTF-8 bytes as one character each). */ int Fl_Input_::replace(int b, int e, const char* text, int ilen) { int ul, om, op; 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;} while (b != e && b > 0 && (size_ - b) > 0 && (fl_utf8len((value_ + b)[0]) < 1)) { b--; } ul = fl_utf8len((char)(value_ + e)[0]); while (e < size_ && e > 0 && ul < 0) { e++; ul = fl_utf8len((char)(value_ + e)[0]); } if (text && !ilen) ilen = (int) strlen(text); if (e<=b && !ilen) return 0; // don't clobber undo for a null operation // we must count UTF-8 *characters* to determine whether we can insert // the full text or only a part of it (and how much this would be) int nchars = 0; // characters in value() - deleted + inserted const char *p = value_; while (p < (char *)(value_+size_)) { if (p == (char *)(value_+b)) { // skip removed part p = (char *)(value_+e); if (p >= (char *)(value_+size_)) break; } int ulen = fl_utf8len(*p); if (ulen < 1) ulen = 1; // invalid UTF-8 character: count as 1 nchars++; p += ulen; } int nlen = 0; // length (in bytes) to be inserted p = text; while (p < (char *)(text+ilen) && nchars < maximum_size()) { int ulen = fl_utf8len(*p); if (ulen < 1) ulen = 1; // invalid UTF-8 character: count as 1 nchars++; p += ulen; nlen += ulen; } ilen = nlen; 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); memcpy(undobuffer, value_+b, e-b); undocut = e-b; undoinsert = 0; } memmove(buffer+b, buffer+e, size_-e+1); size_ -= e-b; undowidget = this; undoat = b; if (input_type() == FL_SECRET_INPUT) yankcut = 0; else yankcut = undocut; } if (ilen) { if (undowidget == this && b == undoat) undoinsert += ilen; else { undocut = 0; undoinsert = ilen; } memmove(buffer+b+ilen, buffer+b, size_-b+1); memcpy(buffer+b, text, ilen); size_ += ilen; } undowidget = this; om = mark_; op = position_; mark_ = position_ = undoat = b+ilen; // Insertions into the word at the end of the line will cause it to // wrap to the next line, so we must indicate that the changes may start // right after the whitespace before the current word. This will // result in sub-optimal update when such wrapping does not happen // but it is too hard to figure out for now... if (wrap()) { // if there is a space in the pasted text, the whole line may have rewrapped int i; for (i=0; i<ilen; i++) if (text[i]==' ') break; if (i==ilen) while (b > 0 && !isspace(index(b) & 255) && index(b)!='\n') b--; else while (b > 0 && index(b)!='\n') b--; } // make sure we redraw the old selection or cursor: if (om < b) b = om; if (op < b) b = op; minimal_update(b); mark_ = position_ = undoat; set_changed(); if (when()&FL_WHEN_CHANGED) do_callback(); return 1; } /** Undoes previous changes to the text buffer. This call undoes a number of previous calls to replace(). \return non-zero if any change was made. */ 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; if (wrap()) while (b1 > 0 && index(b1)!='\n') b1--; minimal_update(b1); set_changed(); if (when()&FL_WHEN_CHANGED) do_callback(); return 1; } /** Copies the \e yank buffer to the clipboard. This method copies all the previous contiguous cuts from the undo information to the clipboard. This function implements the \c ^K shortcut key. \return 0 if the operation did not change the clipboard \see copy(int), cut() */ 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; } /** \internal Checks the when() field and does a callback if indicated. */ void Fl_Input_::maybe_do_callback() { if (changed() || (when()&FL_WHEN_NOT_CHANGED)) { do_callback(); } } /** Handles all kinds of text field related events. This is called by derived classes. \todo Add comment and parameters */ int Fl_Input_::handletext(int event, int X, int Y, int W, int H) { switch (event) { case FL_ENTER: case FL_MOVE: if (active_r() && window()) window()->cursor(FL_CURSOR_INSERT); return 1; case FL_LEAVE: if (active_r() && window()) window()->cursor(FL_CURSOR_DEFAULT); return 1; case FL_FOCUS: fl_set_spot(textfont(), textsize(), x(), y(), w(), h(), window()); if (mark_ == position_) { minimal_update(size()+1); } else //if (Fl::selection_owner() != this) minimal_update(mark_, position_); return 1; case FL_UNFOCUS: if (active_r() && window()) window()->cursor(FL_CURSOR_DEFAULT); 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: fl_reset_spot(); if (!readonly() && (when() & FL_WHEN_RELEASE)) maybe_do_callback(); return 1; case FL_PUSH: if (active_r() && window()) window()->cursor(FL_CURSOR_INSERT); 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: { // Don't allow pastes into readonly widgets... if (readonly()) { fl_beep(FL_BEEP_ERROR); return 1; } // See if we have anything to paste... if (!Fl::event_text() || !Fl::event_length()) return 1; // 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) & 255)) e--; if (!t || e <= t) return 1; // Int/float stuff will crash without this test if (input_type() == FL_INT_INPUT) { while (isspace(*t & 255) && t < e) t ++; const char *p = t; if (*p == '+' || *p == '-') p ++; if (strncmp(p, "0x", 2) == 0) { p += 2; while (isxdigit(*p & 255) && p < e) p ++; } else { while (isdigit(*p & 255) && p < e) p ++; } if (p < e) { fl_beep(FL_BEEP_ERROR); return 1; } else return replace(0, size(), t, (int) (e-t)); } else if (input_type() == FL_FLOAT_INPUT) { while (isspace(*t & 255) && t < e) t ++; const char *p = t; if (*p == '+' || *p == '-') p ++; while (isdigit(*p & 255) && p < e) p ++; if (*p == '.') { p ++; while (isdigit(*p & 255) && p < e) p ++; if (*p == 'e' || *p == 'E') { p ++; if (*p == '+' || *p == '-') p ++; while (isdigit(*p & 255) && p < e) p ++; } } if (p < e) { fl_beep(FL_BEEP_ERROR); return 1; } else return replace(0, size(), t, (int) (e-t)); } return replace(position(), mark(), t, (int) (e-t));} case FL_SHORTCUT: if (!(shortcut() ? Fl::test_shortcut(shortcut()) : test_shortcut())) return 0; if (Fl::visible_focus() && handle(FL_FOCUS)) { Fl::focus(this); return 1; } // else fall through default: return 0; } } /*------------------------------*/ /** Creates a new Fl_Input_ widget. This function creates a new Fl_Input_ widget and adds it to the current Fl_Group. The value() is set to \c NULL. The default boxtype is \c FL_DOWN_BOX. \param X, Y, W, H the dimensions of the new widget \param l an optional label text */ 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; shortcut_ = 0; set_flag(SHORTCUT_LABEL); set_flag(MAC_USE_ACCENTS_MENU); tab_nav(1); } /** Copies the value from a possibly static entry into the internal buffer. \param [in] len size of the current text */ 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; } // Note: the following code is equivalent to: // // if (moveit) value_ = value_ - buffer; // char* nbuffer = (char*)realloc(buffer, bufsize); // if (moveit) value_ = value_ + nbuffer; // buffer = nbuffer; // // We just optimized the pointer arithmetic for value_... // char* nbuffer = (char*)realloc(buffer, bufsize); if (moveit) value_ += (nbuffer-buffer); buffer = nbuffer; } memmove(buffer, value_, size_); buffer[size_] = 0; value_ = buffer; } /** Changes the widget text. This function changes the text and sets the mark and the point to the end of it. The string is \e not copied. If the user edits the string it is copied to the internal buffer then. This can save a great deal of time and memory if your program is rapidly changing the values of text fields, but this will only work if the passed string remains unchanged until either the Fl_Input is destroyed or value() is called again. You can use the \p len parameter to directly set the length if you know it already or want to put \c nul characters in the text. \param [in] str the new text \param [in] len the length of the new text \return non-zero if the new value is different than the current one */ 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++) {/*empty*/} 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(readonly() ? 0 : size()); return 1; } /** Changes the widget text. This function changes the text and sets the mark and the point to the end of it. The string is \e not copied. If the user edits the string it is copied to the internal buffer then. This can save a great deal of time and memory if your program is rapidly changing the values of text fields, but this will only work if the passed string remains unchanged until either the Fl_Input is destroyed or value() is called again. \param [in] str the new text \return non-zero if the new value is different than the current one */ int Fl_Input_::static_value(const char* str) { return static_value(str, str ? (int) strlen(str) : 0); } /** Changes the widget text. This function changes the text and sets the mark and the point to the end of it. The string is copied to the internal buffer. Passing \c NULL is the same as "". You can use the \p length parameter to directly set the length if you know it already or want to put \c nul characters in the text. \param [in] str the new text \param [in] len the length of the new text \return non-zero if the new value is different than the current one \see Fl_Input_::value(const char* str), Fl_Input_::value() */ int Fl_Input_::value(const char* str, int len) { int r = static_value(str, len); if (len) put_in_buffer(len); return r; } /** Changes the widget text. This function changes the text and sets the mark and the point to the end of it. The string is copied to the internal buffer. Passing \c NULL is the same as \c "". \param [in] str the new text \return non-zero if the new value is different than the current one \see Fl_Input_::value(const char* str, int len), Fl_Input_::value() */ int Fl_Input_::value(const char* str) { return value(str, str ? (int) strlen(str) : 0); } /** Changes the size of the widget. This call updates the text layout so that the cursor is visible. \param [in] X, Y, W, H new size of the widget \see Fl_Widget::resize(int, int, int, int) */ 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); } /** Destroys the widget. The destructor clears all allocated buffers and removes the widget from the parent Fl_Group. */ Fl_Input_::~Fl_Input_() { if (undowidget == this) undowidget = 0; if (bufsize) free((void*)buffer); } /** \internal Returns the number of lines displayed on a single page. \return widget height divided by the font height */ int Fl_Input_::linesPerPage() { int n = 1; if (input_type() == FL_MULTILINE_INPUT) { fl_font(textfont(),textsize()); //ensure current font is set to ours n = h()/fl_height(); // number of lines to scroll if (n<=0) n = 1; } return n; } /** Returns the character at index \p i. This function returns the UTF-8 character at \p i as a ucs4 character code. \param [in] i index into the value field \return the character at index \p i */ unsigned int Fl_Input_::index(int i) const { int len = 0; return fl_utf8decode(value_+i, value_+size_, &len); } // // End of "$Id$". //