// Copyright (C) 2005  Davis E. King (davisking@users.sourceforge.net), Keita Mochizuki
// License: Boost Software License   See LICENSE.txt for the full license.
#ifndef DLIB_BASE_WIDGETs_CPP_
#define DLIB_BASE_WIDGETs_CPP_

#include "base_widgets.h"
#include "../assert.h"
#include <iostream>


namespace dlib
{

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // dragable object methods  
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    dragable::~dragable() {}

// ----------------------------------------------------------------------------------------

    void dragable::
    on_mouse_move (
        unsigned long state,
        long x,
        long y
    )
    {
        if (drag && (state & base_window::LEFT) && enabled && !hidden)
        {
            // the user is trying to drag this object.  we should calculate the new
            // x and y positions for the upper left corner of this object's rectangle

            long new_x = x - this->x;
            long new_y = y - this->y;

            // make sure these points are inside the dragable area.  
            if (new_x < area.left())
                new_x = area.left();
            if (new_x + static_cast<long>(rect.width()) - 1 > area.right())
                new_x = area.right() - rect.width() + 1;

            if (new_y + static_cast<long>(rect.height()) - 1 > area.bottom())
                new_y = area.bottom() - rect.height() + 1;
            if (new_y < area.top())
                new_y = area.top();

            // now make the new rectangle for this object
            rectangle new_rect(
                new_x,
                new_y,
                new_x + rect.width() - 1,
                new_y + rect.height() - 1
            );

            // only do anything if this is a new rectangle and it is inside area
            if (new_rect != rect && area.intersect(new_rect) == new_rect)
            {
                parent.invalidate_rectangle(new_rect + rect);
                rect = new_rect;

                // call the on_drag() event handler
                on_drag();
            }
        }
        else
        {
            drag = false;
        }
    }

// ----------------------------------------------------------------------------------------

