dlib C++ Library - base_widgets.h

    // Copyright (C) 2005  Davis E. King (davis@dlib.net), Keita Mochizuki
    // License: Boost Software License   See LICENSE.txt for the full license.

    #ifndef DLIB_BASE_WIDGETs_
    #define DLIB_BASE_WIDGETs_

    #include <cctype>
    #include <memory>

    #include "base_widgets_abstract.h"
    #include "drawable.h"
    #include "../gui_core.h"
    #include "../algs.h"
    #include "../member_function_pointer.h"
    #include "../timer.h"
    #include "../map.h"
    #include "../set.h"
    #include "../array2d.h"
    #include "../pixel.h"
    #include "../image_transforms/assign_image.h"
    #include "../array.h"
    #include "style.h"
    #include "../unicode.h"
    #include "../any.h"

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class draggable
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class draggable : public drawable
        {
            /*!
                INITIAL VALUE
                    - drag == false

                CONVENTION
                    - if (the user is holding the left button down over this object) then
                        - drag == true
                        - x == the x position of the mouse relative to the upper left corner
                          of this object.
                        - y == the y position of the mouse relative to the upper left corner
                          of this object.
                    - else
                        - drag == false
            !*/

        public:

            draggable(
                drawable_window& w,
                unsigned long events = 0
            ) :
                drawable(w,events | MOUSE_MOVE | MOUSE_CLICK),
                drag(false)
            {}

            virtual ~draggable(
            ) = 0;

            rectangle draggable_area (
            ) const { auto_mutex M(m); return area; }

            void set_draggable_area (
                const rectangle& area_
            ) { auto_mutex M(m); area = area_; }

        protected:

            bool is_being_dragged (
            ) const { return drag; }

            virtual void on_drag (
            ){}

            virtual void on_drag_stop (
            ){}

            void on_mouse_move (
                unsigned long state,
                long x,
                long y
            );

            void on_mouse_down (
                unsigned long btn,
                unsigned long ,
                long x,
                long y,
                bool
            );

            void on_mouse_up (
                unsigned long btn,
                unsigned long state,
                long x,
                long y
            );

        private:

            rectangle area;
            bool drag;
            long x, y;

            // restricted functions
            draggable(draggable&);        // copy constructor
            draggable& operator=(draggable&);    // assignment operator
        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class mouse_over_event
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class mouse_over_event : public drawable
        {
            /*!
                INITIAL VALUE
                    - is_mouse_over_ == false

                CONVENTION
                    - is_mouse_over_ == is_mouse_over()
            !*/

        public:

            mouse_over_event(
                drawable_window& w,
                unsigned long events = 0
            ) :
                drawable(w,events | MOUSE_MOVE),
                is_mouse_over_(false)
            {}

            virtual ~mouse_over_event(
            ) = 0;

            int next_free_user_event_number() const
            {
                return drawable::next_free_user_event_number()+1;
            }

        protected:

            bool is_mouse_over (
            ) const;

            virtual void on_mouse_over (
            ){}

            virtual void on_mouse_not_over (
            ){}

            void on_mouse_leave (
            );

            void on_mouse_move (
                unsigned long state,
                long x,
                long y
            );

            void on_user_event (
                int num
            );

        private:
            mutable bool is_mouse_over_;

            // restricted functions
            mouse_over_event(mouse_over_event&);        // copy constructor
            mouse_over_event& operator=(mouse_over_event&);    // assignment operator
        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class button_action
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class button_action : public mouse_over_event
        {
            /*!
                INITIAL VALUE
                    - is_depressed_ == false
                    - seen_click == false

                CONVENTION
                    - is_depressed_ == is_depressed()
                    - if (the user has clicked the button but hasn't yet released the
                          left mouse button) then
                        - seen_click == true
                    - else
                        - seen_click == false
            !*/

        public:

            button_action(
                drawable_window& w,
                unsigned long events = 0
            ) :
                mouse_over_event(w,events | MOUSE_MOVE | MOUSE_CLICK),
                is_depressed_(false),
                seen_click(false)
            {}

            virtual ~button_action(
            ) = 0;

            int next_free_user_event_number() const
            {
                return mouse_over_event::next_free_user_event_number()+1;
            }

        protected:

            bool is_depressed (
            ) const;

            virtual void on_button_down (
            ){}

            virtual void on_button_up (
                bool
            ){}

            void on_mouse_not_over (
            );

            void on_mouse_down (
                unsigned long btn,
                unsigned long ,
                long x,
                long y,
                bool
            );

            void on_mouse_move (
                unsigned long state,
                long x,
                long y
            );

            void on_mouse_up (
                unsigned long btn,
                unsigned long,
                long x,
                long y
            );

        private:
            mutable bool is_depressed_;
            bool seen_click;

            void on_user_event (
                int num
            );

            // restricted functions
            button_action(button_action&);        // copy constructor
            button_action& operator=(button_action&);    // assignment operator
        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class widget_group
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class widget_group : public drawable
        {
            /*!
                INITIAL VALUE
                    widgets.size() == 0

                CONVENTION
                    - widgets contains all the drawable objects and their relative positions
                      that are in *this.
                    - wg_widgets contains pointers to just the widgets that happen
                      to be widget_group objects.
            !*/

            struct relpos
            {
                unsigned long x;
                unsigned long y;
            };

        public:
            widget_group(
                drawable_window& w
            ) : drawable(w) { rect = rectangle(0,0,-1,-1); enable_events();}

            virtual ~widget_group(
            ){ disable_events(); }

            void empty (
            );

            void add (
                drawable& widget,
                unsigned long x,
                unsigned long y
            );

            void add (
                widget_group& widget,
                unsigned long x,
                unsigned long y
            );

            bool is_member (
                const drawable& widget
            ) const;

            void remove (
                const drawable& widget
            );

            size_t size (
            ) const;

            void set_pos (
                long x,
                long y
            );

            void set_z_order (
                long order
            );

            void show (
            );

            void hide (
            );

            void enable (
            );

            void disable (
            );

            void fit_to_contents (
            );

        protected:

            // this object doesn't draw anything but also isn't abstract
            void draw (
                const canvas&
            ) const {}

        private:

            map<drawable*,relpos>::kernel_1a_c widgets;
            set<widget_group*>::kernel_1a_c wg_widgets;

            // restricted functions
            widget_group(widget_group&);        // copy constructor
            widget_group& operator=(widget_group&);    // assignment operator
        };

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

        class image_widget : public draggable
        {
            /*!
                INITIAL VALUE
                    - img.size() == 0

                CONVENTION
                    - img == the image this object displays
            !*/

        public:

            image_widget(
                drawable_window& w
            ): draggable(w)  { enable_events(); }

            ~image_widget(
            )
            {
                disable_events();
                parent.invalidate_rectangle(rect);
            }

            template <
                typename image_type
                >
            void set_image (
                const image_type& new_img
            )
            {
                auto_mutex M(m);
                assign_image_scaled(img,new_img);
                rectangle old(rect);
                rect.set_right(rect.left()+num_columns(img)-1);
                rect.set_bottom(rect.top()+num_rows(img)-1);
                parent.invalidate_rectangle(rect+old);
            }

        private:

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

                draw_image(c, point(rect.left(),rect.top()), img);
            }

            array2d<rgb_alpha_pixel> img;

            // restricted functions
            image_widget(image_widget&);        // copy constructor
            image_widget& operator=(image_widget&);    // assignment operator
        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class tooltip
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class tooltip : public mouse_over_event
        {
            /*!
                INITIAL VALUE
                    - stuff.get() == 0
                    - events_are_enabled() == false

                CONVENTION
                    - if (events_are_enabled() == true) then
                        - stuff.get() != 0
            !*/

        public:

            tooltip(
                drawable_window& w
            ) :
                mouse_over_event(w,MOUSE_CLICK)
            {}

            ~tooltip(
            ){ disable_events();}

            void set_size (
                unsigned long width,
                unsigned long height
            )
            {
                auto_mutex M(m);
                rect = resize_rect(rect,width,height);
            }

            void set_text (
                const std::string& str
            )
            {
                set_text(convert_to_utf32(str));
            }

            void set_text (
                const std::wstring& str
            )
            {
                set_text(convert_to_utf32(str));
            }

            void set_text (
                const ustring& str
            )
            {
                auto_mutex M(m);
                if (!stuff)
                {
                    stuff.reset(new data(*this));
                    enable_events();
                }

                stuff->win.set_text(str);
            }

            const std::string text (
            ) const
            {
                return convert_wstring_to_mbstring(wtext());
            }

            const std::wstring wtext (
            ) const
            {
                return convert_utf32_to_wstring(utext());
            }

            const dlib::ustring utext (
            ) const
            {
                auto_mutex M(m);
                dlib::ustring temp;
                if (stuff)
                {
                    temp = stuff->win.text;
                }
                return temp.c_str();
            }

            void hide (
            )
            {
                auto_mutex M(m);
                mouse_over_event::hide();
                if (stuff)
                {
                    stuff->tt_timer.stop();
                    stuff->win.hide();
                }
            }

            void disable (
            )
            {
                auto_mutex M(m);
                mouse_over_event::disable();
                if (stuff)
                {
                    stuff->tt_timer.stop();
                    stuff->win.hide();
                }
            }

        protected:

            void on_mouse_over()
            {
                stuff->x = lastx;
                stuff->y = lasty;
                stuff->tt_timer.start();
            }

            void on_mouse_not_over ()
            {
                stuff->tt_timer.stop();
                stuff->win.hide();
            }

            void on_mouse_down (
                unsigned long btn,
                unsigned long state,
                long x,
                long y,
                bool is_double_click
            )
            {
                mouse_over_event::on_mouse_down(btn,state,x,y,is_double_click);
                stuff->tt_timer.stop();
                stuff->win.hide();
            }

            void draw (
                const canvas&
            ) const{}

        private:

            class tooltip_window : public base_window
            {
            public:
                tooltip_window (const std::shared_ptr<font>& f) : base_window(false,true), pad(3), mfont(f)
                {
                }

                ustring text;
                rectangle rect_all;
                rectangle rect_text;
                const unsigned long pad;
                const std::shared_ptr<font> mfont;

                void set_text (
                    const std::string& str
                )
                {
                    set_text(convert_to_utf32(str));
                }

                void set_text (
                    const std::wstring& str
                )
                {
                    set_text(convert_to_utf32(str));
                }

                void set_text (
                    const dlib::ustring& str
                )
                {
                    text = str.c_str();

                    unsigned long width, height;
                    mfont->compute_size(text,width,height);

                    set_size(width+pad*2, height+pad*2);
                    rect_all.set_left(0);
                    rect_all.set_top(0);
                    rect_all.set_right(width+pad*2-1);
                    rect_all.set_bottom(height+pad*2-1);

                    rect_text = move_rect(rectangle(width,height),pad,pad);
                }

                void paint(const canvas& c)
                {
                    c.fill(255,255,150);
                    draw_rectangle(c, rect_all);
                    mfont->draw_string(c,rect_text,text);
                }
            };

            void show_tooltip (
            )
            {
                auto_mutex M(m);
                long x, y;
                // if the mouse has moved since we started the timer then
                // keep waiting until the user stops moving it
                if (lastx != stuff->x || lasty != stuff->y)
                {
                    stuff->x = lastx;
                    stuff->y = lasty;
                    return;
                }

                unsigned long display_width, display_height;
                // stop the timer
                stuff->tt_timer.stop();
                parent.get_pos(x,y);
                x += lastx+15;
                y += lasty+15;

                // make sure the tooltip isn't going to be off the screen
                parent.get_display_size(display_width, display_height);
                rectangle wrect(move_rect(stuff->win.rect_all,x,y));
                rectangle srect(display_width, display_height);
                if (srect.contains(wrect) == false)
                {
                    rectangle temp(srect.intersect(wrect));
                    x -= wrect.width()-temp.width();
                    y -= wrect.height()-temp.height();
                }

                stuff->win.set_pos(x,y);
                stuff->win.show();
            }

            // put all this stuff in data so we can arrange to only
            // construct it when someone is actually using the tooltip widget
            // rather than just instantiating it.
            struct data
            {
                data(
                    tooltip& self
                ) :
                    x(-1),
                    y(-1),
                    win(self.mfont),
                    tt_timer(self,&tooltip::show_tooltip)
                {
                    tt_timer.set_delay_time(400);
                }

                long x, y;
                tooltip_window win;
                timer<tooltip> tt_timer;

            };
            friend struct data;
            std::unique_ptr<data> stuff;

            // restricted functions
            tooltip(tooltip&);        // copy constructor
            tooltip& operator=(tooltip&);    // assignment operator
        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class button
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class button : public button_action
        {
        public:
            button(
                drawable_window& w
            ) :
                button_action(w),
                btn_tooltip(w)
            {
                style.reset(new button_style_default());
                enable_events();
            }

            ~button() { disable_events(); parent.invalidate_rectangle(style->get_invalidation_rect(rect)); }

            void set_size (
                unsigned long width,
                unsigned long height
            );

            void set_name (
                const std::string& name_
            );

            void set_name (
                const std::wstring& name_
            );

            void set_name (
                const dlib::ustring& name_
            );

            const std::string name (
            ) const;

            const std::wstring wname (
            ) const;

            const dlib::ustring uname (
            ) const;

            void set_tooltip_text (
                const std::string& text
            );

            void set_tooltip_text (
                const std::wstring& text
            );

            void set_tooltip_text (
                const dlib::ustring& text
            );

            void set_pos(
                long x,
                long y
            );

            const std::string tooltip_text (
            ) const;

            const std::wstring tooltip_wtext (
            ) const;

            const dlib::ustring tooltip_utext (
            ) const;

            void set_main_font (
                const std::shared_ptr<font>& f
            );

            void show (
            );

            void hide (
            );

            void enable (
            );

            void disable (
            );

            template <
                typename style_type
                >
            void set_style (
                const style_type& style_
            )
            {
                auto_mutex M(m);
                style.reset(new style_type(style_));
                rect = move_rect(style->get_min_size(name_,*mfont), rect.left(), rect.top());
                parent.invalidate_rectangle(style->get_invalidation_rect(rect));
            }

            template <
                typename T
                >
            void set_click_handler (
                T& object,
                void (T::*event_handler_)()
            )
            {
                auto_mutex M(m);
                event_handler = make_mfp(object,event_handler_);
                event_handler_self.clear();
            }

            void set_click_handler (
                const any_function<void()>& event_handler_
            )
            {
                auto_mutex M(m);
                event_handler = event_handler_;
                event_handler_self.clear();
            }

            template <
                typename T
                >
            void set_click_handler (
                T& object,
                void (T::*event_handler_)(button&)
            )
            {
                auto_mutex M(m);
                event_handler_self = make_mfp(object,event_handler_);
                event_handler.clear();
            }

            void set_sourced_click_handler (
                const any_function<void(button&)>& event_handler_
            )
            {
                auto_mutex M(m);
                event_handler_self = event_handler_;
                event_handler.clear();
            }

            bool is_depressed (
            ) const
            {
                auto_mutex M(m);
                return button_action::is_depressed();
            }

            template <
                typename T
                >
            void set_button_down_handler (
                T& object,
                void (T::*event_handler)()
            )
            {
                auto_mutex M(m);
                button_down_handler = make_mfp(object,event_handler);
            }

            void set_button_down_handler (
                const any_function<void()>& event_handler
            )
            {
                auto_mutex M(m);
                button_down_handler = event_handler;
            }

            template <
                typename T
                >
            void set_button_up_handler (
                T& object,
                void (T::*event_handler)(bool mouse_over)
            )
            {
                auto_mutex M(m);
                button_up_handler = make_mfp(object,event_handler);
            }

            void set_button_up_handler (
                const any_function<void(bool)>& event_handler
            )
            {
                auto_mutex M(m);
                button_up_handler = event_handler;
            }

            template <
                typename T
                >
            void set_button_down_handler (
                T& object,
                void (T::*event_handler)(button&)
            )
            {
                auto_mutex M(m);
                button_down_handler_self = make_mfp(object,event_handler);
            }

            void set_sourced_button_down_handler (
                const any_function<void(button&)>& event_handler
            )
            {
                auto_mutex M(m);
                button_down_handler_self = event_handler;
            }

            template <
                typename T
                >
            void set_button_up_handler (
                T& object,
                void (T::*event_handler)(bool mouse_over, button&)
            )
            {
                auto_mutex M(m);
                button_up_handler_self = make_mfp(object,event_handler);
            }

            void set_sourced_button_up_handler (
                const any_function<void(bool,button&)>& event_handler
            )
            {
                auto_mutex M(m);
                button_up_handler_self = event_handler;
            }

        private:

            // restricted functions
            button(button&);        // copy constructor
            button& operator=(button&);    // assignment operator

            dlib::ustring name_;
            tooltip btn_tooltip;

            any_function<void()> event_handler;
            any_function<void(button&)> event_handler_self;
            any_function<void()> button_down_handler;
            any_function<void(bool)> button_up_handler;
            any_function<void(button&)> button_down_handler_self;
            any_function<void(bool,button&)> button_up_handler_self;

            std::unique_ptr<button_style> style;

        protected:

            void draw (
                const canvas& c
            ) const { style->draw_button(c,rect,enabled,*mfont,lastx,lasty,name_,is_depressed()); }

            void on_button_up (
                bool mouse_over
            );

            void on_button_down (
            );

            void on_mouse_over (
            ){ if (style->redraw_on_mouse_over()) parent.invalidate_rectangle(style->get_invalidation_rect(rect)); }

            void on_mouse_not_over (
            ){ if (style->redraw_on_mouse_over()) parent.invalidate_rectangle(style->get_invalidation_rect(rect)); }
        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class scroll_bar
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class scroll_bar : public drawable
        {
            /*!
                INITIAL VALUE
                    - ori == a value given by the constructor
                    - style == a scroll_bar_style_default object
                    - pos == 0
                    - max_pos == 0
                    - js == 10

                CONVENTION
                    - ori == orientation()
                    - b1 == the button that is near the 0 end of the scroll bar
                    - b2 == the button that is near the max_pos() end of the scroll bar

                    - max_pos == max_slider_pos()
                    - pos == slider_pos()
                    - js == jump_size()
            !*/

        public:
            enum bar_orientation
            {
                HORIZONTAL,
                VERTICAL
            };

            scroll_bar(
                drawable_window& w,
                bar_orientation orientation_
            );

            virtual ~scroll_bar(
            );

            bar_orientation orientation (
            ) const;

            void set_length (
                unsigned long length
            );

            long max_slider_pos (
            ) const;

            void set_max_slider_pos (
                long mpos
            );

            void set_slider_pos (
                long pos
            );

            long slider_pos (
            ) const;

            template <
                typename T
                >
            void set_scroll_handler (
                T& object,
                void (T::*eh)()
            ) { auto_mutex M(m); scroll_handler = make_mfp(object,eh); }

            void set_scroll_handler (
                const any_function<void()>& eh
            ) { auto_mutex M(m); scroll_handler = eh; }

            void set_pos (
                long x,
                long y
            );

            void enable (
            )
            {
                auto_mutex M(m);
                if (!hidden)
                    show_slider();
                if (max_pos != 0)
                {
                    b1.enable();
                    b2.enable();
                }
                drawable::enable();
            }

            void disable (
            )
            {
                auto_mutex M(m);
                hide_slider();
                b1.disable();
                b2.disable();
                drawable::disable();
            }

            void hide (
            )
            {
                auto_mutex M(m);
                hide_slider();
                top_filler.hide();
                bottom_filler.hide();
                b1.hide();
                b2.hide();
                drawable::hide();
            }

            void show (
            )
            {
                auto_mutex M(m);
                b1.show();
                b2.show();
                drawable::show();
                top_filler.show();
                if (enabled)
                    show_slider();
            }

            void set_z_order (
                long order
            )
            {
                auto_mutex M(m);
                slider.set_z_order(order);
                top_filler.set_z_order(order);
                bottom_filler.set_z_order(order);
                b1.set_z_order(order);
                b2.set_z_order(order);
                drawable::set_z_order(order);
            }

            void set_jump_size (
                long js
            );

            long jump_size (
            ) const;

            template <
                typename style_type
                >
            void set_style (
                const style_type& style_
            )
            {
                auto_mutex M(m);
                style.reset(new style_type(style_));

                if (ori == HORIZONTAL)
                {
                    b1.set_style(style_.get_left_button_style());
                    b2.set_style(style_.get_right_button_style());
                    set_length(rect.width());
                }
                else
                {
                    b1.set_style(style_.get_up_button_style());
                    b2.set_style(style_.get_down_button_style());
                    set_length(rect.height());
                }

            }

        private:

            void hide_slider (
            );
            /*!
                ensures
                    - hides the slider and makes any other changes needed so that the
                      scroll_bar still looks right.
            !*/

            void show_slider (
            );
            /*!
                ensures
                    - shows the slider and makes any other changes needed so that the
                      scroll_bar still looks right.
            !*/

            void on_slider_drag (
            );
            /*!
                requires
                    - is called whenever the user drags the slider
            !*/

            void draw (
                const canvas& c
            ) const;

            void b1_down (
            );

            void b1_up (
                bool mouse_over
            );

            void b2_down (
            );

            void b2_up (
                bool mouse_over
            );

            void top_filler_down (
            );

            void top_filler_up (
                bool mouse_over
            );

            void bottom_filler_down (
            );

            void bottom_filler_up (
                bool mouse_over
            );

            void on_user_event (
                int i
            );

            void delayed_set_slider_pos (
                unsigned long dpos
            );

            void b1_down_t (
            );

            void b2_down_t (
            );

            void top_filler_down_t (
            );

            void bottom_filler_down_t (
            );

            friend class filler;
            class filler : public button_action
            {
                friend class scroll_bar;
            public:
                filler (
                    drawable_window& w,
                    scroll_bar& object,
                    void (scroll_bar::*down)(),
                    void (scroll_bar::*up)(bool)
                ):
                    button_action(w),
                    my_scroll_bar(object)
                {
                    bup = make_mfp(object,up);
                    bdown = make_mfp(object,down);

                    enable_events();
                }

                ~filler (
                )
                {
                   disable_events();
                }

                void set_size (
                    unsigned long width,
                    unsigned long height
                )
                {
                    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);
                }

            private:

                void draw (
                    const canvas& c
                ) const
                {
                    my_scroll_bar.style->draw_scroll_bar_background(c,rect,enabled,lastx,lasty,is_depressed());
                }

                void on_button_down (
                ) { bdown(); }

                void on_button_up (
                    bool mouse_over
                ) { bup(mouse_over); }

                scroll_bar& my_scroll_bar;
                any_function<void()> bdown;
                any_function<void(bool)> bup;
            };

            friend class slider_class;
            class slider_class : public draggable
            {
                friend class scroll_bar;
            public:
                slider_class (
                    drawable_window& w,
                    scroll_bar& object,
                    void (scroll_bar::*handler)()
                ) :
                    draggable(w, MOUSE_MOVE),
                    mouse_in_widget(false),
                    my_scroll_bar(object)
                {
                    callback = make_mfp(object,handler);
                    enable_events();
                }

                ~slider_class (
                )
                {
                   disable_events();
                }

                void set_size (
                    unsigned long width,
                    unsigned long height
                )
                {
                    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);
                }

            private:
                virtual void on_mouse_move (
                    unsigned long state,
                    long x,
                    long y
                )
                {
                    draggable::on_mouse_move(state,x,y);
                    if (!hidden && my_scroll_bar.style->redraw_on_mouse_over_slider())
                    {
                        if (rect.contains(x,y) && !mouse_in_widget)
                        {
                            mouse_in_widget = true;
                            parent.invalidate_rectangle(rect);
                        }
                        else if (rect.contains(x,y) == false && mouse_in_widget)
                        {
                            mouse_in_widget = false;
                            parent.invalidate_rectangle(rect);
                        }
                    }
                }

                void on_mouse_leave (
                )
                {
                    if (mouse_in_widget && my_scroll_bar.style->redraw_on_mouse_over_slider())
                    {
                        mouse_in_widget = false;
                        parent.invalidate_rectangle(rect);
                    }
                }

                void on_drag_stop (
                )
                {
                    if (my_scroll_bar.style->redraw_on_mouse_over_slider())
                        parent.invalidate_rectangle(rect);
                }

                void on_drag (
                )
                {
                    callback();
                }

                void draw (
                    const canvas& c
                ) const
                {
                    my_scroll_bar.style->draw_scroll_bar_slider(c,rect,enabled,lastx,lasty, is_being_dragged());
                }

                bool mouse_in_widget;
                scroll_bar& my_scroll_bar;
                any_function<void()> callback;
            };

            void adjust_fillers (
            );
            /*!
                ensures
                    - top_filler and bottom_filler appear in their correct positions
                      relative to the current positions of the slider and the b1 and
                      b2 buttons
            !*/

            unsigned long get_slider_size (
            ) const;
            /*!
                ensures
                    - returns the length in pixels the slider should have based on the current
                      state of this scroll bar
            !*/

            button b1, b2;
            slider_class slider;
            bar_orientation ori;
            filler top_filler, bottom_filler;
            any_function<void()> scroll_handler;

            long pos;
            long max_pos;
            long js;

            timer<scroll_bar> b1_timer;
            timer<scroll_bar> b2_timer;
            timer<scroll_bar> top_filler_timer;
            timer<scroll_bar> bottom_filler_timer;
            long delayed_pos;
            std::unique_ptr<scroll_bar_style> style;

            // restricted functions
            scroll_bar(scroll_bar&);        // copy constructor
            scroll_bar& operator=(scroll_bar&);    // assignment operator
        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class popup_menu
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class menu_item
        {
        public:
            virtual ~menu_item() {}

            virtual rectangle get_left_size (
            ) const { return rectangle(); }
            virtual rectangle get_middle_size (
            ) const = 0;
            virtual rectangle get_right_size (
            ) const { return rectangle(); }

            virtual unichar get_hot_key (
            ) const { return 0; }

            virtual void draw_background (
                const canvas& ,
                const rectangle& ,
                const bool ,
                const bool
            ) const {}

            virtual void draw_left (
                const canvas& ,
                const rectangle& ,
                const bool ,
                const bool
            ) const {}

            virtual void draw_middle (
                const canvas& ,
                const rectangle& ,
                const bool ,
                const bool
            ) const = 0;

            virtual void draw_right (
                const canvas& ,
                const rectangle& ,
                const bool ,
                const bool
            ) const {}

            virtual void on_click (
            ) const {}

            virtual bool has_click_event (
            ) const { return false; }

        };

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

        class menu_item_submenu : public menu_item
        {
            void initialize (
                unichar hk
            )
            {
                const dlib::ustring &str = text;
                if (hk != 0)
                {
                    std::string::size_type pos = str.find_first_of(hk);
                    if (pos != std::string::npos)
                    {
                        // now compute the location of the underline bar
                        rectangle r1 = f->compute_cursor_rect( rectangle(100000,100000), str, pos);
                        rectangle r2 = f->compute_cursor_rect( rectangle(100000,100000), str, pos+1);

                        underline_p1.x() = r1.left()+1;
                        underline_p2.x() = r2.left()-1;
                        underline_p1.y() = r1.bottom()-f->height()+f->ascender()+2;
                        underline_p2.y() = r2.bottom()-f->height()+f->ascender()+2;
                    }
                }
            }
        public:
            menu_item_submenu (
                const std::string& str,
                unichar hk = 0
            ) :
                text(convert_to_utf32(str)),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(hk);
            }

            menu_item_submenu (
                const std::wstring& str,
                unichar hk = 0
            ) :
                text(convert_to_utf32(str)),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(hk);
            }

            menu_item_submenu (
                const dlib::ustring& str,
                unichar hk = 0
            ) :
                text(str),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(hk);
            }

            virtual unichar get_hot_key (
            ) const { return hotkey; }

            virtual rectangle get_middle_size (
            ) const
            {
                unsigned long width, height;
                f->compute_size(text,width,height);
                return rectangle(width+30,height);
            }

            virtual rectangle get_right_size (
            ) const
            {
                return rectangle(15, 5);
            }

            virtual void draw_background (
                const canvas& c,
                const rectangle& rect,
                const bool enabled,
                const bool is_selected
            ) const
            {
                if (c.intersect(rect).is_empty())
                    return;

                if (enabled && is_selected)
                {
                    fill_rect_with_vertical_gradient(c, rect,rgb_alpha_pixel(0,200,0,100), rgb_alpha_pixel(0,0,0,100));
                    draw_rectangle(c, rect,rgb_alpha_pixel(0,0,0,100));
                }
            }

            virtual void draw_right (
                const canvas& c,
                const rectangle& rect,
                const bool enabled,
                const bool
            ) const
            {
                if (c.intersect(rect).is_empty())
                    return;

                unsigned char color = 0;

                if (enabled == false)
                    color = 128;

                long x, y;
                x = rect.right() - 7;
                y = rect.top() + rect.height()/2;

                for ( unsigned long i = 0; i < 5; ++i)
                    draw_line (c, point(x - i, y + i), point(x - i, y - i), color);
            }

            virtual void draw_middle (
                const canvas& c,
                const rectangle& rect,
                const bool enabled,
                const bool
            ) const
            {
                if (c.intersect(rect).is_empty())
                    return;

                if (enabled)
                {
                    f->draw_string(c,rect,text);
                }
                else
                {
                    f->draw_string(c,rect,text,128);
                }

                if (underline_p1 != underline_p2)
                {
                    point base(rect.left(),rect.top());
                    draw_line(c, base+underline_p1, base+underline_p2);
                }
            }

        private:
            dlib::ustring text;
            const std::shared_ptr<font> f;
            any_function<void()> action;
            unichar hotkey;
            point underline_p1;
            point underline_p2;
        };

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

        class menu_item_text : public menu_item
        {
            void initialize (
                const any_function<void()>& event_handler_,
                unichar hk
            )
            {
                dlib::ustring &str = text;
                action = event_handler_;

                if (hk != 0)
                {
                    std::string::size_type pos = str.find_first_of(hk);
                    if (pos != std::string::npos)
                    {
                        // now compute the location of the underline bar
                        rectangle r1 = f->compute_cursor_rect( rectangle(100000,100000), str, pos);
                        rectangle r2 = f->compute_cursor_rect( rectangle(100000,100000), str, pos+1);

                        underline_p1.x() = r1.left()+1;
                        underline_p2.x() = r2.left()-1;
                        underline_p1.y() = r1.bottom()-f->height()+f->ascender()+2;
                        underline_p2.y() = r2.bottom()-f->height()+f->ascender()+2;
                    }
                }
            }

        public:
            template <typename T>
            menu_item_text (
                const std::string& str,
                T& object,
                void (T::*event_handler_)(),
                unichar hk = 0
            ) :
                text(convert_to_utf32(str)),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(make_mfp(object, event_handler_), hk);
            }

            menu_item_text (
                const std::string& str,
                const any_function<void()>& event_handler_,
                unichar hk = 0
            ) :
                text(convert_to_utf32(str)),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(event_handler_, hk);
            }

            template <typename T>
            menu_item_text (
                const std::wstring& str,
                T& object,
                void (T::*event_handler_)(),
                unichar hk = 0
            ) :
                text(convert_to_utf32(str)),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(make_mfp(object, event_handler_), hk);
            }

            menu_item_text (
                const std::wstring& str,
                const any_function<void()>& event_handler_,
                unichar hk = 0
            ) :
                text(convert_to_utf32(str)),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(event_handler_, hk);
            }

            template <typename T>
            menu_item_text (
                const dlib::ustring& str,
                T& object,
                void (T::*event_handler_)(),
                unichar hk = 0
            ) :
                text(str),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(make_mfp(object, event_handler_), hk);
            }

            menu_item_text (
                const dlib::ustring& str,
                const any_function<void()>& event_handler_,
                unichar hk = 0
            ) :
                text(str),
                f(default_font::get_font()),
                hotkey(hk)
            {
                initialize(event_handler_, hk);
            }

            virtual unichar get_hot_key (
            ) const { return hotkey; }

            virtual rectangle get_middle_size (
            ) const
            {
                unsigned long width, height;
                f->compute_size(text,width,height);
                return rectangle(width,height);
            }

            virtual void draw_background (
                const canvas& c,
                const rectangle& rect,
                const bool enabled,
                const bool is_selected
            ) const
            {
                if (c.intersect(rect).is_empty())
                    return;

                if (enabled && is_selected)
                {
                    fill_rect_with_vertical_gradient(c, rect,rgb_alpha_pixel(0,200,0,100), rgb_alpha_pixel(0,0,0,100));
                    draw_rectangle(c, rect,rgb_alpha_pixel(0,0,0,100));
                }
            }

            virtual void draw_middle (
                const canvas& c,
                const rectangle& rect,
                const bool enabled,
                const bool
            ) const
            {
                if (c.intersect(rect).is_empty())
                    return;

                unsigned char color = 0;

                if (enabled == false)
                    color = 128;

                f->draw_string(c,rect,text,color);

                if (underline_p1 != underline_p2)
                {
                    point base(rect.left(),rect.top());
                    draw_line(c, base+underline_p1, base+underline_p2, color);
                }
            }

            virtual void on_click (
            ) const
            {
                action();
            }

            virtual bool has_click_event (
            ) const { return true; }

        private:
            dlib::ustring text;
            const std::shared_ptr<font> f;
            any_function<void()> action;
            unichar hotkey;
            point underline_p1;
            point underline_p2;
        };

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

        class menu_item_separator : public menu_item
        {
        public:
            virtual rectangle get_middle_size (
            ) const
            {
                return rectangle(10,4);
            }

            virtual void draw_background (
                const canvas& c,
                const rectangle& rect,
                const bool ,
                const bool
            ) const
            {
                if (c.intersect(rect).is_empty())
                    return;

                point p1(rect.left(),rect.top()+rect.height()/2-1);
                point p2(rect.right(),rect.top()+rect.height()/2-1);

                point p3(rect.left(),rect.top()+rect.height()/2);
                point p4(rect.right(),rect.top()+rect.height()/2);
                draw_line(c, p1,p2,128);
                draw_line(c, p3,p4,255);
            }

            virtual void draw_middle (
                const canvas& ,
                const rectangle& ,
                const bool ,
                const bool
            ) const
            {
            }
        };

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

        class popup_menu : public base_window
        {
            /*!
                INITIAL VALUE
                    - pad == 2
                    - item_pad == 3
                    - cur_rect == rectangle(pad,pad,pad-1,pad-1)
                    - left_width == 0
                    - middle_width == 0
                    - selected_item == 0
                    - submenu_open == false
                    - items.size() == 0
                    - item_enabled.size() == 0
                    - left_rects.size() == 0
                    - middle_rects.size() == 0
                    - right_rects.size() == 0
                    - line_rects.size() == 0
                    - submenus.size() == 0
                    - hide_handlers.size() == 0

                CONVENTION
                    - pad = 2
                    - item_pad = 3
                    - all of the following arrays have the same size:
                        - items.size()
                        - item_enabled.size()
                        - left_rects.size()
                        - middle_rects.size()
                        - right_rects.size()
                        - line_rects.size()
                        - submenus.size()

                    - win_rect == a rectangle that is the exact size of this window and with
                      its upper left corner at (0,0)
                    - cur_rect == the rect inside which all the menu items are drawn

                    - if (a menu_item is supposed to be selected) then
                        - selected_item == the index in menus of the menu_item
                    - else
                        - selected_item == submenus.size()

                    - if (there is a selected submenu and it is currently open) then
                        - submenu_open == true
                    - else
                        - submenu_open == false

                    - for all valid i:
                        - items[i] == a pointer to the ith menu_item
                        - item_enabled[i] == true if the ith menu_item is enabled, false otherwise
                        - left_rects[i] == the left rectangle for the ith menu item
                        - middle_rects[i] == the middle rectangle for the ith menu item
                        - right_rects[i] == the right rectangle for the ith menu item
                        - line_rects[i] == the rectangle for the entire line on which the ith menu
                          item appears.
                        - if (submenus[i] != 0) then
                            - the ith menu item has a submenu and it is pointed to by submenus[i]

                    - hide_handlers == an array of all the on_hide events registered for
                      this popup_menu
            !*/

        public:

            popup_menu (
            );

            template <
                typename menu_item_type
                >
            unsigned long add_menu_item (
                const menu_item_type& new_item
            )
            {
                auto_mutex M(wm);
                bool t = true;
                std::unique_ptr<menu_item> item(new menu_item_type(new_item));
                items.push_back(item);
                item_enabled.push_back(t);

                // figure out how big the window should be now and what not
                rectangle left = new_item.get_left_size();
                rectangle middle = new_item.get_middle_size();
                rectangle right = new_item.get_right_size();

                bool recalc_rect_positions = false;
                const rectangle all = left+middle+right;

                // make sure left_width contains the max of all the left rectangles
                if (left.width() > left_width)
                {
                    left_width = left.width();
                    recalc_rect_positions = true;
                }
                // make sure middle_width contains the max of all the middle rectangles
                if (middle.width() > middle_width)
                {
                    middle_width = middle.width();
                    recalc_rect_positions = true;
                }

                // make the current rectangle wider if necessary
                if (cur_rect.width() < left_width + middle_width + right.width() + 2*item_pad)
                {
                    cur_rect = resize_rect_width(cur_rect, left_width + middle_width + right.width() + 2*item_pad);
                    recalc_rect_positions = true;
                }

                const long y = cur_rect.bottom()+1 + item_pad;
                const long x = cur_rect.left() + item_pad;

                // make the current rectangle taller to account for this new menu item
                cur_rect.set_bottom(cur_rect.bottom()+all.height() + 2*item_pad);

                // adjust all the saved rectangles since the width of the window changed
                // or left_width changed
                if (recalc_rect_positions)
                {
                    long y = cur_rect.top() + item_pad;
                    for (unsigned long i = 0; i < left_rects.size(); ++i)
                    {
                        middle_rects[i] = move_rect(middle_rects[i], x+left_width, y);
                        right_rects[i] = move_rect(right_rects[i], x+cur_rect.width()-right_rects[i].width()-item_pad, y);
                        line_rects[i] = resize_rect_width(line_rects[i], cur_rect.width());

                        y += line_rects[i].height();
                    }
                }

                // save the rectangles for later use.  Also position them at the
                // right spots
                left = move_rect(left,x,y);
                middle = move_rect(middle,x+left_width,y);
                right = move_rect(right,x+cur_rect.width()-right.width()-item_pad,y);
                rectangle line(move_rect(rectangle(cur_rect.width(),all.height()+2*item_pad), x-item_pad, y-item_pad));

                // make sure the left, middle, and right rectangles are centered in the
                // line.
                if (left.height() < all.height())
                    left = translate_rect(left,0, (all.height()-left.height())/2);
                if (middle.height() < all.height())
                    middle = translate_rect(middle,0, (all.height()-middle.height())/2);
                if (right.height() < all.height())
                    right = translate_rect(right,0, (all.height()-right.height())/2);

                left_rects.push_back(left);
                middle_rects.push_back(middle);
                right_rects.push_back(right);
                line_rects.push_back(line);

                popup_menu* junk = 0;
                submenus.push_back(junk);

                win_rect.set_right(cur_rect.right()+pad);
                win_rect.set_bottom(cur_rect.bottom()+pad);
                set_size(win_rect.width(),win_rect.height());

                // make it so that nothing is selected
                selected_item = submenus.size();

                return items.size()-1;
            }

            template <
                typename menu_item_type
                >
            unsigned long add_submenu (
                const menu_item_type& new_item,
                popup_menu& submenu
            )
            {
                auto_mutex M(wm);

                submenus[add_menu_item(new_item)] = &submenu;

                submenu.set_on_hide_handler(*this,&popup_menu::on_submenu_hide);

                return items.size()-1;
            }

            void enable_menu_item (
                unsigned long idx
            );

            void disable_menu_item (
                unsigned long idx
            );

            size_t size (
            ) const;

            void clear (
            );

            void show (
            );

            void hide (
            );

            template <typename T>
            void set_on_hide_handler (
                T& object,
                void (T::*event_handler)()
            )
            {
                auto_mutex M(wm);

                member_function_pointer<> temp;
                temp.set(object,event_handler);

                // if this handler isn't already registered then add it
                bool found_handler = false;
                for (unsigned long i = 0; i < hide_handlers.size(); ++i)
                {
                    if (hide_handlers[i] == temp)
                    {
                        found_handler = true;
                        break;
                    }
                }

                if (found_handler == false)
                {
                    hide_handlers.push_back(temp);
                }
            }

            void select_first_item (
            );

            bool forwarded_on_keydown (
                unsigned long key,
                bool is_printable,
                unsigned long state
            );

        private:

            void on_submenu_hide (
            );

            void on_window_resized(
            );

            void on_mouse_up (
                unsigned long btn,
                unsigned long,
                long x,
                long y
            );

            void on_mouse_move (
                unsigned long state,
                long x,
                long y
            );

            void close_submenu (
            );

            bool display_selected_submenu (
            );
            /*!
                ensures
                    - if (submenus[selected_item] isn't null) then
                        - displays the selected submenu
                        - returns true
                    - else
                        - returns false
            !*/

            void on_mouse_leave (
            );

            void paint (
                const canvas& c
            );

            const long pad;
            const long item_pad;
            rectangle cur_rect;
            rectangle win_rect;
            unsigned long left_width;
            unsigned long middle_width;
            array<std::unique_ptr<menu_item> > items;
            array<bool> item_enabled;
            array<rectangle> left_rects;
            array<rectangle> middle_rects;
            array<rectangle> right_rects;
            array<rectangle> line_rects;
            array<popup_menu*> submenus;
            unsigned long selected_item;
            bool submenu_open;
            array<member_function_pointer<> > hide_handlers;

            // restricted functions
            popup_menu(popup_menu&);        // copy constructor
            popup_menu& operator=(popup_menu&);    // assignment operator
        };

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

        class zoomable_region : public drawable
        {
            /*!
                INITIAL VALUE
                    - min_scale == 0.15
                    - max_scale == 1.0
                    - zoom_increment_ == 0.02
                    - scale == 1.0
                    - mouse_drag_screen == false

                CONVENTION
                    - zoom_increment() == zoom_increment_
                    - min_zoom_scale() == min_scale
                    - max_zoom_scale() == max_scale
                    - zoom_scale() == scale
                    - if (the user is currently dragging the graph around via the mouse) then
                        - mouse_drag_screen == true
                    - else
                        - mouse_drag_screen == false

                    - max_graph_point() == lr_point
                    - display_rect() == display_rect_
                    - gui_to_graph_space(point(display_rect.left(),display_rect.top())) == gr_orig
            !*/

        public:

            zoomable_region (
                drawable_window& w,
                unsigned long events = 0
            );

            virtual ~zoomable_region (
            )= 0;

            virtual void set_pos (
                long x,
                long y
            );

            template <
                typename style_type
                >
            void set_style (
                const style_type& style_
            )
            {
                auto_mutex M(m);
                style.reset(new style_type(style_));
                hsb.set_style(style_.get_horizontal_scroll_bar_style());
                vsb.set_style(style_.get_vertical_scroll_bar_style());

                // do this just so that everything gets redrawn right
                set_size(rect.width(), rect.height());
            }

            void set_zoom_increment (
                double zi
            );

            double zoom_increment (
            ) const;

            void set_max_zoom_scale (
                double ms
            );

            void set_min_zoom_scale (
                double ms
            );

            double min_zoom_scale (
            ) const;

            double max_zoom_scale (
            ) const;

            virtual void set_size (
                unsigned long width,
                unsigned long height
            );

            void show (
            );

            void hide (
            );

            void enable (
            );

            void disable (
            );

            void set_z_order (
                long order
            );

        protected:

            virtual void on_view_changed () {}

            point graph_to_gui_space (
                const vector<double,2>& p
            ) const;

            vector<double,2> gui_to_graph_space (
                const point& p
            ) const;

            point max_graph_point (
            ) const;

            rectangle display_rect (
            ) const;

            double zoom_scale (
            ) const;

            void set_zoom_scale (
                double new_scale
            );

            void center_display_at_graph_point (
                const vector<double,2>& p
            );

        // ----------- event handlers ---------------

            void on_wheel_down (
                unsigned long state
            );

            void on_wheel_up (
                unsigned long state
            );

            void on_mouse_move (
                unsigned long state,
                long x,
                long y
            );

            void on_mouse_up (
                unsigned long btn,
                unsigned long state,
                long x,
                long y
            );

            void on_mouse_down (
                unsigned long btn,
                unsigned long state,
                long x,
                long y,
                bool is_double_click
            );

            void draw (
                const canvas& c
            ) const;

        private:

            void on_h_scroll (
            );

            void on_v_scroll (
            );

            void redraw_graph (
            );

            void adjust_origin (
                const point& gui_p,
                const vector<double,2>& graph_p
            );
            /*!
                ensures
                    - adjusts gr_orig so that we are as close to the following as possible:
                        - graph_to_gui_space(graph_p) == gui_p
                        - gui_to_graph_space(gui_p) == graph_p
            !*/

            vector<double,2> gr_orig; // point in graph space such that it's gui space point is the upper left of display_rect_
            vector<double,2> lr_point; // point in graph space such that it is at the lower right corner of the screen at max zoom

            mutable std::ostringstream sout;

            double scale; // 0 < scale <= 1
            double min_scale;
            double max_scale;
            double zoom_increment_;
            rectangle display_rect_;

            bool mouse_drag_screen;  // true if the user is dragging the white background area
            vector<double,2> drag_screen_point; // the starting point the mouse was at in graph space for the background area drag

            scroll_bar vsb;
            scroll_bar hsb;

            std::unique_ptr<scrollable_region_style> style;

            // restricted functions
            zoomable_region(zoomable_region&);        // copy constructor
            zoomable_region& operator=(zoomable_region&);    // assignment operator

        };

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

        class scrollable_region : public drawable
        {
            /*!
                INITIAL VALUE
                    - hscroll_bar_inc == 1
                    - vscroll_bar_inc == 1
                    - h_wheel_scroll_bar_inc == 1
                    - v_wheel_scroll_bar_inc == 1
                    - mouse_drag_enabled_ == false
                    - user_is_dragging_mouse == false

                CONVENTION
                    - mouse_drag_enabled() == mouse_drag_enabled_
                    - horizontal_scroll_increment() == hscroll_bar_inc
                    - vertical_scroll_increment() == vscroll_bar_inc
                    - horizontal_mouse_wheel_scroll_increment() == h_wheel_scroll_bar_inc
                    - vertical_mouse_wheel_scroll_increment() == v_wheel_scroll_bar_inc
                    - vertical_scroll_pos() == vsb.slider_pos()
                    - horizontal_scroll_pos() == hsb.slider_pos()
                    - total_rect() == total_rect_
                    - display_rect() == display_rect_

                    - if (the user is currently dragging the total_rect around with a mouse drag) then
                        - user_is_dragging_mouse == true
                        - drag_origin == the point the mouse was at, with respect to total_rect,
                          when the dragging started
                    - else
                        - user_is_dragging_mouse == false
            !*/

        public:

            scrollable_region (
                drawable_window& w,
                unsigned long events = 0
            );

            virtual ~scrollable_region (
            ) = 0;

            template <
                typename style_type
                >
            void set_style (
                const style_type& style_
            )
            {
                auto_mutex M(m);
                style.reset(new style_type(style_));
                hsb.set_style(style_.get_horizontal_scroll_bar_style());
                vsb.set_style(style_.get_vertical_scroll_bar_style());

                // do this just so that everything gets redrawn right
                set_size(rect.width(), rect.height());
            }

            void show (
            );

            void hide (
            );

            void enable (
            );

            void disable (
            );

            void set_z_order (
                long order
            );

            virtual void set_size (
                unsigned long width,
                unsigned long height
            );

            unsigned long horizontal_mouse_wheel_scroll_increment (
            ) const;

            unsigned long vertical_mouse_wheel_scroll_increment (
            ) const;

            void set_horizontal_mouse_wheel_scroll_increment (
                unsigned long inc
            );

            void set_vertical_mouse_wheel_scroll_increment (
                unsigned long inc
            );

            unsigned long horizontal_scroll_increment (
            ) const;

            unsigned long vertical_scroll_increment (
            ) const;

            void set_horizontal_scroll_increment (
                unsigned long inc
            );

            void set_vertical_scroll_increment (
                unsigned long inc
            );

            long horizontal_scroll_pos (
            ) const;

            long vertical_scroll_pos (
            ) const;

            void set_horizontal_scroll_pos (
                long pos
            );

            void set_vertical_scroll_pos (
                long pos
            );

            virtual void set_pos (
                long x,
                long y
            );

            bool mouse_drag_enabled (
            ) const;

            void enable_mouse_drag (
            );

            void disable_mouse_drag (
            );

        protected:

            virtual void on_view_changed () {}

            const rectangle& display_rect (
            ) const;

            void set_total_rect_size (
                unsigned long width,
                unsigned long height
            );

            const rectangle& total_rect (
            ) const;

            void scroll_to_rect (
                const rectangle& r_
            );

            void on_wheel_down (
                unsigned long state
            );

            void on_mouse_move (
                unsigned long state,
                long x,
                long y
            );

            void on_mouse_down (
                unsigned long btn,
                unsigned long state,
                long x,
                long y,
                bool is_double_click
            );

            void on_mouse_up   (
                unsigned long btn,
                unsigned long state,
                long x,
                long y
            );

            void on_wheel_up (
                unsigned long state
            );

            void draw (
                const canvas& c
            ) const;

        private:

            bool need_h_scroll (
            ) const;

            bool need_v_scroll (
            ) const;

            void on_h_scroll (
            );

            void on_v_scroll (
            );

            rectangle total_rect_;
            rectangle display_rect_;
            scroll_bar hsb;
            scroll_bar vsb;
            unsigned long hscroll_bar_inc;
            unsigned long vscroll_bar_inc;
            unsigned long h_wheel_scroll_bar_inc;
            unsigned long v_wheel_scroll_bar_inc;
            bool mouse_drag_enabled_;
            bool user_is_dragging_mouse;
            point drag_origin;
            std::unique_ptr<scrollable_region_style> style;

        };

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // class popup_menu_region
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        class popup_menu_region : public drawable
        {
            /*!
                CONVENTION
                    popup_menu_visible() == popup_menu_shown
            !*/

        public:

            popup_menu_region(
                drawable_window& w
            );

            virtual ~popup_menu_region(
            );

            void set_size (
                unsigned long width,
                unsigned long height
            );

            void set_rect (
                const rectangle& new_rect
            );

            popup_menu& menu (
            );

            void hide (
            );

            void disable (
            );

            bool popup_menu_visible (
            ) const { auto_mutex M(m); return popup_menu_shown; }

        protected:

            void on_keydown (
                unsigned long key,
                bool is_printable,
                unsigned long state
            );

            void on_focus_lost (
            );

            void on_focus_gained (
            );

            void on_window_moved(
            );

            void on_mouse_down (
                unsigned long btn,
                unsigned long state,
                long x,
                long y,
                bool is_double_click
            );

            void on_menu_becomes_hidden (
            );

            void draw (
                const canvas&
            ) const;

        private:

            popup_menu menu_;
            bool popup_menu_shown;

            // restricted functions
            popup_menu_region(popup_menu_region&);        // copy constructor
            popup_menu_region& operator=(popup_menu_region&);    // assignment operator
        };

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

    }

    #ifdef NO_MAKEFILE
    #include "base_widgets.cpp"
    #endif

    #endif // DLIB_BASE_WIDGETs_