Include a stripped-down version of FLTK in tree and add a USE_INCLUDED_FLTK option to build against it.


git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4603 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/common/fltk/src/Fl_Browser_.cxx b/common/fltk/src/Fl_Browser_.cxx
new file mode 100644
index 0000000..1f738b1
--- /dev/null
+++ b/common/fltk/src/Fl_Browser_.cxx
@@ -0,0 +1,1107 @@
+//
+// "$Id: Fl_Browser_.cxx 7903 2010-11-28 21:06:39Z matt $"
+//
+// Base Browser widget class for the Fast Light Tool Kit (FLTK).
+//
+// Copyright 1998-2010 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 on the following page:
+//
+//     http://www.fltk.org/str.php
+//
+
+#define DISPLAY_SEARCH_BOTH_WAYS_AT_ONCE
+
+#include <stdio.h>
+#include <FL/Fl.H>
+#include <FL/Fl_Widget.H>
+#include <FL/Fl_Browser_.H>
+#include <FL/fl_draw.H>
+
+
+// This is the base class for browsers.  To be useful it must be
+// subclassed and several virtual functions defined.  The
+// Forms-compatible browser and the file chooser's browser are
+// subclassed off of this.
+
+// Yes, I know this should be a template...
+
+// This has been designed so that the subclass has complete control
+// over the storage of the data, although because next() and prev()
+// functions are used to index, it works best as a linked list or as a
+// large block of characters in which the line breaks must be searched
+// for.
+
+// A great deal of work has been done so that the "height" of a data
+// object does not need to be determined until it is drawn.  This was
+// done for the file chooser, because the height requires doing stat()
+// to see if the file is a directory, which can be annoyingly slow
+// over the network.
+
+/* redraw bits:
+   1 = redraw children (the scrollbar)
+   2 = redraw one or two items
+   4 = redraw all items
+*/
+
+static void scrollbar_callback(Fl_Widget* s, void*) {
+  ((Fl_Browser_*)(s->parent()))->position(int(((Fl_Scrollbar*)s)->value()));
+}
+
+static void hscrollbar_callback(Fl_Widget* s, void*) {
+  ((Fl_Browser_*)(s->parent()))->hposition(int(((Fl_Scrollbar*)s)->value()));
+}
+
+// return where to draw the actual box:
+/**
+  Returns the bounding box for the interior of the list's display window, inside
+  the scrollbars.
+  \param[out] X,Y,W,H The returned bounding box.\n
+                      (The original contents of these parameters are overwritten)
+*/
+void Fl_Browser_::bbox(int& X, int& Y, int& W, int& H) const {
+  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+  Fl_Boxtype b = box() ? box() : FL_DOWN_BOX;
+  X = x()+Fl::box_dx(b);
+  Y = y()+Fl::box_dy(b);
+  W = w()-Fl::box_dw(b);
+  H = h()-Fl::box_dh(b);
+  if (scrollbar.visible()) {
+    W -= scrollsize;
+    if (scrollbar.align() & FL_ALIGN_LEFT) X += scrollsize;
+  }
+  if (W < 0) W = 0;
+  if (hscrollbar.visible()) {
+    H -= scrollsize;
+    if (scrollbar.align() & FL_ALIGN_TOP) Y += scrollsize;
+  }
+  if (H < 0) H = 0;
+}
+
+/**
+  This method returns the X position of the left edge of the list area
+  after adjusting for the scrollbar and border, if any.
+  \returns The X position of the left edge of the list, in pixels.
+  \see Fl_Browser_::bbox()
+*/
+int Fl_Browser_::leftedge() const {
+  int X, Y, W, H; bbox(X, Y, W, H);
+  return X;
+}
+
+// The scrollbars may be moved again by draw(), since each one's size
+// depends on whether the other is visible or not.  This skips over
+// Fl_Group::resize since it moves the scrollbars uselessly.
+/**
+  Repositions and/or resizes the browser.
+  \param[in] X,Y,W,H The new position and size for the browser, in pixels.
+*/
+void Fl_Browser_::resize(int X, int Y, int W, int H) {
+  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+  Fl_Widget::resize(X, Y, W, H);
+  // move the scrollbars so they can respond to events:
+  bbox(X,Y,W,H);
+  scrollbar.resize(
+	scrollbar.align()&FL_ALIGN_LEFT ? X-scrollsize : X+W,
+	Y, scrollsize, H);
+  hscrollbar.resize(
+	X, scrollbar.align()&FL_ALIGN_TOP ? Y-scrollsize : Y+H,
+	W, scrollsize);
+}
+
+// Cause minimal update to redraw the given item:
+/**
+  This method should be called when the contents of \p item has changed,
+  but not its height.
+  \param[in] item The item that needs to be redrawn.
+  \see redraw_lines(), redraw_line()
+*/
+void Fl_Browser_::redraw_line(void* item) {
+  if (!redraw1 || redraw1 == item) {redraw1 = item; damage(FL_DAMAGE_EXPOSE);}
+  else if (!redraw2 || redraw2 == item) {redraw2 = item; damage(FL_DAMAGE_EXPOSE);}
+  else damage(FL_DAMAGE_SCROLL);
+}
+
+// Figure out top() based on position():
+void Fl_Browser_::update_top() {
+  if (!top_) top_ = item_first();
+  if (position_ != real_position_) {
+    void* l;
+    int ly;
+    int yy = position_;
+    // start from either head or current position, whichever is closer:
+    if (!top_ || yy <= (real_position_/2)) {
+      l = item_first();
+      ly = 0;
+    } else {
+      l = top_;
+      ly = real_position_-offset_;
+    }
+    if (!l) {
+      top_ = 0;
+      offset_ = 0;
+      real_position_ = 0;
+    } else {
+      int hh = item_quick_height(l);
+      // step through list until we find line containing this point:
+      while (ly > yy) {
+	void* l1 = item_prev(l);
+	if (!l1) {ly = 0; break;} // hit the top
+	l  = l1;
+	hh = item_quick_height(l);
+	ly -= hh;
+      }
+      while ((ly+hh) <= yy) {
+	void* l1 = item_next(l);
+	if (!l1) {yy = ly+hh-1; break;}
+	l = l1;
+	ly += hh;
+	hh = item_quick_height(l);
+      }
+      // top item must *really* be visible, use slow height:
+      for (;;) {
+	hh = item_height(l);
+	if ((ly+hh) > yy) break; // it is big enough to see
+	// go up to top of previous item:
+	void* l1 = item_prev(l);
+	if (!l1) {ly = yy = 0; break;} // hit the top
+	l = l1; yy = position_ = ly = ly-item_quick_height(l);
+      }
+      // use it:
+      top_ = l;
+      offset_ = yy-ly;
+      real_position_ = yy;
+    }
+    damage(FL_DAMAGE_SCROLL);
+  }
+}
+
+// Change position(), top() will update when update_top() is called
+// (probably by draw() or handle()):
+/**
+  Sets the vertical scroll position of the list to pixel position \p pos.
+  The position is how many pixels of the list are scrolled off the top edge
+  of the screen. Example: A position of '3' scrolls the top three pixels of
+  the list off the top edge of the screen.
+  \param[in] pos The vertical position (in pixels) to scroll the browser to.
+  \see position(), hposition()
+*/
+void Fl_Browser_::position(int pos) {
+  if (pos < 0) pos = 0;
+  if (pos == position_) return;
+  position_ = pos;
+  if (pos != real_position_) redraw_lines();
+}
+
+/**
+  Sets the horizontal scroll position of the list to pixel position \p pos.
+  The position is how many pixels of the list are scrolled off the left edge
+  of the screen. Example: A position of '18' scrolls the left 18 pixels of the list
+  off the left edge of the screen.
+  \param[in] pos The horizontal position (in pixels) to scroll the browser to.
+  \see position(), hposition()
+*/
+void Fl_Browser_::hposition(int pos) {
+  if (pos < 0) pos = 0;
+  if (pos == hposition_) return;
+  hposition_ = pos;
+  if (pos != real_hposition_) redraw_lines();
+}
+
+// Tell whether item is currently displayed:
+/**
+  Returns non-zero if \p item has been scrolled to a position where it is being displayed.
+  Checks to see if the item's vertical position is within the top and bottom
+  edges of the display window. This does NOT take into account the hide()/show()
+  status of the widget or item.
+  \param[in] item The item to check
+  \returns 1 if visible, 0 if not visible.
+  \see display(), displayed()
+*/
+int Fl_Browser_::displayed(void* item) const {
+  int X, Y, W, H; bbox(X, Y, W, H);
+  int yy = H+offset_;
+  for (void* l = top_; l && yy > 0; l = item_next(l)) {
+    if (l == item) return 1;
+    yy -= item_height(l);
+  }
+  return 0;
+}
+
+// Ensure this item is displayed:
+// Messy because we have no idea if it is before top or after bottom:
+/**
+  Displays the \p item, scrolling the list as necessary.
+  \param[in] item The item to be displayed.
+  \see display(), displayed()
+*/
+void Fl_Browser_::display(void* item) {
+
+  // First special case - want to display first item in the list?
+  update_top();
+  if (item == item_first()) {position(0); return;}
+
+  int X, Y, W, H, Yp; bbox(X, Y, W, H);
+  void* l = top_;
+  Y = Yp = -offset_;
+  int h1;
+
+  // 2nd special case - want to display item already displayed at top of browser?
+  if (l == item) {position(real_position_+Y); return;} // scroll up a bit
+
+  // 3rd special case - want to display item just above top of browser?
+  void* lp = item_prev(l);
+  if (lp == item) {position(real_position_+Y-item_quick_height(lp)); return;}
+
+#ifdef DISPLAY_SEARCH_BOTH_WAYS_AT_ONCE
+  // search for item.  We search both up and down the list at the same time,
+  // this evens up the execution time for the two cases - the old way was
+  // much slower for going up than for going down.
+  while (l || lp) {
+    if (l) {
+      h1 = item_quick_height(l);
+      if (l == item) {
+	if (Y <= H) { // it is visible or right at bottom
+	  Y = Y+h1-H; // find where bottom edge is
+	  if (Y > 0) position(real_position_+Y); // scroll down a bit
+	} else {
+	  position(real_position_+Y-(H-h1)/2); // center it
+	}
+	return;
+      }
+      Y += h1;
+      l = item_next(l);
+    }
+    if (lp) {
+      h1 = item_quick_height(lp);
+      Yp -= h1;
+      if (lp == item) {
+	if ((Yp + h1) >= 0) position(real_position_+Yp);
+	else position(real_position_+Yp-(H-h1)/2);
+	return;
+      }
+      lp = item_prev(lp);
+    }
+  }
+#else
+  // Old version went forwards and then backwards:
+  // search forward for it:
+  l = top_;
+  for (; l; l = item_next(l)) {
+    h1 = item_quick_height(l);
+    if (l == item) {
+      if (Y <= H) { // it is visible or right at bottom
+	Y = Y+h1-H; // find where bottom edge is
+	if (Y > 0) position(real_position_+Y); // scroll down a bit
+      } else {
+	position(real_position_+Y-(H-h1)/2); // center it
+      }
+      return;
+    }
+    Y += h1;
+  }
+  // search backward for it, if found center it:
+  l = lp;
+  Y = -offset_;
+  for (; l; l = item_prev(l)) {
+    h1 = item_quick_height(l);
+    Y -= h1;
+    if (l == item) {
+      if ((Y + h1) >= 0) position(real_position_+Y);
+      else position(real_position_+Y-(H-h1)/2);
+      return;
+    }
+  }
+#endif
+}
+
+// redraw, has side effect of updating top and setting scrollbar:
+/**
+  Draws the list within the normal widget bounding box.
+*/
+void Fl_Browser_::draw() {
+  int drawsquare = 0;
+  update_top();
+  int full_width_ = full_width();
+  int full_height_ = full_height();
+  int X, Y, W, H; bbox(X, Y, W, H);
+  int dont_repeat = 0;
+J1:
+  if (damage() & FL_DAMAGE_ALL) { // redraw the box if full redraw
+    Fl_Boxtype b = box() ? box() : FL_DOWN_BOX;
+    draw_box(b, x(), y(), w(), h(), color());
+    drawsquare = 1;
+  }
+  // see if scrollbar needs to be switched on/off:
+  if ((has_scrollbar_ & VERTICAL) && (
+	(has_scrollbar_ & ALWAYS_ON) || position_ || full_height_ > H)) {
+    if (!scrollbar.visible()) {
+      scrollbar.set_visible();
+      drawsquare = 1;
+      bbox(X, Y, W, H);
+    }
+  } else {
+    top_ = item_first(); real_position_ = offset_ = 0;
+    if (scrollbar.visible()) {
+      scrollbar.clear_visible();
+      clear_damage((uchar)(damage()|FL_DAMAGE_SCROLL));
+    }
+  }
+
+  if ((has_scrollbar_ & HORIZONTAL) && (
+	(has_scrollbar_ & ALWAYS_ON) || hposition_ || full_width_ > W)) {
+    if (!hscrollbar.visible()) {
+      hscrollbar.set_visible();
+      drawsquare = 1;
+      bbox(X, Y, W, H);
+    }
+  } else {
+    real_hposition_ = 0;
+    if (hscrollbar.visible()) {
+      hscrollbar.clear_visible();
+      clear_damage((uchar)(damage()|FL_DAMAGE_SCROLL));
+    }
+  }
+
+  // Check the vertical scrollbar again, just in case it needs to be drawn
+  // because the horizontal one is drawn.  There should be a cleaner way
+  // to do this besides copying the same code...
+  if ((has_scrollbar_ & VERTICAL) && (
+	(has_scrollbar_ & ALWAYS_ON) || position_ || full_height_ > H)) {
+    if (!scrollbar.visible()) {
+      scrollbar.set_visible();
+      drawsquare = 1;
+      bbox(X, Y, W, H);
+    }
+  } else {
+    top_ = item_first(); real_position_ = offset_ = 0;
+    if (scrollbar.visible()) {
+      scrollbar.clear_visible();
+      clear_damage((uchar)(damage()|FL_DAMAGE_SCROLL));
+    }
+  }
+
+  bbox(X, Y, W, H);
+
+  fl_push_clip(X, Y, W, H);
+  // for each line, draw it if full redraw or scrolled.  Erase background
+  // if not a full redraw or if it is selected:
+  void* l = top();
+  int yy = -offset_;
+  for (; l && yy < H; l = item_next(l)) {
+    int hh = item_height(l);
+    if (hh <= 0) continue;
+    if ((damage()&(FL_DAMAGE_SCROLL|FL_DAMAGE_ALL)) || l == redraw1 || l == redraw2) {
+      if (item_selected(l)) {
+	fl_color(active_r() ? selection_color() : fl_inactive(selection_color()));
+	fl_rectf(X, yy+Y, W, hh);
+      } else if (!(damage()&FL_DAMAGE_ALL)) {
+	fl_push_clip(X, yy+Y, W, hh);
+	draw_box(box() ? box() : FL_DOWN_BOX, x(), y(), w(), h(), color());
+	fl_pop_clip();
+      }
+      item_draw(l, X-hposition_, yy+Y, W+hposition_, hh);
+      if (l == selection_ && Fl::focus() == this) {
+	draw_box(FL_BORDER_FRAME, X, yy+Y, W, hh, color());
+	draw_focus(FL_NO_BOX, X, yy+Y, W+1, hh+1);
+      }
+      int ww = item_width(l);
+      if (ww > max_width) {max_width = ww; max_width_item = l;}
+    }
+    yy += hh;
+  }
+  // erase the area below last line:
+  if (!(damage()&FL_DAMAGE_ALL) && yy < H) {
+    fl_push_clip(X, yy+Y, W, H-yy);
+    draw_box(box() ? box() : FL_DOWN_BOX, x(), y(), w(), h(), color());
+    fl_pop_clip();
+  }
+  fl_pop_clip();
+  redraw1 = redraw2 = 0;
+
+  if (!dont_repeat) {
+    dont_repeat = 1;
+    // see if changes to full_height caused by calls to slow_height
+    // caused scrollbar state to change, in which case we have to redraw:
+    full_height_ = full_height();
+    full_width_ = full_width();
+    if ((has_scrollbar_ & VERTICAL) &&
+	((has_scrollbar_ & ALWAYS_ON) || position_ || full_height_>H)) {
+      if (!scrollbar.visible()) { damage(FL_DAMAGE_ALL); goto J1; }
+    } else {
+      if (scrollbar.visible()) { damage(FL_DAMAGE_ALL); goto J1; }
+    }
+    if ((has_scrollbar_ & HORIZONTAL) &&
+	((has_scrollbar_ & ALWAYS_ON) || hposition_ || full_width_>W)) {
+      if (!hscrollbar.visible()) { damage(FL_DAMAGE_ALL); goto J1; }
+    } else {
+      if (hscrollbar.visible()) { damage(FL_DAMAGE_ALL); goto J1; }
+    }
+  }
+
+  // update the scrollbars and redraw them:
+  int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
+  int dy = top_ ? item_quick_height(top_) : 0; if (dy < 10) dy = 10;
+  if (scrollbar.visible()) {
+    scrollbar.damage_resize(
+	scrollbar.align()&FL_ALIGN_LEFT ? X-scrollsize : X+W,
+	Y, scrollsize, H);
+    scrollbar.value(position_, H, 0, full_height_);
+    scrollbar.linesize(dy);
+    if (drawsquare) draw_child(scrollbar);
+    else update_child(scrollbar);
+  }
+  if (hscrollbar.visible()) {
+    hscrollbar.damage_resize(
+	X, scrollbar.align()&FL_ALIGN_TOP ? Y-scrollsize : Y+H,
+	W, scrollsize);
+    hscrollbar.value(hposition_, W, 0, full_width_);
+    hscrollbar.linesize(dy);
+    if (drawsquare) draw_child(hscrollbar);
+    else update_child(hscrollbar);
+  }
+
+  // draw that little square between the scrollbars:
+  if (drawsquare && scrollbar.visible() && hscrollbar.visible()) {
+    fl_color(parent()->color());
+    fl_rectf(scrollbar.x(), hscrollbar.y(), scrollsize, scrollsize);
+  }
+
+  real_hposition_ = hposition_;
+}
+
+// Quick way to delete and reset everything:
+/**
+  This method should be called when the list data is completely replaced
+  or cleared. It informs the Fl_Browser_ widget that any cached
+  information it has concerning the items is invalid.
+  This method does not clear the list, it just handles the follow up
+  bookkeeping after the list has been cleared.
+*/
+void Fl_Browser_::new_list() {
+  top_ = 0;
+  position_ = real_position_ = 0;
+  hposition_ = real_hposition_ = 0;
+  selection_ = 0;
+  offset_ = 0;
+  max_width = 0;
+  max_width_item = 0;
+  redraw_lines();
+}
+
+// Tell it that this item is going away, and that this must remove
+// all pointers to it:
+/**
+  This method should be used when \p item is being deleted from the list.
+  It allows the Fl_Browser_ to discard any cached data it has on the item.
+  This method does not actually delete the item, but handles the follow up
+  bookkeeping after the item has just been deleted.
+  \param[in] item The item being deleted.
+*/
+void Fl_Browser_::deleting(void* item) {
+  if (displayed(item)) {
+    redraw_lines();
+    if (item == top_) {
+      real_position_ -= offset_;
+      offset_ = 0;
+      top_ = item_next(item);
+      if (!top_) top_ = item_prev(item);
+    }
+  } else {
+    // we don't know where this item is, recalculate top...
+    real_position_ = 0;
+    offset_ = 0;
+    top_ = 0;
+  }
+  if (item == selection_) selection_ = 0;
+  if (item == max_width_item) {max_width_item = 0; max_width = 0;}
+}
+
+/**
+  This method should be used when item \p a is being replaced by item \p b.
+  It allows the Fl_Browser_ to update its cache data as needed,
+  schedules a redraw for the item being changed, and tries to maintain the selection.
+  This method does not actually replace the item, but handles the follow up
+  bookkeeping after the item has just been replaced.
+  \param[in] a Item being replaced
+  \param[in] b Item to replace 'a'
+*/
+void Fl_Browser_::replacing(void* a, void* b) {
+  redraw_line(a);
+  if (a == selection_) selection_ = b;
+  if (a == top_) top_ = b;
+  if (a == max_width_item) {max_width_item = 0; max_width = 0;}
+}
+
+/**
+  This method should be used when two items \p a and \p b are being swapped.
+  It allows the Fl_Browser_ to update its cache data as needed,
+  schedules a redraw for the two items, and tries to maintain the current selection.
+  This method does not actually swap items, but handles the follow up
+  bookkeeping after items have been swapped.
+  \param[in] a,b Items being swapped.
+*/
+void Fl_Browser_::swapping(void* a, void* b) {
+  redraw_line(a);
+  redraw_line(b);
+  if (a == selection_) selection_ = b;
+  else if (b == selection_) selection_ = a;
+  if (a == top_) top_ = b;
+  else if (b == top_) top_ = a;
+}
+
+/**
+  This method should be used when an item is in the process of
+  being inserted into the list.
+  It allows the Fl_Browser_ to update its cache data as needed,
+  scheduling a redraw for the affected lines.
+  This method does not actually insert items, but handles the 
+  follow up bookkeeping after items have been inserted.
+  \param[in] a The starting item position
+  \param[in] b The new item being inserted
+*/
+void Fl_Browser_::inserting(void* a, void* b) {
+  if (displayed(a)) redraw_lines();
+  if (a == top_) top_ = b;
+}
+
+/**
+  This method returns the item under mouse y position \p ypos.
+  NULL is returned if no item is displayed at that position.
+  \param[in] ypos The y position (eg. Fl::event_y()) to find an item under.
+  \returns The item, or NULL if not found
+*/
+void* Fl_Browser_::find_item(int ypos) {
+  update_top();
+  int X, Y, W, H; bbox(X, Y, W, H);
+  int yy = Y-offset_;
+  for (void *l = top_; l; l = item_next(l)) {
+    int hh = item_height(l); if (hh <= 0) continue;
+    yy += hh;
+    if (ypos <= yy || yy>=(Y+H)) return l;
+  }
+  return 0;
+}
+
+/**
+  Sets the selection state of \p item to \p val,
+  and returns 1 if the state changed or 0 if it did not.
+  
+  If \p docallbacks is non-zero, select tries to call
+  the callback function for the widget.
+
+  \param[in] item The item whose selection state is to be changed
+  \param[in] val The new selection state (1=select, 0=de-select)
+  \param[in] docallbacks If 1, invokes widget callback if item changed.\n
+                         If 0, doesn't do callback (default).
+  \returns 1 if state was changed, 0 if not.
+*/
+int Fl_Browser_::select(void* item, int val, int docallbacks) {
+  if (type() == FL_MULTI_BROWSER) {
+    if (selection_ != item) {
+      if (selection_) redraw_line(selection_);
+      selection_ = item;
+      redraw_line(item);
+    }
+    if ((!val)==(!item_selected(item))) return 0;
+    item_select(item, val);
+    redraw_line(item);
+  } else {
+    if (val && selection_ == item) return 0;
+    if (!val && selection_ != item) return 0;
+    if (selection_) {
+      item_select(selection_, 0);
+      redraw_line(selection_);
+      selection_ = 0;
+    }
+    if (val) {
+      item_select(item, 1);
+      selection_ = item;
+      redraw_line(item);
+      display(item);
+    }
+  }	    
+  if (docallbacks) {
+    set_changed();
+    do_callback();
+  }
+  return 1;
+}
+
+/**
+  Deselects all items in the list and returns 1 if the state changed
+  or 0 if it did not.
+  
+  If the optional \p docallbacks parameter is non-zero, deselect tries
+  to call the callback function for the widget.
+
+  \param[in] docallbacks If 1, invokes widget callback if item changed.\n
+                         If 0, doesn't do callback (default).
+*/
+int Fl_Browser_::deselect(int docallbacks) {
+  if (type() == FL_MULTI_BROWSER) {
+    int change = 0;
+    for (void* p = item_first(); p; p = item_next(p))
+      change |= select(p, 0, docallbacks);
+    return change;
+  } else {
+    if (!selection_) return 0;
+    item_select(selection_, 0);
+    redraw_line(selection_);
+    selection_ = 0;
+    return 1;
+  }
+}
+
+/**
+  Selects \p item and returns 1 if the state changed or 0 if it did not.
+  Any other items in the list are deselected.
+  \param[in] item The \p item to select.
+  \param[in] docallbacks If 1, invokes widget callback if item changed.\n
+                         If 0, doesn't do callback (default).
+*/
+int Fl_Browser_::select_only(void* item, int docallbacks) {
+  if (!item) return deselect(docallbacks);
+  int change = 0;
+  Fl_Widget_Tracker wp(this);
+  if (type() == FL_MULTI_BROWSER) {
+    for (void* p = item_first(); p; p = item_next(p)) {
+      if (p != item) change |= select(p, 0, docallbacks);
+      if (wp.deleted()) return change;
+    }
+  }
+  change |= select(item, 1, docallbacks);
+  if (wp.deleted()) return change;
+  display(item);
+  return change;
+}
+
+/**
+  Handles the \p event within the normal widget bounding box.
+  \param[in] event The event to process.
+  \returns 1 if event was processed, 0 if not.
+*/
+int Fl_Browser_::handle(int event) {
+
+  // NOTE:
+  // We use Fl_Widget_Tracker to test if the user has deleted
+  // this widget in a callback. Callbacks can be called by:
+  //  - do_callback()
+  //  - select()
+  //  - select_only()
+  //  - deselect()
+  // Thus we must test wp.deleted() after each of these calls,
+  // unless we return directly after one of these.
+  // If wp.deleted() is true, we return 1 because we used the event.
+
+  Fl_Widget_Tracker wp(this);
+
+  // must do shortcuts first or the scrollbar will get them...
+  if (event == FL_ENTER || event == FL_LEAVE) return 1;
+  if (event == FL_KEYBOARD && type() >= FL_HOLD_BROWSER) {
+    void* l1 = selection_;
+    void* l = l1; if (!l) l = top_; if (!l) l = item_first();
+    if (l) {
+      if (type()==FL_HOLD_BROWSER) {
+        switch (Fl::event_key()) {
+        case FL_Down:
+          while ((l = item_next(l)))
+            if (item_height(l)>0) {select_only(l, when()); break;}
+            return 1;
+        case FL_Up:
+          while ((l = item_prev(l))) {
+	    if (item_height(l)>0) {
+	      select_only(l, when());
+	      break; // no need to test wp (return 1)
+	    }
+	  }
+          return 1;
+        } 
+      } else  {
+        switch (Fl::event_key()) {
+        case FL_Enter:
+        case FL_KP_Enter:
+          select_only(l, when() & ~FL_WHEN_ENTER_KEY);
+	  if (wp.deleted()) return 1;
+	  if (when() & FL_WHEN_ENTER_KEY) {
+	    set_changed();
+	    do_callback();
+	  }
+          return 1;
+        case ' ':
+          selection_ = l;
+          select(l, !item_selected(l), when() & ~FL_WHEN_ENTER_KEY);
+          return 1;
+        case FL_Down:
+          while ((l = item_next(l))) {
+            if (Fl::event_state(FL_SHIFT|FL_CTRL))
+              select(l, l1 ? item_selected(l1) : 1, when());
+	    if (wp.deleted()) return 1;
+            if (item_height(l)>0) goto J1;
+          }
+          return 1;
+        case FL_Up:
+          while ((l = item_prev(l))) {
+            if (Fl::event_state(FL_SHIFT|FL_CTRL))
+              select(l, l1 ? item_selected(l1) : 1, when());
+	    if (wp.deleted()) return 1;
+            if (item_height(l)>0) goto J1;
+          }
+          return 1;
+J1:
+          if (selection_) redraw_line(selection_);
+          selection_ = l; redraw_line(l);
+          display(l);
+          return 1;
+        }
+      }
+    }
+  }
+  
+  if (Fl_Group::handle(event)) return 1;
+  if (wp.deleted()) return 1;
+
+  int X, Y, W, H; bbox(X, Y, W, H);
+  int my;
+// NOTE:
+// instead of:
+//     change = select_only(find_item(my), when() & FL_WHEN_CHANGED)
+// we use the construct:
+//     change = select_only(find_item(my), 0);
+//     if (change && (when() & FL_WHEN_CHANGED)) {
+//	 set_changed();
+//       do_callback();
+//     }
+// See str #834
+// The first form calls the callback *before* setting change.
+// The callback may execute an Fl::wait(), resulting in another
+// call of Fl_Browser_::handle() for the same widget. The sequence
+// of events can be an FL_PUSH followed by an FL_RELEASE.
+// This second call of Fl_Browser_::handle() may result in a -
+// somewhat unexpected - second concurrent invocation of the callback.
+
+  static char change;
+  static char whichway;
+  static int py;
+  switch (event) {
+  case FL_PUSH:
+    if (!Fl::event_inside(X, Y, W, H)) return 0;
+    if (Fl::visible_focus()) {
+      Fl::focus(this);
+      redraw();
+    }
+    my = py = Fl::event_y();
+    change = 0;
+    if (type() == FL_NORMAL_BROWSER || !top_)
+      ;
+    else if (type() != FL_MULTI_BROWSER) {
+      change = select_only(find_item(my), 0);
+      if (wp.deleted()) return 1;
+      if (change && (when() & FL_WHEN_CHANGED)) {
+	set_changed();
+	do_callback();
+	if (wp.deleted()) return 1;
+      }
+    } else {
+      void* l = find_item(my);
+      whichway = 1;
+      if (Fl::event_state(FL_CTRL)) { // toggle selection:
+      TOGGLE:
+	if (l) {
+	  whichway = !item_selected(l);
+	  change = select(l, whichway, 0);
+	  if (wp.deleted()) return 1;
+	  if (change && (when() & FL_WHEN_CHANGED)) {
+	    set_changed();
+	    do_callback();
+	    if (wp.deleted()) return 1;
+	  }
+	}
+      } else if (Fl::event_state(FL_SHIFT)) { // extend selection:
+	if (l == selection_) goto TOGGLE;
+	// state of previous selection determines new value:
+	whichway = l ? !item_selected(l) : 1;
+	// see which of the new item or previous selection is earlier,
+	// by searching from the previous forward for this one:
+	int down;
+	if (!l) down = 1;
+	else {for (void* m = selection_; ; m = item_next(m)) {
+	  if (m == l) {down = 1; break;}
+	  if (!m) {down = 0; break;}
+	}}
+	if (down) {
+	  for (void* m = selection_; m != l; m = item_next(m)) {
+	    select(m, whichway, when() & FL_WHEN_CHANGED);
+	    if (wp.deleted()) return 1;
+	  }
+	} else {
+	  void* e = selection_;
+	  for (void* m = item_next(l); m; m = item_next(m)) {
+	    select(m, whichway, when() & FL_WHEN_CHANGED);
+	    if (wp.deleted()) return 1;
+	    if (m == e) break;
+	  }
+	}
+	// do the clicked item last so the select box is around it:
+	change = 1;
+	if (l) select(l, whichway, when() & FL_WHEN_CHANGED);
+	if (wp.deleted()) return 1;
+      } else { // select only this item
+	change = select_only(l, 0);
+	if (wp.deleted()) return 1;
+	if (change && (when() & FL_WHEN_CHANGED)) {
+	  set_changed();
+	  do_callback();
+	  if (wp.deleted()) return 1;
+	}
+      }
+    }
+    return 1;
+  case FL_DRAG:
+    // do the scrolling first:
+    my = Fl::event_y();
+    if (my < Y && my < py) {
+      int p = real_position_+my-Y;
+      if (p<0) p = 0;
+      position(p);
+    } else if (my > (Y+H) && my > py) {
+      int p = real_position_+my-(Y+H);
+      int hh = full_height()-H; if (p > hh) p = hh;
+      if (p<0) p = 0;
+      position(p);
+    }
+    if (type() == FL_NORMAL_BROWSER || !top_)
+      ;
+    else if (type() == FL_MULTI_BROWSER) {
+      void* l = find_item(my);
+      void* t; void* b; // this will be the range to change
+      if (my > py) { // go down
+	t = selection_ ? item_next(selection_) : 0;
+	b = l ? item_next(l) : 0;
+      } else {	// go up
+	t = l;
+	b = selection_;
+      }
+      for (; t && t != b; t = item_next(t)) {
+	char change_t;
+	change_t = select(t, whichway, 0);
+	if (wp.deleted()) return 1;
+	change |= change_t;
+	if (change_t && (when() & FL_WHEN_CHANGED)) {
+	  set_changed();
+	  do_callback();
+	  if (wp.deleted()) return 1;
+	}
+      }
+      if (l) selection_ = l;
+    } else {
+      void* l1 = selection_;
+      void* l =
+	(Fl::event_x()<x() || Fl::event_x()>x()+w()) ? selection_ :
+	find_item(my);
+      change = (l != l1);
+      select_only(l, when() & FL_WHEN_CHANGED);
+      if (wp.deleted()) return 1;
+    }
+    py = my;
+    return 1;
+  case FL_RELEASE:
+    if (type() == FL_SELECT_BROWSER) {
+      void* t = selection_;
+      deselect();
+      if (wp.deleted()) return 1;
+      selection_ = t;
+    }
+    if (change) {
+      set_changed();
+      if (when() & FL_WHEN_RELEASE) do_callback();
+    } else {
+      if (when() & FL_WHEN_NOT_CHANGED) do_callback();
+    }
+    if (wp.deleted()) return 1;
+
+    // double click calls the callback: (like Enter Key)
+    if (Fl::event_clicks() && (when() & FL_WHEN_ENTER_KEY)) {
+      set_changed();
+      do_callback();
+    }
+    return 1;
+  case FL_FOCUS:
+  case FL_UNFOCUS:
+    if (type() >= FL_HOLD_BROWSER && Fl::visible_focus()) {
+      redraw();
+      return 1;
+    } else return 0;
+  }
+
+  return 0;
+}
+
+/**
+  The constructor makes an empty browser.
+  \param[in] X,Y,W,H position and size.
+  \param[in] L The label string, may be NULL.
+*/
+Fl_Browser_::Fl_Browser_(int X, int Y, int W, int H, const char* L)
+  : Fl_Group(X, Y, W, H, L),
+    scrollbar(0, 0, 0, 0, 0), // they will be resized by draw()
+    hscrollbar(0, 0, 0, 0, 0)
+{
+  box(FL_NO_BOX);
+  align(FL_ALIGN_BOTTOM);
+  position_ = real_position_ = 0;
+  hposition_ = real_hposition_ = 0;
+  offset_ = 0;
+  top_ = 0;
+  when(FL_WHEN_RELEASE_ALWAYS);
+  selection_ = 0;
+  color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
+  scrollbar.callback(scrollbar_callback);
+//scrollbar.align(FL_ALIGN_LEFT|FL_ALIGN_BOTTOM); // back compatibility?
+  hscrollbar.callback(hscrollbar_callback);
+  hscrollbar.type(FL_HORIZONTAL);
+  textfont_ = FL_HELVETICA;
+  textsize_ = FL_NORMAL_SIZE;
+  textcolor_ = FL_FOREGROUND_COLOR;
+  has_scrollbar_ = BOTH;
+  max_width = 0;
+  max_width_item = 0;
+  scrollbar_size_ = 0;
+  redraw1 = redraw2 = 0;
+  end();
+}
+
+/**
+  Sort the items in the browser based on \p flags.
+  item_swap(void*, void*) and item_text(void*) must be implemented for this call.
+  \param[in] flags FL_SORT_ASCENDING -- sort in ascending order\n
+                   FL_SORT_DESCENDING -- sort in descending order\n
+		   Values other than the above will cause undefined behavior\n
+		   Other flags may appear in the future.
+  \todo Add a flag to ignore case
+*/
+void Fl_Browser_::sort(int flags) {
+  //
+  // Simple bubble sort - pure lazyness on my side.
+  //
+  int i, j, n = -1, desc = ((flags&FL_SORT_DESCENDING)==FL_SORT_DESCENDING);
+  void *a =item_first(), *b, *c;
+  if (!a) return;
+  while (a) {
+    a = item_next(a);
+    n++;
+  }
+  for (i=n; i>0; i--) {
+    char swapped = 0;
+    a = item_first();
+    b = item_next(a);
+    for (j=0; j<i; j++) {
+      const char *ta = item_text(a);
+      const char *tb = item_text(b);
+      c = item_next(b);
+      if (desc) {
+        if (strcmp(ta, tb)<0) {
+          item_swap(a, b);
+          swapped = 1;
+        }
+      } else {
+        if (strcmp(ta, tb)>0) {
+          item_swap(a, b);
+          swapped = 1;
+        }
+      }
+      if (!c) break;
+      b = c; a = item_prev(b);
+    }
+    if (!swapped)
+      break;
+  }
+}
+
+// Default versions of some of the virtual functions:
+
+/**
+  This method may be provided by the subclass to return the height of the
+  \p item, in pixels.  
+  Allow for two additional pixels for the list selection box.
+  This method differs from item_height in that it is only called for 
+  selection and scrolling operations. 
+  The default implementation calls item_height.
+  \param[in] item The item whose height to return.
+  \returns The height, in pixels.
+*/
+int Fl_Browser_::item_quick_height(void* item) const {
+  return item_height(item);
+}
+
+/**
+  This method may be provided to return the average height of all items
+  to be used for scrolling. 
+  The default implementation uses the height of the first item.
+  \returns The average height of items, in pixels.
+*/
+int Fl_Browser_::incr_height() const {
+  return item_quick_height(item_first());
+}
+
+/**
+  This method may be provided by the subclass to indicate the full height
+  of the item list, in pixels. 
+  The default implementation computes the full height from the item heights. 
+  Includes the items that are scrolled off screen.
+  \returns The height of the entire list, in pixels.
+*/
+int Fl_Browser_::full_height() const {
+  int t = 0;
+  for (void* p = item_first(); p; p = item_next(p))
+    t += item_quick_height(p);
+  return t;
+}
+
+/**
+  This method may be provided by the subclass to indicate the full width
+  of the item list, in pixels. 
+  The default implementation computes the full width from the item widths.
+  \returns The maximum width of all the items, in pixels.
+*/
+int Fl_Browser_::full_width() const {
+  return max_width;
+}
+
+/**
+  This method must be implemented by the subclass if it supports 
+  multiple selections; sets the selection state to \p val for the \p item.
+  Sets the selection state for \p item, where optional \p val is 1 (select, the default)
+  or 0 (de-select).
+  \param[in] item The item to be selected
+  \param[in] val The optional selection state; 1=select, 0=de-select.\n
+                 The default is to select the item (1).
+*/
+void Fl_Browser_::item_select(void *item, int val) {}
+
+/**
+  This method must be implemented by the subclass if it supports
+  multiple selections; returns the selection state for \p item.
+  The method should return 1 if \p item is selected, or 0 otherwise.
+  \param[in] item The item to test.
+*/
+int Fl_Browser_::item_selected(void* item) const { return item==selection_ ? 1 : 0; }
+
+//
+// End of "$Id: Fl_Browser_.cxx 7903 2010-11-28 21:06:39Z matt $".
+//