    void dragable::
    on_mouse_down (
        unsigned long btn,
        unsigned long ,
        long x,
        long y,
        bool 
    )
    {
        if (enabled && !hidden && rect.contains(x,y) && btn == base_window::LEFT)
        {
            drag = true;
            this->x = x - rect.left();
            this->y = y - rect.top();
        }
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // mouse_over_event object methods  
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    mouse_over_event::~mouse_over_event() {}

// ----------------------------------------------------------------------------------------

    void mouse_over_event::
    on_mouse_leave (
    )
    {
        if (enabled && !hidden && is_mouse_over_)
        {
            is_mouse_over_ = false;
            on_mouse_not_over();
        }
    }

// ----------------------------------------------------------------------------------------

    void mouse_over_event::
    on_mouse_move (
        unsigned long state,
        long x,
        long y
    )
    {
        if (enabled == false || hidden == true)
            return;

        if (rect.contains(x,y) == false)
        {
            if (is_mouse_over_)
            {
                is_mouse_over_ = false;
                on_mouse_not_over();
            }
        }
        else if (is_mouse_over_ == false)
        {
            is_mouse_over_ = true;
            on_mouse_over();
        }
    }

// ----------------------------------------------------------------------------------------

    bool mouse_over_event::
    is_mouse_over (
    ) const
    {
        // check if the mouse is still really over this button
        if (enabled && !hidden && is_mouse_over_ && rect.contains(lastx,lasty) == false)
        {
            // trigger a user event to call on_mouse_not_over() and repaint this object.
            // we must do this in another event because someone might call is_mouse_over()
            // from draw() and you don't want this function to end up calling 
            // parent.invalidate_rectangle().  It would lead to draw() being called over
            // and over.
            parent.trigger_user_event((void*)this,drawable::next_free_user_event_number());
            return false;
        }

        return is_mouse_over_;
    }

// ----------------------------------------------------------------------------------------

    void mouse_over_event::
    on_user_event (
        int num 
    )
    {
        if (is_mouse_over_ && num == drawable::next_free_user_event_number())
        {
            is_mouse_over_ = false;
            on_mouse_not_over();
        }
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // button_action object methods  
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    button_action::~button_action() {}

// ----------------------------------------------------------------------------------------

    void button_action::
    on_mouse_down (
        unsigned long btn,
        unsigned long ,
        long x,
        long y,
        bool
    )
    {
        if (enabled && !hidden && btn == base_window::LEFT && rect.contains(x,y))
        {
            is_depressed_ = true;
            seen_click = true;
            parent.invalidate_rectangle(rect);
            on_button_down();
        }
    }

// ----------------------------------------------------------------------------------------

    void button_action::
    on_mouse_not_over (
    )
    {
        if (is_depressed_)
        {
            is_depressed_ = false;
            parent.invalidate_rectangle(rect);
            on_button_up(false);
        }
    }

// ----------------------------------------------------------------------------------------

    void button_action::
    on_mouse_move (
        unsigned long state,
        long x,
        long y
    )
    {
        // forward event to the parent class so it can do it's thing as well as us
        mouse_over_event::on_mouse_move(state,x,y);

        if (enabled == false || hidden == true)
            return;


        if ((state & base_window::LEFT) == 0)
        {
            seen_click = false;
            if (is_depressed_)
            {
                is_depressed_ = false;
                parent.invalidate_rectangle(rect);
                on_button_up(false);
            }

            // the left button isn't down so we don't care about anything else
            return;
        }

        if (rect.contains(x,y) == false)
        {
            if (is_depressed_)
            {
                is_depressed_ = false;
                parent.invalidate_rectangle(rect);
                on_button_up(false);
            }
        }
        else if (is_depressed_ == false && seen_click)
        {
            is_depressed_ = true;
            parent.invalidate_rectangle(rect);
            on_button_down();
        }
    }

// ----------------------------------------------------------------------------------------

    void button_action::
    on_mouse_up (
        unsigned long btn,
        unsigned long,
        long x,
        long y
    )
    {
        if (enabled && !hidden && btn == base_window::LEFT)
        {
            if (is_depressed_)
            {
                is_depressed_ = false;
                parent.invalidate_rectangle(rect);

                if (rect.contains(x,y))                
                {
                    on_button_up(true);
                }
                else
                {
                    on_button_up(false);
                }
            }
            else if (seen_click && rect.contains(x,y))
            {
                // this case here covers the unlikly event that you click on a button,
                // move the mouse off the button and then move it back very quickly and
                // release the mouse button.   It is possible that this mouse up event
                // will occurr before any mouse move event so you might not have set
                // that the button is depressed yet.
                
                // So we should say that this triggers an on_button_down() event and
                // then an on_button_up(true) event.

                parent.invalidate_rectangle(rect);

                on_button_down();
                on_button_up(true);
            }

            seen_click = false;
        }
    }

// ----------------------------------------------------------------------------------------

    bool button_action::
    is_depressed (
    ) const
    {
        // check if the mouse is still really over this button
        if (enabled && !hidden && is_depressed_ && rect.contains(lastx,lasty) == false)
        {
            // trigger a user event to call on_button_up() and repaint this object.
            // we must do this in another event because someone might call is_depressed()
            // from draw() and you don't want this function to end up calling 
            // parent.invalidate_rectangle().  It would lead to draw() being called over
            // and over.
            parent.trigger_user_event((void*)this,mouse_over_event::next_free_user_event_number());
            return false;
        }

        return is_depressed_;
    }

// ----------------------------------------------------------------------------------------

    void button_action::
    on_user_event (
        int num
    )
    {
        // forward event to the parent class so it can do it's thing as well as us
        mouse_over_event::on_user_event(num);

        if (is_depressed_ && num == mouse_over_event::next_free_user_event_number())
        {
            is_depressed_ = false;
            parent.invalidate_rectangle(rect);
            on_button_up(false);
        }
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // arrow_button object methods  
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    void arrow_button::
    set_size (
        unsigned long width,
        unsigned long height
    )
    {
        auto_mutex M(m);

        rectangle old(rect);
        const unsigned long x = rect.left();
        const unsigned long y = rect.top();
        rect.set_right(x+width-1);
        rect.set_bottom(y+height-1);

        parent.invalidate_rectangle(rect+old);
    }

// ----------------------------------------------------------------------------------------

    void arrow_button::
    draw (
        const canvas& c
    ) const
    {
        rectangle area = rect.intersect(c);
        if (area.is_empty())
            return;

        fill_rect(c,rect,rgb_pixel(212,208,200));

        const long height = rect.height();
        const long width = rect.width();

        const long smallest = (width < height) ? width : height; 

        const long rows = (smallest+3)/4;
        const long start = rows + rows/2-1;
        long dep;

        long tip_x = 0;
        long tip_y = 0;
        long wy = 0;
        long hy = 0;
        long wx = 0; 
        long hx = 0;

        if (button_action::is_depressed())
        {
            dep = 0;

            // draw the button's border
            draw_button_down(c,rect); 
        }
        else
        {
            dep = -1;

            // draw the button's border
            draw_button_up(c,rect);
        }


        switch (dir)
        {
            case UP:
                tip_x = width/2 + rect.left() + dep;
                tip_y = (height - start)/2 + rect.top() + dep + 1;
                wy = 0;
                hy = 1;
                wx = 1;
                hx = 0;
                break;

            case DOWN:
                tip_x = width/2 + rect.left() + dep;
                tip_y = rect.bottom() - (height - start)/2 + dep;
                wy = 0;
                hy = -1;
                wx = 1;
                hx = 0;
                break;

            case LEFT:
                tip_x = rect.left() + (width - start)/2 + dep + 1;
                tip_y = height/2 + rect.top() + dep;
                wy = 1;
                hy = 0;
                wx = 0;
                hx = 1;
                break;

            case RIGHT:
                tip_x = rect.right() - (width - start)/2 + dep;
                tip_y = height/2 + rect.top() + dep;
                wy = 1;
                hy = 0;
                wx = 0;
                hx = -1;
                break;
        }


        rgb_pixel color;
        if (enabled)
        {
            color.red = 0;
            color.green = 0;
            color.blue = 0;
        }
        else
        {
            color.red = 128;
            color.green = 128;
            color.blue = 128;
        }



        for (long i = 0; i < rows; ++i)
        {
            draw_line(c,point(tip_x + wx*i + hx*i, tip_y + wy*i + hy*i), 
                      point(tip_x + wx*i*-1 + hx*i, tip_y + wy*i*-1 + hy*i), 
                       color);
        }

    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // scroll_bar object methods  
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    scroll_bar::
    scroll_bar(  
        drawable_window& w,
        bar_orientation orientation 
    ) :
        drawable(w),
        width_(16),
        b1(w,arrow_button::UP),
        b2(w,arrow_button::DOWN),
        slider(w,*this,&scroll_bar::on_slider_drag),
        ori(orientation),
        top_filler(w,*this,&scroll_bar::top_filler_down,&scroll_bar::top_filler_up),
        bottom_filler(w,*this,&scroll_bar::bottom_filler_down,&scroll_bar::bottom_filler_up),
        pos(0),
        max_pos(0),
        js(10),
        b1_timer(*this,&scroll_bar::b1_down_t),
        b2_timer(*this,&scroll_bar::b2_down_t),
        top_filler_timer(*this,&scroll_bar::top_filler_down_t),
        bottom_filler_timer(*this,&scroll_bar::bottom_filler_down_t)
    {
        if (ori == HORIZONTAL)
        {
            b1.set_direction(arrow_button::LEFT);
            b2.set_direction(arrow_button::RIGHT);
        }

        // don't show the slider when there is no place it can move.
        slider.hide();

        set_length(100);

        b1.set_button_down_handler(*this,&scroll_bar::b1_down);
        b2.set_button_down_handler(*this,&scroll_bar::b2_down);

        b1.set_button_up_handler(*this,&scroll_bar::b1_up);
        b2.set_button_up_handler(*this,&scroll_bar::b2_up);
        b1.disable();
        b2.disable();
        enable_events();
    }

// ----------------------------------------------------------------------------------------

    scroll_bar::
    ~scroll_bar(
    )
    {
        disable_events();
        parent.invalidate_rectangle(rect); 
        // wait for all the timers to be stopped
        b1_timer.stop_and_wait();
        b2_timer.stop_and_wait();
        top_filler_timer.stop_and_wait();
        bottom_filler_timer.stop_and_wait();
    }

// ----------------------------------------------------------------------------------------

    scroll_bar::bar_orientation scroll_bar::
    orientation (
    ) const
    {
        auto_mutex M(m);
        return ori;
    }

// ----------------------------------------------------------------------------------------
    
    void scroll_bar::
    set_orientation (
        bar_orientation new_orientation   
    )
    {
        auto_mutex M(m);
        unsigned long length;

        if (ori == HORIZONTAL)
            length = rect.width();
        else
            length = rect.height();

        rectangle old_rect = rect;

        if (new_orientation == HORIZONTAL)
        {
            rect.set_right(rect.left() + length - 1 );
            rect.set_bottom(rect.top() + width_ - 1 );
            b1.set_direction(arrow_button::LEFT);
            b2.set_direction(arrow_button::RIGHT);

        }
        else
        {
            rect.set_right(rect.left() + width_ - 1);
            rect.set_bottom(rect.top() + length - 1);
            b1.set_direction(arrow_button::UP);
            b2.set_direction(arrow_button::DOWN);
        }
        ori = new_orientation;

        parent.invalidate_rectangle(old_rect);
        parent.invalidate_rectangle(rect);

        // call this to put everything is in the right spot.
        set_pos (rect.left(),rect.top());
    }

// ----------------------------------------------------------------------------------------
    
    void scroll_bar::
    set_length (
        unsigned long length
    )
    {
        // make the min length be at least 1
        if (length == 0)
        {
            length = 1;
        }

        auto_mutex M(m);

        parent.invalidate_rectangle(rect);

        if (ori == HORIZONTAL)
        {
            rect.set_right(rect.left() + length - 1);
            rect.set_bottom(rect.top() + width_ - 1);

            // if the length is too small then we have to smash up the arrow buttons
            // and hide the slider.
            if (length <= width_*2)
            {
                b1.set_size(length/2,width_);
                b2.set_size(length/2,width_);
            }
            else
            {
                b1.set_size(width_,width_);
                b2.set_size(width_,width_);

                // now adjust the slider
                if (max_pos != 0)
                {
                    slider.set_size(get_slider_size(),width_);
                }
            }

        }
        else
        {
            rect.set_right(rect.left() + width_ - 1);
            rect.set_bottom(rect.top() + length - 1);

            // if the length is too small then we have to smush up the arrow buttons
            // and hide the slider.
            if (length <= width_*2)
            {
                b1.set_size(width_,length/2);
                b2.set_size(width_,length/2);
            }
            else
            {

                b1.set_size(width_,width_);
                b2.set_size(width_,width_);

                // now adjust the slider
                if (max_pos != 0)
                {
                    slider.set_size(width_,get_slider_size());
                }
            }


        }

        // call this to put everything is in the right spot.
        set_pos (rect.left(),rect.top());

        if ((b2.get_rect().top() - b1.get_rect().bottom() - 1 <= 8 && ori == VERTICAL) || 
            (b2.get_rect().left() - b1.get_rect().right() - 1 <= 8 && ori == HORIZONTAL) || 
            max_pos == 0)
        {
            hide_slider();
        }
        else if (enabled && !hidden)
        {
            show_slider();
        }
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    set_pos (
        long x,
        long y
    )
    {
        auto_mutex M(m);
        drawable::set_pos(x,y);

        b1.set_pos(rect.left(),rect.top());
        if (ori == HORIZONTAL)
        {
            // make the b2 button appear at the end of the scroll_bar
            b2.set_pos(rect.right()-b2.get_rect().width() + 1,rect.top());

            if (max_pos != 0)
            {
                double range = b2.get_rect().left() - b1.get_rect().right() - slider.get_rect().width() - 1;
                double slider_pos = pos;
                slider_pos /= max_pos;
                slider_pos *= range;
                slider.set_pos(
                    static_cast<long>(slider_pos)+rect.left() + b1.get_rect().width(),
                    rect.top()
                    );

                // move the dragable area for the slider to the new location
                rectangle area = rect;
                area.set_left(area.left() + width_);
                area.set_right(area.right() - width_);
                slider.set_dragable_area(area);

            }

            
        }
        else
        {
            // make the b2 button appear at the end of the scroll_bar
            b2.set_pos(rect.left(), rect.bottom() - b2.get_rect().height() + 1);

            if (max_pos != 0)
            {
                double range = b2.get_rect().top() - b1.get_rect().bottom() - slider.get_rect().height() - 1;
                double slider_pos = pos;
                slider_pos /= max_pos;
                slider_pos *= range;
                slider.set_pos(
                    rect.left(), 
                    static_cast<long>(slider_pos) + rect.top() + b1.get_rect().height()
                    );

                // move the dragable area for the slider to the new location
                rectangle area = rect;
                area.set_top(area.top() + width_);
                area.set_bottom(area.bottom() - width_);
                slider.set_dragable_area(area);
            }
        }

        adjust_fillers();
    }

// ----------------------------------------------------------------------------------------

    unsigned long scroll_bar::
    get_slider_size (
    ) const
    {
        double range;
        if (ori == HORIZONTAL)
        {
            range = rect.width() - b2.get_rect().width() - b1.get_rect().width();
        }
        else
        {
            range = rect.height() - b2.get_rect().height() - b1.get_rect().height(); 
        }

        double scale_factor = 30.0/(max_pos + 30.0);

        if (scale_factor < 0.1)
            scale_factor = 0.1;


        double fraction = range/(max_pos + range)*scale_factor;
        double result = fraction * range;
        unsigned long res = static_cast<unsigned long>(result);
        if (res < 8)
            res = 8;
        return res;
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    adjust_fillers (
    )
    {
        rectangle top(rect), bottom(rect);

        if (ori == HORIZONTAL)
        {
            if (slider.is_hidden())
            {
                top.set_left(b1.get_rect().right()+1);
                top.set_right(b2.get_rect().left()-1);
                bottom.set_left(1);
                bottom.set_right(-1);
            }
            else
            {
                top.set_left(b1.get_rect().right()+1);
                top.set_right(slider.get_rect().left()-1);
                bottom.set_left(slider.get_rect().right()+1);
                bottom.set_right(b2.get_rect().left()-1);
            }
        }
        else
        {
            if (slider.is_hidden())
            {
                top.set_top(b1.get_rect().bottom()+1);
                top.set_bottom(b2.get_rect().top()-1);
                bottom.set_top(1);
                bottom.set_bottom(-1);
            }
            else
            {
                top.set_top(b1.get_rect().bottom()+1);
                top.set_bottom(slider.get_rect().top()-1);
                bottom.set_top(slider.get_rect().bottom()+1);
                bottom.set_bottom(b2.get_rect().top()-1);
            }
        }

        top_filler.rect = top;
        bottom_filler.rect = bottom;
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    hide_slider (
    )
    {
        rectangle top(rect), bottom(rect);
        slider.hide();
        top_filler.disable();
        bottom_filler.disable();
        bottom_filler.hide();
        if (ori == HORIZONTAL)
        {
            top.set_left(b1.get_rect().right()+1);
            top.set_right(b2.get_rect().left()-1);
        }
        else
        {
            top.set_top(b1.get_rect().bottom()+1);
            top.set_bottom(b2.get_rect().top()-1);
        }
        top_filler.rect = top;
        bottom_filler.rect = bottom;
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    show_slider (
    )
    {
        if ((b2.get_rect().top() - b1.get_rect().bottom() - 1 <= 8 && ori == VERTICAL) || 
            (b2.get_rect().left() - b1.get_rect().right() - 1 <= 8 && ori == HORIZONTAL) || 
            max_pos == 0)
            return;

        rectangle top(rect), bottom(rect);
        slider.show();
        top_filler.enable();
        bottom_filler.enable();
        bottom_filler.show();
        if (ori == HORIZONTAL)
        {
            top.set_left(b1.get_rect().right()+1);
            top.set_right(slider.get_rect().left()-1);
            bottom.set_left(slider.get_rect().right()+1);
            bottom.set_right(b2.get_rect().left()-1);
        }
        else
        {
            top.set_top(b1.get_rect().bottom()+1);
            top.set_bottom(slider.get_rect().top()-1);
            bottom.set_top(slider.get_rect().bottom()+1);
            bottom.set_bottom(b2.get_rect().top()-1);
        }
        top_filler.rect = top;
        bottom_filler.rect = bottom;
    }

// ----------------------------------------------------------------------------------------
    long scroll_bar::
    max_slider_pos (
    ) const
    {
        auto_mutex M(m);
        return max_pos;
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    set_max_slider_pos (
        long mpos
    )
    {
        auto_mutex M(m);
        max_pos = mpos;
        if (pos > mpos)
            pos = mpos;

        if (ori == HORIZONTAL)
            set_length(rect.width());
        else
            set_length(rect.height());

        if (mpos != 0 && enabled)
        {
            b1.enable();
            b2.enable();
        }
        else
        {
            b1.disable();
            b2.disable();
        }
             
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    set_slider_pos (
        long pos
    )
    {
        auto_mutex M(m);
        if (pos < 0)
            pos = 0;
        if (pos > max_pos)
            pos = max_pos;

        this->pos = pos;

        // move the slider object to its new position
        set_pos(rect.left(),rect.top());
    }

// ----------------------------------------------------------------------------------------

    long scroll_bar::
    slider_pos (
    ) const
    {
        auto_mutex M(m);
        return pos;
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    on_slider_drag (
    )
    {
        if (ori == HORIZONTAL)
        {
            double slider_pos = slider.get_rect().left() - b1.get_rect().right() - 1;
            double range = b2.get_rect().left() - b1.get_rect().right() - slider.get_rect().width() - 1;
            slider_pos /= range;
            slider_pos *= max_pos;
            pos = static_cast<unsigned long>(slider_pos);
        }
        else
        {
            double slider_pos = slider.get_rect().top() - b1.get_rect().bottom() - 1;
            double range = b2.get_rect().top() - b1.get_rect().bottom() - slider.get_rect().height() - 1;
            slider_pos /= range;
            slider_pos *= max_pos;
            pos = static_cast<unsigned long>(slider_pos);
        }

        adjust_fillers();
        
        if (scroll_handler.is_set())
            scroll_handler();
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    draw (
        const canvas& 
    ) const
    {
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    b1_down (
    )
    {
        if (pos != 0)
        {
            set_slider_pos(pos-1);
            if (scroll_handler.is_set())
                scroll_handler();

            if (b1_timer.delay_time() == 1000)
                b1_timer.set_delay_time(500);
            else
                b1_timer.set_delay_time(50);
            b1_timer.start();
        }
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    b1_up (
        bool 
    )
    {
        b1_timer.stop();
        b1_timer.set_delay_time(1000);
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    b2_down (
    )
    {
        if (pos != max_pos)
        {
            set_slider_pos(pos+1);
            if (scroll_handler.is_set())
                scroll_handler();

            if (b2_timer.delay_time() == 1000)
                b2_timer.set_delay_time(500);
            else
                b2_timer.set_delay_time(50);
            b2_timer.start();
        }
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    b2_up (
        bool 
    )
    {
        b2_timer.stop();
        b2_timer.set_delay_time(1000);
    }
        
// ----------------------------------------------------------------------------------------

    void scroll_bar::
    top_filler_down (
    )
    {
        // ignore this if the mouse is now outside this object.  This could happen
        // since the timers are also calling this function.
        if (top_filler.rect.contains(lastx,lasty) == false)
        {
            top_filler_up(false);
            return;
        }

        if (pos != 0)
        {
            if (pos < js)
            {
                // if there is less than jump_size() space left then jump the remaining
                // amount.
                delayed_set_slider_pos(0);
            }
            else
            {
                delayed_set_slider_pos(pos-js);
            }

            if (top_filler_timer.delay_time() == 1000)
                top_filler_timer.set_delay_time(500);
            else
                top_filler_timer.set_delay_time(50);
            top_filler_timer.start();
        }
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    top_filler_up (
        bool 
    )
    {
        top_filler_timer.stop();
        top_filler_timer.set_delay_time(1000);
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    bottom_filler_down (
    )
    {
        // ignore this if the mouse is now outside this object.  This could happen
        // since the timers are also calling this function.
        if (bottom_filler.rect.contains(lastx,lasty) == false)
        {
            bottom_filler_up(false);
            return;
        }

        if (pos != max_pos)
        {
            if (max_pos - pos < js)
            {
                // if there is less than jump_size() space left then jump the remaining
                // amount.
                delayed_set_slider_pos(max_pos);
            }
            else
            {
                delayed_set_slider_pos(pos+js);
            }

            if (bottom_filler_timer.delay_time() == 1000)
                bottom_filler_timer.set_delay_time(500);
            else
                bottom_filler_timer.set_delay_time(50);
            bottom_filler_timer.start();
        }
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    bottom_filler_up (
        bool 
    )
    {
        bottom_filler_timer.stop();
        bottom_filler_timer.set_delay_time(1000);
    }

// ----------------------------------------------------------------------------------------

    void scroll_bar::
    set_jump_size (
        long js_
    )
    {
        auto_mutex M(m);
        if (js_ < 1)
            js = 1;
        else
            js = js_;
    }

// ----------------------------------------------------------------------------------------

    long scroll_bar::
    jump_size (
    ) const
    {
        auto_mutex M(m);
        return js;
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
//                  widget_group object methods  
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    void widget_group::
    empty (
    ) 
    {  
        auto_mutex M(m); 
        widgets.clear(); 
    }

// ----------------------------------------------------------------------------------------

    void widget_group::
    add (
        drawable& widget,
        unsigned long x,
        unsigned long y
    )
    {
        auto_mutex M(m); 
        drawable* w = &widget;
        relpos rp;
        rp.x = x;
        rp.y = y;
        if (widgets.is_in_domain(w))
        {
            widgets[w].x = x;
            widgets[w].y = y;
        }
        else
        {
            widgets.add(w,rp);
        }
        if (is_hidden())
            widget.hide();
        else
            widget.show();

        if (is_enabled())
            widget.enable();
        else
            widget.disable();

        widget.set_z_order(z_order());
        widget.set_pos(x+rect.left(),y+rect.top());
    }

// ----------------------------------------------------------------------------------------

    bool widget_group::
    is_member (
        const drawable& widget
    ) const 
    { 
        auto_mutex M(m); 
        drawable* w = const_cast<drawable*>(&widget);
        return widgets.is_in_domain(w); 
    }

// ----------------------------------------------------------------------------------------

    void widget_group::
    remove (
        const drawable& widget
    )
    {
        auto_mutex M(m); 
        drawable* w = const_cast<drawable*>(&widget);
        if (widgets.is_in_domain(w))
        {
            relpos junk;
            drawable* junk2;
            widgets.remove(w,junk2,junk);
        }
    }

// ----------------------------------------------------------------------------------------

    unsigned long widget_group::
    size (
    ) const 
    {  
        auto_mutex M(m); 
        return widgets.size(); 
    }

// ----------------------------------------------------------------------------------------

    void widget_group::