fltk/src/Fl_Input_.cxx
Albrecht Schlosser 8ed93f0065 fix a typo and indenting.
git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@9326 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
2012-04-05 14:30:19 +00:00

1303 lines
37 KiB
C++

//
// "$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);
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));
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 && 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);
}
}
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 ucs4-aware.
*/
static int isword(char c) {
return (c&128 || isalnum(c) || strchr("#%&-/@\\_~", c));
}
/**
Finds the end of a word.
This call calculates the end of a word based on the given
index \p i. Calling this function repeatedly will move
forwards to the end of the text.
\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++;
while (i < size() && isword(index(i))) i++;
return i;
}
/**
Finds the start of a word.
This call calculates the start of a word based on the given
index \p i. Calling this function repeatedly will move
backwards to the beginning of the text.
\param [in] i starting index for the search
\return start of the word
*/
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--;
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;
}
/**
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 = 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 = (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);
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++);
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$".
//