dlib C++ Library - widgets.h

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

    #ifndef DLIB_WIDGETs_
    #define DLIB_WIDGETs_

    #include "../algs.h"
    #include "widgets_abstract.h"
    #include "drawable.h"
    #include "../gui_core.h"
    #include "fonts.h"
    #include <string>
    #include <sstream>
    #include "../timer.h"
    #include "base_widgets.h"
    #include "../member_function_pointer.h"
    #include "../array.h"
    #include "../sequence.h"
    #include "../dir_nav.h"
    #include "../queue.h"
    #include "../smart_pointers.h"
    #include "style.h"
    #include "../string.h"
    #include "../misc_api.h"
    #include <cctype>

    #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 shared_ptr_thread_safe<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 shared_ptr_thread_safe<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.set(object,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.set(object,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;

            member_function_pointer<>::kernel_1a event_handler;
            member_function_pointer<toggle_button&>::kernel_1a event_handler_self;

            scoped_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)
            {
                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);
                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 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_main_font (
                const shared_ptr_thread_safe<font>& f
            );

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

            void disable (
            );

            void hide (
            );

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

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

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

        private:

            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;
            member_function_pointer<>::kernel_1a_c text_modified_handler;
            member_function_pointer<>::kernel_1a_c enter_key_handler;
            member_function_pointer<>::kernel_1a_c focus_lost_handler;

            scoped_ptr<text_field_style> style;

            timer<text_field>::kernel_2a t;

            // 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 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 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.set(object,eh);
            }

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

            void show (
            );

            void hide (
            );

            void enable (
            );

            void disable (
            );

            void set_main_font (
                const shared_ptr_thread_safe<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>::kernel_2a_c tabs;

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

            member_function_pointer<unsigned long,unsigned long>::kernel_1a 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 shared_ptr_thread_safe<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 shared_ptr_thread_safe<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 (
                );

                template <
                    typename T
                    >
                void set_click_handler (
                    T& object,
                    void (T::*event_handler_)()
                )
                {
                    auto_mutex M(wm);
                    event_handler.set(object,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;

                member_function_pointer<>::kernel_1a 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(object,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.set(object,eh); }

            template <
                typename T
                >
            void set_click_handler (
                T& object,
                void (T::*eh)(unsigned long index)
            ) { auto_mutex M(m); single_click_event_handler.set(object,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 shared_ptr_thread_safe<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;
            typename array<data<S> >::kernel_2a_c items;
            member_function_pointer<unsigned long>::kernel_1a event_handler;
            member_function_pointer<unsigned long>::kernel_1a single_click_event_handler;
            unsigned long last_selected;

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

                template <
                    typename T
                    >
                void set_click_handler (
                    T& object,
                    void (T::*event_handler_)(const std::string&)
                )
                {
                    auto_mutex M(wm);
                    event_handler.set(object,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;

                member_function_pointer<const std::string&>::kernel_1a event_handler;
                sequence<scoped_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(object,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(object,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(object,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 shared_ptr_thread_safe<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>::kernel_2a_c 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.set(object,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.set(object,event_handler_);
            }

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

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

        protected:

            void on_keydown (
                unsigned long key,
                bool is_printable,
                unsigned long state
            )
            {
                // 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> 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> 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> 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> 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> 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> 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> 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> 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> v(p-center);
                            v = v.normalize();

                            dlib::vector<double> cross = z.cross(v).normalize();
                            dlib::vector<double> 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;

            member_function_pointer<unsigned long>::kernel_1a node_selected_handler;
            member_function_pointer<unsigned long>::kernel_1a node_deselected_handler;
            member_function_pointer<>::kernel_1a node_deleted_handler;
            member_function_pointer<>::kernel_1a 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.set(object,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>::kernel_1a_c grid;
            array<unsigned long>::kernel_2a_c col_width;
            array<unsigned long>::kernel_2a_c row_height;
            bool has_focus;
            long active_col;
            long active_row;
            long cursor_pos;
            bool show_cursor;
            bool recent_cursor_move;
            timer<text_grid>::kernel_2a cursor_timer;
            rgb_pixel border_color_;
            member_function_pointer<unsigned long, unsigned long>::kernel_1a_c text_modified_handler;
        };

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

    }

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

    #endif // DLIB_WIDGETs_