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

#ifndef DLIB_WIDGETs_
#define DLIB_WIDGETs_

#include <cctype>
#include <memory>
#include <set>
#include <sstream>
#include <string>
#include <vector>

#include "../algs.h"
#include "widgets_abstract.h"
#include "drawable.h"
#include "../gui_core.h"
#include "fonts.h"
#include "../timer.h"
#include "base_widgets.h"
#include "../member_function_pointer.h"
#include "../array.h"
#include "../array2d.h"
#include "../sequence.h"
#include "../dir_nav.h"
#include "../queue.h"
#include "style.h"
#include "../string.h"
#include "../misc_api.h"
#include "../any.h"
#include "../image_processing/full_object_detection.h"

#ifdef _MSC_VER
// This #pragma directive is also located in the algs.h file but for whatever
// reason visual studio 9 just ignores it when it is only there. 

// this is to disable the "'this' : used in base member initializer list"
// warning you get from some of the GUI objects since all the objects
// require that their parent class be passed into their constructor. 
// In this case though it is totally safe so it is ok to disable this warning.
#pragma warning(disable : 4355)
#endif

namespace dlib
{

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class label  
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class label : public drawable
    {
    public:
        label(
            drawable_window& w
        ) : 
            drawable(w),
            text_color_(0,0,0)
        {
            enable_events();
        }

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

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

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

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

        const std::string text (
        ) const;

        const std::wstring wtext (
        ) const;

        const dlib::ustring utext (
        ) const;

        void set_text_color (
            const rgb_pixel color
        );

        const rgb_pixel text_color (
        ) const;

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

    private:
        dlib::ustring text_;
        rgb_pixel text_color_;


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

    protected:

        void draw (
            const canvas& c
        ) const;

    };

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class toggle_button
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class toggle_button : public button_action 
    {
        /*!
            INITIAL VALUE
                - checked == false

            CONVENTION
                - is_checked() == checked
        !*/

    public:

        toggle_button(
            drawable_window& w
        ) : 
            button_action(w),
            btn_tooltip(w),
            checked(false)
        {
            style.reset(new toggle_button_style_default());
            enable_events();
        }
        
        ~toggle_button() { disable_events(); parent.invalidate_rectangle(rect); }

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

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

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

        void set_size (
            unsigned long width_,
            unsigned long height_
        );

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

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

        void set_tooltip_text (
            const ustring& text
        );

        const std::string tooltip_text (
        ) const;

        const std::wstring tooltip_wtext (
        ) const;

        const dlib::ustring tooltip_utext (
        ) const;

        bool is_checked (
        ) const;

        const std::string name (
        ) const;

        const std::wstring wname (
        ) const;

        const dlib::ustring uname (
        ) const;

        void set_checked (
        );

        void set_unchecked (
        );

        void show (
        );

        void hide (
        );

        void enable (
        );

        void disable (
        );

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

        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_));
            rect = move_rect(style->get_min_size(name_,*mfont), rect.left(), rect.top());
            parent.invalidate_rectangle(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_)(toggle_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(toggle_button&)>& event_handler_
        )
        {
            auto_mutex M(m);
            event_handler_self = event_handler_;
            event_handler.clear();
        }

    private:

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

        dlib::ustring name_;
        tooltip btn_tooltip;
        bool checked;

        any_function<void()> event_handler;
        any_function<void(toggle_button&)> event_handler_self;

        std::unique_ptr<toggle_button_style> style;

    protected:

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

        void on_button_up (
            bool mouse_over
        );

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

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

    class text_field : public drawable
    {
        /*!
            INITIAL VALUE
                text_color_ == rgb_pixel(0,0,0)
                bg_color_ == rgb_pixel(255,255,255)
                cursor_pos == 0
                text_width == 0
                text_ == ""
                has_focus == false
                cursor_visible == false
                recent_movement == false
                highlight_start == 0
                highlight_end == -1
                shift_pos == -1
                text_pos == 0
    
            CONVENTION
                - cursor_pos == the position of the cursor in the string text_.  The 
                  cursor appears before the letter text_[cursor_pos]
                - cursor_x == the x coordinate of the cursor relative to the left side 
                  of rect.  i.e. the number of pixels that separate the cursor from the
                  left side of the text_field.
                - has_focus == true if this text field has keyboard input focus
                - cursor_visible == true if the cursor should be painted
                - text_ == text()
                - text_pos == the index of the first letter in text_ that appears in 
                  this text field.
                - text_width == the width of text_[text_pos] though text_[text.size()-1]

                - if (has_focus && the user has recently moved the cursor) then
                    - recent_movement == true
                - else
                    - recent_movement == false

                - if (highlight_start <= highlight_end) then
                    - text[highlight_start] though text[highlight_end] should be
                      highlighted

                - if (shift_pos != -1) then
                    - has_focus == true
                    - the shift key is being held down or the left mouse button is
                      being held down.
                    - shift_pos == the position of the cursor when the shift or mouse key
                      was first pressed.

                - text_color() == text_color_
                - background_color() == bg_color_
        !*/

    public:
        text_field(
            drawable_window& w
        ) : 
            drawable(w,MOUSE_CLICK | KEYBOARD_EVENTS | MOUSE_MOVE | STRING_PUT),
            text_color_(0,0,0),
            bg_color_(255,255,255),
            text_width(0),
            text_pos(0),
            recent_movement(false),
            has_focus(false),
            cursor_visible(false),
            cursor_pos(0),
            highlight_start(0),
            highlight_end(-1),
            shift_pos(-1),
            t(*this,&text_field::timer_action),
            right_click_menu(w)
        {
            style.reset(new text_field_style_default());
            rect.set_bottom(mfont->height()+ (style->get_padding(*mfont))*2);
            rect.set_right((style->get_padding(*mfont))*2);
            cursor_x = style->get_padding(*mfont);

            right_click_menu.menu().add_menu_item(menu_item_text("Cut",*this,&text_field::on_cut,'t'));
            right_click_menu.menu().add_menu_item(menu_item_text("Copy",*this,&text_field::on_copy,'C'));
            right_click_menu.menu().add_menu_item(menu_item_text("Paste",*this,&text_field::on_paste,'P'));
            right_click_menu.menu().add_menu_item(menu_item_text("Delete",*this,&text_field::on_delete_selected,'D'));
            right_click_menu.menu().add_menu_item(menu_item_separator());
            right_click_menu.menu().add_menu_item(menu_item_text("Select All",*this,&text_field::on_select_all,'A'));

            right_click_menu.set_rect(get_text_rect());
            enable_events();

            t.set_delay_time(500);
        }

        ~text_field (
        )
        {
            disable_events();
            parent.invalidate_rectangle(rect); 
            t.stop_and_wait();
        }

        template <
            typename style_type
            >
        void set_style (
            const style_type& style_
        )
        {
            auto_mutex M(m);
            style.reset(new style_type(style_));
            // call this just so that this widget redraws itself with the new style
            set_main_font(mfont);
        }

        void set_text (
            const std::string& text_
        );

        void set_text (
            const std::wstring& text_
        );

        void give_input_focus (
        );

        bool has_input_focus (
        ) const;

        void select_all_text (
        );

        void set_text (
            const dlib::ustring& text_
        );

        const std::string text (
        ) const;

        const std::wstring wtext (
        ) const;

        const dlib::ustring utext (
        ) const;

        void set_text_color (
            const rgb_pixel color
        );

        const rgb_pixel text_color (
        ) const;

        void set_background_color (
            const rgb_pixel color
        );

        const rgb_pixel background_color (
        ) const;

        void set_width (
            unsigned long width
        );

        void set_pos (
            long x,
            long y
        );

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

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

        void disable (
        );

        void enable (
        );

        void hide (
        );

        void show (
        );

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

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

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

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

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

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

    private:

        void on_cut (
        );
        
        void on_copy (
        );

        void on_paste (
        );

        void on_select_all (
        );

        void on_delete_selected (
        );

        void on_text_is_selected (
        );

        void on_no_text_selected (
        );

        void on_user_event (
            int num
        )
        {
            // ignore this user event if it isn't for us
            if (num != drawable::next_free_user_event_number())
                return;

            if (recent_movement == false)
            {
                cursor_visible = !cursor_visible; 
                parent.invalidate_rectangle(rect); 
            }
            else
            {
                if (cursor_visible == false)
                {
                    cursor_visible = true;
                    parent.invalidate_rectangle(rect); 
                }
                recent_movement = false;
            }
        }

        void timer_action (
        ) { parent.trigger_user_event(this,drawable::next_free_user_event_number()); }
        /*!
            ensures
                - flips the state of cursor_visible
        !*/

        void move_cursor (
            unsigned long pos
        );
        /*!
            requires
                - pos <= text_.size() 
            ensures
                - moves the cursor to the position given by pos and moves the text 
                  in the text box if necessary
                - if the position changes then the parent window will be updated
        !*/

        rectangle get_text_rect (
        ) const;
        /*!
            ensures
                - returns the rectangle that should contain the text in this widget
        !*/

        dlib::ustring text_;
        rgb_pixel text_color_;
        rgb_pixel bg_color_;

        unsigned long text_width;
        unsigned long text_pos;


        bool recent_movement;
        bool has_focus;
        bool cursor_visible;
        long cursor_pos;
        unsigned long cursor_x;

        // this tells you what part of the text is highlighted
        long highlight_start;
        long highlight_end;
        long shift_pos;
        any_function<void()> text_modified_handler;
        any_function<void()> enter_key_handler;
        any_function<void()> focus_lost_handler;

        std::unique_ptr<text_field_style> style;

        timer<text_field> t;

        popup_menu_region right_click_menu;

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


    protected:

        void draw (
            const canvas& c
        ) const;


        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_mouse_move (
            unsigned long state,
            long x,
            long y
        );

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

        void on_string_put (
            const std::wstring &str
        );
    };

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class text_box
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class text_box : public scrollable_region
    {
        /*!
            INITIAL VALUE
                text_color_ == rgb_pixel(0,0,0)
                bg_color_ == rgb_pixel(255,255,255)
                cursor_pos == 0
                text_ == ""
                has_focus == false
                cursor_visible == false
                recent_movement == false
                highlight_start == 0
                highlight_end == -1
                shift_pos == -1
    
            CONVENTION
                - cursor_pos == the position of the cursor in the string text_.  The 
                  cursor appears before the letter text_[cursor_pos]
                - cursor_rect == The rectangle that should be drawn for the cursor. 
                  The position is relative to total_rect().
                - has_focus == true if this text field has keyboard input focus
                - cursor_visible == true if the cursor should be painted
                - text_ == text()

                - if (has_focus && the user has recently moved the cursor) then
                    - recent_movement == true
                - else
                    - recent_movement == false

                - if (highlight_start <= highlight_end) then
                    - text[highlight_start] though text[highlight_end] should be
                      highlighted

                - if (shift_pos != -1) then
                    - has_focus == true
                    - the shift key is being held down or the left mouse button is
                      being held down.
                    - shift_pos == the position of the cursor when the shift or mouse key
                      was first pressed.

                - text_color() == text_color_
                - background_color() == bg_color_
        !*/

    public:
        text_box(
            drawable_window& w
        ) : 
            scrollable_region(w,MOUSE_CLICK | KEYBOARD_EVENTS | MOUSE_MOVE | STRING_PUT),
            text_color_(0,0,0),
            bg_color_(255,255,255),
            recent_movement(false),
            has_focus(false),
            cursor_visible(false),
            cursor_pos(0),
            highlight_start(0),
            highlight_end(-1),
            shift_pos(-1),
            t(*this,&text_box::timer_action),
            right_click_menu(w)
        {
            style.reset(new text_box_style_default());

            const long padding = static_cast<long>(style->get_padding(*mfont));
            cursor_rect = mfont->compute_cursor_rect(rectangle(padding,padding,1000000,1000000), text_, 0);

            adjust_total_rect();

            set_vertical_mouse_wheel_scroll_increment(mfont->height());
            set_horizontal_mouse_wheel_scroll_increment(mfont->height());

            right_click_menu.menu().add_menu_item(menu_item_text("Cut",*this,&text_box::on_cut,'t'));
            right_click_menu.menu().add_menu_item(menu_item_text("Copy",*this,&text_box::on_copy,'C'));
            right_click_menu.menu().add_menu_item(menu_item_text("Paste",*this,&text_box::on_paste,'P'));
            right_click_menu.menu().add_menu_item(menu_item_text("Delete",*this,&text_box::on_delete_selected,'D'));
            right_click_menu.menu().add_menu_item(menu_item_separator());
            right_click_menu.menu().add_menu_item(menu_item_text("Select All",*this,&text_box::on_select_all,'A'));

            right_click_menu.set_rect(get_text_rect());

            set_size(100,100);

            enable_events();

            t.set_delay_time(500);
        }

        ~text_box (
        )
        {
            disable_events();
            parent.invalidate_rectangle(rect); 
            t.stop_and_wait();
        }

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

            scrollable_region::set_style(style_.get_scrollable_region_style());
            // call this just so that this widget redraws itself with the new style
            set_main_font(mfont);
        }

        void set_text (
            const std::string& text_
        );

        void set_text (
            const std::wstring& text_
        );

        void set_text (
            const dlib::ustring& text_
        );

        const std::string text (
        ) const;

        const std::wstring wtext (
        ) const;

        const dlib::ustring utext (
        ) const;

        void set_text_color (
            const rgb_pixel color
        );

        const rgb_pixel text_color (
        ) const;

        void set_background_color (
            const rgb_pixel color
        );

        const rgb_pixel background_color (
        ) const;

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

        void set_pos (
            long x,
            long y
        );

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

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

        void disable (
        );

        void enable (
        );

        void hide (
        );

        void show (
        );

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

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

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

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

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

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

    private:

        void on_cut (
        );
        
        void on_copy (
        );

        void on_paste (
        );

        void on_select_all (
        );

        void on_delete_selected (
        );

        void on_text_is_selected (
        );

        void on_no_text_selected (
        );

        void on_user_event (
            int num
        )
        {
            // ignore this user event if it isn't for us
            if (num != scrollable_region::next_free_user_event_number())
                return;

            if (recent_movement == false)
            {
                cursor_visible = !cursor_visible; 
                parent.invalidate_rectangle(rect); 
            }
            else
            {
                if (cursor_visible == false)
                {
                    cursor_visible = true;
                    parent.invalidate_rectangle(rect); 
                }
                recent_movement = false;
            }
        }

        // The reason for using user actions here rather than just having the timer just call
        // what it needs directly is to avoid a potential deadlock during destruction of this widget.
        void timer_action (
        ) { parent.trigger_user_event(this,scrollable_region::next_free_user_event_number()); }
        /*!
            ensures
                - flips the state of cursor_visible
        !*/

        void move_cursor (
            unsigned long pos
        );
        /*!
            requires
                - pos <= text_.size() 
            ensures
                - moves the cursor to the position given by pos and moves the text 
                  in the text box if necessary
                - if the position changes then the parent window will be updated
        !*/

        rectangle get_text_rect (
        ) const;
        /*!
            ensures
                - returns the rectangle that should contain the text in this widget
        !*/

        void adjust_total_rect (
        );
        /*!
            ensures
                - adjusts total_rect() so that it is big enough to contain the text
                  currently in this object.
        !*/

        dlib::ustring text_;
        rgb_pixel text_color_;
        rgb_pixel bg_color_;



        bool recent_movement;
        bool has_focus;
        bool cursor_visible;
        long cursor_pos;
        rectangle cursor_rect;

        // this tells you what part of the text is highlighted
        long highlight_start;
        long highlight_end;
        long shift_pos;
        any_function<void()> text_modified_handler;
        any_function<void()> enter_key_handler;
        any_function<void()> focus_lost_handler;

        std::unique_ptr<text_box_style> style;

        timer<text_box> t;

        popup_menu_region right_click_menu;

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


    protected:

        void draw (
            const canvas& c
        ) const;


        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_mouse_move (
            unsigned long state,
            long x,
            long y
        );

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

        void on_string_put (
            const std::wstring &str
        );
    };

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class check_box
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class check_box : public toggle_button 
    {
    public:
        check_box(  
            drawable_window& w
        ) : toggle_button(w)
        {
            set_style(toggle_button_style_check_box());
        }

    };

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class radio_button
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class radio_button : public toggle_button 
    {
    public:
        radio_button (  
            drawable_window& w
        ) : toggle_button(w)
        {
            set_style(toggle_button_style_radio_button());
        }

    };

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class tabbed_display
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class tabbed_display : public drawable
    {
        /*!
            INITIAL VALUE
                - tabs.size() == 0
                - selected_tab_ == 0

            CONVENTION
                - number_of_tabs() == tabs.size()
                - tab_name(idx) == tabs[idx]
                - if (tabs.size() > 0) then
                    - selected_tab_ == the index of the tab that is currently selected

                - for all valid i:
                    - tabs[i].width == mfont->compute_size(tabs[i].name)
                    - tabs[i].rect == the rectangle that defines where this tab is
                    - if (tabs[i].group != 0) then
                        - tabs[i].group == a pointer to the widget_group for this tab.

                - left_pad == the amount of padding in a tab to the left of the name string.
                - right_pad == the amount of padding in a tab to the right of the name string.
                - top_pad == the amount of padding in a tab to the top of the name string.
                - bottom_pad == the amount of padding in a tab to the bottom of the name string.

                - if (event_handler.is_set()) then
                    - event_handler() is what is called to process click events
                      on this object.
        !*/

    public:

        tabbed_display(  
            drawable_window& w
        );

        virtual ~tabbed_display(
        );

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

        void set_number_of_tabs (
            unsigned long num
        );

        unsigned long selected_tab (
        ) const;

        unsigned long number_of_tabs (
        ) const;

        const std::string tab_name (
            unsigned long idx
        ) const;

        const std::wstring tab_wname (
            unsigned long idx
        ) const;

        const dlib::ustring& tab_uname (
            unsigned long idx
        ) const;

        void set_tab_name (
            unsigned long idx,
            const std::string& new_name
        );

        void set_tab_name (
            unsigned long idx,
            const std::wstring& new_name
        );

        void set_tab_name (
            unsigned long idx,
            const dlib::ustring& new_name
        );

        void set_pos (
            long x,
            long y
        );

        template <
            typename T
            >
        void set_click_handler (
            T& object,
            void (T::*eh)(unsigned long new_idx,unsigned long old_idx)
        )
        {
            auto_mutex M(m);
            event_handler = make_mfp(object,eh);
        }

        void set_click_handler (
            const any_function<void(unsigned long,unsigned long)>& eh
        )
        {
            auto_mutex M(m);
            event_handler = eh;
        }

        void set_tab_group (
            unsigned long idx,
            widget_group& group
        );

        void show (
        );

        void hide (
        );

        void enable (
        );

        void disable (
        );

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

        void fit_to_contents (
        );

    protected:
        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 recompute_tabs (
        );
        /*!
            ensures
                - recomputes the rectangles for all the tabs and makes this object
                  wider if needed
        !*/

        void draw_tab (
            const rectangle& tab,
            const canvas& c
        ) const;
        /*!
            ensures
                - draws the outline of a tab as given by the rectangle onto c
        !*/

        struct tab_data
        {
            tab_data() : width(0), group(0) {}

            dlib::ustring name;
            unsigned long width;
            rectangle rect;
            widget_group* group;
        };

        unsigned long selected_tab_;

        array<tab_data> tabs;

        const long left_pad;
        const long right_pad;
        const long top_pad;
        const long bottom_pad;

        any_function<void(unsigned long,unsigned long)> event_handler;

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

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class named_rectangle
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class named_rectangle : public drawable 
    {
        /*!
            INITIAL VALUE
                name == ""

            CONVENTION
                name_ == name()
        !*/

    public:

        named_rectangle(  
            drawable_window& w
        );

        virtual ~named_rectangle(
        );

        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 wrap_around (
            const rectangle& rect
        );

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

    protected:

        void draw (
            const canvas& c
        ) const;

    private:

        void make_name_fit_in_rect (
        );

        dlib::ustring name_;
        unsigned long name_width;
        unsigned long name_height;

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

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class mouse_tracker
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class mouse_tracker : public draggable 
    {

    public:

        mouse_tracker(  
            drawable_window& w
        ); 

        ~mouse_tracker(
        );

        void show (
        );

        void hide (
        );

        void enable (
        );

        void disable (
        );

        void set_pos (
            long x,
            long y
        );

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

    protected:

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

        void on_drag (
        );

        void draw (
            const canvas& c
        ) const;

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


    private:

        const long offset;
        named_rectangle nr;
        label x_label;
        label y_label; 
        std::ostringstream sout;

        long click_x, click_y;

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

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // function message_box()  
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    namespace message_box_helper
    {
        class box_win : public drawable_window
        {
            void initialize (
            );
        public:
            box_win (
                const std::string& title_,
                const std::string& message_
            );

            box_win (
                const std::wstring& title_,
                const std::wstring& message_
            );

            box_win (
                const dlib::ustring& title_,
                const dlib::ustring& message_
            );

            ~box_win (
            );

            void set_click_handler (
                const any_function<void()>& event_handler_
            )
            {
                auto_mutex M(wm);
                event_handler = event_handler_;
            }

        private:

            static void deleter_thread (
                void* param
            );

            void on_click (
            );

            on_close_return_code on_window_close (
            );

            const std::wstring title;
            const std::wstring message;
            label msg;
            button btn_ok;

            any_function<void()> event_handler;
        };

        class blocking_box_win : public drawable_window
        {
            void initialize (
            );

        public:
            blocking_box_win (
                const std::string& title_,
                const std::string& message_
            );

            blocking_box_win (
                const std::wstring& title_,
                const std::wstring& message_
            );

            blocking_box_win (
                const dlib::ustring& title_,
                const dlib::ustring& message_
            );

            ~blocking_box_win (
            );

        private:

            void on_click (
            );

            const std::wstring title;
            const std::wstring message;
            label msg;
            button btn_ok;
        };
    }

    template <
        typename T
        >
    void message_box (
        const std::string& title,
        const std::string& message,
        T& object,
        void (T::*event_handler)() 
    )
    {
        using namespace message_box_helper;
        box_win* win = new box_win(title,message);
        win->set_click_handler(make_mfp(object,event_handler));
    }

    inline void message_box (
        const std::string& title,
        const std::string& message,
        const any_function<void()>& event_handler
    )
    {
        using namespace message_box_helper;
        box_win* win = new box_win(title,message);
        win->set_click_handler(event_handler);
    }

    inline void message_box (
        const std::string& title,
        const std::string& message
    )
    {
        using namespace message_box_helper;
        new box_win(title,message);
    }

    inline void message_box_blocking (
        const std::string& title,
        const std::string& message
    )
    {
        using namespace message_box_helper;
        blocking_box_win w(title,message);
        w.wait_until_closed();
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class list_box
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    namespace list_box_helper{
    template <typename S = std::string>
    class list_box : public scrollable_region, 
                     public enumerable<const S>
    {
        /*!
            INITIAL VALUE
                - ms_enabled == false
                - items.size() == 0
                - last_selected = 0

            CONVENTION
                - size() == items.size()
                - (*this)[i] == items[i].name
                - is_selected(i) == items[i].is_selected
                - ms_enabled == multiple_select_enabled()

                - items[i].width == the width of items[i].name as given by font::compute_size() 
                - items[i].height == the height of items[i].name as given by font::compute_size() 

                - last_selected == the last item the user selected
        !*/

    public:

        list_box(  
            drawable_window& w
        );

        ~list_box(
        );

        bool is_selected (
            unsigned long index
        ) const;

        void select (
            unsigned long index 
        );

        void unselect (
            unsigned long index 
        );

        template <
            typename style_type
            >
        void set_style (
            const style_type& style_
        )
        {
            auto_mutex M(m);
            style.reset(new style_type(style_));
            scrollable_region::set_style(style_.get_scrollable_region_style());
            parent.invalidate_rectangle(rect);
        }

        template <typename T>
        void get_selected (
            T& list
        ) const
        {
            auto_mutex M(m);
            list.clear();
            for (unsigned long i = 0; i < items.size(); ++i)
            {
                if (items[i].is_selected)
                {
                    unsigned long idx = i;
                    list.enqueue(idx);
                }
            }
        }

        template <typename T>
        void load (
            const T& list
        )
        {
            auto_mutex M(m);
            items.clear();
            unsigned long i = 0;
            items.set_max_size(list.size());
            items.set_size(list.size());
            list.reset();
            unsigned long max_width = 0;
            unsigned long total_height = 0;
            while (list.move_next())
            {
                items[i].is_selected = false;
                items[i].name = list.element();
                mfont->compute_size(items[i].name,items[i].width, items[i].height);

                if (items[i].width > max_width)
                    max_width = items[i].width;
                total_height += items[i].height;

                ++i;
            }
            set_total_rect_size(max_width, total_height);

            parent.invalidate_rectangle(rect);
            last_selected = 0;
        }

        const S& operator[] (
            unsigned long index
        ) const;

        bool multiple_select_enabled (
        ) const;

        void enable_multiple_select (
        ); 

        void disable_multiple_select (
        );

        template <
            typename T
            >
        void set_double_click_handler (
            T& object,
            void (T::*eh)(unsigned long index)
        ) { auto_mutex M(m); event_handler = make_mfp(object,eh); }

        void set_double_click_handler (
            const any_function<void(unsigned long)>& eh
        ) { auto_mutex M(m); event_handler = eh; }

        template <
            typename T
            >
        void set_click_handler (
            T& object,
            void (T::*eh)(unsigned long index)
        ) { auto_mutex M(m); single_click_event_handler = make_mfp(object,eh); }

        void set_click_handler (
            const any_function<void(unsigned long)>& eh
        ) { auto_mutex M(m); single_click_event_handler = eh; }

        bool at_start (
        ) const;

        void reset (
        ) const;

        bool current_element_valid (
        ) const;

        const S& element (
        ) const;

        const S& element (
        );

        bool move_next (
        ) const;

        unsigned long size (
        ) const;

        unsigned long get_selected (
        ) const;

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

    private:

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

        void draw (
            const canvas& c
        ) const;

        template <typename SS>
        struct data
        {
            SS name;
            bool is_selected;
            unsigned long width;
            unsigned long height;
        };

        bool ms_enabled;
        array<data<S> > items;
        any_function<void(unsigned long)> event_handler;
        any_function<void(unsigned long)> single_click_event_handler;
        unsigned long last_selected;

        std::unique_ptr<list_box_style> style;

        // restricted functions
        list_box(list_box&);        // copy constructor
        list_box& operator=(list_box&);    // assignment operator
    };
    }
    typedef list_box_helper::list_box<std::string> list_box;
    typedef list_box_helper::list_box<std::wstring> wlist_box;
    typedef list_box_helper::list_box<dlib::ustring> ulist_box;

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // function open_file_box() 
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    namespace open_file_box_helper
    {
        class box_win : public drawable_window
        {
        public:
            box_win (
                const std::string& title,
                bool has_text_field = false
            );

            ~box_win (
            );

            void set_click_handler (
                const any_function<void(const std::string&)>& event_handler_
            )
            {
                auto_mutex M(wm);
                event_handler = event_handler_;
            }

        private:

            void set_sizes(
            );

            void on_window_resized (
            );

            void deleter_thread (
            );

            void enter_folder (
                const std::string& folder_name
            );

            void on_dirs_click (
                unsigned long idx
            );

            void on_files_click (
                unsigned long idx
            );

            void on_files_double_click (
                unsigned long 
            );

            void on_cancel_click (
            );

            void on_open_click (
            );

            void on_path_button_click (
                toggle_button& btn
            );

            bool set_dir (
                const std::string& dir
            );

            void on_root_click (
            );

            on_close_return_code on_window_close (
            );

            label lbl_dirs;
            label lbl_files;
            label lbl_file_name;
            list_box lb_dirs;
            list_box lb_files;
            button btn_ok;
            button btn_cancel;
            toggle_button btn_root;
            text_field tf_file_name;
            std::string path;
            std::string prefix;
            int cur_dir;

            any_function<void(const std::string&)> event_handler;
            sequence<std::unique_ptr<toggle_button> >::kernel_2a_c sob;
        };
    }

    template <
        typename T
        >
    void open_file_box (
        T& object,
        void (T::*event_handler)(const std::string&) 
    )
    {
        using namespace open_file_box_helper;
        box_win* win = new box_win("Open File",true);
        win->set_click_handler(make_mfp(object,event_handler));
    }

    inline void open_file_box (
        const any_function<void(const std::string&)>& event_handler
    )
    {
        using namespace open_file_box_helper;
        box_win* win = new box_win("Open File",true);
        win->set_click_handler(event_handler);
    }

    template <
        typename T
        >
    void open_existing_file_box (
        T& object,
        void (T::*event_handler)(const std::string&) 
    )
    {
        using namespace open_file_box_helper;
        box_win* win = new box_win("Open File");
        win->set_click_handler(make_mfp(object,event_handler));
    }

    inline void open_existing_file_box (
        const any_function<void(const std::string&)>& event_handler
    )
    {
        using namespace open_file_box_helper;
        box_win* win = new box_win("Open File");
        win->set_click_handler(event_handler);
    }

    template <
        typename T
        >
    void save_file_box (
        T& object,
        void (T::*event_handler)(const std::string&) 
    )
    {
        using namespace open_file_box_helper;
        box_win* win = new box_win("Save File",true);
        win->set_click_handler(make_mfp(object,event_handler));
    }

    inline void save_file_box (
        const any_function<void(const std::string&)>& event_handler
    )
    {
        using namespace open_file_box_helper;
        box_win* win = new box_win("Save File",true);
        win->set_click_handler(event_handler);
    }

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class menu_bar
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class menu_bar : public drawable
    {
        /*!
            INITIAL VALUE
                - menus.size() == 0
                - open_menu == 0 

            CONVENTION 
                - size() == menus.size() 
                - all menu data is stored in menus
                - menus[x].name == the name of the xth menu
                - if (menus[x].underline_pos != std::string::npos) then
                    - menus[x].underline_pos == the position of the character in the
                      menu name that should be underlined
                    - menus[x].underline_p1 != menus[x].underline_p2
                      and these two points define the underline bar
                - else
                    - menus[x].underline_p1 == menus[x].underline_p2
                - menus[x].menu == menu(x)
                - menus[x].rect == the rectangle in which menus[x].name is drawn
                - menus[x].bgrect == the rectangle for the xth menu button

                - if (there is an open menu on the screen) then
                    - open_menu == the index of the open menu from menus 
                - else
                    - open_menu == menus.size() 
        !*/

    public:
        menu_bar(
            drawable_window& w
        );

        ~menu_bar();

        // this function does nothing
        void set_pos(long,long){}

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

        void set_number_of_menus (
            unsigned long num
        );

        unsigned long number_of_menus (
        ) const;

        void set_menu_name (
            unsigned long idx,
            const std::string name,
            char underline_ch = '\0'
        );

        void set_menu_name (
            unsigned long idx,
            const std::wstring name,
            char underline_ch = '\0'
        );

        void set_menu_name (
            unsigned long idx,
            const dlib::ustring name,
            char underline_ch = '\0'
        );

        const std::string menu_name (
            unsigned long idx
        ) const;

        const std::wstring menu_wname (
            unsigned long idx
        ) const;

        const dlib::ustring menu_uname (
            unsigned long idx
        ) const;

        popup_menu& menu (
            unsigned long idx
        );

        const popup_menu& menu (
            unsigned long idx
        ) const;

    protected:

        void on_window_resized (
        );

        void draw (
            const canvas& c
        ) const;

        void on_window_moved (
        );

        void on_focus_lost (
        );

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

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

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

    private:

        void show_menu (
            unsigned long i
        );

        void hide_menu (
        );

        void on_popup_hide (
        );

        void compute_menu_geometry (
        );

        void adjust_position (
        );

        struct menu_data
        {
            menu_data():underline_pos(dlib::ustring::npos){}

            dlib::ustring name;
            dlib::ustring::size_type underline_pos;
            popup_menu menu;
            rectangle rect;
            rectangle bgrect;
            point underline_p1;
            point underline_p2;
        };

        array<menu_data> menus;
        unsigned long open_menu;

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

    };

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class directed_graph_drawer
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    template <typename graph_type>
    class directed_graph_drawer : public zoomable_region 
    {
        /*!
            INITIAL VALUE
                - edge_selected == false
                - mouse_drag == false
                - selected_node == 0
                - graph_.number_of_nodes() == 0
                - external_graph.number_of_nodes() == 0
                - radius == 25
                - last_mouse_click_in_display == false

            CONVENTION
                - radius == the radius of the nodes when they aren't zoomed
                - external_graph and graph_ have the same graph structure
                - external_graph == graph()
                - external_graph.node(i) == graph_node(i)

                - if (one of the nodes is selected) then
                    - selected_node < graph_.number_of_nodes()
                    - graph_.node(selected_node) == the selected node
                - else
                    - selected_node == graph_.number_of_nodes()

                - if (the user is dragging a node with the mouse) then
                    - mouse_drag == true
                    - drag_offset == the vector from the mouse position to the
                      center of the node
                - else
                    - mouse_drag == false

                - if (the user has selected an edge) then
                    - edge_selected == true
                    - the parent node is graph_.node(selected_edge_parent)
                    - the child node is graph_.node(selected_edge_parent)
                - else
                    - edge_selected == false

                - for all valid i:
                    - graph_.node(i).data.p == the center of the node in graph space
                    - graph_.node(i).data.name == node_label(i) 
                    - graph_.node(i).data.color == node_color(i) 
                    - graph_.node(i).data.str_rect == a rectangle sized to contain graph_.node(i).data.name

                - if (the last mouse click in our parent window as in our display_rect_ ) then
                    - last_mouse_click_in_display == true
                - else
                    - last_mouse_click_in_display == false
        !*/

    public:
        directed_graph_drawer (
            drawable_window& w
        ) :
            zoomable_region(w,MOUSE_CLICK | MOUSE_WHEEL | KEYBOARD_EVENTS),
            radius(25),
            edge_selected(false),
            last_mouse_click_in_display(false)
        {
            mouse_drag = false;
            selected_node = 0;

            // Whenever you make your own drawable (or inherit from draggable or button_action)
            // you have to remember to call this function to enable the events.  The idea
            // here is that you can perform whatever setup you need to do to get your 
            // object into a valid state without needing to worry about event handlers 
            // triggering before you are ready.
            enable_events();
        }

        ~directed_graph_drawer (
        )
        {
            // Disable all further events for this drawable object.  We have to do this 
            // because we don't want draw() events coming to this object while or after 
            // it has been destructed.
            disable_events();

            // Tell the parent window to redraw its area that previously contained this
            // drawable object.
            parent.invalidate_rectangle(rect);
        }

        void clear_graph (
        )
        {
            auto_mutex M(m);
            graph_.clear();
            external_graph.clear();
            parent.invalidate_rectangle(display_rect());
        }

        const typename graph_type::node_type& graph_node (
            unsigned long i
        )  const
        {
            DLIB_ASSERT ( i < number_of_nodes() ,
                    "\tgraph_type::node_type& directed_graph_drawer::graph_node(i)"
                    << "\n\ti:                 " << i 
                    << "\n\tnumber_of_nodes(): " << number_of_nodes() 
                    );
            return external_graph.node(i);
        }

        typename graph_type::node_type& graph_node (
            unsigned long i
        ) 
        {
            DLIB_ASSERT ( i < number_of_nodes() ,
                    "\tgraph_type::node_type& directed_graph_drawer::graph_node(i)"
                    << "\n\ti:                 " << i 
                    << "\n\tnumber_of_nodes(): " << number_of_nodes() 
                    );
            return external_graph.node(i);
        }

        const graph_type& graph (
        ) const
        {
            return external_graph;
        }

        void save_graph (
            std::ostream& out
        )
        {
            auto_mutex M(m);
            serialize(external_graph, out);
            serialize(graph_, out);
            parent.invalidate_rectangle(display_rect());
        }

        void load_graph (
            std::istream& in 
        )
        {
            auto_mutex M(m);
            deserialize(external_graph, in);
            deserialize(graph_, in);
            parent.invalidate_rectangle(display_rect());
        }

        unsigned long number_of_nodes (
        ) const
        {
            auto_mutex M(m);
            return graph_.number_of_nodes();
        }

        void set_node_label (
            unsigned long i,
            const std::string& label
        )
        {
            set_node_label(i, convert_mbstring_to_wstring(label));
        }

        void set_node_label (
            unsigned long i,
            const std::wstring& label
        )
        {
            set_node_label(i, convert_wstring_to_utf32(label));
        }

        void set_node_label (
            unsigned long i,
            const dlib::ustring& label
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( i < number_of_nodes() ,
                    "\tvoid directed_graph_drawer::set_node_label(i,label)"
                    << "\n\ti:                 " << i 
                    << "\n\tlabel:             " << narrow(label) 
                    << "\n\tnumber_of_nodes(): " << number_of_nodes() 
                    );
            graph_.node(i).data.name = label.c_str();
            unsigned long width, height;
            mfont->compute_size(label,width,height);
            graph_.node(i).data.str_rect = rectangle(width,height);
            parent.invalidate_rectangle(display_rect());
        }

        void set_node_color (
            unsigned long i,
            rgb_pixel color
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( i < number_of_nodes() ,
                    "\tvoid directed_graph_drawer::set_node_color(i,label)"
                    << "\n\ti:                 " << i 
                    << "\n\tnumber_of_nodes(): " << number_of_nodes() 
                    );
            graph_.node(i).data.color = color;
            parent.invalidate_rectangle(display_rect());
        }

        rgb_pixel node_color (
            unsigned long i
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( i < number_of_nodes() ,
                    "\trgb_pixel directed_graph_drawer::node_color(i)"
                    << "\n\ti:                 " << i 
                    << "\n\tnumber_of_nodes(): " << number_of_nodes() 
                    );
            return graph_.node(i).data.color;
        }

        const std::string node_label (
            unsigned long i
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( i < number_of_nodes() ,
                    "\tconst std::ustring directed_graph_drawer::node_label(i)"
                    << "\n\ti:                 " << i 
                    << "\n\tnumber_of_nodes(): " << number_of_nodes() 
                    );
            return narrow(graph_.node(i).data.name);
        }

        const std::wstring node_wlabel (
            unsigned long i
        ) const
        {
            return convert_utf32_to_wstring(node_ulabel(i));
        }

        const dlib::ustring node_ulabel (
            unsigned long i
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( i < number_of_nodes() ,
                    "\tconst std::ustring directed_graph_drawer::node_label(i)"
                    << "\n\ti:                 " << i 
                    << "\n\tnumber_of_nodes(): " << number_of_nodes() 
                    );
            return graph_.node(i).data.name.c_str();
        }

        template <
            typename T
            >
        void set_node_selected_handler (
            T& object,
            void (T::*event_handler_)(unsigned long)
        )
        {
            auto_mutex M(m);
            node_selected_handler = make_mfp(object,event_handler_);
        }

        void set_node_selected_handler (
            const any_function<void(unsigned long)>& event_handler_
        )
        {
            auto_mutex M(m);
            node_selected_handler = event_handler_;
        }

        template <
            typename T
            >
        void set_node_deselected_handler (
            T& object,
            void (T::*event_handler_)(unsigned long)
        )
        {
            auto_mutex M(m);
            node_deselected_handler = make_mfp(object,event_handler_);
        }

        void set_node_deselected_handler (
            const any_function<void(unsigned long)>& event_handler_
        )
        {
            auto_mutex M(m);
            node_deselected_handler = event_handler_;
        }

        template <
            typename T
            >
        void set_node_deleted_handler (
            T& object,
            void (T::*event_handler_)()
        )
        {
            auto_mutex M(m);
            node_deleted_handler = make_mfp(object,event_handler_);
        }

        void set_node_deleted_handler (
            const any_function<void()>& event_handler_
        )
        {
            auto_mutex M(m);
            node_deleted_handler = event_handler_;
        }

        template <
            typename T
            >
        void set_graph_modified_handler (
            T& object,
            void (T::*event_handler_)()
        )
        {
            auto_mutex M(m);
            graph_modified_handler = make_mfp(object,event_handler_);
        }

        void set_graph_modified_handler (
            const any_function<void()>& event_handler_
        )
        {
            auto_mutex M(m);
            graph_modified_handler = event_handler_;
        }

    protected:

        void on_keydown (
            unsigned long key,          
            bool ,
            unsigned long 
        )
        {
            // ignore all keyboard input if the last thing the user clicked on 
            // wasn't the display area
            if (last_mouse_click_in_display == false)
                return;

            // if a node is selected
            if (selected_node != graph_.number_of_nodes())
            {
                // deselect the node if the user hits escape
                if (key == base_window::KEY_ESC)
                {
                    parent.invalidate_rectangle(display_rect());
                    if (node_deselected_handler.is_set())
                        node_deselected_handler(selected_node);
                    selected_node = graph_.number_of_nodes();
                }

                // delete the node if the user hits delete 
                if (key == base_window::KEY_DELETE || key == base_window::KEY_BACKSPACE)
                {
                    parent.invalidate_rectangle(display_rect());
                    graph_.remove_node(selected_node);
                    external_graph.remove_node(selected_node);
                    selected_node = graph_.number_of_nodes();
                    mouse_drag = false;
                    if (graph_modified_handler.is_set())
                        graph_modified_handler();
                    if (node_deleted_handler.is_set())
                        node_deleted_handler();
                }
            }

            // if an edge is selected
            if (edge_selected)
            {
                // deselect the node if the user hits escape
                if (key == base_window::KEY_ESC)
                {
                    parent.invalidate_rectangle(display_rect());
                    edge_selected = false;
                }

                // delete the node if the user hits delete 
                if (key == base_window::KEY_DELETE || key == base_window::KEY_BACKSPACE)
                {
                    parent.invalidate_rectangle(display_rect());
                    graph_.remove_edge(selected_edge_parent, selected_edge_child);
                    external_graph.remove_edge(selected_edge_parent, selected_edge_child);
                    edge_selected = false;

                    if (graph_modified_handler.is_set())
                        graph_modified_handler();
                }
            }
        }


        void on_mouse_move (
            unsigned long state,
            long x,
            long y
        )
        {
            if (mouse_drag)
            {
                const point p(nearest_point(display_rect(),point(x,y)));

                point center = drag_offset + p;
                graph_.node(selected_node).data.p = gui_to_graph_space(center);
                parent.invalidate_rectangle(display_rect());
            }
            else
            {
                zoomable_region::on_mouse_move(state,x,y);
            }

            // check if the mouse isn't being dragged anymore
            if ((state & base_window::LEFT) == 0)
            {
                mouse_drag = false;
            }
        }

        void on_mouse_up (
            unsigned long btn,
            unsigned long state,
            long x,
            long y
        )
        {
            mouse_drag = false;
            zoomable_region::on_mouse_up(btn,state,x,y);
        }

        void on_mouse_down (
            unsigned long btn,
            unsigned long state,
            long x,
            long y,
            bool is_double_click
        )
        {
            bool redraw = false;

            if (display_rect().contains(x,y) && 
                (btn == base_window::RIGHT || btn == base_window::LEFT) && 
                (state & base_window::SHIFT) == 0 )
            {
                // start out saying no edge is selected
                if (edge_selected)
                {
                    edge_selected = false;
                    redraw = true;
                }

                bool click_hit_node = false;
                dlib::vector<double,2> p(gui_to_graph_space(point(x,y)));
                // check if this click is on an existing node
                for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
                {
                    dlib::vector<double,2> n(graph_.node(i).data.p);
                    if ((p-n).length() < radius)
                    {
                        click_hit_node = true;
                        point center = graph_to_gui_space(graph_.node(i).data.p);
                        mouse_drag = true;
                        drag_offset = center - point(x,y);

                        // only do something if the click isn't on the currently
                        // selected node
                        if (selected_node != i)
                        {
                            // send out the deselected event if appropriate
                            if (selected_node != graph_.number_of_nodes() && node_deselected_handler.is_set())
                                node_deselected_handler(selected_node);

                            selected_node = i;
                            redraw = true;
                            if (node_selected_handler.is_set())
                                node_selected_handler(selected_node);
                        }
                        break;
                    }
                }

                // if the click didn't hit any node then make sure nothing is selected
                if (click_hit_node == false && selected_node != graph_.number_of_nodes())
                {
                    if (node_deselected_handler.is_set())
                        node_deselected_handler(selected_node);
                    selected_node = graph_.number_of_nodes();
                    redraw = true;
                }


                // check if this click is on an edge if we didn't click on a node
                if (click_hit_node == false)
                {
                    for (unsigned long n = 0; n < graph_.number_of_nodes() && edge_selected == false; ++n)
                    {
                        const dlib::vector<double,2> parent_center(graph_to_gui_space(graph_.node(n).data.p));
                        for (unsigned long e = 0; e < graph_.node(n).number_of_children() && edge_selected == false; ++e)
                        {
                            const dlib::vector<double,2> child_center(graph_to_gui_space(graph_.node(n).child(e).data.p));

                            rectangle area;
                            area += parent_center;
                            area += child_center;
                            // if the point(x,y) is between the two nodes then lets consider it further
                            if (area.contains(point(x,y)))
                            {
                                p = point(x,y);
                                const dlib::vector<double> z(0,0,1);
                                // find the distance from the line between the two nodes
                                const dlib::vector<double,2> perpendicular(z.cross(parent_center-child_center).normalize());
                                double distance = std::abs((child_center-p).dot(perpendicular));
                                if (distance < 8)
                                {
                                    edge_selected = true;
                                    selected_edge_parent = n;
                                    selected_edge_child = graph_.node(n).child(e).index();
                                    redraw = true;
                                }
                            }
                        }
                    }
                }


                // if the click didn't land on any node then add a new one if this was
                // a right mouse button click
                if (click_hit_node == false && btn == base_window::RIGHT)
                {
                    const unsigned long n = graph_.add_node();
                    external_graph.add_node();

                    graph_.node(n).data.p = gui_to_graph_space(point(x,y));

                    redraw = true;
                    selected_node = n;
                    mouse_drag = false;
                    if (graph_modified_handler.is_set())
                        graph_modified_handler();

                    if (node_selected_handler.is_set())
                        node_selected_handler(selected_node);

                }
                else if (selected_node == graph_.number_of_nodes())
                {
                    // in this case the click landed in the white area between nodes
                    zoomable_region::on_mouse_down( btn, state, x, y, is_double_click);
                }
            }

            // If the user is shift clicking with the mouse then see if we
            // should add a new edge.
            if (display_rect().contains(x,y) && 
                btn == base_window::LEFT && 
                (state & base_window::SHIFT) && 
                selected_node != graph_.number_of_nodes() )
            {
                dlib::vector<double,2> p(gui_to_graph_space(point(x,y)));
                // check if this click is on an existing node
                for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
                {
                    dlib::vector<double,2> n(graph_.node(i).data.p);
                    if ((p-n).length() < radius)
                    {
                        // add the edge if it doesn't already exist and isn't an edge back to 
                        // the same node
                        if (graph_.has_edge(selected_node,i) == false && selected_node != i &&
                            graph_.has_edge(i, selected_node) == false)
                        {
                            graph_.add_edge(selected_node,i);
                            external_graph.add_edge(selected_node,i);
                            redraw = true;

                            if (graph_modified_handler.is_set())
                                graph_modified_handler();
                        }
                        break;
                    }
                }
            }


            if (redraw)
                parent.invalidate_rectangle(display_rect());


            if (display_rect().contains(x,y) == false)
                last_mouse_click_in_display = false;
            else
                last_mouse_click_in_display = true;
        }

        void draw (
            const canvas& c
        ) const
        {
            zoomable_region::draw(c);

            rectangle area = c.intersect(display_rect());
            if (area.is_empty() == true)
                return;


            if (enabled)
                fill_rect(c,display_rect(),255);
            else
                fill_rect(c,display_rect(),128);


            const unsigned long rad = static_cast<unsigned long>(radius*zoom_scale());
            point center;


            // first draw all the edges
            for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
            {
                center = graph_to_gui_space(graph_.node(i).data.p);
                const rectangle circle_area(centered_rect(center,2*(rad+8),2*(rad+8)));

                // draw lines to all this node's parents 
                const dlib::vector<double> z(0,0,1);
                for (unsigned long j = 0; j < graph_.node(i).number_of_parents(); ++j)
                {
                    point p(graph_to_gui_space(graph_.node(i).parent(j).data.p));

                    rgb_pixel color(0,0,0);
                    // if this is the selected edge then draw it with red instead of black
                    if (edge_selected && selected_edge_child == i && selected_edge_parent == graph_.node(i).parent(j).index())
                    {
                        color.red = 255;
                        // we need to be careful when drawing this line to not draw it over the node dots since it 
                        // has a different color from them and would look weird
                        dlib::vector<double,2> v(p-center);
                        v = v.normalize()*rad;
                        draw_line(c,center+v,p-v ,color, area);
                    }
                    else
                    {
                        draw_line(c,center,p ,color, area);
                    }


                    // draw the triangle pointing to this node
                    if (area.intersect(circle_area).is_empty() == false)
                    {
                        dlib::vector<double,2> v(p-center);
                        v = v.normalize();

                        dlib::vector<double,2> cross = z.cross(v).normalize();
                        dlib::vector<double,2> r(center + v*rad);
                        for (double i = 0; i < 8*zoom_scale(); i += 0.1)
                            draw_line(c,(r+v*i)+cross*i, (r+v*i)-cross*i,color,area);
                    }
                }
            }


            // now draw all the node dots
            for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
            {
                center = graph_to_gui_space(graph_.node(i).data.p);
                const rectangle circle_area(centered_rect(center,2*(rad+8),2*(rad+8)));

                // draw the actual dot for this node
                if (area.intersect(circle_area).is_empty()==false)
                {
                    rgb_alpha_pixel color;
                    assign_pixel(color, graph_.node(i).data.color);
                    // this node is in area so lets draw it and all of it's edges as well
                    draw_solid_circle(c,center,rad-3,color,area);
                    color.alpha = 240;
                    draw_circle(c,center,rad-3,color,area);
                    color.alpha = 200;
                    draw_circle(c,center,rad-2.5,color,area);
                    color.alpha = 160;
                    draw_circle(c,center,rad-2.0,color,area);
                    color.alpha = 120;
                    draw_circle(c,center,rad-1.5,color,area);
                    color.alpha = 80;
                    draw_circle(c,center,rad-1.0,color,area);
                    color.alpha = 40;
                    draw_circle(c,center,rad-0.5,color,area);

                }


                if (i == selected_node)
                    draw_circle(c,center,rad+5,rgb_pixel(0,0,255),area);
            }


            // now draw all the strings last
            for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
            {
                center = graph_to_gui_space(graph_.node(i).data.p);
                rectangle circle_area(centered_rect(center,2*rad+3,2*rad+3));
                if (area.intersect(circle_area).is_empty()==false)
                {
                    rgb_pixel color = graph_.node(i).data.color;
                    // invert this color
                    color.red = 255-color.red;
                    color.green = 255-color.green;
                    color.blue = 255-color.blue;
                    sout << i;
                    unsigned long width, height;
                    mfont->compute_size(sout.str(),width,height);
                    rectangle str_rect(centered_rect(center, width,height));
                    if (circle_area.contains(str_rect))
                    {
                        mfont->draw_string(c,str_rect,sout.str(),color,0,std::string::npos,area);

                        // draw the label for this node if it isn't empty
                        if(graph_.node(i).data.name.size() > 0)
                        {
                            rectangle str_rect(graph_.node(i).data.str_rect);
                            str_rect = centered_rect(center.x(), center.y()-rad-mfont->height(),  str_rect.width(), str_rect.height());
                            mfont->draw_string(c,str_rect,graph_.node(i).data.name,0,0,std::string::npos,area);
                        }
                    }
                    sout.str("");
                }
            }
        }

    private:

        struct data
        {
            data() : color(0,0,0) {}
            vector<double> p;
            dlib::ustring name;
            rectangle str_rect;
            rgb_pixel color;
        };

        friend void serialize(const data& item, std::ostream& out)
        {
            serialize(item.p, out);
            serialize(item.name, out);
            serialize(item.str_rect, out);
            serialize(item.color, out);
        }

        friend void deserialize(data& item, std::istream& in)
        {
            deserialize(item.p, in);
            deserialize(item.name, in);
            deserialize(item.str_rect, in);
            deserialize(item.color, in);
        }

        mutable std::ostringstream sout;

        const double radius;
        unsigned long selected_node;
        bool mouse_drag; // true if the user is dragging a node
        point drag_offset; 

        bool edge_selected;
        unsigned long selected_edge_parent;
        unsigned long selected_edge_child;

        any_function<void(unsigned long)> node_selected_handler;
        any_function<void(unsigned long)> node_deselected_handler;
        any_function<void()> node_deleted_handler;
        any_function<void()> graph_modified_handler;

        graph_type external_graph;
        // rebind the graph_ type to make us a graph_ of data structs
        typename graph_type::template rebind<data,char, typename graph_type::mem_manager_type>::other graph_;

        bool last_mouse_click_in_display;
    };

// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
    // class text_grid
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------

    class text_grid : public scrollable_region 
    {
        /*!
            INITIAL VALUE
                - has_focus == false
                - vertical_scroll_increment() == 10
                - horizontal_scroll_increment() == 10
                - border_color_ == rgb_pixel(128,128,128)

            CONVENTION
                - grid.nr() == row_height.size()
                - grid.nc() == col_width.size()
                - border_color() == border_color_
                - text(r,c) == grid[r][c].text
                - text_color(r,c) == grid[r][c].text_color
                - background_color(r,c) == grid[r][c].bg_color

                - if (the user has clicked on this widget and caused one of the
                    boxes to have input focus) then
                    - has_focus == true
                    - grid[active_row][active_col] == the active text box
                    - cursor_pos == the position of the cursor in the above box
                    - if (the cursor should be displayed) then
                        - show_cursor == true
                    - else
                        - show_cursor == false
                - else
                    - has_focus == false
        !*/

    public:
        text_grid (
            drawable_window& w
        ); 

        ~text_grid (
        );

        void set_grid_size (
            unsigned long rows,
            unsigned long cols
        );

        unsigned long number_of_columns (
        ) const;

        unsigned long number_of_rows (
        ) const;

        int next_free_user_event_number (
        ) const;

        rgb_pixel border_color (
        ) const;

        void set_border_color (
            rgb_pixel color
        );

        const std::string text (
            unsigned long row,
            unsigned long col
        ) const;

        const std::wstring wtext (
            unsigned long row,
            unsigned long col
        ) const;

        const dlib::ustring utext (
            unsigned long row,
            unsigned long col
        ) const;

        void set_text (
            unsigned long row,
            unsigned long col,
            const std::string& str
        );

        void set_text (
            unsigned long row,
            unsigned long col,
            const std::wstring& str
        );

        void set_text (
            unsigned long row,
            unsigned long col,
            const dlib::ustring& str
        );

        const rgb_pixel text_color (
            unsigned long row,
            unsigned long col
        ) const;

        void set_text_color (
            unsigned long row,
            unsigned long col,
            const rgb_pixel color
        );

        const rgb_pixel background_color (
            unsigned long row,
            unsigned long col
        ) const;

        void set_background_color (
            unsigned long row,
            unsigned long col,
            const rgb_pixel color
        );

        bool is_editable (
            unsigned long row,
            unsigned long col
        ) const;

        void set_editable (
            unsigned long row,
            unsigned long col,
            bool editable
        );

        void set_column_width (
            unsigned long col,
            unsigned long width
        );

        void set_row_height (
            unsigned long row,
            unsigned long height 
        );

        void disable (
        );

        void hide (
        );

        template <
            typename T
            >
        void set_text_modified_handler (
            T& object,
            void (T::*eh)(unsigned long, unsigned long)
        ) { text_modified_handler = make_mfp(object,eh); }

        void set_text_modified_handler (
            const any_function<void(unsigned long, unsigned long)>& eh
        ) { text_modified_handler = eh; }

    private:

        void on_user_event (
            int num
        );

        void timer_action (
        ); 
        /*!
            ensures
                - flips the state of show_cursor 
        !*/

        void compute_bg_rects (
        );

        void compute_total_rect (
        );

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

        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_focus_lost (
        );

        void draw (
            const canvas& c
        ) const;

        rectangle get_text_rect (
            unsigned long row,
            unsigned long col
        ) const;

        rectangle get_bg_rect (
            unsigned long row,
            unsigned long col
        ) const;

        struct data_type
        {
            data_type(): text_color(0,0,0), bg_color(255,255,255),
            first(0), is_editable(true) 
            {}

            dlib::ustring text;
            rgb_pixel text_color;
            rgb_pixel bg_color;
            rectangle bg_rect;
            dlib::ustring::size_type first;
            bool is_editable;
        };

        void drop_input_focus (
        );

        void move_cursor (
            long row,
            long col,
            long new_cursor_pos
        );

        array2d<data_type> grid;
        array<unsigned long> col_width;
        array<unsigned long> row_height;
        bool has_focus;
        long active_col;
        long active_row;
        long cursor_pos;
        bool show_cursor;
        bool recent_cursor_move;
        timer<text_grid> cursor_timer;
        rgb_pixel border_color_;
        any_function<void(unsigned long, unsigned long)> text_modified_handler;
    };

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

    class image_display : public scrollable_region 
    {
        /*!
            INITIAL VALUE
                - img.size() == 0
                - overlay_rects.size() == 0
                - overlay_lines.size() == 0
                - drawing_rect == false
                - rect_is_selected == false

            CONVENTION
                - img == the image this object displays
                - overlay_rects == the overlay rectangles this object displays
                - overlay_lines == the overlay lines this object displays

                - if (drawing_rect) then
                    - the user is drawing a rectangle on the screen and is
                      thus holding down CTRL and the left mouse button.
                    - rect_anchor == the point on the screen where the user
                      clicked to begin drawing the rectangle.  
                    - rect_to_draw == the rectangle which should appear on the screen.

                - if (rect_is_selected) then
                    - selected_rect == the index in overlay_rects of the user selected
                      rectangle.
                    - last_right_click_pos == the last place we saw the user right click
                      the mouse.
                    - parts_menu.is_enabled() == true
                    - if (it is actually a part of this rect that is selected) then
                        - selected_part_name == the name of the part in overlay_rects[selected_rect].parts
                          that is selected.
                    - else
                        - selected_part_name.size() == 0
                - else
                    - parts_menu.is_enabled() == false
                    - selected_part_name.size() == 0

                - if (moving_overlay) then
                    - moving_rect == the index in overlay_rects that the move applies to.  
                    - if (moving_what == MOVING_PART) then
                        - moving_part_name == the name of the part in
                          overlay_rects[moving_rect] that is being moved around with the
                          mouse.
                    - else
                        - moving_what will tell us which side of the rectangle in
                          overlay_rects[moving_rect] is being moved by the mouse.
        !*/

    public:

        image_display(  
            drawable_window& w
        );

        ~image_display(
        );

        template <
            typename image_type
            >
        void set_image (
            const image_type& new_img
        )
        {
            auto_mutex M(m);

            // if the new image has a different size when compared to the previous image
            // then we should readjust the total rectangle size.
            if (num_rows(new_img) != img.nr() || num_columns(new_img) != img.nc())
            {
                if (zoom_in_scale != 1)
                    set_total_rect_size(num_columns(new_img)*zoom_in_scale, num_rows(new_img)*zoom_in_scale);
                else
                    set_total_rect_size(num_columns(new_img)/zoom_out_scale, num_rows(new_img)/zoom_out_scale);
            }
            else
            {
                parent.invalidate_rectangle(rect);
            }

            highlighted_rect = std::numeric_limits<unsigned long>::max();
            rect_is_selected = false;
            parts_menu.disable();
            assign_image_scaled(img,new_img);
        }

        virtual void set_pos (
            long x,
            long y
        )
        {
            auto_mutex lock(m);
            scrollable_region::set_pos(x,y);
            parts_menu.set_rect(rect);
        }

        virtual void set_size (
            unsigned long width,
            unsigned long height 
        )
        {
            auto_mutex lock(m);
            scrollable_region::set_size(width,height);
            parts_menu.set_rect(rect);
        }

        struct overlay_rect
        {
            overlay_rect() :crossed_out(false) { assign_pixel(color, 0);}

            template <typename pixel_type>
            overlay_rect(const rectangle& r, pixel_type p) 
                : rect(r),crossed_out(false) { assign_pixel(color, p); }

            template <typename pixel_type>
            overlay_rect(const rectangle& r, pixel_type p, const std::string& l) 
                : rect(r),label(l),crossed_out(false) { assign_pixel(color, p); }

            template <typename pixel_type>
            overlay_rect(const rectangle& r, pixel_type p, const std::string& l, const std::map<std::string,point>& parts_) 
                : rect(r),label(l),parts(parts_),crossed_out(false) { assign_pixel(color, p); }

            rectangle rect;
            rgb_alpha_pixel color;
            std::string label;
            std::map<std::string,point> parts;
            bool crossed_out;
        };

        struct overlay_line
        {
            overlay_line() { assign_pixel(color, 0);}

            template <typename pixel_type>
            overlay_line(const point& p1_, const point& p2_, pixel_type p) 
                : p1(p1_), p2(p2_) { assign_pixel(color, p); }

            point p1;
            point p2;
            rgb_alpha_pixel color;
        };

        struct overlay_circle
        {
            overlay_circle():radius(0) { assign_pixel(color, 0);}

            template <typename pixel_type>
            overlay_circle(const point& center_, const int radius_, pixel_type p) 
                : center(center_), radius(radius_) { assign_pixel(color, p); }

            template <typename pixel_type>
            overlay_circle(const point& center_, const int radius_, pixel_type p, const std::string& l) 
                : center(center_), radius(radius_), label(l) { assign_pixel(color, p); }

            point center;
            int radius;
            rgb_alpha_pixel color;
            std::string label;
        };

        void add_overlay (
            const overlay_rect& overlay
        );

        void add_overlay (
            const overlay_line& overlay
        );

        void add_overlay (
            const overlay_circle& overlay
        );

        void add_overlay (
            const std::vector<overlay_rect>& overlay
        );

        void add_overlay (
            const std::vector<overlay_line>& overlay
        );

        void add_overlay (
            const std::vector<overlay_circle>& overlay
        );

        void clear_overlay (
        );

        rectangle get_image_display_rect (
        ) const;

        std::vector<overlay_rect> get_overlay_rects (
        ) const;

        void set_default_overlay_rect_label (
            const std::string& label
        );

        std::string get_default_overlay_rect_label (
        ) const;

        void set_default_overlay_rect_color (
            const rgb_alpha_pixel& color
        );

        rgb_alpha_pixel get_default_overlay_rect_color (
        ) const;

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

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

        template <
            typename T
            >
        void set_overlay_rect_selected_handler (
            T& object,
            void (T::*event_handler_)(const overlay_rect& orect)
        )
        {
            auto_mutex M(m);
            orect_selected_event_handler = make_mfp(object,event_handler_);
        }

        void set_overlay_rect_selected_handler (
            const any_function<void(const overlay_rect& orect)>& event_handler_
        )
        {
            auto_mutex M(m);
            orect_selected_event_handler = event_handler_;
        }

        template <
            typename T
            >
        void set_image_clicked_handler (
            T& object,
            void (T::*event_handler_)(const point& p, bool is_double_click, unsigned long btn)
        )
        {
            auto_mutex M(m);
            image_clicked_handler = make_mfp(object,event_handler_);
        }

        void set_image_clicked_handler (
            const any_function<void(const point& p, bool is_double_click, unsigned long btn)>& event_handler_
        )
        {
            auto_mutex M(m);
            image_clicked_handler = event_handler_;
        }

        void add_labelable_part_name (
            const std::string& name
        );

        void clear_labelable_part_names (
        );

        void enable_overlay_editing (
        ) { auto_mutex M(m); overlay_editing_enabled = true; }

        void disable_overlay_editing (
        ) 
        { 
            auto_mutex M(m); 
            overlay_editing_enabled = false;  
            rect_is_selected = false;
            drawing_rect = false;
            parent.invalidate_rectangle(rect);
        }
        
        bool overlay_editing_is_enabled (
        ) const { auto_mutex M(m); return overlay_editing_enabled; }

    private:

        void draw (
            const canvas& c
        ) const;

        void on_wheel_up (
            unsigned long state
        );

        void on_wheel_down (
            unsigned long state
        );

        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_mouse_move (
            unsigned long state,
            long x,
            long y
        );

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

        void on_part_add (
            const std::string& part_name
        );

        rectangle get_rect_on_screen (
            unsigned long idx
        ) const;

        rectangle get_rect_on_screen (
            rectangle orect 
        ) const;

        rgb_alpha_pixel invert_pixel (const rgb_alpha_pixel& p) const
        { return rgb_alpha_pixel(255-p.red, 255-p.green, 255-p.blue, p.alpha); }

        virtual int next_free_user_event_number (
        ) const { return scrollable_region::next_free_user_event_number()+1; }
        // The reason for using user actions here rather than just having the timer just call
        // what it needs directly is to avoid a potential deadlock during destruction of this widget.
        void timer_event_unhighlight_rect()
        { 
            highlight_timer.stop(); 
            parent.trigger_user_event(this,scrollable_region::next_free_user_event_number()); 
        }
        void on_user_event (int num)
        {
            // ignore this user event if it isn't for us
            if (num != scrollable_region::next_free_user_event_number())
                return;
            if (highlighted_rect < overlay_rects.size())
            {
                highlighted_rect = std::numeric_limits<unsigned long>::max();
                parent.invalidate_rectangle(rect);
            }
        }


        array2d<rgb_alpha_pixel> img;


        std::vector<overlay_rect> overlay_rects;
        std::vector<overlay_line> overlay_lines;
        std::vector<overlay_circle> overlay_circles;

        long zoom_in_scale;
        long zoom_out_scale;
        bool drawing_rect;
        point rect_anchor;
        rectangle rect_to_draw;
        bool rect_is_selected;
        std::string selected_part_name;
        unsigned long selected_rect;
        rgb_alpha_pixel default_rect_color;
        std::string default_rect_label;
        any_function<void()> event_handler;
        any_function<void(const overlay_rect& orect)> orect_selected_event_handler;
        any_function<void(const point& p, bool is_double_click, unsigned long btn)> image_clicked_handler;
        popup_menu_region parts_menu;
        point last_right_click_pos;
        const double part_width;
        std::set<std::string> part_names;
        bool overlay_editing_enabled;
        timer<image_display> highlight_timer;
        unsigned long highlighted_rect;
        bool holding_shift_key;

        bool moving_overlay;
        unsigned long moving_rect;
        enum  {
            MOVING_RECT_LEFT,
            MOVING_RECT_TOP,
            MOVING_RECT_RIGHT,
            MOVING_RECT_BOTTOM,
            MOVING_PART
        } moving_what;
        std::string moving_part_name;

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

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

    class perspective_display : public drawable, noncopyable
    {
    public:

        perspective_display(  
            drawable_window& w
        );

        ~perspective_display(
        );

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

        struct overlay_line
        {
            overlay_line() { assign_pixel(color, 0);}

            overlay_line(const vector<double>& p1_, const vector<double>& p2_) 
                : p1(p1_), p2(p2_) { assign_pixel(color, 255); }

            template <typename pixel_type>
            overlay_line(const vector<double>& p1_, const vector<double>& p2_, pixel_type p) 
                : p1(p1_), p2(p2_) { assign_pixel(color, p); }

            vector<double> p1;
            vector<double> p2;
            rgb_pixel color;
        };

        struct overlay_dot
        {
            overlay_dot() { assign_pixel(color, 0);}

            overlay_dot(const vector<double>& p_) 
                : p(p_) { assign_pixel(color, 255); }

            template <typename pixel_type>
            overlay_dot(const vector<double>& p_, pixel_type color_) 
                : p(p_) { assign_pixel(color, color_); }

            vector<double> p;
            rgb_pixel color;
        };


        void add_overlay (
            const std::vector<overlay_line>& overlay
        );

        void add_overlay (
            const std::vector<overlay_dot>& overlay
        );

        void clear_overlay (
        );

        template <
            typename T
            >
        void set_dot_double_clicked_handler (
            T& object,
            void (T::*event_handler_)(const vector<double>&)
        )
        {
            auto_mutex M(m);
            dot_clicked_event_handler = make_mfp(object,event_handler_);
        }

        void set_dot_double_clicked_handler (
            const any_function<void(const vector<double>&)>& event_handler_
        );

    private:

        void draw (
            const canvas& c
        ) const;

        void on_wheel_up (
            unsigned long state
        );

        void on_wheel_down (
            unsigned long state
        );

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

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

        static bool compare_second (
            const std::pair<overlay_dot,float>& a,
            const std::pair<overlay_dot,float>& b
        ) { return a.second < b.second; }


        point last;
        std::vector<overlay_line> overlay_lines;
        std::vector<overlay_dot> overlay_dots;

        camera_transform tform;
        vector<double> sum_pts;
        vector<double> max_pts;
        any_function<void(const vector<double>&)> dot_clicked_event_handler;
        mutable array2d<float> depth;
    };

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

    class perspective_window : public drawable_window, noncopyable
    {
    public:

        typedef perspective_display::overlay_line overlay_line;
        typedef perspective_display::overlay_dot overlay_dot;

        perspective_window(
        ) : disp(*this) 
        {
            set_size(100,100);
            on_window_resized();
            show();
        }

        perspective_window(
            const std::vector<dlib::vector<double> >& point_cloud
        ) : 
            disp(*this)
        {  
            set_size(100,100);
            on_window_resized();
            add_overlay(point_cloud); 
            show(); 
        }
        
        perspective_window(
            const std::vector<dlib::vector<double> >& point_cloud,
            const std::string& title
        ) : 
            disp(*this)
        {  
            set_size(100,100);
            on_window_resized();
            add_overlay(point_cloud); 
            set_title(title);
            show(); 
        }
        
        ~perspective_window(
        )
        {
            // You should always call close_window() in the destructor of window
            // objects to ensure that no events will be sent to this window while 
            // it is being destructed.  
            close_window();
        }

        void add_overlay (
            const std::vector<overlay_line>& overlay
        )
        {
            disp.add_overlay(overlay);
        }

        void add_overlay (
            const std::vector<overlay_dot>& overlay
        )
        {
            disp.add_overlay(overlay);
        }

        void clear_overlay (
        )
        {
            disp.clear_overlay();
        }

        template <typename pixel_type>
        void add_overlay(const vector<double>& p1, const vector<double>& p2, pixel_type p)
        {
            add_overlay(std::vector<overlay_line>(1,overlay_line(p1,p2,p)));
        }

        void add_overlay(const std::vector<dlib::vector<double> >& d) 
        { 
            add_overlay(d, 255);
        }

        template <typename pixel_type>
        void add_overlay(const std::vector<dlib::vector<double> >& d, pixel_type p) 
        { 
            std::vector<overlay_dot> temp;
            temp.resize(d.size());
            for (unsigned long i = 0; i < temp.size(); ++i)
                temp[i] = overlay_dot(d[i], p);

            add_overlay(temp);
        }

        template <
            typename T
            >
        void set_dot_double_clicked_handler (
            T& object,
            void (T::*event_handler_)(const vector<double>&)
        )
        {
            disp.set_dot_double_clicked_handler(object,event_handler_);
        }

        void set_dot_double_clicked_handler (
            const any_function<void(const vector<double>&)>& event_handler_
        )
        {
            disp.set_dot_double_clicked_handler(event_handler_);
        }

    private:

        void on_window_resized(
        )
        {
            drawable_window::on_window_resized();
            unsigned long width, height;
            get_size(width,height);
            disp.set_pos(0,0);
            disp.set_size(width, height);
        }
        
        perspective_display disp;
    };

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

    class image_window : public drawable_window 
    {
    public:

        typedef image_display::overlay_rect overlay_rect;
        typedef image_display::overlay_line overlay_line;
        typedef image_display::overlay_circle overlay_circle;

        image_window(
        ); 

        template < typename image_type >
        image_window(
            const image_type& img
        ) : 
            gui_img(*this), 
            window_has_closed(false),
            have_last_click(false),
            mouse_btn(0),
            clicked_signaler(this->wm),
            have_last_keypress(false),
            tie_input_events(false)
        {  
            gui_img.set_image_clicked_handler(*this, &image_window::on_image_clicked);
            gui_img.disable_overlay_editing();
            set_image(img); 
            show(); 
        }
        
        template < typename image_type >
        image_window(
            const image_type& img,
            const std::string& title
        ) : 
            gui_img(*this), 
            window_has_closed(false),
            have_last_click(false),
            mouse_btn(0),
            clicked_signaler(this->wm),
            have_last_keypress(false),
            tie_input_events(false)
        {  
            gui_img.set_image_clicked_handler(*this, &image_window::on_image_clicked);
            gui_img.disable_overlay_editing();
            set_image(img); 
            set_title(title);
            show(); 
        }
        

        ~image_window(
        );

        template < typename image_type >
        void set_image (
            const image_type& img
        ) 
        { 
            const unsigned long padding = scrollable_region_style_default().get_border_size();
            auto_mutex M(wm);
            gui_img.set_image(img); 

            // Only ever mess with the size of the window if the user is giving us an image
            // that is a different size.  Otherwise we assume that they will have already
            // sized the window to whatever they feel is reasonable for an image of the
            // current size.  
            if (previous_image_size != get_rect(img))
            {
                const rectangle r = gui_img.get_image_display_rect();
                if (image_rect != r)
                {
                    // set the size of this window to match the size of the input image
                    set_size(r.width()+padding*2,r.height()+padding*2);

                    // call this to make sure everything else is setup properly
                    on_window_resized();

                    image_rect = r;
                }
                previous_image_size = get_rect(img);
            }
        }

        void add_overlay (
            const overlay_rect& overlay
        );

        template <typename pixel_type>
        void add_overlay(const rectangle& r, pixel_type p) 
        { add_overlay(image_display::overlay_rect(r,p)); }

        void add_overlay(const rectangle& r) 
        { add_overlay(image_display::overlay_rect(r,rgb_pixel(255,0,0))); }

        template <typename pixel_type>
        void add_overlay(const rectangle& r, pixel_type p, const std::string& l) 
        { add_overlay(image_display::overlay_rect(r,p,l)); }

        template <typename pixel_type>
        void add_overlay(const std::vector<rectangle>& r, pixel_type p) 
        { 
            std::vector<overlay_rect> temp;
            temp.resize(r.size());
            for (unsigned long i = 0; i < temp.size(); ++i)
                temp[i] = overlay_rect(r[i], p);

            add_overlay(temp);
        }

        void add_overlay(const std::vector<rectangle>& r) 
        { add_overlay(r, rgb_pixel(255,0,0)); }

        void add_overlay(
            const full_object_detection& object,
            const std::vector<std::string>& part_names
        ) 
        { 

            add_overlay(overlay_rect(object.get_rect(), rgb_pixel(255,0,0)));

            std::vector<overlay_circle> temp;
            temp.reserve(object.num_parts());
            for (unsigned long i = 0; i < object.num_parts(); ++i)
            {
                if (object.part(i) != OBJECT_PART_NOT_PRESENT)
                {
                    if (i < part_names.size())
                        temp.push_back(overlay_circle(object.part(i), 7, rgb_pixel(0,255,0), part_names[i]));
                    else
                        temp.push_back(overlay_circle(object.part(i), 7, rgb_pixel(0,255,0)));
                }
            }

            add_overlay(temp);
        }

        void add_overlay(
            const full_object_detection& object
        ) 
        { 
            std::vector<std::string> part_names;
            add_overlay(object, part_names);
        }

        void add_overlay(
            const std::vector<full_object_detection>& objects,
            const std::vector<std::string>& part_names
        ) 
        { 
            std::vector<overlay_rect> rtemp;
            rtemp.reserve(objects.size());
            for (unsigned long i = 0; i < objects.size(); ++i)
            {
                rtemp.push_back(overlay_rect(objects[i].get_rect(), rgb_pixel(255,0,0)));
            }

            add_overlay(rtemp);

            std::vector<overlay_circle> temp;

            for (unsigned long i = 0; i < objects.size(); ++i)
            {
                for (unsigned long j = 0; j < objects[i].num_parts(); ++j)
                {
                    if (objects[i].part(j) != OBJECT_PART_NOT_PRESENT)
                    {
                        if (j < part_names.size())
                            temp.push_back(overlay_circle(objects[i].part(j), 7, rgb_pixel(0,255,0),part_names[j]));
                        else
                            temp.push_back(overlay_circle(objects[i].part(j), 7, rgb_pixel(0,255,0)));
                    }
                }
            }

            add_overlay(temp);
        }

        void add_overlay(
            const std::vector<full_object_detection>& objects
        ) 
        { 
            std::vector<std::string> part_names;
            add_overlay(objects, part_names);
        }

        void add_overlay (
            const overlay_line& overlay
        );

        void add_overlay (
            const overlay_circle& overlay
        );

        template <typename pixel_type>
        void add_overlay(const point& p1, const point& p2, pixel_type p) 
        { add_overlay(image_display::overlay_line(p1,p2,p)); }

        void add_overlay (
            const std::vector<overlay_rect>& overlay
        );

        void add_overlay (
            const std::vector<overlay_line>& overlay
        );

        void add_overlay (
            const std::vector<overlay_circle>& overlay
        );

        void clear_overlay (
        );

        bool get_next_double_click (
            point& p,
            unsigned long& mouse_button
        ); 

        void tie_events (
        );

        void untie_events (
        );

        bool events_tied (
        ) const;

        bool get_next_double_click (
            point& p
        ) 
        {
            unsigned long mouse_button;
            return get_next_double_click(p, mouse_button);
        }

        bool get_next_keypress (
            unsigned long& key,
            bool& is_printable,
            unsigned long& state
        );

        bool get_next_keypress (
            unsigned long& key,
            bool& is_printable
        )
        {
            unsigned long state;
            return get_next_keypress(key,is_printable,state);
        }

    private:

        virtual base_window::on_close_return_code on_window_close(
        );

        void on_window_resized(
        );
        
        void on_image_clicked (
            const point& p,
            bool is_double_click,
            unsigned long btn
        );

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

        // restricted functions
        image_window(image_window&);
        image_window& operator= (image_window&);

        image_display gui_img;
        rectangle image_rect;
        rectangle previous_image_size;
        bool window_has_closed;
        bool have_last_click;
        point last_clicked_point;
        unsigned long mouse_btn;
        rsignaler clicked_signaler;

        bool have_last_keypress;
        unsigned long next_key;
        bool next_is_printable;
        unsigned long next_state;
        bool tie_input_events;
    };

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

}

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

#endif // DLIB_WIDGETs_

