#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>
#include <FL/fl_utf8.h>
static void scrollbar_callback(Fl_Widget* s, void*) {
((Fl_Browser_*)(s->parent()))->vposition(int(((Fl_Scrollbar*)s)->value()));
}
static void hscrollbar_callback(Fl_Widget* s, void*) {
((Fl_Browser_*)(s->parent()))->hposition(int(((Fl_Scrollbar*)s)->value()));
}
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;
}
int Fl_Browser_::leftedge() const {
int X, Y, W, H; bbox(X, Y, W, H);
return X;
}
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);
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);
max_width = 0;
}
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);
}
void Fl_Browser_::update_top() {
if (!top_) top_ = item_first();
if (position_ != real_position_) {
void* l;
int ly;
int yy = position_;
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) + linespacing();
while (ly > yy) {
void* l1 = item_prev(l);
if (!l1) {ly = 0; break;} l = l1;
hh = item_quick_height(l) + linespacing();
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) + linespacing();
}
for (;;) {
hh = item_height(l) + linespacing();
if ((ly+hh) > yy) break; void* l1 = item_prev(l);
if (!l1) {ly = yy = 0; break;} l = l1; yy = position_ = ly = ly-item_quick_height(l) + linespacing();
}
top_ = l;
offset_ = yy-ly;
real_position_ = yy;
}
damage(FL_DAMAGE_SCROLL);
}
}
void Fl_Browser_::vposition(int pos) {
if (pos < 0) pos = 0;
if (pos == position_) return;
position_ = pos;
if (pos != real_position_) redraw_lines();
}
void Fl_Browser_::hposition(int pos) {
if (pos < 0) pos = 0;
if (pos == hposition_) return;
hposition_ = pos;
if (pos != real_hposition_) redraw_lines();
}
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) + linespacing();
}
return 0;
}
void Fl_Browser_::display(void* item) {
update_top();
if (item == item_first()) { vposition(0); return; }
int X, Y, W, H, Yp; bbox(X, Y, W, H);
void* l = top_;
Y = Yp = -offset_;
int h1;
if (l == item) { vposition(real_position_+Y); return; }
void* lp = item_prev(l);
if (lp == item) { vposition(real_position_+Y-item_quick_height(lp)-linespacing()); return; }
#ifdef DISPLAY_SEARCH_BOTH_WAYS_AT_ONCE
while (l || lp) {
if (l) {
h1 = item_quick_height(l) + linespacing();
if (l == item) {
if (Y <= H) { Y = Y+h1-H; if (Y > 0) vposition(real_position_+Y); } else {
vposition(real_position_+Y-(H-h1)/2); }
return;
}
Y += h1;
l = item_next(l);
}
if (lp) {
h1 = item_quick_height(lp) + linespacing();
Yp -= h1;
if (lp == item) {
if ((Yp + h1) >= 0) vposition(real_position_+Yp);
else vposition(real_position_+Yp-(H-h1)/2);
return;
}
lp = item_prev(lp);
}
}
#else
l = top_;
for (; l; l = item_next(l)) {
h1 = item_quick_height(l) + linespacing();
if (l == item) {
if (Y <= H) { Y = Y+h1-H; if (Y > 0) position(real_position_+Y); } else {
position(real_position_+Y-(H-h1)/2); }
return;
}
Y += h1;
}
l = lp;
Y = -offset_;
for (; l; l = item_prev(l)) {
h1 = item_quick_height(l) + linespacing();
Y -= h1;
if (l == item) {
if ((Y + h1) >= 0) position(real_position_+Y);
else position(real_position_+Y-(H-h1)/2);
return;
}
}
#endif
}
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) { Fl_Boxtype b = box() ? box() : FL_DOWN_BOX;
draw_box(b, x(), y(), w(), h(), color());
drawsquare = 1;
}
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));
}
}
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);
void* l = top();
int yy = -offset_;
for (; l && yy < H; l = item_next(l)) {
int hh = item_height(l) + linespacing();
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;
}
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();
fl_push_clip(x(),y(),w(),h()); redraw1 = redraw2 = 0;
if (!dont_repeat) {
dont_repeat = 1;
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); fl_pop_clip(); goto J1; }
} else {
if (scrollbar.visible()) { damage(FL_DAMAGE_ALL); fl_pop_clip(); goto J1; }
}
if ((has_scrollbar_ & HORIZONTAL) &&
((has_scrollbar_ & ALWAYS_ON) || hposition_ || full_width_>W)) {
if (!hscrollbar.visible()) { damage(FL_DAMAGE_ALL); fl_pop_clip(); goto J1; }
} else {
if (hscrollbar.visible()) { damage(FL_DAMAGE_ALL); fl_pop_clip(); goto J1; }
}
}
int scrollsize = scrollbar_size_ ? scrollbar_size_ : Fl::scrollbar_size();
int dy = top_ ? item_quick_height(top_) + linespacing() : 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);
}
if (drawsquare && scrollbar.visible() && hscrollbar.visible()) {
fl_color(parent()->color());
fl_rectf(scrollbar.x(), hscrollbar.y(), scrollsize, scrollsize);
}
real_hposition_ = hposition_;
fl_pop_clip();
}
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();
}
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 {
real_position_ = 0;
offset_ = 0;
top_ = 0;
}
if (item == selection_) selection_ = 0;
if (item == max_width_item) {max_width_item = 0; max_width = 0;}
}
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;}
}
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;
}
void Fl_Browser_::inserting(void* a, void* b) {
if (displayed(a)) redraw_lines();
if (a == top_) top_ = b;
}
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 + linespacing();
if (ypos <= yy || yy>=(Y+H)) return l;
}
return 0;
}
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(FL_REASON_CHANGED);
}
return 1;
}
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;
}
}
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;
}
int Fl_Browser_::handle(int event) {
Fl_Widget_Tracker wp(this);
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; }
}
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(FL_REASON_CHANGED);
}
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;
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(FL_REASON_CHANGED);
if (wp.deleted()) return 1;
}
} else {
void* l = find_item(my);
whichway = 1;
if (Fl::event_state(FL_COMMAND)) { 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(FL_REASON_CHANGED);
if (wp.deleted()) return 1;
}
}
} else if (Fl::event_state(FL_SHIFT)) { if (l == selection_) goto TOGGLE;
whichway = l ? !item_selected(l) : 1;
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;
}
}
change = 1;
if (l) select(l, whichway, when() & FL_WHEN_CHANGED);
if (wp.deleted()) return 1;
} else { change = select_only(l, 0);
if (wp.deleted()) return 1;
if (change && (when() & FL_WHEN_CHANGED)) {
set_changed();
do_callback(FL_REASON_CHANGED);
if (wp.deleted()) return 1;
}
}
}
return 1;
case FL_DRAG:
my = Fl::event_y();
if (my < Y && my < py) {
int p = real_position_+my-Y;
if (p<0) p = 0;
vposition(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;
vposition(p);
}
if (type() == FL_NORMAL_BROWSER || !top_)
;
else if (type() == FL_MULTI_BROWSER) {
void* l = find_item(my);
void* t; void* b; if (my > py) { t = selection_ ? item_next(selection_) : 0;
b = l ? item_next(l) : 0;
} else { 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(FL_REASON_CHANGED);
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(FL_REASON_CHANGED);
} else {
if (when() & FL_WHEN_NOT_CHANGED) do_callback(FL_REASON_RESELECTED);
}
if (wp.deleted()) return 1;
if (Fl::event_clicks() && (when() & FL_WHEN_ENTER_KEY)) {
set_changed();
do_callback(FL_REASON_CHANGED);
}
return 1;
case FL_FOCUS:
case FL_UNFOCUS:
if (type() >= FL_HOLD_BROWSER && Fl::visible_focus()) {
redraw();
return 1;
} else return 0;
}
return 0;
}
Fl_Browser_::Fl_Browser_(int X, int Y, int W, int H, const char* L)
: Fl_Group(X, Y, W, H, L),
linespacing_(0),
scrollbar(0, 0, 0, 0, 0), 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);
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();
}
void Fl_Browser_::sort(int flags) {
int i, j, n = -1, desc = ((flags&FL_SORT_DESCENDING)==FL_SORT_DESCENDING);
bool caseinsensitive = (flags&FL_SORT_CASEINSENSITIVE);
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 ( (caseinsensitive && fl_utf_strcasecmp(ta, tb) < 0) ||
(!caseinsensitive && strcmp(ta, tb) < 0) ) {
item_swap(a, b);
swapped = 1;
}
} else {
if ( (caseinsensitive && fl_utf_strcasecmp(ta, tb) > 0) ||
(!caseinsensitive && strcmp(ta, tb) > 0) ) {
item_swap(a, b);
swapped = 1;
}
}
if (!c) break;
b = c; a = item_prev(b);
}
if (!swapped)
break;
}
}
int Fl_Browser_::item_quick_height(void* item) const {
return item_height(item);
}
int Fl_Browser_::incr_height() const {
return item_quick_height(item_first()) + linespacing();
}
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;
}
int Fl_Browser_::full_width() const {
return max_width;
}
void Fl_Browser_::item_select(void *item, int val) {}
int Fl_Browser_::item_selected(void* item) const { return item==selection_ ? 1 : 0; }