1c399af444
git-svn-id: file:///fltk/svn/fltk/branches/branch-1.1@4288 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
613 lines
16 KiB
C++
613 lines
16 KiB
C++
//
|
|
// "$Id$"
|
|
//
|
|
// Group widget for the Fast Light Tool Kit (FLTK).
|
|
//
|
|
// Copyright 1998-2005 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
|
|
//
|
|
|
|
// The Fl_Group is the only defined container type in FLTK.
|
|
|
|
// Fl_Window itself is a subclass of this, and most of the event
|
|
// handling is designed so windows themselves work correctly.
|
|
|
|
#include <stdio.h>
|
|
#include <FL/Fl.H>
|
|
#include <FL/Fl_Group.H>
|
|
#include <FL/Fl_Window.H>
|
|
#include <FL/fl_draw.H>
|
|
#include <stdlib.h>
|
|
|
|
Fl_Group* Fl_Group::current_;
|
|
|
|
// Hack: A single child is stored in the pointer to the array, while
|
|
// multiple children are stored in an allocated array:
|
|
Fl_Widget*const* Fl_Group::array() const {
|
|
return children_ <= 1 ? (Fl_Widget**)(&array_) : array_;
|
|
}
|
|
|
|
int Fl_Group::find(const Fl_Widget* o) const {
|
|
Fl_Widget*const* a = array();
|
|
int i; for (i=0; i < children_; i++) if (*a++ == o) break;
|
|
return i;
|
|
}
|
|
|
|
// Metrowerks CodeWarrior and others can't export the static
|
|
// class member: current_, so these methods can't be inlined...
|
|
void Fl_Group::begin() {current_ = this;}
|
|
void Fl_Group::end() {current_ = (Fl_Group*)parent();}
|
|
Fl_Group *Fl_Group::current() {return current_;}
|
|
void Fl_Group::current(Fl_Group *g) {current_ = g;}
|
|
|
|
extern Fl_Widget* fl_oldfocus; // set by Fl::focus
|
|
|
|
// For back-compatability, we must adjust all events sent to child
|
|
// windows so they are relative to that window.
|
|
|
|
static int send(Fl_Widget* o, int event) {
|
|
if (o->type() < FL_WINDOW) return o->handle(event);
|
|
switch ( event )
|
|
{
|
|
case FL_DND_ENTER:
|
|
case FL_DND_DRAG:
|
|
// figure out correct type of event:
|
|
event = (o->contains(Fl::belowmouse())) ? FL_DND_DRAG : FL_DND_ENTER;
|
|
}
|
|
int save_x = Fl::e_x; Fl::e_x -= o->x();
|
|
int save_y = Fl::e_y; Fl::e_y -= o->y();
|
|
int ret = o->handle(event);
|
|
Fl::e_y = save_y;
|
|
Fl::e_x = save_x;
|
|
switch ( event )
|
|
{
|
|
case FL_ENTER:
|
|
case FL_DND_ENTER:
|
|
// Successful completion of FL_ENTER means the widget is now the
|
|
// belowmouse widget, but only call Fl::belowmouse if the child
|
|
// widget did not do so:
|
|
if (!o->contains(Fl::belowmouse())) Fl::belowmouse(o);
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// translate the current keystroke into up/down/left/right for navigation:
|
|
#define ctrl(x) (x^0x40)
|
|
static int navkey() {
|
|
switch (Fl::event_key()) {
|
|
case 0: // not an FL_KEYBOARD/FL_SHORTCUT event
|
|
break;
|
|
case FL_Tab:
|
|
if (!Fl::event_state(FL_SHIFT)) return FL_Right;
|
|
case 0xfe20: // XK_ISO_Left_Tab
|
|
return FL_Left;
|
|
case FL_Right:
|
|
return FL_Right;
|
|
case FL_Left:
|
|
return FL_Left;
|
|
case FL_Up:
|
|
return FL_Up;
|
|
case FL_Down:
|
|
return FL_Down;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int Fl_Group::handle(int event) {
|
|
|
|
Fl_Widget*const* a = array();
|
|
int i;
|
|
Fl_Widget* o;
|
|
|
|
switch (event) {
|
|
|
|
case FL_FOCUS:
|
|
switch (navkey()) {
|
|
default:
|
|
if (savedfocus_ && savedfocus_->take_focus()) return 1;
|
|
case FL_Right:
|
|
case FL_Down:
|
|
for (i = children(); i--;) if ((*a++)->take_focus()) return 1;
|
|
break;
|
|
case FL_Left:
|
|
case FL_Up:
|
|
for (i = children(); i--;) if (a[i]->take_focus()) return 1;
|
|
break;
|
|
}
|
|
return 0;
|
|
|
|
case FL_UNFOCUS:
|
|
savedfocus_ = fl_oldfocus;
|
|
return 0;
|
|
|
|
case FL_KEYBOARD:
|
|
return navigation(navkey());
|
|
|
|
case FL_SHORTCUT:
|
|
for (i = children(); i--;) {
|
|
o = a[i];
|
|
if (o->takesevents() && Fl::event_inside(o) && send(o,FL_SHORTCUT))
|
|
return 1;
|
|
}
|
|
for (i = children(); i--;) {
|
|
o = a[i];
|
|
if (o->takesevents() && !Fl::event_inside(o) && send(o,FL_SHORTCUT))
|
|
return 1;
|
|
}
|
|
if ((Fl::event_key() == FL_Enter || Fl::event_key() == FL_KP_Enter)) return navigation(FL_Down);
|
|
return 0;
|
|
|
|
case FL_ENTER:
|
|
case FL_MOVE:
|
|
for (i = children(); i--;) {
|
|
o = a[i];
|
|
if (o->visible() && Fl::event_inside(o)) {
|
|
if (o->contains(Fl::belowmouse())) {
|
|
return send(o,FL_MOVE);
|
|
} else {
|
|
Fl::belowmouse(o);
|
|
if (send(o,FL_ENTER)) return 1;
|
|
}
|
|
}
|
|
}
|
|
Fl::belowmouse(this);
|
|
return 1;
|
|
|
|
case FL_DND_ENTER:
|
|
case FL_DND_DRAG:
|
|
for (i = children(); i--;) {
|
|
o = a[i];
|
|
if (o->takesevents() && Fl::event_inside(o)) {
|
|
if (o->contains(Fl::belowmouse())) {
|
|
return send(o,FL_DND_DRAG);
|
|
} else if (send(o,FL_DND_ENTER)) {
|
|
if (!o->contains(Fl::belowmouse())) Fl::belowmouse(o);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
Fl::belowmouse(this);
|
|
return 0;
|
|
|
|
case FL_PUSH:
|
|
for (i = children(); i--;) {
|
|
o = a[i];
|
|
if (o->takesevents() && Fl::event_inside(o)) {
|
|
if (send(o,FL_PUSH)) {
|
|
if (Fl::pushed() && !o->contains(Fl::pushed())) Fl::pushed(o);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case FL_RELEASE:
|
|
case FL_DRAG:
|
|
o = Fl::pushed();
|
|
if (o == this) return 0;
|
|
else if (o) send(o,event);
|
|
else {
|
|
for (i = children(); i--;) {
|
|
o = a[i];
|
|
if (o->takesevents() && Fl::event_inside(o)) {
|
|
if (send(o,event)) return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
|
|
case FL_MOUSEWHEEL:
|
|
for (i = children(); i--;) {
|
|
o = a[i];
|
|
if (o->takesevents() && Fl::event_inside(o) && send(o,FL_MOUSEWHEEL))
|
|
return 1;
|
|
}
|
|
for (i = children(); i--;) {
|
|
o = a[i];
|
|
if (o->takesevents() && !Fl::event_inside(o) && send(o,FL_MOUSEWHEEL))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
|
|
case FL_DEACTIVATE:
|
|
case FL_ACTIVATE:
|
|
for (i = children(); i--;) {
|
|
o = *a++;
|
|
if (o->active()) o->handle(event);
|
|
}
|
|
return 1;
|
|
|
|
case FL_SHOW:
|
|
case FL_HIDE:
|
|
for (i = children(); i--;) {
|
|
o = *a++;
|
|
if (event == FL_HIDE && o == Fl::focus()) {
|
|
// Give up input focus...
|
|
int old_event = Fl::e_number;
|
|
o->handle(Fl::e_number = FL_UNFOCUS);
|
|
Fl::e_number = old_event;
|
|
Fl::focus(0);
|
|
}
|
|
if (o->visible()) o->handle(event);
|
|
}
|
|
return 1;
|
|
|
|
default:
|
|
// For all other events, try to give to each child, starting at focus:
|
|
for (i = 0; i < children(); i ++)
|
|
if (Fl::focus_ == a[i]) break;
|
|
|
|
if (i >= children()) i = 0;
|
|
|
|
if (children()) {
|
|
for (int j = i;;) {
|
|
if (a[j]->takesevents() || event != FL_MOUSEWHEEL) {
|
|
if (send(a[j], event)) return 1;
|
|
}
|
|
j++;
|
|
if (j >= children()) j = 0;
|
|
if (j == i) break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
//void Fl_Group::focus(Fl_Widget *o) {Fl::focus(o); o->handle(FL_FOCUS);}
|
|
|
|
#if 0
|
|
const char *nameof(Fl_Widget *o) {
|
|
if (!o) return "NULL";
|
|
if (!o->label()) return "<no label>";
|
|
return o->label();
|
|
}
|
|
#endif
|
|
|
|
// try to move the focus in response to a keystroke:
|
|
int Fl_Group::navigation(int key) {
|
|
if (children() <= 1) return 0;
|
|
int i;
|
|
for (i = 0; ; i++) {
|
|
if (i >= children_) return 0;
|
|
if (array_[i]->contains(Fl::focus())) break;
|
|
}
|
|
Fl_Widget *previous = array_[i];
|
|
|
|
for (;;) {
|
|
switch (key) {
|
|
case FL_Right:
|
|
case FL_Down:
|
|
i++;
|
|
if (i >= children_) {
|
|
if (parent()) return 0;
|
|
i = 0;
|
|
}
|
|
break;
|
|
case FL_Left:
|
|
case FL_Up:
|
|
if (i) i--;
|
|
else {
|
|
if (parent()) return 0;
|
|
i = children_-1;
|
|
}
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
Fl_Widget* o = array_[i];
|
|
if (o == previous) return 0;
|
|
switch (key) {
|
|
case FL_Down:
|
|
case FL_Up:
|
|
// for up/down, the widgets have to overlap horizontally:
|
|
if (o->x() >= previous->x()+previous->w() ||
|
|
o->x()+o->w() <= previous->x()) continue;
|
|
}
|
|
if (o->take_focus()) return 1;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
Fl_Group::Fl_Group(int X,int Y,int W,int H,const char *l)
|
|
: Fl_Widget(X,Y,W,H,l) {
|
|
align(FL_ALIGN_TOP);
|
|
children_ = 0;
|
|
array_ = 0;
|
|
savedfocus_ = 0;
|
|
resizable_ = this;
|
|
sizes_ = 0; // this is allocated when first resize() is done
|
|
// Subclasses may want to construct child objects as part of their
|
|
// constructor, so make sure they are add()'d to this object.
|
|
// But you must end() the object!
|
|
begin();
|
|
}
|
|
|
|
void Fl_Group::clear() {
|
|
Fl_Widget*const* old_array = array();
|
|
int old_children = children();
|
|
// clear everything now, in case fl_fix_focus recursively calls us:
|
|
children_ = 0;
|
|
//array_ = 0; //dont do this, it will clobber old_array if only one child
|
|
savedfocus_ = 0;
|
|
resizable_ = this;
|
|
init_sizes();
|
|
// okay, now it is safe to destroy the children:
|
|
Fl_Widget*const* a = old_array;
|
|
for (int i=old_children; i--;) {
|
|
Fl_Widget* o = *a++;
|
|
if (o->parent() == this) delete o;
|
|
}
|
|
if (old_children > 1) free((void*)old_array);
|
|
}
|
|
|
|
Fl_Group::~Fl_Group() {
|
|
clear();
|
|
}
|
|
|
|
void Fl_Group::insert(Fl_Widget &o, int index) {
|
|
if (o.parent()) {
|
|
Fl_Group* g = (Fl_Group*)(o.parent());
|
|
int n = g->find(o);
|
|
if (g == this) {
|
|
if (index > n) index--;
|
|
if (index == n) return;
|
|
}
|
|
g->remove(o);
|
|
}
|
|
o.parent_ = this;
|
|
if (children_ == 0) { // use array pointer to point at single child
|
|
array_ = (Fl_Widget**)&o;
|
|
} else if (children_ == 1) { // go from 1 to 2 children
|
|
Fl_Widget* t = (Fl_Widget*)array_;
|
|
array_ = (Fl_Widget**)malloc(2*sizeof(Fl_Widget*));
|
|
if (index) {array_[0] = t; array_[1] = &o;}
|
|
else {array_[0] = &o; array_[1] = t;}
|
|
} else {
|
|
if (!(children_ & (children_-1))) // double number of children
|
|
array_ = (Fl_Widget**)realloc((void*)array_,
|
|
2*children_*sizeof(Fl_Widget*));
|
|
int j; for (j = children_; j > index; j--) array_[j] = array_[j-1];
|
|
array_[j] = &o;
|
|
}
|
|
children_++;
|
|
init_sizes();
|
|
}
|
|
|
|
void Fl_Group::add(Fl_Widget &o) {insert(o, children_);}
|
|
|
|
void Fl_Group::remove(Fl_Widget &o) {
|
|
if (!children_) return;
|
|
int i = find(o);
|
|
if (i >= children_) return;
|
|
if (&o == savedfocus_) savedfocus_ = 0;
|
|
o.parent_ = 0;
|
|
children_--;
|
|
if (children_ == 1) { // go from 2 to 1 child
|
|
Fl_Widget *t = array_[!i];
|
|
free((void*)array_);
|
|
array_ = (Fl_Widget**)t;
|
|
} else if (children_ > 1) { // delete from array
|
|
for (; i < children_; i++) array_[i] = array_[i+1];
|
|
}
|
|
init_sizes();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// Rather lame kludge here, I need to detect windows and ignore the
|
|
// changes to X,Y, since all children are relative to X,Y. That
|
|
// is why I check type():
|
|
|
|
// sizes array stores the initial positions of widgets as
|
|
// left,right,top,bottom quads. The first quad is the group, the
|
|
// second is the resizable (clipped to the group), and the
|
|
// rest are the children. This is a convienent order for the
|
|
// algorithim. If you change this be sure to fix Fl_Tile which
|
|
// also uses this array!
|
|
|
|
void Fl_Group::init_sizes() {
|
|
delete[] sizes_; sizes_ = 0;
|
|
}
|
|
|
|
short* Fl_Group::sizes() {
|
|
if (!sizes_) {
|
|
short* p = sizes_ = new short[4*(children_+2)];
|
|
// first thing in sizes array is the group's size:
|
|
if (type() < FL_WINDOW) {p[0] = x(); p[2] = y();} else {p[0] = p[2] = 0;}
|
|
p[1] = p[0]+w(); p[3] = p[2]+h();
|
|
// next is the resizable's size:
|
|
p[4] = p[0]; // init to the group's size
|
|
p[5] = p[1];
|
|
p[6] = p[2];
|
|
p[7] = p[3];
|
|
Fl_Widget* r = resizable();
|
|
if (r && r != this) { // then clip the resizable to it
|
|
int t;
|
|
t = r->x(); if (t > p[0]) p[4] = t;
|
|
t +=r->w(); if (t < p[1]) p[5] = t;
|
|
t = r->y(); if (t > p[2]) p[6] = t;
|
|
t +=r->h(); if (t < p[3]) p[7] = t;
|
|
}
|
|
// next is all the children's sizes:
|
|
p += 8;
|
|
Fl_Widget*const* a = array();
|
|
for (int i=children_; i--;) {
|
|
Fl_Widget* o = *a++;
|
|
*p++ = o->x();
|
|
*p++ = o->x()+o->w();
|
|
*p++ = o->y();
|
|
*p++ = o->y()+o->h();
|
|
}
|
|
}
|
|
return sizes_;
|
|
}
|
|
|
|
void Fl_Group::resize(int X, int Y, int W, int H) {
|
|
|
|
if (!resizable() || W==w() && H==h() ) {
|
|
|
|
if (type() < FL_WINDOW) {
|
|
int dx = X-x();
|
|
int dy = Y-y();
|
|
Fl_Widget*const* a = array();
|
|
for (int i=children_; i--;) {
|
|
Fl_Widget* o = *a++;
|
|
o->resize(o->x()+dx, o->y()+dy, o->w(), o->h());
|
|
}
|
|
}
|
|
|
|
} else if (children_) {
|
|
|
|
short* p = sizes();
|
|
|
|
// get changes in size/position from the initial size:
|
|
int dx = X - p[0];
|
|
int dw = W - (p[1]-p[0]);
|
|
int dy = Y - p[2];
|
|
int dh = H - (p[3]-p[2]);
|
|
if (type() >= FL_WINDOW) dx = dy = 0;
|
|
p += 4;
|
|
|
|
// get initial size of resizable():
|
|
int IX = *p++;
|
|
int IR = *p++;
|
|
int IY = *p++;
|
|
int IB = *p++;
|
|
|
|
Fl_Widget*const* a = array();
|
|
for (int i=children_; i--;) {
|
|
Fl_Widget* o = *a++;
|
|
#if 1
|
|
int XX = *p++;
|
|
if (XX >= IR) XX += dw;
|
|
else if (XX > IX) XX = IX+((XX-IX)*(IR+dw-IX)+(IR-IX)/2)/(IR-IX);
|
|
int R = *p++;
|
|
if (R >= IR) R += dw;
|
|
else if (R > IX) R = IX+((R-IX)*(IR+dw-IX)+(IR-IX)/2)/(IR-IX);
|
|
|
|
int YY = *p++;
|
|
if (YY >= IB) YY += dh;
|
|
else if (YY > IY) YY = IY+((YY-IY)*(IB+dh-IY)+(IB-IY)/2)/(IB-IY);
|
|
int B = *p++;
|
|
if (B >= IB) B += dh;
|
|
else if (B > IY) B = IY+((B-IY)*(IB+dh-IY)+(IB-IY)/2)/(IB-IY);
|
|
#else // much simpler code from Francois Ostiguy:
|
|
int XX = *p++;
|
|
if (XX >= IR) XX += dw;
|
|
else if (XX > IX) XX += dw * (XX-IX)/(IR-IX);
|
|
int R = *p++;
|
|
if (R >= IR) R += dw;
|
|
else if (R > IX) R = R + dw * (R-IX)/(IR-IX);
|
|
|
|
int YY = *p++;
|
|
if (YY >= IB) YY += dh;
|
|
else if (YY > IY) YY = YY + dh*(YY-IY)/(IB-IY);
|
|
int B = *p++;
|
|
if (B >= IB) B += dh;
|
|
else if (B > IY) B = B + dh*(B-IY)/(IB-IY);
|
|
#endif
|
|
o->resize(XX+dx, YY+dy, R-XX, B-YY);
|
|
}
|
|
}
|
|
|
|
Fl_Widget::resize(X,Y,W,H);
|
|
}
|
|
|
|
void Fl_Group::draw_children() {
|
|
Fl_Widget*const* a = array();
|
|
if (damage() & ~FL_DAMAGE_CHILD) { // redraw the entire thing:
|
|
for (int i=children_; i--;) {
|
|
Fl_Widget& o = **a++;
|
|
draw_child(o);
|
|
draw_outside_label(o);
|
|
}
|
|
} else { // only redraw the children that need it:
|
|
for (int i=children_; i--;) update_child(**a++);
|
|
}
|
|
}
|
|
|
|
void Fl_Group::draw() {
|
|
if (damage() & ~FL_DAMAGE_CHILD) { // redraw the entire thing:
|
|
draw_box();
|
|
draw_label();
|
|
}
|
|
draw_children();
|
|
}
|
|
|
|
// Draw a child only if it needs it:
|
|
void Fl_Group::update_child(Fl_Widget& widget) const {
|
|
if (widget.damage() && widget.visible() && widget.type() < FL_WINDOW &&
|
|
fl_not_clipped(widget.x(), widget.y(), widget.w(), widget.h())) {
|
|
widget.draw();
|
|
widget.clear_damage();
|
|
}
|
|
}
|
|
|
|
// Force a child to redraw:
|
|
void Fl_Group::draw_child(Fl_Widget& widget) const {
|
|
if (widget.visible() && widget.type() < FL_WINDOW &&
|
|
fl_not_clipped(widget.x(), widget.y(), widget.w(), widget.h())) {
|
|
widget.clear_damage(FL_DAMAGE_ALL);
|
|
widget.draw();
|
|
widget.clear_damage();
|
|
}
|
|
}
|
|
|
|
extern char fl_draw_shortcut;
|
|
|
|
// Parents normally call this to draw outside labels:
|
|
void Fl_Group::draw_outside_label(const Fl_Widget& widget) const {
|
|
if (!widget.visible()) return;
|
|
// skip any labels that are inside the widget:
|
|
if (!(widget.align()&15) || (widget.align() & FL_ALIGN_INSIDE)) return;
|
|
// invent a box that is outside the widget:
|
|
int a = widget.align();
|
|
int X = widget.x();
|
|
int Y = widget.y();
|
|
int W = widget.w();
|
|
int H = widget.h();
|
|
if (a & FL_ALIGN_TOP) {
|
|
a ^= (FL_ALIGN_BOTTOM|FL_ALIGN_TOP);
|
|
Y = y();
|
|
H = widget.y()-Y;
|
|
} else if (a & FL_ALIGN_BOTTOM) {
|
|
a ^= (FL_ALIGN_BOTTOM|FL_ALIGN_TOP);
|
|
Y = Y+H;
|
|
H = y()+h()-Y;
|
|
} else if (a & FL_ALIGN_LEFT) {
|
|
a ^= (FL_ALIGN_LEFT|FL_ALIGN_RIGHT);
|
|
X = x();
|
|
W = widget.x()-X-3;
|
|
} else if (a & FL_ALIGN_RIGHT) {
|
|
a ^= (FL_ALIGN_LEFT|FL_ALIGN_RIGHT);
|
|
X = X+W+3;
|
|
W = x()+this->w()-X;
|
|
}
|
|
widget.draw_label(X,Y,W,H,(Fl_Align)a);
|
|
}
|
|
|
|
//
|
|
// End of "$Id$".
|
|
//
|