411 lines
12 KiB
C++
411 lines
12 KiB
C++
//
|
|
// Base widget class for the Fast Light Tool Kit (FLTK).
|
|
//
|
|
// Copyright 1998-2022 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:
|
|
//
|
|
// https://www.fltk.org/COPYING.php
|
|
//
|
|
// Please see the following page on how to report bugs and issues:
|
|
//
|
|
// https://www.fltk.org/bugs.php
|
|
//
|
|
|
|
#include <FL/Fl.H>
|
|
#include <FL/Fl_Widget.H>
|
|
#include <FL/Fl_Group.H>
|
|
#include <FL/Fl_Tooltip.H>
|
|
#include <FL/fl_draw.H>
|
|
#include <FL/fl_string_functions.h>
|
|
#include <stdlib.h>
|
|
#include "flstring.h"
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// for compatibility with Forms, all widgets without callbacks are
|
|
// inserted into a "queue" when they are activated, and the forms
|
|
// compatibility interaction functions (fl_do_events, etc.) will
|
|
// read one widget at a time from this queue and return it:
|
|
|
|
const int QUEUE_SIZE = 20;
|
|
|
|
static Fl_Widget *obj_queue[QUEUE_SIZE];
|
|
static int obj_head, obj_tail;
|
|
|
|
void Fl_Widget::default_callback(Fl_Widget *widget, void * /*v*/) {
|
|
obj_queue[obj_head++] = widget;
|
|
if (obj_head >= QUEUE_SIZE) obj_head = 0;
|
|
if (obj_head == obj_tail) {
|
|
obj_tail++;
|
|
if (obj_tail >= QUEUE_SIZE) obj_tail = 0;
|
|
}
|
|
}
|
|
/**
|
|
Reads the default callback queue and returns the first widget.
|
|
|
|
All Fl_Widgets that don't have a callback defined use the default
|
|
callback \p static Fl_Widget::default_callback() that puts a pointer
|
|
to the widget in a queue. This method reads the oldest widget out
|
|
of this queue.
|
|
|
|
The queue (FIFO) is limited (currently 20 items). If the queue
|
|
overflows, the oldest entry (Fl_Widget *) is discarded.
|
|
|
|
Relying on the default callback and reading the callback queue with
|
|
Fl::readqueue() is not recommended. If you need a callback, you should
|
|
set one with Fl_Widget::callback(Fl_Callback *cb, void *data)
|
|
or one of its variants.
|
|
|
|
\see Fl_Widget::callback()
|
|
\see Fl_Widget::callback(Fl_Callback *cb, void *data)
|
|
\see Fl_Widget::default_callback()
|
|
*/
|
|
Fl_Widget *Fl::readqueue() {
|
|
if (obj_tail==obj_head) return 0;
|
|
Fl_Widget *widget = obj_queue[obj_tail++];
|
|
if (obj_tail >= QUEUE_SIZE) obj_tail = 0;
|
|
return widget;
|
|
}
|
|
/*
|
|
This static internal function removes all pending callbacks for a
|
|
specific widget from the default callback queue (Fl::readqueue()).
|
|
It is only called from Fl_Widget's destructor if the widget
|
|
doesn't have an own callback.
|
|
Note: There's no need to have this in the Fl:: namespace.
|
|
*/
|
|
static void cleanup_readqueue(Fl_Widget *w) {
|
|
|
|
if (obj_tail==obj_head) return;
|
|
|
|
// Read the entire queue and copy over all valid entries.
|
|
// The new head will be determined after the last copied entry.
|
|
|
|
int old_head = obj_head; // save newest entry
|
|
int entry = obj_tail; // oldest entry
|
|
obj_head = obj_tail; // new queue start
|
|
for (;;) {
|
|
Fl_Widget *o = obj_queue[entry++];
|
|
if (entry >= QUEUE_SIZE) entry = 0;
|
|
if (o != w) { // valid entry
|
|
obj_queue[obj_head++] = o;
|
|
if (obj_head >= QUEUE_SIZE) obj_head = 0;
|
|
} // valid entry
|
|
if (entry == old_head) break;
|
|
}
|
|
return;
|
|
}
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
int Fl_Widget::handle(int) {
|
|
return 0;
|
|
}
|
|
|
|
/** Default font size for widgets */
|
|
Fl_Fontsize FL_NORMAL_SIZE = 14;
|
|
|
|
Fl_Widget::Fl_Widget(int X, int Y, int W, int H, const char* L) {
|
|
|
|
x_ = X; y_ = Y; w_ = W; h_ = H;
|
|
|
|
label_.value = L;
|
|
label_.image = 0;
|
|
label_.deimage = 0;
|
|
label_.type = FL_NORMAL_LABEL;
|
|
label_.font = FL_HELVETICA;
|
|
label_.size = FL_NORMAL_SIZE;
|
|
label_.color = FL_FOREGROUND_COLOR;
|
|
label_.align_ = FL_ALIGN_CENTER;
|
|
tooltip_ = 0;
|
|
callback_ = default_callback;
|
|
user_data_ = 0;
|
|
type_ = 0;
|
|
flags_ = VISIBLE_FOCUS;
|
|
damage_ = 0;
|
|
box_ = FL_NO_BOX;
|
|
color_ = FL_GRAY;
|
|
color2_ = FL_GRAY;
|
|
when_ = FL_WHEN_RELEASE;
|
|
|
|
parent_ = 0;
|
|
if (Fl_Group::current()) Fl_Group::current()->add(this);
|
|
}
|
|
|
|
void Fl_Widget::resize(int X, int Y, int W, int H) {
|
|
x_ = X; y_ = Y; w_ = W; h_ = H;
|
|
}
|
|
|
|
// this is useful for parent widgets to call to resize children:
|
|
int Fl_Widget::damage_resize(int X, int Y, int W, int H) {
|
|
if (x() == X && y() == Y && w() == W && h() == H) return 0;
|
|
resize(X, Y, W, H);
|
|
redraw();
|
|
return 1;
|
|
}
|
|
|
|
int Fl_Widget::take_focus() {
|
|
if (!takesevents()) return 0;
|
|
if (!visible_focus()) return 0;
|
|
if (!handle(FL_FOCUS)) return 0; // see if it wants it
|
|
if (contains(Fl::focus())) return 1; // it called Fl::focus for us
|
|
Fl::focus(this);
|
|
return 1;
|
|
}
|
|
|
|
extern void fl_throw_focus(Fl_Widget*); // in Fl_x.cxx
|
|
|
|
/**
|
|
Destroys the widget, taking care of throwing focus before if any.
|
|
Destruction removes the widget from any parent group! And groups when
|
|
destroyed destroy all their children. This is convenient and fast.
|
|
*/
|
|
Fl_Widget::~Fl_Widget() {
|
|
Fl::clear_widget_pointer(this);
|
|
if (flags() & COPIED_LABEL) free((void *)(label_.value));
|
|
if (flags() & COPIED_TOOLTIP) free((void *)(tooltip_));
|
|
image(NULL);
|
|
deimage(NULL);
|
|
// remove from parent group
|
|
if (parent_) parent_->remove(this);
|
|
#ifdef DEBUG_DELETE
|
|
if (parent_) { // this should never happen
|
|
printf("*** Fl_Widget: parent_->remove(this) failed [%p,%p]\n",parent_,this);
|
|
}
|
|
#endif // DEBUG_DELETE
|
|
parent_ = 0; // Don't throw focus to a parent widget.
|
|
fl_throw_focus(this);
|
|
// remove stale entries from default callback queue (Fl::readqueue())
|
|
if (callback_ == default_callback) cleanup_readqueue(this);
|
|
if ( (flags_ & AUTO_DELETE_USER_DATA) && user_data_)
|
|
delete (Fl_Callback_User_Data*)user_data_;
|
|
}
|
|
|
|
/**
|
|
Draws a focus box for the widget at the given position and size.
|
|
|
|
This method does nothing if
|
|
- the global option Fl::visible_focus() or
|
|
- the per-widget option visible_focus()
|
|
is false (off).
|
|
|
|
This means that Fl_Widget::draw_focus() or one of the more specialized
|
|
methods can be called without checking these visible focus options.
|
|
|
|
\note This method must only be called if the widget has the focus.
|
|
This is not tested internally.
|
|
|
|
The boxtype \p bt is used to calculate the inset so the focus box is drawn
|
|
inside the box borders.
|
|
|
|
The default focus box drawing color is black. The background color \p bg
|
|
is used to determine a better visible color if necessary by using
|
|
fl_contrast() with the given background color.
|
|
|
|
\param[in] bt Boxtype that needs to be considered (frame width)
|
|
\param[in] X,Y,W,H Bounding box
|
|
\param[in] bg Background color
|
|
|
|
\see Fl_Widget::draw_focus()
|
|
\see Fl_Widget::draw_focus(Fl_Boxtype, int, int, int, int) const
|
|
*/
|
|
void Fl_Widget::draw_focus(Fl_Boxtype bt, int X, int Y, int W, int H, Fl_Color bg) const {
|
|
if (!Fl::visible_focus()) return;
|
|
if (!visible_focus()) return;
|
|
fl_draw_box_focus(bt, X, Y, W, H, FL_BLACK, bg);
|
|
}
|
|
|
|
void Fl_Widget::activate() {
|
|
if (!active()) {
|
|
clear_flag(INACTIVE);
|
|
if (active_r()) {
|
|
redraw();
|
|
redraw_label();
|
|
handle(FL_ACTIVATE);
|
|
if (inside(Fl::focus())) Fl::focus()->take_focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Fl_Widget::deactivate() {
|
|
if (active_r()) {
|
|
set_flag(INACTIVE);
|
|
redraw();
|
|
redraw_label();
|
|
handle(FL_DEACTIVATE);
|
|
fl_throw_focus(this);
|
|
} else {
|
|
set_flag(INACTIVE);
|
|
}
|
|
}
|
|
|
|
int Fl_Widget::active_r() const {
|
|
for (const Fl_Widget* o = this; o; o = o->parent())
|
|
if (!o->active()) return 0;
|
|
return 1;
|
|
}
|
|
|
|
void Fl_Widget::show() {
|
|
if (!visible()) {
|
|
clear_flag(INVISIBLE);
|
|
if (visible_r()) {
|
|
redraw();
|
|
redraw_label();
|
|
handle(FL_SHOW);
|
|
if (inside(Fl::focus())) Fl::focus()->take_focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Fl_Widget::hide() {
|
|
if (visible_r()) {
|
|
set_flag(INVISIBLE);
|
|
for (Fl_Widget *p = parent(); p; p = p->parent())
|
|
if (p->box() || !p->parent()) {p->redraw(); break;}
|
|
handle(FL_HIDE);
|
|
fl_throw_focus(this);
|
|
} else {
|
|
set_flag(INVISIBLE);
|
|
}
|
|
}
|
|
|
|
int Fl_Widget::visible_r() const {
|
|
for (const Fl_Widget* o = this; o; o = o->parent())
|
|
if (!o->visible()) return 0;
|
|
return 1;
|
|
}
|
|
|
|
// return true if widget is inside (or equal to) this:
|
|
// Returns false for null widgets.
|
|
int Fl_Widget::contains(const Fl_Widget *o) const {
|
|
for (; o; o = o->parent_) if (o == this) return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
void Fl_Widget::label(const char *a) {
|
|
if (flags() & COPIED_LABEL) {
|
|
// reassigning a copied label remains the same copied label
|
|
if (label_.value == a)
|
|
return;
|
|
free((void *)(label_.value));
|
|
clear_flag(COPIED_LABEL);
|
|
}
|
|
label_.value=a;
|
|
redraw_label();
|
|
}
|
|
|
|
|
|
void Fl_Widget::copy_label(const char *a) {
|
|
// reassigning a copied label remains the same copied label
|
|
if ((flags() & COPIED_LABEL) && (label_.value == a))
|
|
return;
|
|
if (a) {
|
|
label(fl_strdup(a));
|
|
set_flag(COPIED_LABEL);
|
|
} else {
|
|
label(0);
|
|
}
|
|
}
|
|
|
|
void Fl_Widget::image(Fl_Image* img) {
|
|
if (image_bound()) {
|
|
if (label_.image && (label_.image != img)) {
|
|
label_.image->release();
|
|
}
|
|
bind_image(0);
|
|
}
|
|
label_.image = img;
|
|
}
|
|
|
|
void Fl_Widget::image(Fl_Image& img) {
|
|
image(&img);
|
|
}
|
|
|
|
void Fl_Widget::bind_image(Fl_Image* img) {
|
|
image(img);
|
|
bind_image( (img != NULL) );
|
|
}
|
|
|
|
void Fl_Widget::deimage(Fl_Image* img) {
|
|
if (deimage_bound()) {
|
|
if (label_.deimage && (label_.deimage != img)) {
|
|
label_.deimage->release();
|
|
}
|
|
bind_deimage(0);
|
|
}
|
|
label_.deimage = img;
|
|
}
|
|
|
|
void Fl_Widget::deimage(Fl_Image& img) {
|
|
deimage(&img);
|
|
}
|
|
|
|
void Fl_Widget::bind_deimage(Fl_Image* img) {
|
|
deimage(img);
|
|
bind_deimage( (img != NULL) );
|
|
}
|
|
|
|
/** Calls the widget callback function with arbitrary arguments.
|
|
|
|
All overloads of do_callback() call this method.
|
|
It does nothing if the widget's callback() is NULL.
|
|
It clears the widget's \e changed flag \b after the callback was
|
|
called unless the callback is the default callback. Hence it is not
|
|
necessary to call clear_changed() after calling do_callback()
|
|
in your own widget's handle() method.
|
|
|
|
A \p reason must be set for widgets if different actions can trigger
|
|
the same callback.
|
|
|
|
\note It is legal to delete the widget in the callback (i.e. in user code),
|
|
but you must not access the widget in the handle() method after
|
|
calling do_callback() if the widget was deleted in the callback.
|
|
We recommend to use Fl_Widget_Tracker to check whether the widget
|
|
was deleted in the callback.
|
|
|
|
\param[in] widget call the callback with \p widget as the first argument
|
|
\param[in] arg use \p arg as the user data (second) argument
|
|
\param[in] reason give a reason to why this callback was called, defaults to \ref FL_REASON_UNKNOWN
|
|
|
|
\see default_callback()
|
|
\see callback()
|
|
\see class Fl_Widget_Tracker
|
|
\see Fl::callback_reason()
|
|
*/
|
|
void Fl_Widget::do_callback(Fl_Widget *widget, void *arg, Fl_Callback_Reason reason) {
|
|
Fl::callback_reason_ = reason;
|
|
if (!callback_) return;
|
|
Fl_Widget_Tracker wp(this);
|
|
callback_(widget, arg);
|
|
if (wp.deleted()) return;
|
|
if (callback_ != default_callback)
|
|
clear_changed();
|
|
}
|
|
|
|
/*
|
|
\brief Sets the user data for this widget.
|
|
Sets the new user data (void *) argument that is passed to the callback function.
|
|
\param[in] v new user data
|
|
*/
|
|
void Fl_Widget::user_data(void* v) {
|
|
if ((flags_ & AUTO_DELETE_USER_DATA) && user_data_)
|
|
delete (Fl_Callback_User_Data*)user_data_;
|
|
clear_flag(AUTO_DELETE_USER_DATA);
|
|
user_data_ = v;
|
|
}
|
|
|
|
/*
|
|
\brief Sets the user data for this widget.
|
|
Sets the new user data (void *) argument that is passed to the callback function.
|
|
\param[in] v new user data
|
|
\param[in] auto_free if set, the widget will free user data when destroyed; defaults to false
|
|
*/
|
|
void Fl_Widget::user_data(Fl_Callback_User_Data* v, bool auto_free) {
|
|
user_data((void*)v);
|
|
if (auto_free)
|
|
set_flag(AUTO_DELETE_USER_DATA);
|
|
}
|
|
|