dlib C++ Library - widgets.cpp

    // 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_CPP_
    #define DLIB_WIDGETs_CPP_

    #include <algorithm>
    #include <memory>

    #include "widgets.h"
    #include "../string.h"

    namespace dlib
    {

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // toggle_button object methods
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        void toggle_button::
        set_size (
            unsigned long width,
            unsigned long height
        )
        {
            auto_mutex M(m);
            rectangle min_rect = style->get_min_size(name_,*mfont);
            // only change the size if it isn't going to be too small to fit the name
            if (height >= min_rect.height() &&
                width >= min_rect.width())
            {
                rectangle old(rect);
                rect = resize_rect(rect,width,height);
                parent.invalidate_rectangle(rect+old);
                btn_tooltip.set_size(width,height);
            }
        }

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

        void toggle_button::
        set_checked (
        )
        {
            auto_mutex M(m);
            checked = true;
            parent.invalidate_rectangle(rect);
        }

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

        void toggle_button::
        set_unchecked (
        )
        {
            auto_mutex M(m);
            checked = false;
            parent.invalidate_rectangle(rect);
        }

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

        bool toggle_button::
        is_checked (
        ) const
        {
            auto_mutex M(m);
            return checked;
        }

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

        void toggle_button::
        show (
        )
        {
            button_action::show();
            btn_tooltip.show();
        }

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

        void toggle_button::
        hide (
        )
        {
            button_action::hide();
            btn_tooltip.hide();
        }

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

        void toggle_button::
        enable (
        )
        {
            button_action::enable();
            btn_tooltip.enable();
        }

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

        void toggle_button::
        disable (
        )
        {
            button_action::disable();
            btn_tooltip.disable();
        }

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

        void toggle_button::
        set_tooltip_text (
            const std::string& text
        )
        {
            btn_tooltip.set_text(text);
        }

        void toggle_button::
        set_tooltip_text (
            const std::wstring& text
        )
        {
            btn_tooltip.set_text(text);
        }

        void toggle_button::
        set_tooltip_text (
            const dlib::ustring& text
        )
        {
            btn_tooltip.set_text(text);
        }

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

        const std::string toggle_button::
        tooltip_text (
        ) const
        {
            return btn_tooltip.text();
        }

        const std::wstring toggle_button::
        tooltip_wtext (
        ) const
        {
            return btn_tooltip.wtext();
        }

        const dlib::ustring toggle_button::
        tooltip_utext (
        ) const
        {
            return btn_tooltip.utext();
        }

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

        void toggle_button::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            mfont = f;
            set_name(name_);
        }

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

        void toggle_button::
        set_pos (
            long x,
            long y
        )
        {
            auto_mutex M(m);
            button_action::set_pos(x,y);
            btn_tooltip.set_pos(x,y);
        }

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

        void toggle_button::
        set_name (
            const std::string& name
        )
        {
            set_name(convert_to_utf32(name));
        }

        void toggle_button::
        set_name (
            const std::wstring& name
        )
        {
            set_name(convert_to_utf32(name));
        }

        void toggle_button::
        set_name (
            const dlib::ustring& name
        )
        {
            auto_mutex M(m);
            name_ = name;
            // do this to get rid of any reference counting that may be present in
            // the std::string implementation.
            name_[0] = name_[0];

            rectangle old(rect);
            rect = move_rect(style->get_min_size(name,*mfont),rect.left(),rect.top());
            btn_tooltip.set_size(rect.width(),rect.height());

            parent.invalidate_rectangle(rect+old);
        }

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

        const std::string toggle_button::
        name (
        ) const
        {
            return convert_wstring_to_mbstring(wname());
        }

        const std::wstring toggle_button::
        wname (
        ) const
        {
            return convert_utf32_to_wstring(uname());
        }

        const dlib::ustring toggle_button::
        uname (
        ) const
        {
            auto_mutex M(m);
            dlib::ustring temp = name_;
            // do this to get rid of any reference counting that may be present in
            // the std::string implementation.
            temp[0] = name_[0];
            return temp;
        }

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

        void toggle_button::
        on_button_up (
            bool mouse_over
        )
        {
            if (mouse_over)
            {
                checked = !checked;
                // this is a valid toggle_button click
                if (event_handler.is_set())
                    event_handler();
                else if (event_handler_self.is_set())
                    event_handler_self(*this);
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // label object methods
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        void label::
        draw (
            const canvas& c
        ) const
        {
            rectangle area = rect.intersect(c);
            if (area.is_empty() || text_.size() == 0)
                return;

            using namespace std;
            unsigned char r = text_color_.red;
            unsigned char g = text_color_.green;
            unsigned char b = text_color_.blue;
            if (!enabled)
            {
                r = 128;
                g = 128;
                b = 128;
            }

            rectangle text_rect(rect);

            string::size_type first, last;
            first = 0;
            last = text_.find_first_of('\n');
            mfont->draw_string(c,text_rect,text_,rgb_pixel(r,g,b),first,last);

            while (last != string::npos)
            {
                first = last+1;
                last = text_.find_first_of('\n',first);
                text_rect.set_top(text_rect.top()+mfont->height());
                mfont->draw_string(c,text_rect,text_,rgb_pixel(r,g,b),first,last);
            }
        }

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

        void label::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            mfont = f;
            set_text(text_);
        }

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

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

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

        void label::
        set_text (
            const dlib::ustring& text
        )
        {
            using namespace std;
            auto_mutex M(m);
            text_ = text;
            // do this to get rid of any reference counting that may be present in
            // the std::string implementation.
            text_[0] = text[0];

            rectangle old(rect);

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

            rect.set_right(rect.left() + width - 1);
            rect.set_bottom(rect.top() + height - 1);

            parent.invalidate_rectangle(rect+old);
        }

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

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

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

        const dlib::ustring label::
        utext (
        ) const
        {
            auto_mutex M(m);
            dlib::ustring temp = text_;
            // do this to get rid of any reference counting that may be present in
            // the std::string implementation.
            temp[0] = text_[0];
            return temp;
        }

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

        void label::
        set_text_color (
            const rgb_pixel color
        )
        {
            m.lock();
            text_color_ = color;
            parent.invalidate_rectangle(rect);
            m.unlock();
        }

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

        const rgb_pixel label::
        text_color (
        ) const
        {
            auto_mutex M(m);
            return text_color_;
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // text_field object methods
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        rectangle text_field::
        get_text_rect (
        ) const
        {
            // figure out where the text string should appear
            unsigned long vertical_pad = (rect.height() - mfont->height())/2+1;

            rectangle text_rect;
            text_rect.set_left(rect.left()+style->get_padding(*mfont));
            text_rect.set_top(rect.top()+vertical_pad);
            text_rect.set_right(rect.right()-style->get_padding(*mfont));
            text_rect.set_bottom(text_rect.top()+mfont->height()-1);
            return text_rect;
        }

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

        void text_field::
        enable (
        )
        {
            drawable::enable();
            right_click_menu.enable();
        }

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

        void text_field::
        give_input_focus (
        )
        {
            auto_mutex M(m);
            has_focus = true;
            cursor_visible = true;
            parent.invalidate_rectangle(rect);
            t.start();
        }

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

        bool text_field::
        has_input_focus (
        ) const
        {
            auto_mutex M(m);
            return has_focus;
        }

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

        void text_field::
        select_all_text (
        )
        {
            auto_mutex M(m);
            on_select_all();
        }

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

        void text_field::
        on_cut (
        )
        {
            on_copy();
            on_delete_selected();
        }

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

        void text_field::
        on_copy (
        )
        {
            if (highlight_start <= highlight_end)
            {
                put_on_clipboard(text_.substr(highlight_start, highlight_end-highlight_start+1));
            }
        }

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

        void text_field::
        on_paste (
        )
        {
            ustring temp_str;
            get_from_clipboard(temp_str);

            // If this is a multi line string then just take the first line.
            ustring::size_type pos = temp_str.find_first_of('\n');
            if (pos != ustring::npos)
            {
                temp_str = temp_str.substr(0,pos);
            }

            if (highlight_start <= highlight_end)
            {
                text_ = text_.substr(0,highlight_start) + temp_str +
                    text_.substr(highlight_end+1,text_.size()-highlight_end-1);
                move_cursor(highlight_start+temp_str.size());
                highlight_start = 0;
                highlight_end = -1;
                parent.invalidate_rectangle(rect);
                on_no_text_selected();

                // send out the text modified event
                if (text_modified_handler.is_set())
                    text_modified_handler();
            }
            else
            {
                text_ = text_.substr(0,cursor_pos) + temp_str +
                    text_.substr(cursor_pos,text_.size()-cursor_pos);
                move_cursor(cursor_pos+temp_str.size());

                // send out the text modified event
                if (temp_str.size() != 0 && text_modified_handler.is_set())
                    text_modified_handler();
            }
        }

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

        void text_field::
        on_select_all (
        )
        {
            move_cursor(static_cast<long>(text_.size()));
            highlight_start = 0;
            highlight_end = static_cast<long>(text_.size()-1);
            if (highlight_start <= highlight_end)
                on_text_is_selected();
            parent.invalidate_rectangle(rect);
        }

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

        void text_field::
        on_delete_selected (
        )
        {
            if (highlight_start <= highlight_end)
            {
                text_ = text_.erase(highlight_start,highlight_end-highlight_start+1);
                move_cursor(highlight_start);
                highlight_start = 0;
                highlight_end = -1;

                on_no_text_selected();
                // send out the text modified event
                if (text_modified_handler.is_set())
                    text_modified_handler();

                parent.invalidate_rectangle(rect);
            }
        }

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

        void text_field::
        on_text_is_selected (
        )
        {
            right_click_menu.menu().enable_menu_item(0);
            right_click_menu.menu().enable_menu_item(1);
            right_click_menu.menu().enable_menu_item(3);
        }

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

        void text_field::
        on_no_text_selected (
        )
        {
            right_click_menu.menu().disable_menu_item(0);
            right_click_menu.menu().disable_menu_item(1);
            right_click_menu.menu().disable_menu_item(3);
        }

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

        void text_field::
        show (
        )
        {
            drawable::show();
            right_click_menu.show();
        }

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

        void text_field::
        disable (
        )
        {
            auto_mutex M(m);
            drawable::disable();
            t.stop();
            has_focus = false;
            cursor_visible = false;
            right_click_menu.disable();
        }

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

        void text_field::
        hide (
        )
        {
            auto_mutex M(m);
            drawable::hide();
            t.stop();
            has_focus = false;
            cursor_visible = false;
        }

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

        void text_field::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            mfont = f;
            // adjust the height of this text field so that it is appropriate for the current
            // font size
            rect.set_bottom(rect.top() + mfont->height()+ (style->get_padding(*mfont))*2);
            set_text(text_);
            right_click_menu.set_rect(get_text_rect());
        }

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

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

            style->draw_text_field(c,rect,get_text_rect(), enabled, *mfont, text_, cursor_x, text_pos,
                                   text_color_, bg_color_, has_focus, cursor_visible, highlight_start,
                                   highlight_end);
        }

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

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

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

        void text_field::
        set_text (
            const dlib::ustring& text
        )
        {
            DLIB_ASSERT ( text.find_first_of('\n') == std::string::npos ,
                    "\tvoid text_field::set_text()"
                    << "\n\ttext:  " << narrow(text) );
            auto_mutex M(m);
            // do this to get rid of any reference counting that may be present in
            // the std::string implementation.
            text_ = text.c_str();

            move_cursor(0);

            highlight_start = 0;
            highlight_end = -1;

            parent.invalidate_rectangle(rect);
        }

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

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

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

        const dlib::ustring text_field::
        utext (
        ) const
        {
            auto_mutex M(m);
            // do this to get rid of any reference counting that may be present in
            // the dlib::ustring implementation.
            dlib::ustring temp = text_.c_str();
            return temp;
        }

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

        void text_field::
        set_width (
            unsigned long width
        )
        {
            auto_mutex M(m);
            if (width < style->get_padding(*mfont)*2)
                return;

            rectangle old(rect);

            rect.set_right(rect.left() + width - 1);

            right_click_menu.set_rect(get_text_rect());
            parent.invalidate_rectangle(rect+old);
        }

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

        void text_field::
        set_pos (
            long x,
            long y
        )
        {
            drawable::set_pos(x,y);
            right_click_menu.set_rect(get_text_rect());
        }

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

        void text_field::
        set_background_color (
            const rgb_pixel color
        )
        {
            auto_mutex M(m);
            bg_color_ = color;
            parent.invalidate_rectangle(rect);
        }

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

        const rgb_pixel text_field::
        background_color (
        ) const
        {
            auto_mutex M(m);
            return bg_color_;
        }

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

        void text_field::
        set_text_color (
            const rgb_pixel color
        )
        {
            auto_mutex M(m);
            text_color_ = color;
            parent.invalidate_rectangle(rect);
        }

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

        const rgb_pixel text_field::
        text_color (
        ) const
        {
            auto_mutex M(m);
            return text_color_;
        }

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

        void text_field::
        on_mouse_move (
            unsigned long state,
            long x,
            long y
        )
        {
            if (!enabled || hidden || !has_focus)
            {
                return;
            }

            if (state & base_window::LEFT)
            {
                if (highlight_start <= highlight_end)
                {
                    if (highlight_start == cursor_pos)
                        shift_pos = highlight_end + 1;
                    else
                        shift_pos = highlight_start;
                }

                unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y,text_pos);
                if (static_cast<long>(new_pos) != cursor_pos)
                {
                    move_cursor(new_pos);
                    parent.invalidate_rectangle(rect);
                }
            }
            else if (shift_pos != -1)
            {
                shift_pos = -1;
            }

        }

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

        void text_field::
        on_mouse_up (
            unsigned long btn,
            unsigned long,
            long ,
            long
        )
        {
            if (!enabled || hidden)
                return;

            if (btn == base_window::LEFT)
                shift_pos = -1;
        }

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

        void text_field::
        on_mouse_down (
            unsigned long btn,
            unsigned long state,
            long x,
            long y,
            bool double_clicked
        )
        {
            using namespace std;
            if (!enabled || hidden || btn != (unsigned long)base_window::LEFT)
                return;

            if (rect.contains(x,y))
            {
                has_focus = true;
                cursor_visible = true;
                parent.invalidate_rectangle(rect);
                t.start();

                if (double_clicked)
                {
                    // highlight the double clicked word
                    string::size_type first, last;
                    const ustring ustr = convert_to_utf32(std::string(" \t\n"));
                    first = text_.substr(0,cursor_pos).find_last_of(ustr.c_str());
                    last = text_.find_first_of(ustr.c_str(),cursor_pos);
                    long f = static_cast<long>(first);
                    long l = static_cast<long>(last);
                    if (first == string::npos)
                        f = -1;
                    if (last == string::npos)
                        l = static_cast<long>(text_.size());

                    ++f;
                    --l;

                    move_cursor(l+1);
                    highlight_start = f;
                    highlight_end = l;
                    on_text_is_selected();
                }
                else
                {
                    if (state & base_window::SHIFT)
                    {
                        if (highlight_start <= highlight_end)
                        {
                            if (highlight_start == cursor_pos)
                                shift_pos = highlight_end + 1;
                            else
                                shift_pos = highlight_start;
                        }
                        else
                        {
                            shift_pos = cursor_pos;
                        }
                    }

                    bool at_end = false;
                    if (cursor_pos == 0 || cursor_pos == static_cast<long>(text_.size()))
                        at_end = true;
                    const long old_pos = cursor_pos;

                    unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y,text_pos);
                    if (static_cast<long>(new_pos) != cursor_pos)
                    {
                        move_cursor(new_pos);
                        parent.invalidate_rectangle(rect);
                    }
                    shift_pos = cursor_pos;

                    if (at_end && cursor_pos == old_pos)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }

            }
            else if (has_focus)
            {
                t.stop();
                has_focus = false;
                cursor_visible = false;
                shift_pos = -1;
                highlight_start = 0;
                highlight_end = -1;
                on_no_text_selected();

                if (focus_lost_handler.is_set())
                    focus_lost_handler();
                parent.invalidate_rectangle(rect);
            }
        }

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

        void text_field::
        on_keydown (
            unsigned long key,
            bool is_printable,
            unsigned long state
        )
        {
            // If the right click menu is up then we don't want to do anything with
            // the keyboard ourselves.  Let the popup menu use the keyboard for now.
            if (right_click_menu.popup_menu_visible())
                return;

            const ustring space_str = convert_to_utf32(std::string(" \t\n"));
            const bool shift = (state&base_window::KBD_MOD_SHIFT) != 0;
            const bool ctrl = (state&base_window::KBD_MOD_CONTROL) != 0;
            if (has_focus && enabled && !hidden)
            {
                if (shift && is_printable == false)
                {
                    if (shift_pos == -1)
                    {
                        if (highlight_start <= highlight_end)
                        {
                            if (highlight_start == cursor_pos)
                                shift_pos = highlight_end + 1;
                            else
                                shift_pos = highlight_start;
                        }
                        else
                        {
                            shift_pos = cursor_pos;
                        }
                    }
                }
                else
                {
                    shift_pos = -1;
                }

                if (key == base_window::KEY_LEFT ||
                    key == base_window::KEY_UP)
                {
                    if (cursor_pos != 0)
                    {
                        unsigned long new_pos;
                        if (ctrl)
                        {
                            // find the first non-whitespace to our left
                            std::string::size_type pos = text_.find_last_not_of(space_str.c_str(),cursor_pos);
                            if (pos != std::string::npos)
                            {
                                pos = text_.find_last_of(space_str.c_str(),pos);
                                if (pos != std::string::npos)
                                    new_pos = static_cast<unsigned long>(pos);
                                else
                                    new_pos = 0;
                            }
                            else
                            {
                                new_pos = 0;
                            }
                        }
                        else
                        {
                            new_pos = cursor_pos-1;
                        }

                        move_cursor(new_pos);
                    }
                    else if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }

                }
                else if (key == base_window::KEY_RIGHT ||
                    key == base_window::KEY_DOWN)
                {
                    if (cursor_pos != static_cast<long>(text_.size()))
                    {
                        unsigned long new_pos;
                        if (ctrl)
                        {
                            // find the first non-whitespace to our left
                            std::string::size_type pos = text_.find_first_not_of(space_str.c_str(),cursor_pos);
                            if (pos != std::string::npos)
                            {
                                pos = text_.find_first_of(space_str.c_str(),pos);
                                if (pos != std::string::npos)
                                    new_pos = static_cast<unsigned long>(pos+1);
                                else
                                    new_pos = static_cast<unsigned long>(text_.size());
                            }
                            else
                            {
                                new_pos = static_cast<unsigned long>(text_.size());
                            }
                        }
                        else
                        {
                            new_pos = cursor_pos+1;
                        }

                        move_cursor(new_pos);
                    }
                    else if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }
                else if (is_printable)
                {
                    if (ctrl)
                    {
                        if (key == 'a')
                        {
                            on_select_all();
                        }
                        else if (key == 'c')
                        {
                            on_copy();
                        }
                        else if (key == 'v')
                        {
                            on_paste();
                        }
                        else if (key == 'x')
                        {
                            on_cut();
                        }
                    }
                    else if (key != '\n')
                    {
                        if (highlight_start <= highlight_end)
                        {
                            text_ = text_.substr(0,highlight_start) + static_cast<unichar>(key) +
                                text_.substr(highlight_end+1,text_.size()-highlight_end-1);
                            move_cursor(highlight_start+1);
                            highlight_start = 0;
                            highlight_end = -1;
                            on_no_text_selected();
                            parent.invalidate_rectangle(rect);
                        }
                        else
                        {
                            text_ = text_.substr(0,cursor_pos) + static_cast<unichar>(key) +
                                text_.substr(cursor_pos,text_.size()-cursor_pos);
                            move_cursor(cursor_pos+1);
                        }
                        unsigned long height;
                        mfont->compute_size(text_,text_width,height,text_pos);

                        // send out the text modified event
                        if (text_modified_handler.is_set())
                            text_modified_handler();
                    }
                    else if (key == '\n')
                    {
                        if (enter_key_handler.is_set())
                            enter_key_handler();
                    }
                }
                else if (key == base_window::KEY_BACKSPACE)
                {
                    // if something is highlighted then delete that
                    if (highlight_start <= highlight_end)
                    {
                        on_delete_selected();
                    }
                    else if (cursor_pos != 0)
                    {
                        text_ = text_.erase(cursor_pos-1,1);
                        move_cursor(cursor_pos-1);

                        // send out the text modified event
                        if (text_modified_handler.is_set())
                            text_modified_handler();
                    }
                    else
                    {
                        // do this just so it repaints itself right
                        move_cursor(cursor_pos);
                    }
                    unsigned long height;
                    mfont->compute_size(text_,text_width,height,text_pos);
                    parent.invalidate_rectangle(rect);
                }
                else if (key == base_window::KEY_DELETE)
                {
                    // if something is highlighted then delete that
                    if (highlight_start <= highlight_end)
                    {
                        on_delete_selected();
                    }
                    else if (cursor_pos != static_cast<long>(text_.size()))
                    {
                        text_ = text_.erase(cursor_pos,1);

                        // send out the text modified event
                        if (text_modified_handler.is_set())
                            text_modified_handler();
                    }
                    else
                    {
                        // do this just so it repaints itself right
                        move_cursor(cursor_pos);
                    }
                    parent.invalidate_rectangle(rect);

                    unsigned long height;
                    mfont->compute_size(text_,text_width,height,text_pos);
                }
                else if (key == base_window::KEY_HOME)
                {
                    move_cursor(0);
                    if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }
                else if (key == base_window::KEY_END)
                {
                    move_cursor(static_cast<unsigned long>(text_.size()));
                    if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }
                cursor_visible = true;
                recent_movement = true;

            }
        }

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

        void text_field::
        on_string_put(
            const std::wstring &str
        )
        {
            if (has_focus && enabled && !hidden){
                ustring ustr = convert_to_utf32(str);
                if (highlight_start <= highlight_end)
                {
                    text_ = text_.substr(0,highlight_start) + ustr +
                        text_.substr(highlight_end+1,text_.size()-highlight_end-1);
                    move_cursor(highlight_start+ustr.size());
                    highlight_start = 0;
                    highlight_end = -1;
                    on_no_text_selected();
                    parent.invalidate_rectangle(rect);
                }
                else
                {
                    text_ = text_.substr(0,cursor_pos) + ustr +
                        text_.substr(cursor_pos,text_.size()-cursor_pos);
                    move_cursor(cursor_pos+ustr.size());
                }
                unsigned long height;
                mfont->compute_size(text_,text_width,height,text_pos);

                // send out the text modified event
                if (text_modified_handler.is_set())
                    text_modified_handler();
            }
        }

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

        void text_field::
        move_cursor (
            unsigned long pos
        )
        {
            using namespace std;
            const long old_cursor_pos = cursor_pos;

            if (text_pos >= pos)
            {
                // the cursor should go all the way to the left side of the text
                if (pos >= 6)
                    text_pos = pos-6;
                else
                    text_pos = 0;

                cursor_pos = pos;
                unsigned long height;
                mfont->compute_size(text_,text_width,height,text_pos);

                unsigned long width;
                unsigned long new_x = style->get_padding(*mfont);
                if (static_cast<long>(cursor_pos)-1 >= static_cast<long>(text_pos))
                {
                    mfont->compute_size(text_,width,height,text_pos,cursor_pos-1);
                    if (cursor_pos != 0)
                        new_x += width - mfont->right_overflow();
                }

                cursor_x = new_x;
            }
            else
            {
                unsigned long height;
                unsigned long width;
                mfont->compute_size(text_,width,height,text_pos,pos-1);

                unsigned long new_x = style->get_padding(*mfont) +
                    width - mfont->right_overflow();

                // move the text to the left if necessary
                if (new_x + 4 > rect.width())
                {
                    while (new_x > rect.width() - rect.width()/5)
                    {
                        new_x -= (*mfont)[text_[text_pos]].width();
                        ++text_pos;
                    }
                }

                cursor_x = new_x;
                cursor_pos = pos;
                mfont->compute_size(text_,text_width,height,text_pos);
            }

            parent.set_im_pos(rect.left()+cursor_x, rect.top());

            if (old_cursor_pos != cursor_pos)
            {
                if (shift_pos != -1)
                {
                    highlight_start = std::min(shift_pos,cursor_pos);
                    highlight_end = std::max(shift_pos,cursor_pos)-1;
                }
                else
                {
                    highlight_start = 0;
                    highlight_end = -1;
                }

                if (highlight_start > highlight_end)
                    on_no_text_selected();
                else
                    on_text_is_selected();

                recent_movement = true;
                cursor_visible = true;
                parent.invalidate_rectangle(rect);
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //             tabbed_display object methods
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        tabbed_display::
        tabbed_display(
            drawable_window& w
        ) :
            drawable(w,MOUSE_CLICK),
            selected_tab_(0),
            left_pad(6),
            right_pad(4),
            top_pad(3),
            bottom_pad(3)
        {
            rect = rectangle(0,0,40,mfont->height()+top_pad+bottom_pad);
            enable_events();
            tabs.set_max_size(1);
            tabs.set_size(1);
        }

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

        tabbed_display::
        ~tabbed_display(
        )
        {
            disable_events();
            parent.invalidate_rectangle(rect);
        }

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

        void tabbed_display::
        set_pos (
            long x,
            long y
        )
        {
            auto_mutex M(m);
            // we have to adjust the positions of all the tab rectangles
            const long xdelta = rect.left() - x;
            const long ydelta = rect.top() - y;
            for (unsigned long i = 0; i < tabs.size(); ++i)
            {
                tabs[i].rect.set_left(tabs[i].rect.left()+xdelta);
                tabs[i].rect.set_right(tabs[i].rect.right()+xdelta);

                tabs[i].rect.set_top(tabs[i].rect.top()+ydelta);
                tabs[i].rect.set_bottom(tabs[i].rect.bottom()+ydelta);

                // adjust the position of the group associated with this tab if it exists
                if (tabs[i].group)
                    tabs[i].group->set_pos(x+3, y+mfont->height()+top_pad+bottom_pad+3);
            }
            drawable::set_pos(x,y);
            recompute_tabs();
        }

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

        void tabbed_display::
        fit_to_contents (
        )
        {
            auto_mutex M(m);
            rectangle new_rect;
            point p(rect.left(),rect.top());
            new_rect += p;

            for (unsigned long i = 0; i < tabs.size(); ++i)
            {
                if (tabs[i].group)
                {
                    tabs[i].group->fit_to_contents();
                    new_rect += tabs[i].group->get_rect();
                }
            }

            // and give the new rect an additional 4 pixels on the bottom and right sides
            // so that the contents to hit the edge of the tabbed display
            new_rect = resize_rect(new_rect, new_rect.width()+4, new_rect.height()+4);

            parent.invalidate_rectangle(new_rect+rect);
            rect = new_rect;
        }

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

        void tabbed_display::
        set_size (
            unsigned long width,
            unsigned long height
        )
        {
            auto_mutex M(m);
            rectangle old(rect);
            const long x = rect.left();
            const long y = rect.top();
            rect.set_right(x+width-1);
            rect.set_bottom(y+height-1);

            recompute_tabs();

            parent.invalidate_rectangle(rect+old);
        }

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

        void tabbed_display::
        set_number_of_tabs (
            unsigned long num
        )
        {
            auto_mutex M(m);

            DLIB_ASSERT ( num > 0 ,
                    "\tvoid tabbed_display::set_number_of_tabs()"
                    << "\n\tnum:  " << num );

            tabs.set_max_size(num);
            tabs.set_size(num);

            selected_tab_ = 0;

            recompute_tabs();
            parent.invalidate_rectangle(rect);
        }

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

        unsigned long tabbed_display::
        selected_tab (
        ) const
        {
            auto_mutex M(m);
            return selected_tab_;
        }

        unsigned long tabbed_display::
        number_of_tabs (
        ) const
        {
            auto_mutex M(m);
            return tabs.size();
        }

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

        const std::string tabbed_display::
        tab_name (
            unsigned long idx
        ) const
        {
            return convert_wstring_to_mbstring(tab_wname(idx));
        }

        const std::wstring tabbed_display::
        tab_wname (
            unsigned long idx
        ) const
        {
            return convert_utf32_to_wstring(tab_uname(idx));
        }

        const dlib::ustring& tabbed_display::
        tab_uname (
            unsigned long idx
        ) const
        {
            auto_mutex M(m);

            DLIB_ASSERT ( idx < number_of_tabs() ,
                    "\tvoid tabbed_display::tab_name()"
                    << "\n\tidx:              " << idx
                    << "\n\tnumber_of_tabs(): " << number_of_tabs() );

            return tabs[idx].name;
        }

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

        void tabbed_display::
        set_tab_name (
            unsigned long idx,
            const std::string& new_name
        )
        {
            set_tab_name(idx, convert_to_utf32(new_name));
        }

        void tabbed_display::
        set_tab_name (
            unsigned long idx,
            const std::wstring& new_name
        )
        {
            set_tab_name(idx, convert_to_utf32(new_name));
        }

        void tabbed_display::
        set_tab_name (
            unsigned long idx,
            const dlib::ustring& new_name
        )
        {
            auto_mutex M(m);

            DLIB_ASSERT ( idx < number_of_tabs() ,
                    "\tvoid tabbed_display::set_tab_name()"
                    << "\n\tidx:              " << idx
                    << "\n\tnumber_of_tabs(): " << number_of_tabs() );

            tabs[idx].name = new_name;
            // do this so that there isn't any reference counting going on
            tabs[idx].name[0] = tabs[idx].name[0];
            unsigned long height;
            mfont->compute_size(new_name,tabs[idx].width,height);

            recompute_tabs();

            parent.invalidate_rectangle(rect);
        }

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

        void tabbed_display::
        on_mouse_down (
            unsigned long btn,
            unsigned long,
            long x,
            long y,
            bool
        )
        {
            if (rect.contains(x,y) && btn == base_window::LEFT && enabled && !hidden)
            {
                rectangle temp = rect;
                const long offset = mfont->height() + bottom_pad + top_pad;
                temp.set_bottom(rect.top()+offset);
                if (temp.contains(x,y))
                {
                    // now we have to figure out which tab was clicked
                    for (unsigned long i = 0; i < tabs.size(); ++i)
                    {
                        if (selected_tab_ != i && tabs[i].rect.contains(x,y) &&
                            tabs[selected_tab_].rect.contains(x,y) == false)
                        {
                            unsigned long old_idx = selected_tab_;
                            selected_tab_ = i;
                            recompute_tabs();
                            parent.invalidate_rectangle(temp);

                            // adjust the widget_group objects for these tabs if they exist
                            if (tabs[i].group)
                                tabs[i].group->show();
                            if (tabs[old_idx].group)
                                tabs[old_idx].group->hide();

                            if (event_handler.is_set())
                                event_handler(i,old_idx);
                            break;
                        }
                    }
                }
            }
        }

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

        void tabbed_display::
        set_tab_group (
            unsigned long idx,
            widget_group& group
        )
        {
            auto_mutex M(m);

            DLIB_ASSERT ( idx < number_of_tabs() ,
                    "\tvoid tabbed_display::set_tab_group()"
                    << "\n\tidx:              " << idx
                    << "\n\tnumber_of_tabs(): " << number_of_tabs() );

            tabs[idx].group = &group;
            group.set_pos(rect.left()+3,rect.top()+mfont->height()+top_pad+bottom_pad+2);
            if (idx == selected_tab_)
                group.show();
            else
                group.hide();
        }

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

        void tabbed_display::
        disable (
        )
        {
            auto_mutex M(m);
            if (tabs[selected_tab_].group)
                tabs[selected_tab_].group->disable();
            drawable::disable();
        }

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

        void tabbed_display::
        enable (
        )
        {
            auto_mutex M(m);
            if (tabs[selected_tab_].group)
                tabs[selected_tab_].group->enable();
            drawable::enable();
        }

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

        void tabbed_display::
        hide (
        )
        {
            auto_mutex M(m);
            if (tabs[selected_tab_].group)
                tabs[selected_tab_].group->hide();
            drawable::hide();
        }

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

        void tabbed_display::
        show (
        )
        {
            auto_mutex M(m);
            if (tabs[selected_tab_].group)
                tabs[selected_tab_].group->show();
            drawable::show();
        }

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

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

            // draw the main border first
            rectangle main_box(rect.left(),rect.top()+mfont->height()+top_pad+bottom_pad,rect.right(),rect.bottom());
            draw_button_up(c,main_box);
            draw_pixel(c,point(main_box.right()-1,main_box.top()),rgb_pixel(128,128,128));

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

            // draw the tabs
            for (unsigned long i = 0; i < tabs.size(); ++i)
            {
                if (selected_tab_ != i)
                    draw_tab(tabs[i].rect,c);

                // draw the name string
                rectangle temp = tabs[i].rect;
                temp.set_top(temp.top()+top_pad);
                temp.set_bottom(temp.bottom()+bottom_pad);
                temp.set_left(temp.left()+left_pad);
                temp.set_right(temp.right()+right_pad);
                mfont->draw_string(c,temp,tabs[i].name,color);
            }
            draw_tab(tabs[selected_tab_].rect,c);
            draw_line(c,
                point(tabs[selected_tab_].rect.left()+1,
                tabs[selected_tab_].rect.bottom()),
                point(tabs[selected_tab_].rect.right()-2,
                tabs[selected_tab_].rect.bottom()),
                rgb_pixel(212,208,200));
        }

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

        void tabbed_display::
        draw_tab (
            const rectangle& tab,
            const canvas& c
        ) const
        {
            const rgb_pixel white(255,255,255);
            const rgb_pixel background(212,208,200);
            const rgb_pixel dark_gray(64,64,64);
            const rgb_pixel gray(128,128,128);
            draw_line(c,point(tab.left(),tab.top()+2),point(tab.left(),tab.bottom()),white);
            draw_line(c,point(tab.left()+1,tab.top()+2),point(tab.left()+1,tab.bottom()),background);
            draw_line(c,point(tab.right(),tab.top()+2),point(tab.right(),tab.bottom()),dark_gray);
            draw_line(c,point(tab.right()-1,tab.top()+2),point(tab.right()-1,tab.bottom()),gray);
            draw_line(c,point(tab.left()+2,tab.top()),point(tab.right()-2,tab.top()),white);
            draw_pixel(c,point(tab.left()+1,tab.top()+1),white);
            draw_pixel(c,point(tab.right()-1,tab.top()+1),dark_gray);
        }

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

        void tabbed_display::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            mfont = f;

            for (unsigned long i = 0; i < tabs.size(); ++i)
            {
                unsigned long height;
                mfont->compute_size(tabs[i].name,tabs[i].width,height);
            }

            recompute_tabs();
            set_pos(rect.left(), rect.top());

            parent.invalidate_rectangle(rect);
        }

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

        void tabbed_display::
        recompute_tabs (
        )
        {
            const long offset = mfont->height() + bottom_pad + top_pad;

            // figure out the size and position of all the tabs
            rectangle sel_tab_rect, other_tab;
            sel_tab_rect.set_top(rect.top());
            sel_tab_rect.set_bottom(rect.top()+offset);

            other_tab.set_top(rect.top()+2);
            other_tab.set_bottom(rect.top()+offset-1);

            long cur_x = rect.left();
            for (unsigned long i = 0; i < tabs.size(); ++i)
            {
                const unsigned long str_width = tabs[i].width;
                if (selected_tab_ != i)
                {
                    other_tab.set_left(cur_x);
                    cur_x += left_pad + str_width + right_pad;
                    other_tab.set_right(cur_x);
                    tabs[i].rect = other_tab;
                    ++cur_x;

                }
                else
                {
                    if (i != 0)
                        sel_tab_rect.set_left(cur_x-2);
                    else
                        sel_tab_rect.set_left(cur_x);

                    cur_x += left_pad + str_width + right_pad;

                    if (i != tabs.size()-1)
                        sel_tab_rect.set_right(cur_x+2);
                    else
                        sel_tab_rect.set_right(cur_x);
                    ++cur_x;

                    tabs[i].rect = sel_tab_rect;
                }
            }

            // make sure this object is wide enough
            const rectangle& last = tabs[tabs.size()-1].rect;
            const rectangle& first = tabs[0].rect;
            rect = last + rect + first;

        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //             named_rectangle object methods
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        named_rectangle::
        named_rectangle(
            drawable_window& w
        ) :
            drawable(w),
            name_width(0),
            name_height(0)
        {
            make_name_fit_in_rect();
            enable_events();
        }

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

        named_rectangle::
        ~named_rectangle(
        )
        {
            disable_events();
            parent.invalidate_rectangle(rect);
        }

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

        void named_rectangle::
        set_size (
            unsigned long width,
            unsigned long height
        )
        {
            auto_mutex M(m);
            rectangle old(rect);
            const long x = rect.left();
            const long y = rect.top();
            rect.set_right(x+width-1);
            rect.set_bottom(y+height-1);

            make_name_fit_in_rect();
            parent.invalidate_rectangle(rect+old);
        }

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

        void named_rectangle::
        wrap_around (
            const rectangle& r
        )
        {
            auto_mutex M(m);
            rectangle old(rect);
            const unsigned long pad = name_height/2;

            rect = rectangle(r.left()-pad, r.top()-name_height*4/3, r.right()+pad, r.bottom()+pad);

            make_name_fit_in_rect();
            parent.invalidate_rectangle(rect+old);
        }

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

        void named_rectangle::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            mfont = f;
            mfont->compute_size(name_,name_width,name_height);
            make_name_fit_in_rect();
            parent.invalidate_rectangle(rect);
        }

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

        void named_rectangle::
        make_name_fit_in_rect (
        )
        {
            // make sure the named rectangle is big enough to contain the name
            const unsigned long wtemp = mfont->height() + name_width;
            const unsigned long htemp = mfont->height() + name_height;
            if (rect.width() < wtemp)
                rect.set_right(rect.left() + wtemp - 1 );
            if (rect.height() < htemp)
                rect.set_bottom(rect.bottom() + htemp - 1 );
        }

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

        void named_rectangle::
        set_name (
            const std::string& name
        )
        {
            set_name(convert_to_utf32(name));
        }

        void named_rectangle::
        set_name (
            const std::wstring& name
        )
        {
            set_name(convert_to_utf32(name));
        }

        void named_rectangle::
        set_name (
            const dlib::ustring& name
        )
        {
            auto_mutex M(m);
            name_ = name.c_str();
            mfont->compute_size(name_,name_width,name_height);

            make_name_fit_in_rect();
            parent.invalidate_rectangle(rect);
        }

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

        const std::string named_rectangle::
        name (
        ) const
        {
            return convert_wstring_to_mbstring(wname());
        }

        const std::wstring named_rectangle::
        wname (
        ) const
        {
            return convert_utf32_to_wstring(uname());
        }

        const dlib::ustring named_rectangle::
        uname (
        ) const
        {
            auto_mutex M(m);
            return dlib::ustring(name_.c_str());
        }

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

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

            const unsigned long gap = mfont->height()/2;
            rectangle strrect = rect;
            strrect.set_left(rect.left() + gap);

            const unsigned long rtop = rect.top() + name_height/2;

            const rgb_pixel white(255,255,255);
            const rgb_pixel gray(128,128,128);

            mfont->draw_string(c,strrect,name_);
            draw_line(c,point(rect.left(), rtop),
                      point(rect.left()+gap/2, rtop), gray);
            draw_line(c,point(rect.left(), rtop),
                      point(rect.left(), rect.bottom()-1), gray);
            draw_line(c,point(rect.left(), rect.bottom()-1),
                      point(rect.right()-1, rect.bottom()-1), gray);
            draw_line(c,point(rect.right()-1, rtop),
                      point(rect.right()-1, rect.bottom()-2), gray);
            draw_line(c,point(strrect.left() + name_width + 2, rtop),
                      point(rect.right()-1, rtop), gray);

            draw_line(c,point(strrect.left() + name_width + 2, rtop+1),
                      point( rect.right()-2, rtop+1), white);
            draw_line(c,point(rect.right(), rtop),
                      point(rect.right(), rect.bottom()), white);
            draw_line(c,point(rect.left(), rect.bottom()),
                      point(rect.right(), rect.bottom()), white);
            draw_line(c,point(rect.left()+1, rtop+1),
                      point(rect.left()+1, rect.bottom()-2), white);
            draw_line(c,point(rect.left()+1, rtop+1),
                      point(rect.left()+gap/2, rtop+1), white);
        }

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

        mouse_tracker::
        mouse_tracker(
            drawable_window& w
        ) :
            draggable(w),
            offset(18),
            nr(w),
            x_label(w),
            y_label(w),
            click_x(-1),
            click_y(-1)
        {
            set_draggable_area(rectangle(0,0,500,500));

            x_label.set_text("x: ");
            y_label.set_text("y: ");
            nr.set_name("mouse position");

            x_label.set_pos(offset,offset);
            y_label.set_pos(x_label.get_rect().left(), x_label.get_rect().bottom()+3);

            nr.wrap_around(x_label.get_rect() + y_label.get_rect());
            rect = nr.get_rect();

            set_z_order(2000000000);
            x_label.set_z_order(2000000001);
            y_label.set_z_order(2000000001);
            nr.set_z_order(2000000001);

            enable_events();
        }

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

        mouse_tracker::
        ~mouse_tracker(
        )
        {
            disable_events();
            parent.invalidate_rectangle(rect);
        }

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

        void mouse_tracker::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            nr.set_main_font(f);
            x_label.set_main_font(f);
            y_label.set_main_font(f);
            mfont = f;
            nr.wrap_around(x_label.get_rect() + y_label.get_rect());
            rect = nr.get_rect();
        }

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

        void mouse_tracker::
        set_pos (
            long x,
            long y
        )
        {
            draggable::set_pos(x,y);
            nr.set_pos(x,y);
            x_label.set_pos(rect.left()+offset,rect.top()+offset);
            y_label.set_pos(x_label.get_rect().left(), x_label.get_rect().bottom()+3);
        }

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

        void mouse_tracker::
        show (
        )
        {
            draggable::show();
            nr.show();
            x_label.show();
            y_label.show();
        }

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

        void mouse_tracker::
        hide (
        )
        {
            draggable::hide();
            nr.hide();
            x_label.hide();
            y_label.hide();
        }

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

        void mouse_tracker::
        enable (
        )
        {
            draggable::enable();
            nr.enable();
            x_label.enable();
            y_label.enable();
        }

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

        void mouse_tracker::
        disable (
        )
        {
            draggable::disable();
            nr.disable();
            x_label.disable();
            y_label.disable();
        }

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

        void mouse_tracker::
        on_mouse_down (
            unsigned long btn,
            unsigned long state,
            long x,
            long y,
            bool double_clicked
        )
        {
            draggable::on_mouse_down(btn,state,x,y,double_clicked);
            if ((state & base_window::SHIFT) && (btn == base_window::LEFT) && enabled && !hidden)
            {
                parent.invalidate_rectangle(rectangle(x,y,x,y));
                parent.invalidate_rectangle(rectangle(click_x,click_y,click_x,click_y));
                click_x = x;
                click_y = y;

                y_label.set_text("y: 0");
                x_label.set_text("x: 0");
            }
        }

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

        void mouse_tracker::
        on_mouse_move (
            unsigned long state,
            long x,
            long y
        )
        {
            if (!hidden && enabled)
            {
                parent.invalidate_rectangle(rect);
                draggable::on_mouse_move(state,x,y);

                long dx = 0;
                long dy = 0;
                if (click_x != -1)
                    dx = click_x;
                if (click_y != -1)
                    dy = click_y;

                sout.str("");
                sout << "y: " << y - dy;
                y_label.set_text(sout.str());

                sout.str("");
                sout << "x: " << x - dx;
                x_label.set_text(sout.str());
            }
        }

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

        void mouse_tracker::
        on_drag (
        )
        {
            nr.set_pos(rect.left(),rect.top());
            x_label.set_pos(rect.left()+offset,rect.top()+offset);
            y_label.set_pos(x_label.get_rect().left(), x_label.get_rect().bottom()+3);

            long x = 0;
            long y = 0;
            if (click_x != -1)
                x = click_x;
            if (click_y != -1)
                y = click_y;

            sout.str("");
            sout << "y: " << lasty - y;
            y_label.set_text(sout.str());

            sout.str("");
            sout << "x: " << lastx - x;
            x_label.set_text(sout.str());
        }

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

        void mouse_tracker::
        draw (
            const canvas& c
        ) const
        {
            fill_rect(c, rect,rgb_pixel(212,208,200));
            draw_pixel(c, point(click_x,click_y),rgb_pixel(255,0,0));
        }

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

        namespace list_box_helper{
        template <typename S>
        list_box<S>::
        list_box(
            drawable_window& w
        ) :
            scrollable_region(w,MOUSE_WHEEL|MOUSE_CLICK),
            ms_enabled(false),
            last_selected(0)
        {
            set_vertical_scroll_increment(mfont->height());
            set_horizontal_scroll_increment(mfont->height());

            style.reset(new list_box_style_default());
            enable_events();
        }

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

        template <typename S>
        list_box<S>::
        ~list_box(
        )
        {
            disable_events();
            parent.invalidate_rectangle(rect);
        }

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

        template <typename S>
        void list_box<S>::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            mfont = f;
            // recompute the sizes of all the items
            for (unsigned long i = 0; i < items.size(); ++i)
            {
                mfont->compute_size(items[i].name,items[i].width, items[i].height);
            }
            set_vertical_scroll_increment(mfont->height());
            parent.invalidate_rectangle(rect);
        }

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

        template <typename S>
        bool list_box<S>::
        is_selected (
            unsigned long index
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( index < size() ,
                    "\tbool list_box::is_selected(index)"
                    << "\n\tindex:  " << index
                    << "\n\tsize(): " << size() );

            return items[index].is_selected;
        }

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

        template <typename S>
        void list_box<S>::
        select (
            unsigned long index
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( index < size() ,
                    "\tvoid list_box::select(index)"
                    << "\n\tindex:  " << index
                    << "\n\tsize(): " << size() );

            last_selected = index;
            items[index].is_selected = true;
            parent.invalidate_rectangle(rect);
        }

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

        template <typename S>
        void list_box<S>::
        unselect (
            unsigned long index
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( index < size() ,
                    "\tvoid list_box::unselect(index)"
                    << "\n\tindex:  " << index
                    << "\n\tsize(): " << size() );
            items[index].is_selected = false;
            parent.invalidate_rectangle(rect);
        }

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

        template <typename S>
        const S& list_box<S>::operator [] (
            unsigned long index
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( index < size() ,
                    "\tconst std::string& list_box::operator[](index)"
                    << "\n\tindex:  " << index
                    << "\n\tsize(): " << size() );
            return items[index].name;
        }

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

        template <typename S>
        bool list_box<S>::
        multiple_select_enabled (
        ) const
        {
            auto_mutex M(m);
            return ms_enabled;
        }

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

        template <typename S>
        void list_box<S>::
        enable_multiple_select (
        )
        {
            auto_mutex M(m);
            ms_enabled = true;
        }

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

        template <typename S>
        void list_box<S>::
        disable_multiple_select (
        )
        {
            auto_mutex M(m);
            ms_enabled = false;
        }

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

        template <typename S>
        bool list_box<S>::
        at_start (
        ) const
        {
            auto_mutex M(m);
            return items.at_start();
        }

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

        template <typename S>
        void list_box<S>::
        reset (
        ) const
        {
            auto_mutex M(m);
            items.reset();
        }

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

        template <typename S>
        bool list_box<S>::
        current_element_valid (
        ) const
        {
            auto_mutex M(m);
            return items.current_element_valid();
        }

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

        template <typename S>
        const S &list_box<S>::
        element (
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( current_element_valid() ,
                    "\tconst std::string& list_box::element()"
                     );
            return items.element().name;
        }

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

        template <typename S>
        const S &list_box<S>::
        element (
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( current_element_valid() ,
                    "\tconst std::string& list_box::element()"
                     );
            return items.element().name;
        }

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

        template <typename S>
        bool list_box<S>::
        move_next (
        ) const
        {
            auto_mutex M(m);
            return items.move_next();
        }

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

        template <typename S>
        size_t list_box<S>::
        size (
        ) const
        {
            auto_mutex M(m);
            return items.size();
        }

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

        template <typename S>
        void list_box<S>::
        draw (
            const canvas& c
        ) const
        {
            scrollable_region::draw(c);

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

            style->draw_list_box_background(c, display_rect(), enabled);

            long y = total_rect().top();
            for (unsigned long i = 0; i < items.size(); ++i)
            {
                if (y+(long)items[i].height <= area.top())
                {
                    y += items[i].height;
                    continue;
                }

                rectangle r(total_rect().left(), y, display_rect().right(), y+items[i].height-1);

                style->draw_list_box_item(c,r, display_rect(), enabled, *mfont, items[i].name, items[i].is_selected);

                y += items[i].height;

                if (y > area.bottom())
                    break;
            }
        }

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

        template <typename S>
        void list_box<S>::
        on_mouse_down (
            unsigned long btn,
            unsigned long state,
            long x,
            long y,
            bool is_double_click
        )
        {
            if (display_rect().contains(x,y) && btn == base_window::LEFT && enabled && !hidden )
            {
                if ( ms_enabled == false ||
                     ((!(state&base_window::CONTROL)) && !(state&base_window::SHIFT)))
                {
                    items.reset();
                    while (items.move_next())
                    {
                        items.element().is_selected = false;
                    }
                }

                y -= total_rect().top();
                long h = 0;
                for (unsigned long i = 0; i < items.size(); ++i)
                {
                    h += items[i].height;
                    if (h >= y)
                    {
                        if (ms_enabled)
                        {
                            if (state&base_window::CONTROL)
                            {
                                items[i].is_selected = !items[i].is_selected;
                                if (items[i].is_selected)
                                    last_selected = i;
                            }
                            else if (state&base_window::SHIFT)
                            {
                                // we want to select everything between (and including) the
                                // current thing clicked and last_selected.
                                const unsigned long first = std::min(i,last_selected);
                                const unsigned long last = std::max(i,last_selected);
                                for (unsigned long j = first; j <= last; ++j)
                                    items[j].is_selected = true;
                            }
                            else
                            {
                                items[i].is_selected = true;
                                last_selected = i;
                                if (is_double_click && event_handler.is_set())
                                    event_handler(i);
                                else if (single_click_event_handler.is_set())
                                    single_click_event_handler(i);
                            }
                        }
                        else
                        {
                            items[i].is_selected = true;
                            last_selected = i;
                            if (is_double_click && event_handler.is_set())
                                event_handler(i);
                            else if (single_click_event_handler.is_set())
                                single_click_event_handler(i);
                        }

                        break;
                    }
                }

                parent.invalidate_rectangle(rect);
            }
        }

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

        template <typename S>
        unsigned long list_box<S>::
        get_selected (
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( multiple_select_enabled() == false,
                    "\tunsigned long list_box::get_selected()"
                     );
            for (unsigned long i = 0; i < items.size(); ++i)
            {
                if (items[i].is_selected)
                    return i;
            }
            return items.size();
        }
    // ----------------------------------------------------------------------------------------

       // making instance of template
       template class list_box<std::string>;
       template class list_box<std::wstring>;
       template class list_box<dlib::ustring>;
       }

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

        namespace message_box_helper
        {
            void box_win::
            initialize (
            )
            {
                msg.set_pos(20,20);
                msg.set_text(message);
                rectangle msg_rect = msg.get_rect();
                btn_ok.set_name("OK");
                btn_ok.set_size(60,btn_ok.height());
                if (msg_rect.width() >= 60)
                    btn_ok.set_pos(msg_rect.width()/2+msg_rect.left()-btn_ok.width()/2,msg_rect.bottom()+15);
                else
                    btn_ok.set_pos(20,msg_rect.bottom()+15);
                btn_ok.set_click_handler(*this,&box_win::on_click);

                rectangle size = btn_ok.get_rect() + msg_rect;
                set_size(size.right()+20,size.bottom()+20);

                show();
                set_title(title);
            }

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

            box_win::
            box_win (
                const std::string& title_,
                const std::string& message_
            ) :
                drawable_window(false),
                title(convert_mbstring_to_wstring(title_)),
                message(convert_mbstring_to_wstring(message_)),
                msg(*this),
                btn_ok(*this)
            {
                initialize();
            }

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

            box_win::
            box_win (
                const std::wstring& title_,
                const std::wstring& message_
            ) :
                drawable_window(false),
                title(title_),
                message(message_),
                msg(*this),
                btn_ok(*this)
            {
                initialize();
            }

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

            box_win::
            box_win (
                const dlib::ustring& title_,
                const dlib::ustring& message_
            ) :
                drawable_window(false),
                title(convert_utf32_to_wstring(title_)),
                message(convert_utf32_to_wstring(message_)),
                msg(*this),
                btn_ok(*this)
            {
                initialize();
            }

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

            box_win::
            ~box_win (
            )
            {
                close_window();
            }

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

            void box_win::
            deleter_thread (
                void* param
            )
            {
                // The point of this extra event_handler stuff is to allow the user
                // to end the program from within the callback.  So we want to destroy the
                // window *before* we call their callback.
                box_win& w = *static_cast<box_win*>(param);
                w.close_window();
                any_function<void()> event_handler(w.event_handler);
                delete &w;
                if (event_handler.is_set())
                    event_handler();
            }

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

            void box_win::
            on_click (
            )
            {
                hide();
                create_new_thread(&deleter_thread,this);
            }

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

            base_window::on_close_return_code box_win::
            on_window_close (
            )
            {
                // The point of this extra event_handler stuff is to allow the user
                // to end the program within the callback.  So we want to destroy the
                // window *before* we call their callback.
                any_function<void()> event_handler_copy(event_handler);
                delete this;
                if (event_handler_copy.is_set())
                    event_handler_copy();
                return CLOSE_WINDOW;
            }

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

            void blocking_box_win::
            initialize (
            )
            {
                msg.set_pos(20,20);
                msg.set_text(message);
                rectangle msg_rect = msg.get_rect();
                btn_ok.set_name("OK");
                btn_ok.set_size(60,btn_ok.height());
                if (msg_rect.width() >= 60)
                    btn_ok.set_pos(msg_rect.width()/2+msg_rect.left()-btn_ok.width()/2,msg_rect.bottom()+15);
                else
                    btn_ok.set_pos(20,msg_rect.bottom()+15);
                btn_ok.set_click_handler(*this,&blocking_box_win::on_click);

                rectangle size = btn_ok.get_rect() + msg_rect;
                set_size(size.right()+20,size.bottom()+20);

                set_title(title);
                show();
            }

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

            blocking_box_win::
            blocking_box_win (
                const std::string& title_,
                const std::string& message_
            ) :
                drawable_window(false),
                title(convert_mbstring_to_wstring(title_)),
                message(convert_mbstring_to_wstring(message_)),
                msg(*this),
                btn_ok(*this)
            {
                initialize();
            }

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

            blocking_box_win::
            blocking_box_win (
                const std::wstring& title_,
                const std::wstring& message_
            ) :
                drawable_window(false),
                title(title_),
                message(message_),
                msg(*this),
                btn_ok(*this)
            {
                initialize();
            }

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

            blocking_box_win::
            blocking_box_win (
                const dlib::ustring& title_,
                const dlib::ustring& message_
            ) :
                drawable_window(false),
                title(convert_utf32_to_wstring(title_)),
                message(convert_utf32_to_wstring(message_)),
                msg(*this),
                btn_ok(*this)
            {
                initialize();
            }

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

            blocking_box_win::
            ~blocking_box_win (
            )
            {
                close_window();
            }

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

            void blocking_box_win::
            on_click (
            )
            {
                close_window();
            }

        }

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

        namespace open_file_box_helper
        {
            box_win::
            box_win (
                const std::string& title,
                bool has_text_field
            ) :
                lbl_dirs(*this),
                lbl_files(*this),
                lbl_file_name(*this),
                lb_dirs(*this),
                lb_files(*this),
                btn_ok(*this),
                btn_cancel(*this),
                btn_root(*this),
                tf_file_name(*this)
            {
                if (has_text_field == false)
                {
                    tf_file_name.hide();
                    lbl_file_name.hide();
                }
                else
                {
                    lbl_file_name.set_text("File: ");
                }

                cur_dir = -1;
                set_size(500,300);

                lbl_dirs.set_text("Directories:");
                lbl_files.set_text("Files:");
                btn_ok.set_name("Ok");
                btn_cancel.set_name("Cancel");
                btn_root.set_name("/");

                btn_root.set_click_handler(*this,&box_win::on_root_click);
                btn_cancel.set_click_handler(*this,&box_win::on_cancel_click);
                btn_ok.set_click_handler(*this,&box_win::on_open_click);
                lb_dirs.set_double_click_handler(*this,&box_win::on_dirs_click);
                lb_files.set_click_handler(*this,&box_win::on_files_click);
                lb_files.set_double_click_handler(*this,&box_win::on_files_double_click);

                btn_root.set_pos(5,5);

                set_sizes();
                set_title(title);

                on_root_click();

                // make it so that the file box starts out in our current working
                // directory
                std::string full_name(get_current_dir());

                while (full_name.size() > 0)
                {
                    std::string::size_type pos = full_name.find_first_of("\\/");
                    std::string left(full_name.substr(0,pos));
                    if (pos != std::string::npos)
                        full_name = full_name.substr(pos+1);
                    else
                        full_name.clear();

                    if (left.size() > 0)
                        enter_folder(left);
                }

                show();
            }

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

            box_win::
            ~box_win (
            )
            {
                close_window();
            }

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

            void box_win::
            set_sizes(
            )
            {
                unsigned long width, height;
                get_size(width,height);

                if (lbl_file_name.is_hidden())
                {
                    lbl_dirs.set_pos(0,btn_root.bottom()+5);
                    lb_dirs.set_pos(0,lbl_dirs.bottom());
                    lb_dirs.set_size(width/2,height-lb_dirs.top()-btn_cancel.height()-10);

                    lbl_files.set_pos(lb_dirs.right(),btn_root.bottom()+5);
                    lb_files.set_pos(lb_dirs.right(),lbl_files.bottom());
                    lb_files.set_size(width-lb_files.left(),height-lb_files.top()-btn_cancel.height()-10);

                    btn_ok.set_pos(width - btn_ok.width()-25,lb_files.bottom()+5);
                    btn_cancel.set_pos(btn_ok.left() - btn_cancel.width()-5,lb_files.bottom()+5);
                }
                else
                {

                    lbl_dirs.set_pos(0,btn_root.bottom()+5);
                    lb_dirs.set_pos(0,lbl_dirs.bottom());
                    lb_dirs.set_size(width/2,height-lb_dirs.top()-btn_cancel.height()-10-tf_file_name.height());

                    lbl_files.set_pos(lb_dirs.right(),btn_root.bottom()+5);
                    lb_files.set_pos(lb_dirs.right(),lbl_files.bottom());
                    lb_files.set_size(width-lb_files.left(),height-lb_files.top()-btn_cancel.height()-10-tf_file_name.height());

                    lbl_file_name.set_pos(lb_files.left(), lb_files.bottom()+8);
                    tf_file_name.set_pos(lbl_file_name.right(), lb_files.bottom()+5);
                    tf_file_name.set_width(width-tf_file_name.left()-5);

                    btn_ok.set_pos(width - btn_ok.width()-25,tf_file_name.bottom()+5);
                    btn_cancel.set_pos(btn_ok.left() - btn_cancel.width()-5,tf_file_name.bottom()+5);
                }

            }

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

            void box_win::
            on_window_resized (
            )
            {
                set_sizes();
            }

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

            void box_win::
            deleter_thread (
            )
            {
                close_window();
                delete this;
            }

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

            void box_win::
            enter_folder (
                const std::string& folder_name
            )
            {
                if (btn_root.is_checked())
                    btn_root.set_unchecked();
                if (cur_dir != -1)
                    sob[cur_dir]->set_unchecked();

                const std::string old_path = path;
                const long old_cur_dir = cur_dir;

                std::unique_ptr<toggle_button> new_btn(new toggle_button(*this));
                new_btn->set_name(folder_name);
                new_btn->set_click_handler(*this,&box_win::on_path_button_click);

                // remove any path buttons that won't be part of the path anymore
                if (sob.size())
                {
                    while (sob.size() > (unsigned long)(cur_dir+1))
                    {
                        std::unique_ptr<toggle_button> junk;
                        sob.remove(cur_dir+1,junk);
                    }
                }

                if (sob.size())
                    new_btn->set_pos(sob[sob.size()-1]->right()+5,sob[sob.size()-1]->top());
                else
                    new_btn->set_pos(btn_root.right()+5,btn_root.top());

                cur_dir = sob.size();
                sob.add(sob.size(),new_btn);

                path += folder_name + directory::get_separator();
                if (set_dir(prefix + path) == false)
                {
                    sob.remove(sob.size()-1,new_btn);
                    path = old_path;
                    cur_dir = old_cur_dir;
                }
                else
                {

                    sob[cur_dir]->set_checked();
                }
            }

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

            void box_win::
            on_dirs_click (
                unsigned long idx
            )
            {
                enter_folder(lb_dirs[idx]);
            }

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

            void box_win::
            on_files_click (
                unsigned long idx
            )
            {
                if (tf_file_name.is_hidden() == false)
                {
                    tf_file_name.set_text(lb_files[idx]);
                }
            }

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

            void box_win::
            on_files_double_click (
                unsigned long
            )
            {
                on_open_click();
            }

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

            void box_win::
            on_cancel_click (
            )
            {
                hide();
                create_new_thread<box_win,&box_win::deleter_thread>(*this);
            }

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

            void box_win::
            on_open_click (
            )
            {
                if (lb_files.get_selected() != lb_files.size() || tf_file_name.text().size() > 0)
                {
                    if (event_handler.is_set())
                    {
                        if (tf_file_name.is_hidden())
                            event_handler(prefix + path + lb_files[lb_files.get_selected()]);
                        else if (tf_file_name.text().size() > 0)
                            event_handler(prefix + path + tf_file_name.text());
                    }
                    hide();
                    create_new_thread<box_win,&box_win::deleter_thread>(*this);
                }
            }

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

            void box_win::
            on_path_button_click (
                toggle_button& btn
            )
            {
                if (btn_root.is_checked())
                    btn_root.set_unchecked();
                if (cur_dir != -1)
                    sob[cur_dir]->set_unchecked();
                std::string new_path;

                for (unsigned long i = 0; i < sob.size(); ++i)
                {
                    new_path += sob[i]->name() + directory::get_separator();
                    if (sob[i].get() == &btn)
                    {
                        cur_dir = i;
                        sob[i]->set_checked();
                        break;
                    }
                }
                if (path != new_path)
                {
                    path = new_path;
                    set_dir(prefix+path);
                }
            }

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

            struct case_insensitive_compare
            {
                bool operator() (
                    const std::string& a,
                    const std::string& b
                ) const
                {
                    std::string::size_type i, size;
                    size = std::min(a.size(),b.size());
                    for (i = 0; i < size; ++i)
                    {
                        if (std::tolower(a[i]) < std::tolower(b[i]))
                            return true;
                        else if (std::tolower(a[i]) > std::tolower(b[i]))
                            return false;
                    }
                    if (a.size() < b.size())
                        return true;
                    else
                        return false;
                }
            };

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

            bool box_win::
            set_dir (
                const std::string& dir
            )
            {
                try
                {
                    directory d(dir);
                    queue<directory>::kernel_1a_c qod;
                    queue<file>::kernel_1a_c qof;
                    queue<std::string>::sort_1a_c qos;
                    d.get_dirs(qod);
                    d.get_files(qof);

                    qod.reset();
                    while (qod.move_next())
                    {
                        std::string temp = qod.element().name();
                        qos.enqueue(temp);
                    }
                    qos.sort(case_insensitive_compare());
                    lb_dirs.load(qos);
                    qos.clear();

                    qof.reset();
                    while (qof.move_next())
                    {
                        std::string temp = qof.element().name();
                        qos.enqueue(temp);
                    }
                    qos.sort(case_insensitive_compare());
                    lb_files.load(qos);
                    return true;
                }
                catch (directory::listing_error& )
                {
                    return false;
                }
                catch (directory::dir_not_found&)
                {
                    return false;
                }
            }

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

            void box_win::
            on_root_click (
            )
            {
                btn_root.set_checked();
                if (cur_dir != -1)
                    sob[cur_dir]->set_unchecked();

                queue<directory>::kernel_1a_c qod, qod2;
                queue<file>::kernel_1a_c qof;
                queue<std::string>::sort_1a_c qos;
                get_filesystem_roots(qod);
                path.clear();
                cur_dir = -1;
                if (qod.size() == 1)
                {
                    qod.current().get_files(qof);
                    qod.current().get_dirs(qod2);
                    prefix = qod.current().full_name();

                    qod2.reset();
                    while (qod2.move_next())
                    {
                        std::string temp = qod2.element().name();
                        qos.enqueue(temp);
                    }
                    qos.sort(case_insensitive_compare());
                    lb_dirs.load(qos);
                    qos.clear();

                    qof.reset();
                    while (qof.move_next())
                    {
                        std::string temp = qof.element().name();
                        qos.enqueue(temp);
                    }
                    qos.sort(case_insensitive_compare());
                    lb_files.load(qos);
                }
                else
                {
                    prefix.clear();
                    qod.reset();
                    while (qod.move_next())
                    {
                        std::string temp = qod.element().full_name();
                        temp = temp.substr(0,temp.size()-1);
                        qos.enqueue(temp);
                    }
                    qos.sort(case_insensitive_compare());
                    lb_dirs.load(qos);
                    qos.clear();
                    lb_files.load(qos);
                }
            }

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

            base_window::on_close_return_code box_win::
            on_window_close (
            )
            {
                delete this;
                return CLOSE_WINDOW;
            }

        }

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

        menu_bar::
        menu_bar(
            drawable_window& w
        ) :
            drawable(w, 0xFFFF), // listen for all events
            open_menu(0)
        {
            adjust_position();
            enable_events();
        }

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

        menu_bar::
        ~menu_bar()
        {
            disable_events();
            parent.invalidate_rectangle(rect);
        }

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

        void menu_bar::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            mfont = f;
            adjust_position();
            compute_menu_geometry();
            parent.invalidate_rectangle(rect);
        }

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

        void menu_bar::
        set_number_of_menus (
            unsigned long num
        )
        {
            auto_mutex M(m);
            menus.set_max_size(num);
            menus.set_size(num);
            open_menu = menus.size();
            compute_menu_geometry();

            for (unsigned long i = 0; i < menus.size(); ++i)
            {
                menus[i].menu.set_on_hide_handler(*this,&menu_bar::on_popup_hide);
            }

            parent.invalidate_rectangle(rect);
        }

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

        unsigned long menu_bar::
        number_of_menus (
        ) const
        {
            auto_mutex M(m);
            return menus.size();
        }

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

        void menu_bar::
        set_menu_name (
            unsigned long idx,
            const std::string name,
            char underline_ch
        )
        {
            set_menu_name(idx, convert_to_utf32(name), underline_ch);
        }

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

        void menu_bar::
        set_menu_name (
            unsigned long idx,
            const std::wstring name,
            char underline_ch
        )
        {
            set_menu_name(idx, convert_to_utf32(name), underline_ch);
        }

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

        void menu_bar::
        set_menu_name (
            unsigned long idx,
            const dlib::ustring name,
            char underline_ch
        )
        {
            DLIB_ASSERT ( idx < number_of_menus() ,
                          "\tvoid menu_bar::set_menu_name()"
                          << "\n\tidx:               " << idx
                          << "\n\tnumber_of_menus(): " << number_of_menus()
            );
            auto_mutex M(m);
            menus[idx].name = name.c_str();
            menus[idx].underline_pos = name.find_first_of(underline_ch);
            compute_menu_geometry();
            parent.invalidate_rectangle(rect);
        }

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

        const std::string menu_bar::
        menu_name (
            unsigned long idx
        ) const
        {
            return convert_wstring_to_mbstring(menu_wname(idx));
        }

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

        const std::wstring menu_bar::
        menu_wname (
            unsigned long idx
        ) const
        {
            return convert_utf32_to_wstring(menu_uname(idx));
        }

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

        const dlib::ustring menu_bar::
        menu_uname (
            unsigned long idx
        ) const
        {
            DLIB_ASSERT ( idx < number_of_menus() ,
                          "\tstd::string menu_bar::menu_name()"
                          << "\n\tidx:               " << idx
                          << "\n\tnumber_of_menus(): " << number_of_menus()
            );
            auto_mutex M(m);
            return menus[idx].name.c_str();
        }

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

        popup_menu& menu_bar::
        menu (
            unsigned long idx
        )
        {
            DLIB_ASSERT ( idx < number_of_menus() ,
                          "\tpopup_menu& menu_bar::menu()"
                          << "\n\tidx:               " << idx
                          << "\n\tnumber_of_menus(): " << number_of_menus()
            );
            auto_mutex M(m);
            return menus[idx].menu;
        }

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

        const popup_menu& menu_bar::
        menu (
            unsigned long idx
        ) const
        {
            DLIB_ASSERT ( idx < number_of_menus() ,
                          "\tconst popup_menu& menu_bar::menu()"
                          << "\n\tidx:               " << idx
                          << "\n\tnumber_of_menus(): " << number_of_menus()
            );
            auto_mutex M(m);
            return menus[idx].menu;
        }

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

        void menu_bar::
        on_window_resized (
        )
        {
            adjust_position();
            hide_menu();
        }

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

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

            const unsigned char opacity = 40;
            fill_rect_with_vertical_gradient(c, rect,rgb_alpha_pixel(255,255,255,opacity),
                                             rgb_alpha_pixel(0,0,0,opacity));

            // first draw the border between the menu and the rest of the window
            draw_line(c, point(rect.left(),rect.bottom()-1),
                      point(rect.right(),rect.bottom()-1), 100);
            draw_line(c, point(rect.left(),rect.bottom()),
                      point(rect.right(),rect.bottom()), 255);

            // now draw all the menu buttons
            for (unsigned long i = 0; i < menus.size(); ++i)
            {
                mfont->draw_string(c,menus[i].rect, menus[i].name );
                if (menus[i].underline_p1 != menus[i].underline_p2)
                    draw_line(c, menus[i].underline_p1, menus[i].underline_p2);

                if (open_menu == i)
                {
                    fill_rect_with_vertical_gradient(c, menus[i].bgrect,rgb_alpha_pixel(255,255,0,40),  rgb_alpha_pixel(0,0,0,40));
                }
            }
        }

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

        void menu_bar::
        on_window_moved (
        )
        {
            hide_menu();
        }

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

        void menu_bar::
        on_focus_lost (
        )
        {
            hide_menu();
        }

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

        void menu_bar::
        on_mouse_down (
            unsigned long btn,
            unsigned long ,
            long x,
            long y,
            bool
        )
        {

            if (rect.contains(x,y) == false || btn != (unsigned long)base_window::LEFT)
            {
                hide_menu();
                return;
            }

            unsigned long old_menu = menus.size();

            // if a menu is currently open then save its index
            if (open_menu != menus.size())
            {
                old_menu = open_menu;
                hide_menu();
            }

            // figure out which menu should be open if any
            for (unsigned long i = 0; i < menus.size(); ++i)
            {
                if (menus[i].bgrect.contains(x,y))
                {
                    if (old_menu != i)
                        show_menu(i);

                    break;
                }
            }

        }

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

        void menu_bar::
        on_mouse_move (
            unsigned long ,
            long x,
            long y
        )
        {
            // if the mouse is over the menu_bar and some menu is currently open
            if (rect.contains(x,y) && open_menu != menus.size())
            {
                // if the mouse is still in the same rectangle then don't do anything
                if (menus[open_menu].bgrect.contains(x,y) == false)
                {
                    // figure out which menu should be instead
                    for (unsigned long i = 0; i < menus.size(); ++i)
                    {
                        if (menus[i].bgrect.contains(x,y))
                        {
                            show_menu(i);
                            break;
                        }
                    }

                }
            }
        }

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

        void menu_bar::
        on_keydown (
            unsigned long key,
            bool is_printable,
            unsigned long state
        )
        {
            if (state&base_window::KBD_MOD_ALT)
            {
                // check if the key matches any of our underlined keys
                for (unsigned long i = 0; i < menus.size(); ++i)
                {
                    // if we have found a matching key
                    if (is_printable &&
                        menus[i].underline_pos != std::string::npos &&
                        std::tolower(menus[i].name[menus[i].underline_pos]) == std::tolower(key))
                    {
                        show_menu(i);
                        menus[open_menu].menu.select_first_item();
                        return;
                    }
                }
            }

            if (open_menu != menus.size())
            {
                unsigned long i = open_menu;
                // if the submenu doesn't use this key for something then we will
                if (menus[open_menu].menu.forwarded_on_keydown(key,is_printable,state) == false)
                {
                    if (key == base_window::KEY_LEFT)
                    {
                        i = (i+menus.size()-1)%menus.size();
                        show_menu(i);
                        menus[open_menu].menu.select_first_item();
                    }
                    else if (key == base_window::KEY_RIGHT)
                    {
                        i = (i+1)%menus.size();
                        show_menu(i);
                        menus[open_menu].menu.select_first_item();
                    }
                    else if (key == base_window::KEY_ESC)
                    {
                        hide_menu();
                    }
                }
            }
        }

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

        void menu_bar::
        show_menu (
            unsigned long i
        )
        {
            rectangle temp;

            // menu already open so do nothing
            if (i == open_menu)
                return;

            // if a menu is currently open
            if (open_menu != menus.size())
            {
                menus[open_menu].menu.hide();
                temp = menus[open_menu].bgrect;
            }

            // display the new menu
            open_menu = i;
            long wx, wy;
            parent.get_pos(wx,wy);
            wx += menus[i].bgrect.left();
            wy += menus[i].bgrect.bottom()+1;
            menus[i].menu.set_pos(wx,wy);
            menus[i].menu.show();
            parent.invalidate_rectangle(menus[i].bgrect+temp);
        }

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

        void menu_bar::
        hide_menu (
        )
        {
            // if a menu is currently open
            if (open_menu != menus.size())
            {
                menus[open_menu].menu.hide();
                parent.invalidate_rectangle(menus[open_menu].bgrect);
                open_menu = menus.size();
            }
        }

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

        void menu_bar::
        on_popup_hide (
        )
        {
            // if a menu is currently open
            if (open_menu != menus.size())
            {
                parent.invalidate_rectangle(menus[open_menu].bgrect);
                open_menu = menus.size();
            }
        }

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

        void menu_bar::
        compute_menu_geometry (
        )
        {
            long x = 7;
            long bg_x = 0;
            for (unsigned long i = 0; i < menus.size(); ++i)
            {
                // compute the locations of the text rectangles
                menus[i].rect.set_top(5);
                menus[i].rect.set_left(x);
                menus[i].rect.set_bottom(rect.bottom()-2);

                unsigned long width, height;
                mfont->compute_size(menus[i].name,width,height);
                menus[i].rect = resize_rect_width(menus[i].rect, width);
                x = menus[i].rect.right()+10;

                menus[i].bgrect.set_top(0);
                menus[i].bgrect.set_left(bg_x);
                menus[i].bgrect.set_bottom(rect.bottom()-2);
                menus[i].bgrect.set_right(x-5);
                bg_x = menus[i].bgrect.right()+1;

                if (menus[i].underline_pos != std::string::npos)
                {
                    // now compute the location of the underline bar
                    rectangle r1 = mfont->compute_cursor_rect(
                        menus[i].rect,
                        menus[i].name,
                        menus[i].underline_pos);

                    rectangle r2 = mfont->compute_cursor_rect(
                        menus[i].rect,
                        menus[i].name,
                        menus[i].underline_pos+1);

                    menus[i].underline_p1.x() = r1.left()+1;
                    menus[i].underline_p2.x() = r2.left()-1;
                    menus[i].underline_p1.y() = r1.bottom()-mfont->height()+mfont->ascender()+2;
                    menus[i].underline_p2.y() = r2.bottom()-mfont->height()+mfont->ascender()+2;
                }
                else
                {
                    // there is no underline in this case
                    menus[i].underline_p1 = menus[i].underline_p2;
                }

            }
        }

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

        void menu_bar::
        adjust_position (
        )
        {
            unsigned long width, height;
            rectangle old(rect);
            parent.get_size(width,height);
            rect.set_left(0);
            rect.set_top(0);
            rect = resize_rect(rect,width,mfont->height()+10);
            parent.invalidate_rectangle(old+rect);
        }

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

        text_grid::
        text_grid (
            drawable_window& w
        ) :
            scrollable_region(w, KEYBOARD_EVENTS | MOUSE_CLICK | FOCUS_EVENTS ),
            has_focus(false),
            cursor_timer(*this,&text_grid::timer_action),
            border_color_(128,128,128)
        {

            cursor_timer.set_delay_time(500);
            set_vertical_scroll_increment(10);
            set_horizontal_scroll_increment(10);
            enable_events();
        }

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

        text_grid::
        ~text_grid (
        )
        {
            // 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();

            // wait for the timer to stop doing its thing
            cursor_timer.stop_and_wait();
            // Tell the parent window to redraw its area that previously contained this
            // drawable object.
            parent.invalidate_rectangle(rect);
        }

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

        void text_grid::
        set_grid_size (
            unsigned long rows,
            unsigned long cols
        )
        {
            auto_mutex M(m);
            row_height.set_max_size(rows);
            row_height.set_size(rows);

            col_width.set_max_size(cols);
            col_width.set_size(cols);

            grid.set_size(rows,cols);

            for (unsigned long i = 0; i < row_height.size(); ++i)
                row_height[i] = (mfont->height()*3)/2;
            for (unsigned long i = 0; i < col_width.size(); ++i)
                col_width[i] = mfont->height()*5;

            compute_total_rect();
            compute_bg_rects();
        }

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

        unsigned long text_grid::
        number_of_columns (
        ) const
        {
            auto_mutex M(m);
            return grid.nc();
        }

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

        unsigned long text_grid::
        number_of_rows (
        ) const
        {
            auto_mutex M(m);
            return grid.nr();
        }

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

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

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

        rgb_pixel text_grid::
        border_color (
        ) const
        {
            auto_mutex M(m);
            return border_color_;
        }

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

        void text_grid::
        set_border_color (
            rgb_pixel color
        )
        {
            auto_mutex M(m);
            border_color_ = color;
            parent.invalidate_rectangle(rect);
        }

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

        const std::string text_grid::
        text (
            unsigned long row,
            unsigned long col
        ) const
        {
            return convert_wstring_to_mbstring(wtext(row, col));
        }

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

        const std::wstring text_grid::
        wtext (
            unsigned long row,
            unsigned long col
        ) const
        {
            return convert_utf32_to_wstring(utext(row, col));
        }

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

        const dlib::ustring text_grid::
        utext (
            unsigned long row,
            unsigned long col
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows()  && col < number_of_columns(),
                          "\tconst std::string text_grid::text(row,col)"
                          << "\n\trow:              " << row
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\tthis:             " << this
            );
            return grid[row][col].text.c_str();
        }

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

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

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

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

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

        void text_grid::
        set_text (
            unsigned long row,
            unsigned long col,
            const dlib::ustring& str
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows()  && col < number_of_columns(),
                          "\tvoid text_grid::set_text(row,col)"
                          << "\n\trow:              " << row
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\tthis:             " << this
            );
            grid[row][col].text = str.c_str();
            parent.invalidate_rectangle(get_text_rect(row,col));
        }

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

        const rgb_pixel text_grid::
        text_color (
            unsigned long row,
            unsigned long col
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows()  && col < number_of_columns(),
                          "\tconst rgb_pixel text_grid::text_color(row,col)"
                          << "\n\trow:              " << row
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\tthis:             " << this
            );
            return grid[row][col].text_color;
        }

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

        void text_grid::
        set_text_color (
            unsigned long row,
            unsigned long col,
            const rgb_pixel color
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows()  && col < number_of_columns(),
                          "\tvoid text_grid::set_text_color(row,col,color)"
                          << "\n\trow:              " << row
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\tthis:             " << this
            );
            grid[row][col].text_color = color;
            parent.invalidate_rectangle(get_text_rect(row,col));
        }

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

        const rgb_pixel text_grid::
        background_color (
            unsigned long row,
            unsigned long col
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows()  && col < number_of_columns(),
                          "\tconst rgb_pixel text_grid::background_color(row,col,color)"
                          << "\n\trow:              " << row
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\tthis:             " << this
            );
            return grid[row][col].bg_color;
        }

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

        void text_grid::
        set_background_color (
            unsigned long row,
            unsigned long col,
            const rgb_pixel color
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows()  && col < number_of_columns(),
                          "\tvoid text_grid::set_background_color(row,col,color)"
                          << "\n\trow:              " << row
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\tthis:             " << this
            );
            grid[row][col].bg_color = color;
            parent.invalidate_rectangle(get_bg_rect(row,col));
        }

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

        bool text_grid::
        is_editable (
            unsigned long row,
            unsigned long col
        ) const
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows()  && col < number_of_columns(),
                          "\tbool text_grid::is_editable(row,col)"
                          << "\n\trow:              " << row
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\tthis:             " << this
            );
            return grid[row][col].is_editable;
        }

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

        void text_grid::
        set_editable (
            unsigned long row,
            unsigned long col,
            bool editable
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows()  && col < number_of_columns(),
                          "\tvoid text_grid::set_editable(row,col,editable)"
                          << "\n\trow:              " << row
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\teditable:         " << editable
                          << "\n\tthis:             " << this
            );
            grid[row][col].is_editable = editable;
            if (has_focus && active_row == static_cast<long>(row) && active_col == static_cast<long>(col))
            {
                drop_input_focus();
            }
        }

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

        void text_grid::
        set_column_width (
            unsigned long col,
            unsigned long width
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( col < number_of_columns(),
                          "\tvoid text_grid::set_column_width(col,width)"
                          << "\n\tcol:              " << col
                          << "\n\tnumber_of_columns(): " << number_of_columns()
                          << "\n\twidth:            " << width
                          << "\n\tthis:             " << this
            );
            col_width[col] = width;
            compute_total_rect();
            compute_bg_rects();
        }

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

        void text_grid::
        set_row_height (
            unsigned long row,
            unsigned long height
        )
        {
            auto_mutex M(m);
            DLIB_ASSERT ( row < number_of_rows() ,
                          "\tvoid text_grid::set_row_height(row,height)"
                          << "\n\trow:              " << row
                          << "\n\tnumber_of_rows(): " << number_of_rows()
                          << "\n\theight:           " << height
                          << "\n\tthis:             " << this
            );
            row_height[row] = height;
            compute_total_rect();
            compute_bg_rects();
        }

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

        void text_grid::
        disable (
        )
        {
            auto_mutex M(m);
            scrollable_region::disable();
            drop_input_focus();
        }

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

        void text_grid::
        hide (
        )
        {
            auto_mutex M(m);
            scrollable_region::hide();
            drop_input_focus();
        }

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

        void text_grid::
        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 (has_focus && !recent_cursor_move && enabled && !hidden)
            {
                show_cursor = !show_cursor;
                parent.invalidate_rectangle(get_text_rect(active_row,active_col));
            }
            recent_cursor_move = false;
        }

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

        void text_grid::
        timer_action (
        )
        {
            parent.trigger_user_event(this,scrollable_region::next_free_user_event_number());
        }

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

        void text_grid::
        compute_bg_rects (
        )
        {
            // loop over each element in the grid and figure out what its rectangle should be
            // with respect to the total_rect()
            point p1, p2;
            p1.y() = total_rect().top();
            for (long row = 0; row < grid.nr(); ++row)
            {
                p1.x() = total_rect().left();
                p2.y() = p1.y() + row_height[row]-1;
                for (long col = 0; col < grid.nc(); ++col)
                {
                    // if this is the last box in this row make it super wide so that it always
                    // goes to the end of the widget
                    if (col+1 == grid.nc())
                        p2.x() = 1000000;
                    else
                        p2.x() = p1.x() + col_width[col]-1;

                    // at this point p1 is the upper left corner of this box and p2 is the
                    // lower right corner of the box;
                    rectangle bg_rect(p1);
                    bg_rect += p2;

                    grid[row][col].bg_rect = translate_rect(bg_rect, -total_rect().left(), -total_rect().top());

                    p1.x() += 1 + col_width[col];
                }
                p1.y() += 1 + row_height[row];
            }
        }

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

        void text_grid::
        compute_total_rect (
        )
        {
            if (grid.size() == 0)
            {
                set_total_rect_size(0,0);
            }
            else
            {
                unsigned long width = col_width.size()-1;
                unsigned long height = row_height.size()-1;

                for (unsigned long i = 0; i < col_width.size(); ++i)
                    width += col_width[i];
                for (unsigned long i = 0; i < row_height.size(); ++i)
                    height += row_height[i];

                set_total_rect_size(width,height);
            }
        }

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

        void text_grid::
        on_keydown (
            unsigned long key,
            bool is_printable,
            unsigned long state
        )
        {
            // ignore this event if we are disabled or hidden
            if (!enabled || hidden)
                return;

            if (has_focus)
            {
                if (is_printable)
                {
                    // if the user hit the tab key then jump to the next box
                    if (key == '\t')
                    {
                        if (active_col+1 == grid.nc())
                        {
                            if (active_row+1 == grid.nr())
                                move_cursor(0,0,0);
                            else
                                move_cursor(active_row+1,0,0);
                        }
                        else
                        {
                            move_cursor(active_row,active_col+1,0);
                        }
                    }
                    if (key == '\n')
                    {
                        // ignore the enter key
                    }
                    else if (grid[active_row][active_col].is_editable)
                    {
                        // insert the key the user pressed into the string
                        grid[active_row][active_col].text.insert(cursor_pos,1,static_cast<char>(key));
                        move_cursor(active_row,active_col,cursor_pos+1);

                        if (text_modified_handler.is_set())
                            text_modified_handler(active_row,active_col);
                    }
                }
                else if ((state & base_window::KBD_MOD_CONTROL))
                {
                    if (key == base_window::KEY_LEFT)
                        move_cursor(active_row,active_col-1,0);
                    else if (key == base_window::KEY_RIGHT)
                        move_cursor(active_row,active_col+1,0);
                    else if (key == base_window::KEY_UP)
                        move_cursor(active_row-1,active_col,0);
                    else if (key == base_window::KEY_DOWN)
                        move_cursor(active_row+1,active_col,0);
                    else if (key == base_window::KEY_END)
                        move_cursor(active_row,active_col,grid[active_row][active_col].text.size());
                    else if (key == base_window::KEY_HOME)
                        move_cursor(active_row,active_col,0);
                }
                else
                {
                    if (key == base_window::KEY_LEFT)
                        move_cursor(active_row,active_col,cursor_pos-1);
                    else if (key == base_window::KEY_RIGHT)
                        move_cursor(active_row,active_col,cursor_pos+1);
                    else if (key == base_window::KEY_UP)
                        move_cursor(active_row-1,active_col,0);
                    else if (key == base_window::KEY_DOWN)
                        move_cursor(active_row+1,active_col,0);
                    else if (key == base_window::KEY_END)
                        move_cursor(active_row,active_col,grid[active_row][active_col].text.size());
                    else if (key == base_window::KEY_HOME)
                        move_cursor(active_row,active_col,0);
                    else if (key == base_window::KEY_BACKSPACE)
                    {
                        if (cursor_pos > 0 && grid[active_row][active_col].is_editable)
                        {
                            grid[active_row][active_col].text.erase(
                                grid[active_row][active_col].text.begin()+cursor_pos-1,
                                grid[active_row][active_col].text.begin()+cursor_pos);
                            move_cursor(active_row,active_col,cursor_pos-1);

                            if (text_modified_handler.is_set())
                                text_modified_handler(active_row,active_col);
                        }
                    }
                    else if (key == base_window::KEY_DELETE)
                    {
                        if (cursor_pos < static_cast<long>(grid[active_row][active_col].text.size()) &&
                            grid[active_row][active_col].is_editable)
                        {
                            grid[active_row][active_col].text.erase(
                                grid[active_row][active_col].text.begin()+cursor_pos);
                            move_cursor(active_row,active_col,cursor_pos);

                            if (text_modified_handler.is_set())
                                text_modified_handler(active_row,active_col);
                        }
                    }
                }
            } // if (has_focus)
        }

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

        void text_grid::
        on_mouse_down (
            unsigned long btn,
            unsigned long state,
            long x,
            long y,
            bool is_double_click
        )
        {
            scrollable_region::on_mouse_down(btn, state, x, y, is_double_click);
            if (display_rect().contains(x,y) && enabled && !hidden)
            {
                // figure out which box this click landed in
                rectangle hit;

                // find which column we hit
                unsigned long col = 0;
                long box_x = total_rect().left();
                for (unsigned long i = 0; i < col_width.size(); ++i)
                {
                    if (box_x <= x && (x < box_x+static_cast<long>(col_width[i]) || (i+1 == col_width.size())))
                    {
                        col = i;
                        hit.set_left(box_x);
                        hit.set_right(box_x+col_width[i]-1);
                        break;
                    }
                    else
                    {
                        box_x += col_width[i]+1;
                    }
                }

                // find which row we hit
                unsigned long row = 0;
                long box_y = total_rect().top();
                for (unsigned long i = 0; i < row_height.size(); ++i)
                {
                    if (box_y <= y && y < box_y+static_cast<long>(row_height[i]))
                    {
                        row = i;
                        hit.set_top(box_y);
                        hit.set_bottom(box_y+row_height[i]-1);
                        break;
                    }
                    else
                    {
                        box_y += row_height[i]+1;
                    }
                }

                // if we hit a box
                if (hit.is_empty() == false)
                {
                    move_cursor(row,
                                col,
                                mfont->compute_cursor_pos(get_text_rect(row,col), grid[row][col].text, x, y, grid[row][col].first)
                    );
                }
                else
                {
                    drop_input_focus();
                }
            }
            else
            {
                drop_input_focus();
            }
        }

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

        void text_grid::
        on_mouse_up (
            unsigned long btn,
            unsigned long state,
            long x,
            long y
        )
        {
            scrollable_region::on_mouse_up(btn, state, x, y);
        }

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

        void text_grid::
        on_focus_lost (
        )
        {
            drop_input_focus();
        }

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

        void text_grid::
        draw (
            const canvas& c
        ) const
        {
            scrollable_region::draw(c);
            rectangle area = c.intersect(display_rect());
            if (area.is_empty() == true)
                return;

            if (enabled)
                fill_rect(c, area, 255);

            // don't do anything if the grid is empty
            if (grid.size() == 0)
                return;

            // draw all the vertical lines
            point p1, p2;
            p1.x() = p2.x() = total_rect().left();
            p1.y() = total_rect().top();
            p2.y() = total_rect().bottom();
            for (unsigned long i = 0; i < col_width.size()-1; ++i)
            {
                p1.x() += col_width[i];
                p2.x() += col_width[i];
                if (enabled)
                    draw_line(c,p1,p2,border_color_,area);
                else
                    draw_line(c,p1,p2,128,area);
                p1.x() += 1;
                p2.x() += 1;
            }

            // draw all the horizontal lines
            p1.y() = p2.y() = total_rect().top();
            p1.x() = display_rect().left();
            p2.x() = display_rect().right();
            for (unsigned long i = 0; i < row_height.size(); ++i)
            {
                p1.y() += row_height[i];
                p2.y() += row_height[i];
                if (enabled)
                    draw_line(c,p1,p2,border_color_,area);
                else
                    draw_line(c,p1,p2,128,area);
                p1.y() += 1;
                p2.y() += 1;
            }

            // draw the backgrounds and text for each box
            for (long row = 0; row < grid.nr(); ++row)
            {
                for (long col = 0; col < grid.nc(); ++col)
                {
                    rectangle bg_rect(get_bg_rect(row,col));

                    rectangle text_rect(get_text_rect(row,col));

                    if (enabled)
                    {
                        fill_rect(c,bg_rect.intersect(area),grid[row][col].bg_color);

                        mfont->draw_string(c,
                                           text_rect,
                                           grid[row][col].text,
                                           grid[row][col].text_color,
                                           grid[row][col].first,
                                           std::string::npos,
                                           area);
                    }
                    else
                    {
                        mfont->draw_string(c,
                                           text_rect,
                                           grid[row][col].text,
                                           128,
                                           grid[row][col].first,
                                           std::string::npos,
                                           area);
                    }

                    // if this box has input focus then draw it with a cursor
                    if (has_focus && active_col == col && active_row == row && show_cursor)
                    {
                        rectangle cursor_rect = mfont->compute_cursor_rect(text_rect,
                                                                           grid[row][col].text,
                                                                           cursor_pos,
                                                                           grid[row][col].first);
                        draw_rectangle(c,cursor_rect,0,area);
                    }

                }
            }

        }

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

        rectangle text_grid::
        get_text_rect (
            unsigned long row,
            unsigned long col
        ) const
        {
            rectangle bg_rect(get_bg_rect(row,col));
            long padding = (bg_rect.height() - mfont->height())/2 + (bg_rect.height() - mfont->height())%2;
            if (padding < 0)
                padding = 0;
            bg_rect.set_left(bg_rect.left()+padding);
            bg_rect.set_top(bg_rect.top()+padding);
            bg_rect.set_right(bg_rect.right()-padding);
            bg_rect.set_bottom(bg_rect.bottom()-padding);
            return bg_rect;
        }

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

        rectangle text_grid::
        get_bg_rect (
            unsigned long row,
            unsigned long col
        ) const
        {
            return translate_rect(grid[row][col].bg_rect, total_rect().left(), total_rect().top());
        }

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

        void text_grid::
        drop_input_focus (
        )
        {
            if (has_focus)
            {
                parent.invalidate_rectangle(get_text_rect(active_row,active_col));
                has_focus = false;
                show_cursor = false;
                cursor_timer.stop();
            }
        }

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

        void text_grid::
        move_cursor (
            long row,
            long col,
            long new_cursor_pos
        )
        {
            // don't do anything if the grid is empty
            if (grid.size() == 0)
            {
                return;
            }

            if (row < 0)
                row = 0;
            if (row >= grid.nr())
                row = grid.nr()-1;
            if (col < 0)
                col = 0;
            if (col >= grid.nc())
                col = grid.nc()-1;

            if (new_cursor_pos < 0)
            {
                if (col == 0)
                {
                    new_cursor_pos = 0;
                }
                else
                {
                    --col;
                    new_cursor_pos = grid[row][col].text.size();
                }
            }

            if (new_cursor_pos > static_cast<long>(grid[row][col].text.size()))
            {
                if (col+1 == grid.nc())
                {
                    new_cursor_pos = grid[row][col].text.size();
                }
                else
                {
                    ++col;
                    new_cursor_pos = 0;
                }
            }

            // if some other box had the input focus then redraw it
            if (has_focus && (active_row != row || active_col != col ))
            {
                parent.invalidate_rectangle(get_text_rect(active_row,active_col));
            }

            if (has_focus == false)
            {
                cursor_timer.start();
            }

            has_focus = true;
            recent_cursor_move = true;
            show_cursor = true;
            active_row = row;
            active_col = col;
            cursor_pos = new_cursor_pos;

            // adjust the first character to draw so that the string is displayed well
            rectangle text_rect(get_text_rect(active_row,active_col));
            rectangle cursor_rect = mfont->compute_cursor_rect(text_rect,
                                                               grid[row][col].text,
                                                               cursor_pos,
                                                               grid[row][col].first);

            // if the cursor rect is too far to the left of the string
            if (cursor_pos < static_cast<long>(grid[row][col].first))
            {
                if (cursor_pos > 5)
                {
                    grid[row][col].first = cursor_pos - 5;
                }
                else
                {
                    grid[row][col].first = 0;
                }
            }
            // if the cursor rect is too far to the right of the string
            else if (cursor_rect.left() > text_rect.right())
            {
                long distance = (cursor_rect.left() - text_rect.right()) + text_rect.width()/3;
                // find the letter that is distance pixels from the start of the string
                long sum = 0;
                for (unsigned long i = grid[row][col].first; i < grid[row][col].text.size(); ++i)
                {
                    sum += (*mfont)[grid[row][col].text[i]].width();
                    if (sum >= distance)
                    {
                        grid[row][col].first = i;
                        break;
                    }
                }
            }

            scroll_to_rect(get_bg_rect(row,col));

            // redraw our box
            parent.invalidate_rectangle(text_rect);

        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
        // text_field object methods
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        rectangle text_box::
        get_text_rect (
        ) const
        {
            const unsigned long padding = style->get_padding(*mfont);

            rectangle text_rect;
            text_rect.set_left(total_rect().left()+padding);
            text_rect.set_top(total_rect().top()+padding);
            text_rect.set_right(total_rect().right()-padding);
            text_rect.set_bottom(total_rect().bottom()-padding);
            return text_rect;
        }

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

        void text_box::
        enable (
        )
        {
            scrollable_region::enable();
            right_click_menu.enable();
        }

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

        void text_box::
        on_cut (
        )
        {
            on_copy();
            on_delete_selected();
        }

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

        void text_box::
        on_copy (
        )
        {
            if (highlight_start <= highlight_end)
            {
                put_on_clipboard(text_.substr(highlight_start, highlight_end-highlight_start+1));
            }
        }

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

        void text_box::
        on_paste (
        )
        {
            ustring temp_str;
            get_from_clipboard(temp_str);

            if (highlight_start <= highlight_end)
            {
                text_ = text_.substr(0,highlight_start) + temp_str +
                    text_.substr(highlight_end+1,text_.size()-highlight_end-1);
                move_cursor(highlight_start+temp_str.size());
                highlight_start = 0;
                highlight_end = -1;
                parent.invalidate_rectangle(rect);
                on_no_text_selected();

                // send out the text modified event
                if (text_modified_handler.is_set())
                    text_modified_handler();
            }
            else
            {
                text_ = text_.substr(0,cursor_pos) + temp_str +
                    text_.substr(cursor_pos,text_.size()-cursor_pos);
                move_cursor(cursor_pos+temp_str.size());

                // send out the text modified event
                if (temp_str.size() != 0 && text_modified_handler.is_set())
                    text_modified_handler();
            }

            adjust_total_rect();
        }

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

        void text_box::
        on_select_all (
        )
        {
            move_cursor(static_cast<long>(text_.size()));
            highlight_start = 0;
            highlight_end = static_cast<long>(text_.size()-1);
            if (highlight_start <= highlight_end)
                on_text_is_selected();
            parent.invalidate_rectangle(rect);
        }

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

        void text_box::
        on_delete_selected (
        )
        {
            if (highlight_start <= highlight_end)
            {
                text_ = text_.erase(highlight_start,highlight_end-highlight_start+1);
                move_cursor(highlight_start);
                highlight_start = 0;
                highlight_end = -1;

                on_no_text_selected();
                // send out the text modified event
                if (text_modified_handler.is_set())
                    text_modified_handler();

                adjust_total_rect();

                parent.invalidate_rectangle(rect);
            }
        }

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

        void text_box::
        on_text_is_selected (
        )
        {
            right_click_menu.menu().enable_menu_item(0);
            right_click_menu.menu().enable_menu_item(1);
            right_click_menu.menu().enable_menu_item(3);
        }

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

        void text_box::
        on_no_text_selected (
        )
        {
            right_click_menu.menu().disable_menu_item(0);
            right_click_menu.menu().disable_menu_item(1);
            right_click_menu.menu().disable_menu_item(3);
        }

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

        void text_box::
        show (
        )
        {
            scrollable_region::show();
            right_click_menu.show();
        }

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

        void text_box::
        disable (
        )
        {
            auto_mutex M(m);
            scrollable_region::disable();
            t.stop();
            has_focus = false;
            cursor_visible = false;
            right_click_menu.disable();
        }

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

        void text_box::
        hide (
        )
        {
            auto_mutex M(m);
            scrollable_region::hide();
            t.stop();
            has_focus = false;
            cursor_visible = false;
        }

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

        void text_box::
        adjust_total_rect (
        )
        {
            const unsigned long padding = style->get_padding(*mfont);
            unsigned long text_width;
            unsigned long text_height;

            mfont->compute_size(text_, text_width, text_height);

            set_total_rect_size(text_width + padding*2, text_height + padding*2);
        }

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

        void text_box::
        set_main_font (
            const std::shared_ptr<font>& f
        )
        {
            auto_mutex M(m);
            mfont = f;
            adjust_total_rect();
            right_click_menu.set_rect(display_rect());
        }

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

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

            const point origin(total_rect().left(), total_rect().top());

            style->draw_text_box(c,display_rect(),get_text_rect(), enabled, *mfont, text_,
                                 translate_rect(cursor_rect, origin),
                                   text_color_, bg_color_, has_focus, cursor_visible, highlight_start,
                                   highlight_end);
        }

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

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

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

        void text_box::
        set_text (
            const dlib::ustring& text
        )
        {
            auto_mutex M(m);
            // do this to get rid of any reference counting that may be present in
            // the std::string implementation.
            text_ = text.c_str();

            adjust_total_rect();
            move_cursor(0);

            highlight_start = 0;
            highlight_end = -1;
        }

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

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

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

        const dlib::ustring text_box::
        utext (
        ) const
        {
            auto_mutex M(m);
            // do this to get rid of any reference counting that may be present in
            // the dlib::ustring implementation.
            dlib::ustring temp = text_.c_str();
            return temp;
        }

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

        void text_box::
        set_size (
            unsigned long width,
            unsigned long height
        )
        {
            auto_mutex M(m);
            scrollable_region::set_size(width,height);
            right_click_menu.set_rect(display_rect());
        }

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

        void text_box::
        set_pos (
            long x,
            long y
        )
        {
            scrollable_region::set_pos(x,y);
            right_click_menu.set_rect(get_text_rect());
        }

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

        void text_box::
        set_background_color (
            const rgb_pixel color
        )
        {
            auto_mutex M(m);
            bg_color_ = color;
            parent.invalidate_rectangle(rect);
        }

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

        const rgb_pixel text_box::
        background_color (
        ) const
        {
            auto_mutex M(m);
            return bg_color_;
        }

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

        void text_box::
        set_text_color (
            const rgb_pixel color
        )
        {
            auto_mutex M(m);
            text_color_ = color;
            parent.invalidate_rectangle(rect);
        }

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

        const rgb_pixel text_box::
        text_color (
        ) const
        {
            auto_mutex M(m);
            return text_color_;
        }

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

        void text_box::
        on_mouse_move (
            unsigned long state,
            long x,
            long y
        )
        {
            if (!enabled || hidden || !has_focus)
            {
                return;
            }

            if (state & base_window::LEFT)
            {
                if (highlight_start <= highlight_end)
                {
                    if (highlight_start == cursor_pos)
                        shift_pos = highlight_end + 1;
                    else
                        shift_pos = highlight_start;
                }

                unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y);
                if (static_cast<long>(new_pos) != cursor_pos)
                {
                    move_cursor(new_pos);
                    parent.invalidate_rectangle(rect);
                }
            }
            else if (shift_pos != -1)
            {
                shift_pos = -1;
            }

        }

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

        void text_box::
        on_mouse_up (
            unsigned long btn,
            unsigned long,
            long ,
            long
        )
        {
            if (!enabled || hidden)
                return;

            if (btn == base_window::LEFT)
                shift_pos = -1;
        }

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

        void text_box::
        on_mouse_down (
            unsigned long btn,
            unsigned long state,
            long x,
            long y,
            bool double_clicked
        )
        {
            using namespace std;
            if (!enabled || hidden || btn != (unsigned long)base_window::LEFT)
                return;

            if (display_rect().contains(x,y))
            {
                has_focus = true;
                cursor_visible = true;
                parent.invalidate_rectangle(rect);
                t.start();

                if (double_clicked)
                {
                    // highlight the double clicked word
                    string::size_type first, last;
                    const ustring ustr = convert_to_utf32(std::string(" \t\n"));
                    first = text_.substr(0,cursor_pos).find_last_of(ustr.c_str());
                    last = text_.find_first_of(ustr.c_str(),cursor_pos);
                    long f = static_cast<long>(first);
                    long l = static_cast<long>(last);
                    if (first == string::npos)
                        f = -1;
                    if (last == string::npos)
                        l = static_cast<long>(text_.size());

                    ++f;
                    --l;

                    move_cursor(l+1);
                    highlight_start = f;
                    highlight_end = l;
                    on_text_is_selected();
                }
                else
                {
                    if (state & base_window::SHIFT)
                    {
                        if (highlight_start <= highlight_end)
                        {
                            if (highlight_start == cursor_pos)
                                shift_pos = highlight_end + 1;
                            else
                                shift_pos = highlight_start;
                        }
                        else
                        {
                            shift_pos = cursor_pos;
                        }
                    }

                    bool at_end = false;
                    if (cursor_pos == 0 || cursor_pos == static_cast<long>(text_.size()))
                        at_end = true;
                    const long old_pos = cursor_pos;

                    unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y);
                    move_cursor(new_pos);

                    shift_pos = cursor_pos;

                    if (at_end && cursor_pos == old_pos)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                    }
                }

            }
            else if (has_focus && rect.contains(x,y) == false)
            {
                t.stop();
                has_focus = false;
                cursor_visible = false;
                shift_pos = -1;
                highlight_start = 0;
                highlight_end = -1;
                on_no_text_selected();

                if (focus_lost_handler.is_set())
                    focus_lost_handler();
                parent.invalidate_rectangle(rect);
            }
            else
            {
                has_focus = false;
            }
        }

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

        void text_box::
        on_keydown (
            unsigned long key,
            bool is_printable,
            unsigned long state
        )
        {
            // If the right click menu is up then we don't want to do anything with
            // the keyboard ourselves.  Let the popup menu use the keyboard for now.
            if (right_click_menu.popup_menu_visible())
                return;

            if (has_focus && enabled && !hidden)
            {
                const ustring space_str = convert_to_utf32(std::string(" \t\n"));
                const bool shift = (state&base_window::KBD_MOD_SHIFT) != 0;
                const bool ctrl = (state&base_window::KBD_MOD_CONTROL) != 0;

                if (shift && is_printable == false)
                {
                    if (shift_pos == -1)
                    {
                        if (highlight_start <= highlight_end)
                        {
                            if (highlight_start == cursor_pos)
                                shift_pos = highlight_end + 1;
                            else
                                shift_pos = highlight_start;
                        }
                        else
                        {
                            shift_pos = cursor_pos;
                        }
                    }
                }
                else
                {
                    shift_pos = -1;
                }

                if (key == base_window::KEY_LEFT)
                {
                    if (cursor_pos != 0)
                    {
                        unsigned long new_pos;
                        if (ctrl)
                        {
                            // find the first non-whitespace to our left
                            std::string::size_type pos = text_.find_last_not_of(space_str.c_str(),cursor_pos);
                            if (pos != std::string::npos)
                            {
                                pos = text_.find_last_of(space_str.c_str(),pos);
                                if (pos != std::string::npos)
                                    new_pos = static_cast<unsigned long>(pos);
                                else
                                    new_pos = 0;
                            }
                            else
                            {
                                new_pos = 0;
                            }
                        }
                        else
                        {
                            new_pos = cursor_pos-1;
                        }

                        move_cursor(new_pos);
                    }
                    else if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }

                }
                else if (key == base_window::KEY_RIGHT)
                {
                    if (cursor_pos != static_cast<long>(text_.size()))
                    {
                        unsigned long new_pos;
                        if (ctrl)
                        {
                            // find the first non-whitespace to our left
                            std::string::size_type pos = text_.find_first_not_of(space_str.c_str(),cursor_pos);
                            if (pos != std::string::npos)
                            {
                                pos = text_.find_first_of(space_str.c_str(),pos);
                                if (pos != std::string::npos)
                                    new_pos = static_cast<unsigned long>(pos+1);
                                else
                                    new_pos = static_cast<unsigned long>(text_.size());
                            }
                            else
                            {
                                new_pos = static_cast<unsigned long>(text_.size());
                            }
                        }
                        else
                        {
                            new_pos = cursor_pos+1;
                        }

                        move_cursor(new_pos);
                    }
                    else if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }
                else if (key == base_window::KEY_UP)
                {
                    if (ctrl)
                    {
                        move_cursor(0);
                    }
                    else
                    {
                        const point origin(total_rect().left(), total_rect().top());
                        // move the cursor so the position that is just a few pixels above
                        // the current cursor_rect
                        move_cursor(mfont->compute_cursor_pos(
                                get_text_rect(), text_, cursor_rect.left()+origin.x(),
                                cursor_rect.top()+origin.y()-mfont->height()/2));

                    }

                    if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }
                else if (key == base_window::KEY_DOWN)
                {
                    if (ctrl)
                    {
                        move_cursor(static_cast<unsigned long>(text_.size()));
                    }
                    else
                    {
                        const point origin(total_rect().left(), total_rect().top());
                        // move the cursor so the position that is just a few pixels above
                        // the current cursor_rect
                        move_cursor(mfont->compute_cursor_pos(
                                get_text_rect(), text_, cursor_rect.left()+origin.x(),
                                cursor_rect.bottom()+origin.y()+mfont->height()/2));
                    }

                    if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }
                else if (is_printable)
                {
                    if (ctrl)
                    {
                        if (key == 'a')
                        {
                            on_select_all();
                        }
                        else if (key == 'c')
                        {
                            on_copy();
                        }
                        else if (key == 'v')
                        {
                            on_paste();
                        }
                        else if (key == 'x')
                        {
                            on_cut();
                        }
                    }
                    else
                    {
                        if (highlight_start <= highlight_end)
                        {
                            text_ = text_.substr(0,highlight_start) + static_cast<unichar>(key) +
                                text_.substr(highlight_end+1,text_.size()-highlight_end-1);

                            adjust_total_rect();
                            move_cursor(highlight_start+1);
                            highlight_start = 0;
                            highlight_end = -1;
                            on_no_text_selected();
                        }
                        else
                        {
                            text_ = text_.substr(0,cursor_pos) + static_cast<unichar>(key) +
                                text_.substr(cursor_pos,text_.size()-cursor_pos);
                            adjust_total_rect();
                            move_cursor(cursor_pos+1);
                        }

                        // send out the text modified event
                        if (text_modified_handler.is_set())
                            text_modified_handler();

                    }

                    if (key == '\n')
                    {
                        if (enter_key_handler.is_set())
                            enter_key_handler();
                    }
                }
                else if (key == base_window::KEY_BACKSPACE)
                {
                    // if something is highlighted then delete that
                    if (highlight_start <= highlight_end)
                    {
                        on_delete_selected();
                    }
                    else if (cursor_pos != 0)
                    {
                        text_ = text_.erase(cursor_pos-1,1);
                        adjust_total_rect();
                        move_cursor(cursor_pos-1);

                        // send out the text modified event
                        if (text_modified_handler.is_set())
                            text_modified_handler();
                    }
                    else
                    {
                        // do this just so it repaints itself right
                        move_cursor(cursor_pos);
                    }

                }
                else if (key == base_window::KEY_DELETE)
                {
                    // if something is highlighted then delete that
                    if (highlight_start <= highlight_end)
                    {
                        on_delete_selected();
                    }
                    else if (cursor_pos != static_cast<long>(text_.size()))
                    {
                        text_ = text_.erase(cursor_pos,1);

                        adjust_total_rect();
                        // send out the text modified event
                        if (text_modified_handler.is_set())
                            text_modified_handler();
                    }
                    else
                    {
                        // do this just so it repaints itself right
                        move_cursor(cursor_pos);
                    }

                }
                else if (key == base_window::KEY_HOME)
                {
                    if (ctrl)
                    {
                        move_cursor(0);
                    }
                    else if (cursor_pos != 0)
                    {
                        // find the start of the current line
                        ustring::size_type pos = text_.find_last_of('\n',cursor_pos-1);
                        if (pos == ustring::npos)
                            pos = 0;
                        else
                            pos += 1;
                        move_cursor(static_cast<unsigned long>(pos));

                    }

                    if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }
                else if (key == base_window::KEY_END)
                {
                    if (ctrl)
                    {
                        move_cursor(static_cast<unsigned long>(text_.size()));
                    }
                    {
                        ustring::size_type pos = text_.find_first_of('\n',cursor_pos);
                        if (pos == ustring::npos)
                            pos = text_.size();

                        move_cursor(static_cast<unsigned long>(pos));
                    }

                    if (shift_pos == -1)
                    {
                        highlight_start = 0;
                        highlight_end = -1;
                        on_no_text_selected();
                        parent.invalidate_rectangle(rect);
                    }
                }
                else if (key == base_window::KEY_PAGE_DOWN || key == base_window::KEY_PAGE_UP)
                {
                    long jump_size = display_rect().height() -
                        std::min(mfont->height()*3, display_rect().height()/5);

                    // if we are supposed to page up then just jump in the other direction
                    if (key == base_window::KEY_PAGE_UP)
                        jump_size = -jump_size;

                    scroll_to_rect(translate_rect(display_rect(), point(0, jump_size )));
                }

                cursor_visible = true;
                recent_movement = true;

            }
        }

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

        void text_box::
        on_string_put(
            const std::wstring &str
        )
        {
            if (has_focus && enabled && !hidden)
            {
                ustring ustr = convert_to_utf32(str);
                if (highlight_start <= highlight_end)
                {
                    text_ = text_.substr(0,highlight_start) + ustr +
                        text_.substr(highlight_end+1,text_.size()-highlight_end-1);

                    adjust_total_rect();
                    move_cursor(highlight_start+ustr.size());
                    highlight_start = 0;
                    highlight_end = -1;
                    on_no_text_selected();
                }
                else
                {
                    text_ = text_.substr(0,cursor_pos) + ustr +
                        text_.substr(cursor_pos,text_.size()-cursor_pos);

                    adjust_total_rect();
                    move_cursor(cursor_pos+ustr.size());
                }

                // send out the text modified event
                if (text_modified_handler.is_set())
                    text_modified_handler();
            }
        }

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

        void text_box::
        move_cursor (
            unsigned long pos
        )
        {
            using namespace std;
            const long old_cursor_pos = cursor_pos;

            // figure out where the cursor is supposed to be
            cursor_rect = mfont->compute_cursor_rect(get_text_rect(), text_, pos);
            const point origin(total_rect().left(), total_rect().top());

            cursor_pos = pos;

            const unsigned long padding = style->get_padding(*mfont);

            // now scroll us so that we can see the current cursor
            scroll_to_rect(centered_rect(cursor_rect, cursor_rect.width() + padding + 6, cursor_rect.height() + 1));

            // adjust the cursor_rect so that it is relative to the total_rect
            cursor_rect = translate_rect(cursor_rect, -origin);

            parent.set_im_pos(cursor_rect.left(), cursor_rect.top());

            if (old_cursor_pos != cursor_pos)
            {
                if (shift_pos != -1)
                {
                    highlight_start = std::min(shift_pos,cursor_pos);
                    highlight_end = std::max(shift_pos,cursor_pos)-1;
                }

                if (highlight_start > highlight_end)
                    on_no_text_selected();
                else
                    on_text_is_selected();

                recent_movement = true;
                cursor_visible = true;
                parent.invalidate_rectangle(display_rect());
            }

            if (shift_pos == -1)
            {
                highlight_start = 0;
                highlight_end = -1;
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //       perspective_display member functions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        perspective_display::
        perspective_display(
            drawable_window& w
        ) :
            drawable(w,MOUSE_MOVE|MOUSE_CLICK|MOUSE_WHEEL)
        {
            clear_overlay();
            enable_events();
        }

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

        perspective_display::
        ~perspective_display(
        )
        {
            disable_events();
            parent.invalidate_rectangle(rect);
        }

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

        void perspective_display::
        set_size (
            unsigned long width,
            unsigned long height
        )
        {
            auto_mutex lock(m);
            rectangle old(rect);
            rect = resize_rect(rect,width,height);
            tform = camera_transform(tform.get_camera_pos(),
                tform.get_camera_looking_at(),
                tform.get_camera_up_direction(),
                tform.get_camera_field_of_view(),
                std::min(rect.width(),rect.height()));
            parent.invalidate_rectangle(old+rect);
        }

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

        void perspective_display::
        add_overlay (
            const std::vector<overlay_line>& overlay
        )
        {
            auto_mutex M(m);
            if (overlay.size() == 0)
                return;
            // push this new overlay into our overlay vector
            overlay_lines.insert(overlay_lines.end(), overlay.begin(), overlay.end());

            for (unsigned long i = 0; i < overlay.size(); ++i)
            {
                sum_pts += overlay[i].p1;
                sum_pts += overlay[i].p2;
                max_pts.x() = std::max(overlay[i].p1.x(), max_pts.x());
                max_pts.x() = std::max(overlay[i].p2.x(), max_pts.x());
                max_pts.y() = std::max(overlay[i].p1.y(), max_pts.y());
                max_pts.y() = std::max(overlay[i].p2.y(), max_pts.y());
                max_pts.z() = std::max(overlay[i].p1.z(), max_pts.z());
                max_pts.z() = std::max(overlay[i].p2.z(), max_pts.z());
            }

            tform = camera_transform(max_pts,
                sum_pts/(overlay_lines.size()*2+overlay_dots.size()),
                vector<double>(0,0,1),
                tform.get_camera_field_of_view(),
                std::min(rect.width(),rect.height()));

            // make the parent window redraw us now that we changed the overlay
            parent.invalidate_rectangle(rect);
        }

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

        void perspective_display::
        add_overlay (
            const std::vector<overlay_dot>& overlay
        )
        {
            auto_mutex M(m);
            if (overlay.size() == 0)
                return;

            for (unsigned long i = 0; i < overlay.size(); ++i)
            {
                overlay_dots.push_back(overlay[i]);

                sum_pts += overlay[i].p;
                max_pts.x() = std::max(overlay[i].p.x(), max_pts.x());
                max_pts.y() = std::max(overlay[i].p.y(), max_pts.y());
                max_pts.z() = std::max(overlay[i].p.z(), max_pts.z());
            }

            tform = camera_transform(max_pts,
                sum_pts/(overlay_lines.size()*2+overlay_dots.size()),
                vector<double>(0,0,1),
                tform.get_camera_field_of_view(),
                std::min(rect.width(),rect.height()));

            // make the parent window redraw us now that we changed the overlay
            parent.invalidate_rectangle(rect);
        }

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

        void perspective_display::
        clear_overlay (
        )
        {
            auto_mutex lock(m);
            overlay_dots.clear();
            overlay_lines.clear();
            sum_pts = vector<double>();
            max_pts = vector<double>(-std::numeric_limits<double>::infinity(),
                -std::numeric_limits<double>::infinity(),
                -std::numeric_limits<double>::infinity());

            parent.invalidate_rectangle(rect);
        }

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

        void perspective_display::
        set_dot_double_clicked_handler (
            const any_function<void(const vector<double>&)>& event_handler_
        )
        {
            auto_mutex M(m);
            dot_clicked_event_handler = event_handler_;
        }

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

        void perspective_display::
        draw (
            const canvas& c
        ) const
        {
            if (depth.nr() < (long)c.height() || depth.nc() < (long)c.width())
                depth.set_size(c.height(), c.width());
            assign_all_pixels(depth, std::numeric_limits<float>::infinity());

            rectangle area = rect.intersect(c);
            fill_rect(c, area, 0);
            for (unsigned long i = 0; i < overlay_lines.size(); ++i)
            {
                draw_line(c, tform(overlay_lines[i].p1)+rect.tl_corner(),
                             tform(overlay_lines[i].p2)+rect.tl_corner(),
                             overlay_lines[i].color,
                             area);
            }
            for (unsigned long i = 0; i < overlay_dots.size(); ++i)
            {
                double scale, distance;
                point p = tform(overlay_dots[i].p, scale, distance) + rect.tl_corner();
                if (area.contains(p) && depth[p.y()-c.top()][p.x()-c.left()] > distance)
                {
                    depth[p.y()-c.top()][p.x()-c.left()] = distance;
                    assign_pixel(c[p.y()-c.top()][p.x()-c.left()], overlay_dots[i].color);
                }
            }

        }

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

        void perspective_display::
        on_wheel_up (
            unsigned long //state
        )
        {
            if (rect.contains(lastx,lasty) == false || hidden || !enabled)
                return;

            const double alpha = 0.10;
            const vector<double> delta = alpha*(tform.get_camera_pos() - tform.get_camera_looking_at());
            tform = camera_transform(
                tform.get_camera_pos() - delta,
                tform.get_camera_looking_at(),
                tform.get_camera_up_direction(),
                tform.get_camera_field_of_view(),
                std::min(rect.width(),rect.height()));
            parent.invalidate_rectangle(rect);
        }

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

        void perspective_display::
        on_wheel_down (
            unsigned long //state
        )
        {
            if (rect.contains(lastx,lasty) == false || hidden || !enabled)
                return;

            const double alpha = 0.10;
            const vector<double> delta = alpha*(tform.get_camera_pos() - tform.get_camera_looking_at());
            tform = camera_transform(
                tform.get_camera_pos() + delta,
                tform.get_camera_looking_at(),
                tform.get_camera_up_direction(),
                tform.get_camera_field_of_view(),
                std::min(rect.width(),rect.height()));
            parent.invalidate_rectangle(rect);
        }

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

        void perspective_display::
        on_mouse_down (
            unsigned long btn,
            unsigned long, //state
            long x,
            long y,
            bool is_double_click
        )
        {
            if (btn == base_window::LEFT || btn == base_window::RIGHT)
            {
                last = point(x,y);
            }
            if (is_double_click && btn == base_window::LEFT && enabled && !hidden && overlay_dots.size() != 0)
            {
                double best_dist = std::numeric_limits<double>::infinity();
                unsigned long best_idx = 0;
                const dpoint pp(x,y);
                for (unsigned long i = 0; i < overlay_dots.size(); ++i)
                {
                    dpoint p = tform(overlay_dots[i].p) + rect.tl_corner();
                    double dist = length_squared(p-pp);
                    if (dist < best_dist)
                    {
                        best_dist = dist;
                        best_idx = i;
                    }
                }
                if (dot_clicked_event_handler.is_set())
                    dot_clicked_event_handler(overlay_dots[best_idx].p);
            }
        }

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

        void perspective_display::
        on_mouse_move (
            unsigned long state,
            long x,
            long y
        )
        {
            if (!enabled || hidden)
                return;

            if (state == base_window::LEFT)
            {
                const point cur(x, y);
                dpoint delta = last-cur;
                last = cur;

                const vector<double> radius = tform.get_camera_pos()-tform.get_camera_looking_at();
                delta *= 2*pi*length(radius)/600.0;
                vector<double> tangent_x = tform.get_camera_up_direction().cross(radius).normalize();
                vector<double> tangent_y = radius.cross(tangent_x).normalize();
                vector<double> new_pos = tform.get_camera_pos() + tangent_x*delta.x() + tangent_y*-delta.y();

                // now make it have the correct radius relative to the looking at point.
                new_pos = (new_pos-tform.get_camera_looking_at()).normalize()*length(radius) + tform.get_camera_looking_at();

                tform = camera_transform(new_pos,
                    tform.get_camera_looking_at(),
                    tangent_y,
                    tform.get_camera_field_of_view(),
                    std::min(rect.width(),rect.height()));
                parent.invalidate_rectangle(rect);
            }
            else if (state == (base_window::LEFT|base_window::SHIFT) ||
                state == base_window::RIGHT)
            {
                const point cur(x, y);
                dpoint delta = last-cur;
                last = cur;

                const vector<double> radius = tform.get_camera_pos()-tform.get_camera_looking_at();
                delta *= 2*pi*length(radius)/600.0;
                vector<double> tangent_x = tform.get_camera_up_direction().cross(radius).normalize();
                vector<double> tangent_y = radius.cross(tangent_x).normalize();

                vector<double> offset = tangent_x*delta.x() + tangent_y*-delta.y();

                tform = camera_transform(
                    tform.get_camera_pos()+offset,
                    tform.get_camera_looking_at()+offset,
                    tform.get_camera_up_direction(),
                    tform.get_camera_field_of_view(),
                    std::min(rect.width(),rect.height()));
                parent.invalidate_rectangle(rect);
            }
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //       image_display member functions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        namespace impl
        {
            class image_display_functor
            {
                const std::string str;
                const member_function_pointer<const std::string&> mfp;
            public:
                image_display_functor (
                    const std::string& str_,
                    const member_function_pointer<const std::string&>& mfp_
                ) : str(str_),
                    mfp(mfp_)
                {}

                void operator() (
                ) const { mfp(str); }
            };
        }

        image_display::
        image_display(
            drawable_window& w
        ):
            scrollable_region(w,KEYBOARD_EVENTS),
            zoom_in_scale(1),
            zoom_out_scale(1),
            drawing_rect(true),
            rect_is_selected(false),
            selected_rect(0),
            default_rect_color(255,0,0,255),
            parts_menu(w),
            part_width(100), // "parts" circles are drawn 1.0/part_width size on the screen relative to the size of the bounding rectangle.
            overlay_editing_enabled(true),
            highlight_timer(*this, &image_display::timer_event_unhighlight_rect),
            highlighted_rect(std::numeric_limits<unsigned long>::max()),
            holding_shift_key(false)
        {
            enable_mouse_drag();

            highlight_timer.set_delay_time(250);
            set_horizontal_scroll_increment(1);
            set_vertical_scroll_increment(1);
            set_horizontal_mouse_wheel_scroll_increment(30);
            set_vertical_mouse_wheel_scroll_increment(30);

            parts_menu.disable();

            enable_events();
        }

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

        void image_display::
        on_part_add (
            const std::string& part_name
        )
        {
            if (!rect_is_selected)
                return;

            const point loc = last_right_click_pos;

            // Transform loc from gui window space into the space used by the overlay
            // rectangles (i.e. relative to the raw image)
            const point origin(total_rect().tl_corner());
            point c1 = loc - origin;
            if (zoom_in_scale != 1)
            {
                c1 = c1/zoom_in_scale;
            }
            else if (zoom_out_scale != 1)
            {
                c1 = c1*zoom_out_scale;
            }

            overlay_rects[selected_rect].parts[part_name] = c1;
            parent.invalidate_rectangle(rect);

            if (event_handler.is_set())
                event_handler();
        }

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

        image_display::
        ~image_display(
        )
        {
            highlight_timer.stop_and_wait();
            disable_events();
            parent.invalidate_rectangle(rect);
        }

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

        rectangle image_display::
        get_image_display_rect (
        ) const
        {
            if (zoom_in_scale != 1)
            {
                return rectangle(0,0, img.nc()*zoom_in_scale-1, img.nr()*zoom_in_scale-1);
            }
            else if (zoom_out_scale != 1)
            {
                return rectangle(0,0, img.nc()/zoom_out_scale-1, img.nr()/zoom_out_scale-1);
            }
            else
            {
                return dlib::get_rect(img);
            }
        }

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

        void image_display::
        add_overlay (
            const overlay_rect& overlay
        )
        {
            auto_mutex M(m);
            // push this new overlay into our overlay vector
            overlay_rects.push_back(overlay);

            // make the parent window redraw us now that we changed the overlay
            parent.invalidate_rectangle(rect);
        }

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

        void image_display::
        add_overlay (
            const overlay_line& overlay
        )
        {
            auto_mutex M(m);

            // push this new overlay into our overlay vector
            overlay_lines.push_back(overlay);

            // make the parent window redraw us now that we changed the overlay
            parent.invalidate_rectangle(get_rect_on_screen(rectangle(overlay.p1, overlay.p2)));
        }

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

        void image_display::
        add_overlay (
            const overlay_circle& overlay
        )
        {
            auto_mutex M(m);

            // push this new overlay into our overlay vector
            overlay_circles.push_back(overlay);

            // make the parent window redraw us now that we changed the overlay
            parent.invalidate_rectangle(rect);
        }

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

        void image_display::
        add_overlay (
            const std::vector<overlay_rect>& overlay
        )
        {
            auto_mutex M(m);

            // push this new overlay into our overlay vector
            overlay_rects.insert(overlay_rects.end(), overlay.begin(), overlay.end());

            // make the parent window redraw us now that we changed the overlay
            parent.invalidate_rectangle(rect);
        }

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

        void image_display::
        add_overlay (
            const std::vector<overlay_line>& overlay
        )
        {
            auto_mutex M(m);

            // push this new overlay into our overlay vector
            overlay_lines.insert(overlay_lines.end(), overlay.begin(), overlay.end());

            // make the parent window redraw us now that we changed the overlay
            parent.invalidate_rectangle(rect);
        }

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

        void image_display::
        add_overlay (
            const std::vector<overlay_circle>& overlay
        )
        {
            auto_mutex M(m);

            // push this new overlay into our overlay vector
            overlay_circles.insert(overlay_circles.end(), overlay.begin(), overlay.end());

            // make the parent window redraw us now that we changed the overlay
            parent.invalidate_rectangle(rect);
        }

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

        void image_display::
        clear_overlay (
        )
        {
            auto_mutex M(m);
            overlay_rects.clear();
            overlay_lines.clear();
            overlay_circles.clear();
            parent.invalidate_rectangle(rect);
        }

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

        rectangle image_display::
        get_rect_on_screen (
            rectangle orect
        ) const
        {
            const point origin(total_rect().tl_corner());
            orect.left()   = orect.left()*zoom_in_scale/zoom_out_scale;
            orect.top()    = orect.top()*zoom_in_scale/zoom_out_scale;
            if (zoom_in_scale != 1)
            {
                // make it so the box surrounds the pixels when we zoom in.
                orect.right()  = (orect.right()+1)*zoom_in_scale/zoom_out_scale;
                orect.bottom() = (orect.bottom()+1)*zoom_in_scale/zoom_out_scale;
            }
            else
            {
                orect.right()  = orect.right()*zoom_in_scale/zoom_out_scale;
                orect.bottom() = orect.bottom()*zoom_in_scale/zoom_out_scale;
            }

            return translate_rect(orect, origin);
        }

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

        rectangle image_display::
        get_rect_on_screen (
            unsigned long idx
        ) const
        {
            return get_rect_on_screen(overlay_rects[idx].rect);
        }

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

        void image_display::
        draw (
            const canvas& c
        ) const
        {
            scrollable_region::draw(c);

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

            const point origin(total_rect().tl_corner());

            // draw the image on the screen
            const double scale = zoom_out_scale/(double)zoom_in_scale;
            const rectangle img_area = total_rect().intersect(area);
            for (long row = img_area.top(); row <= img_area.bottom(); ++row)
            {
                const long rc = row-c.top();
                const long rimg = (row-origin.y())*scale;
                for (long col = img_area.left(); col <= img_area.right(); ++col)
                {
                    assign_pixel(c[rc][col-c.left()],
                                 img[rimg][(col-origin.x())*scale]);
                }
            }

            // draw the mouse cross-hairs
            if (holding_shift_key && total_rect().contains(lastx,lasty) )
            {
                draw_line(c, point(lastx,-10000), point(lastx,100000),rgb_pixel(255,255,0), area);
                draw_line(c, point(-10000,lasty), point(100000,lasty),rgb_pixel(255,255,0), area);
            }

            // now draw all the overlay rectangles
            for (unsigned long i = 0; i < overlay_rects.size(); ++i)
            {
                const rectangle orect = get_rect_on_screen(i);
                rgb_alpha_pixel color = overlay_rects[i].color;
                // draw crossed out boxes slightly faded
                if (overlay_rects[i].crossed_out)
                    color.alpha = 150;

                if (rect_is_selected && selected_rect == i)
                {
                    draw_rectangle(c, orect, invert_pixel(color), area);
                }
                else if (highlighted_rect < overlay_rects.size() && highlighted_rect == i)
                {
                    // Draw the rectangle wider and with a slightly different color that tapers
                    // out at the edges of the line.
                    hsi_pixel temp;
                    assign_pixel(temp, 0);
                    assign_pixel(temp, overlay_rects[i].color);
                    temp.s = 255;
                    temp.h = temp.h + 20;
                    if (temp.i < 245)
                        temp.i += 10;
                    rgb_pixel p;
                    assign_pixel(p, temp);
                    rgb_alpha_pixel po, po2;
                    assign_pixel(po, p);
                    po.alpha = 160;
                    po2 = po;
                    po2.alpha = 90;
                    draw_rectangle(c, grow_rect(orect,2), po2, area);
                    draw_rectangle(c, grow_rect(orect,1), po, area);
                    draw_rectangle(c, orect, p, area);
                    draw_rectangle(c, shrink_rect(orect,1), po, area);
                    draw_rectangle(c, shrink_rect(orect,2), po2, area);
                }
                else
                {
                    draw_rectangle(c, orect, color, area);
                }

                if (overlay_rects[i].label.size() != 0)
                {
                    // make a rectangle that is at the spot we want to draw our string
                    rectangle r(orect.br_corner(),  c.br_corner());
                    mfont->draw_string(c, r, overlay_rects[i].label, color, 0,
                                       std::string::npos, area);
                }

                // draw circles for each "part" in this overlay rectangle.
                std::map<std::string,point>::const_iterator itr;
                for (itr = overlay_rects[i].parts.begin(); itr != overlay_rects[i].parts.end(); ++itr)
                {
                    if (itr->second == OBJECT_PART_NOT_PRESENT)
                        continue;

                    const long part_size = (long)std::max(1.0,std::round(std::sqrt(orect.area())/part_width));
                    rectangle temp = centered_rect(get_rect_on_screen(centered_rect(itr->second,1,1)), part_size, part_size);

                    if (rect_is_selected && selected_rect == i &&
                        selected_part_name.size() != 0 && selected_part_name == itr->first)
                    {
                        draw_circle(c, center(temp), temp.width(), invert_pixel(color), area);
                    }
                    else
                    {
                        draw_circle(c, center(temp), temp.width(), color, area);
                    }

                    // make a rectangle that is at the spot we want to draw our string
                    rectangle r((temp.br_corner() + temp.bl_corner())/2,
                                c.br_corner());
                    mfont->draw_string(c, r, itr->first, color, 0,
                                       std::string::npos, area);
                }

                if (overlay_rects[i].crossed_out)
                {
                    if (rect_is_selected && selected_rect == i)
                    {
                        draw_line(c, orect.tl_corner(), orect.br_corner(),invert_pixel(color), area);
                        draw_line(c, orect.bl_corner(), orect.tr_corner(),invert_pixel(color), area);
                    }
                    else
                    {
                        draw_line(c, orect.tl_corner(), orect.br_corner(),color, area);
                        draw_line(c, orect.bl_corner(), orect.tr_corner(),color, area);
                    }
                }
            }

            // now draw all the overlay lines
            for (unsigned long i = 0; i < overlay_lines.size(); ++i)
            {
                draw_line(c,
                          zoom_in_scale*(overlay_lines[i].p1+dpoint(0.5,0.5))/zoom_out_scale + origin,
                          zoom_in_scale*(overlay_lines[i].p2+dpoint(0.5,0.5))/zoom_out_scale + origin,
                          overlay_lines[i].color, area);
            }

            // now draw all the overlay circles
            for (unsigned long i = 0; i < overlay_circles.size(); ++i)
            {
                const dpoint center = (double)zoom_in_scale*(overlay_circles[i].center+dpoint(0.5,0.5))/zoom_out_scale + origin;
                const double radius = zoom_in_scale*overlay_circles[i].radius/zoom_out_scale;
                draw_circle(c,
                          center,
                          radius,
                          overlay_circles[i].color, area);

                if (overlay_circles[i].label.size() != 0)
                {
                    const point temp = center + dpoint(0,radius);

                    // make a rectangle that is at the spot we want to draw our string
                    rectangle r(temp,  c.br_corner());
                    mfont->draw_string(c, r, overlay_circles[i].label, overlay_circles[i].color, 0,
                                       std::string::npos, area);
                }
            }

            if (drawing_rect)
                draw_rectangle(c, rect_to_draw, invert_pixel(default_rect_color), area);
        }

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

        void image_display::
        on_keydown (
            unsigned long key,
            bool is_printable,
            unsigned long state
        )
        {
            scrollable_region::on_keydown(key,is_printable, state);

            if (!is_printable && key==base_window::KEY_SHIFT)
            {
                if (!holding_shift_key)
                {
                    holding_shift_key = true;
                    parent.invalidate_rectangle(rect);
                }
            }
            else if (holding_shift_key)
            {
                holding_shift_key = false;
                parent.invalidate_rectangle(rect);
            }

            if (!is_printable && !hidden && enabled && rect_is_selected &&
                (key == base_window::KEY_BACKSPACE || key == base_window::KEY_DELETE))
            {
                moving_overlay = false;
                rect_is_selected = false;
                parts_menu.disable();
                if (selected_part_name.size() == 0)
                    overlay_rects.erase(overlay_rects.begin() + selected_rect);
                else
                    overlay_rects[selected_rect].parts.erase(selected_part_name);
                parent.invalidate_rectangle(rect);

                if (event_handler.is_set())
                    event_handler();
            }

            if (!hidden && enabled && rect_is_selected &&
                ((is_printable && key == 'i') || (!is_printable && key==base_window::KEY_END)))
            {
                overlay_rects[selected_rect].crossed_out = !overlay_rects[selected_rect].crossed_out;
                parent.invalidate_rectangle(rect);

                if (event_handler.is_set())
                    event_handler();
            }
        }

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

        void image_display::
        add_labelable_part_name (
            const std::string& name
        )
        {
            auto_mutex lock(m);
            if (part_names.insert(name).second)
            {
                member_function_pointer<const std::string&> mfp;
                mfp.set(*this,&image_display::on_part_add);
                parts_menu.menu().add_menu_item(menu_item_text("Add " + name,impl::image_display_functor(name,mfp)));
            }
        }

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

        void image_display::
        clear_labelable_part_names (
        )
        {
            auto_mutex lock(m);
            part_names.clear();
            parts_menu.menu().clear();
        }

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

        namespace
        {

            bool only_contains_equal_sized_int_strings(
                const std::map<std::string,point>& parts
            )
            {
                if (parts.size() == 0)
                    return true;

                const size_t string_size = parts.begin()->first.size();
                for (const auto& p : parts)
                {
                    if (p.first.size() != string_size)
                        return false;
                    for (auto ch : p.first)
                        if (!std::isdigit(ch))
                            return false;
                }
                return true;
            }

            void zero_pad_part_names (
                std::map<std::string,point>& parts
            )
            {
                if (parts.size() < 5)
                    return;

                const size_t num_digits_required = 1+std::floor(std::log10(parts.size()-1));
                // if the first thing in the map has the right number of digits then assume
                // everything is fine.
                if (parts.begin()->first.size() == num_digits_required)
                    return;

                std::map<std::string,point> new_parts;

                for (auto& p : parts)
                {
                    auto key = p.first;
                    while (key.size() < num_digits_required)
                        key = '0' + key;
                    new_parts[key] = p.second;
                }

                parts.swap(new_parts);
            }

        }

        void image_display::
        on_mouse_down (
            unsigned long btn,
            unsigned long state,
            long x,
            long y,
            bool is_double_click
        )
        {
            scrollable_region::on_mouse_down(btn, state, x, y, is_double_click);

            if (state&base_window::SHIFT)
            {
                holding_shift_key = true;
            }
            else if (holding_shift_key)
            {
                holding_shift_key = false;
                parent.invalidate_rectangle(rect);
            }

            // if the user shift left clicks when a rect is selected add a part annotation, but
            // only if they haven't explicitly indicated part names.  If they have part names
            // then make them pick them from the right click menu.  But if they haven't set
            // part names then we will automatically create integer numbered parts starting from 0.
            // We will also only allow auto part numbering if all the parts are already
            // numbers.
            if (btn == base_window::LEFT && (state&base_window::SHIFT) && rect_is_selected &&
                part_names.size() == 0 && only_contains_equal_sized_int_strings(overlay_rects[selected_rect].parts))
            {
                // Find the next part name.  Just find the next unused integer starting from 0.
                int part_num = 0;
                size_t num_digits_required = 0;
                for (const auto& p : overlay_rects[selected_rect].parts)
                {
                    num_digits_required = p.first.size();
                    const int num = std::atoi(p.first.c_str());
                    if (num != part_num)
                        break;
                    ++part_num;
                }
                std::string part_name = std::to_string(part_num);
                // pad part name so it's the same length as the other parts.
                while (part_name.size() < num_digits_required)
                    part_name = '0' + part_name;

                const point loc(x,y);

                // Transform loc from gui window space into the space used by the overlay
                // rectangles (i.e. relative to the raw image)
                const point origin(total_rect().tl_corner());
                point c1 = loc - origin;
                if (zoom_in_scale != 1)
                {
                    c1 = c1/zoom_in_scale;
                }
                else if (zoom_out_scale != 1)
                {
                    c1 = c1*zoom_out_scale;
                }

                overlay_rects[selected_rect].parts[part_name] = c1;

                zero_pad_part_names(overlay_rects[selected_rect].parts);
                parent.invalidate_rectangle(rect);

                if (event_handler.is_set())
                    event_handler();

                return;
            }

            if (rect.contains(x,y) == false || hidden || !enabled)
                return;

            if (image_clicked_handler.is_set())
            {
                const point origin(total_rect().tl_corner());
                point p(x,y);
                p -= origin;
                if (zoom_in_scale != 1)
                    p = p/zoom_in_scale;
                else if (zoom_out_scale != 1)
                    p = p*zoom_out_scale;

                if (dlib::get_rect(img).contains(p))
                    image_clicked_handler(p, is_double_click, btn);
            }

            if (!overlay_editing_enabled)
                return;

            if (btn == base_window::RIGHT && (state&base_window::SHIFT))
            {
                const bool rect_was_selected = rect_is_selected;
                parts_menu.disable();

                long best_dist = std::numeric_limits<long>::max();
                long best_idx = 0;
                std::string best_part;

                // check if this click landed on any of the overlay rectangles
                for (unsigned long i = 0; i < overlay_rects.size(); ++i)
                {
                    const rectangle orect = get_rect_on_screen(i);

                    const long dist = distance_to_rect_edge(orect, point(x,y));

                    if (dist < best_dist)
                    {
                        best_dist = dist;
                        best_idx = i;
                        best_part.clear();
                    }

                    std::map<std::string,point>::const_iterator itr;
                    for (itr = overlay_rects[i].parts.begin(); itr != overlay_rects[i].parts.end(); ++itr)
                    {
                        if (itr->second == OBJECT_PART_NOT_PRESENT)
                            continue;

                        const long part_size = (long)std::max(1.0,std::round(std::sqrt(orect.area())/part_width));
                        rectangle temp = centered_rect(get_rect_on_screen(centered_rect(itr->second,1,1)), part_size, part_size);
                        point c = center(temp);

                        // distance from edge of part circle
                        const long dist = static_cast<long>(std::abs(length(c - point(x,y)) + 0.5 - temp.width()));
                        if (dist < best_dist)
                        {
                            best_idx = i;
                            best_dist = dist;
                            best_part = itr->first;
                        }
                    }
                }

                if (best_dist < 13)
                {
                    moving_overlay = true;
                    moving_rect = best_idx;
                    moving_part_name = best_part;
                    // If we are moving one of the sides  of the rectangle rather than one of
                    // the parts circles then we need to figure out which side of the rectangle
                    // we are moving.
                    if (best_part.size() == 0)
                    {
                        // which side is the click closest to?
                        const rectangle orect = get_rect_on_screen(best_idx);
                        const point p = nearest_point(orect,point(x,y));
                        long dist_left   = std::abs(p.x()-orect.left());
                        long dist_top    = std::abs(p.y()-orect.top());
                        long dist_right  = std::abs(p.x()-orect.right());
                        long dist_bottom = std::abs(p.y()-orect.bottom());
                        long min_val = std::min(std::min(dist_left,dist_right),std::min(dist_top,dist_bottom));
                        if (dist_left == min_val)
                            moving_what = MOVING_RECT_LEFT;
                        else if (dist_top == min_val)
                            moving_what = MOVING_RECT_TOP;
                        else if (dist_right == min_val)
                            moving_what = MOVING_RECT_RIGHT;
                        else
                            moving_what = MOVING_RECT_BOTTOM;
                    }
                    else
                    {
                        moving_what = MOVING_PART;
                    }
                    // Do this to make the moving stuff snap to the mouse immediately.
                    on_mouse_move(state|btn,x,y);
                }

                if (rect_was_selected)
                    parent.invalidate_rectangle(rect);

                return;
            }

            if (btn == base_window::RIGHT && rect_is_selected)
            {
                last_right_click_pos = point(x,y);
                parts_menu.set_rect(rect);
                return;
            }

            if (btn == base_window::LEFT && (state&base_window::CONTROL) && !drawing_rect)
            {
                long best_dist = std::numeric_limits<long>::max();
                long best_idx = 0;
                // check if this click landed on any of the overlay rectangles
                for (unsigned long i = 0; i < overlay_rects.size(); ++i)
                {
                    const rectangle orect = get_rect_on_screen(i);
                    const long dist = distance_to_rect_edge(orect, point(x,y));

                    if (dist < best_dist)
                    {
                        best_dist = dist;
                        best_idx = i;
                    }
                }
                if (best_dist < 13)
                {
                    overlay_rects[best_idx].label = default_rect_label;
                    overlay_rects[best_idx].color = default_rect_color;
                    highlighted_rect = best_idx;
                    highlight_timer.stop();
                    highlight_timer.start();
                    if (event_handler.is_set())
                        event_handler();
                    parent.invalidate_rectangle(rect);
                }
                return;
            }

            if (!is_double_click && btn == base_window::LEFT && (state&base_window::SHIFT))
            {
                drawing_rect = true;
                rect_anchor = point(x,y);

                if (rect_is_selected)
                {
                    rect_is_selected = false;
                    parts_menu.disable();
                    parent.invalidate_rectangle(rect);
                }
            }
            else if (drawing_rect)
            {
                if (rect_is_selected)
                {
                    rect_is_selected = false;
                    parts_menu.disable();
                }

                drawing_rect = false;
                parent.invalidate_rectangle(rect);
            }
            else if (is_double_click)
            {
                const bool rect_was_selected = rect_is_selected;
                rect_is_selected = false;
                parts_menu.disable();

                long best_dist = std::numeric_limits<long>::max();
                long best_idx = 0;
                std::string best_part;

                // check if this click landed on any of the overlay rectangles
                for (unsigned long i = 0; i < overlay_rects.size(); ++i)
                {
                    const rectangle orect = get_rect_on_screen(i);

                    const long dist = distance_to_rect_edge(orect, point(x,y));

                    if (dist < best_dist)
                    {
                        best_dist = dist;
                        best_idx = i;
                        best_part.clear();
                    }

                    std::map<std::string,point>::const_iterator itr;
                    for (itr = overlay_rects[i].parts.begin(); itr != overlay_rects[i].parts.end(); ++itr)
                    {
                        if (itr->second == OBJECT_PART_NOT_PRESENT)
                            continue;

                        const long part_size = (long)std::max(1.0,std::round(std::sqrt(orect.area())/part_width));
                        rectangle temp = centered_rect(get_rect_on_screen(centered_rect(itr->second,1,1)), part_size, part_size);
                        point c = center(temp);

                        // distance from edge of part circle
                        const long dist = static_cast<long>(std::abs(length(c - point(x,y)) + 0.5 - temp.width()));
                        if (dist < best_dist)
                        {
                            best_idx = i;
                            best_dist = dist;
                            best_part = itr->first;
                        }
                    }
                }

                if (best_dist < 13)
                {
                    rect_is_selected = true;
                    if (part_names.size() != 0)
                        parts_menu.enable();
                    selected_rect = best_idx;
                    selected_part_name = best_part;
                    if (orect_selected_event_handler.is_set())
                        orect_selected_event_handler(overlay_rects[best_idx]);
                }

                if (rect_is_selected || rect_was_selected)
                    parent.invalidate_rectangle(rect);
            }
            else if (rect_is_selected)
            {
                rect_is_selected = false;
                parts_menu.disable();
                parent.invalidate_rectangle(rect);
            }
        }

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

        std::vector<image_display::overlay_rect> image_display::
        get_overlay_rects (
        ) const
        {
            auto_mutex lock(m);
            return overlay_rects;
        }

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

        void image_display::
        set_default_overlay_rect_label (
            const std::string& label
        )
        {
            auto_mutex lock(m);
            default_rect_label = label;
        }

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

        std::string image_display::
        get_default_overlay_rect_label (
        ) const
        {
            auto_mutex lock(m);
            return default_rect_label;
        }

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

        void image_display::
        set_default_overlay_rect_color (
            const rgb_alpha_pixel& color
        )
        {
            auto_mutex lock(m);
            default_rect_color = color;
        }

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

        rgb_alpha_pixel image_display::
        get_default_overlay_rect_color (
        ) const
        {
            auto_mutex lock(m);
            return default_rect_color;
        }

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

        void image_display::
        on_mouse_up (
            unsigned long btn,
            unsigned long state,
            long x,
            long y
        )
        {
            scrollable_region::on_mouse_up(btn,state,x,y);

            if (state&base_window::SHIFT)
            {
                holding_shift_key = true;
            }
            else if (holding_shift_key)
            {
                holding_shift_key = false;
                parent.invalidate_rectangle(rect);
            }

            if (drawing_rect && btn == base_window::LEFT && (state&base_window::SHIFT) &&
                !hidden && enabled)
            {
                const point origin(total_rect().tl_corner());
                point c1 = point(x,y) - origin;
                point c2 = rect_anchor - origin;

                if (zoom_in_scale != 1)
                {
                    c1 = c1/(double)zoom_in_scale;
                    c2 = c2/(double)zoom_in_scale;
                }
                else if (zoom_out_scale != 1)
                {
                    c1 = c1*(double)zoom_out_scale;
                    c2 = c2*(double)zoom_out_scale;
                }

                rectangle new_rect(c1,c2);
                if (zoom_in_scale != 1)
                {
                    // When we are zoomed in we adjust the rectangles a little so they
                    // are drown surrounding the pixels inside the rect.  This adjustment
                    // is necessary to make this code consistent with this goal.
                    new_rect.right() -= 1;
                    new_rect.bottom() -= 1;
                }

                if (new_rect.width() > 0 && new_rect.height() > 0)
                {
                    add_overlay(overlay_rect(new_rect, default_rect_color, default_rect_label));

                    if (event_handler.is_set())
                        event_handler();
                }
            }

            if (drawing_rect)
            {
                drawing_rect = false;
                parent.invalidate_rectangle(rect);
            }
            if (moving_overlay)
            {
                moving_overlay = false;
            }
        }

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

        void image_display::
        on_mouse_move (
            unsigned long state,
            long x,
            long y
        )
        {
            scrollable_region::on_mouse_move(state,x,y);

            if (enabled && !hidden)
            {
                if (holding_shift_key)
                    parent.invalidate_rectangle(rect);

                if (state&base_window::SHIFT)
                    holding_shift_key = true;
                else if (holding_shift_key)
                    holding_shift_key = false;
            }

            if (drawing_rect)
            {
                if ((state&base_window::LEFT) && (state&base_window::SHIFT) && !hidden && enabled)
                {
                    rectangle new_rect(point(x,y), rect_anchor);
                    parent.invalidate_rectangle(new_rect + rect_to_draw);
                    rect_to_draw = new_rect;
                }
                else
                {
                    drawing_rect = false;
                    parent.invalidate_rectangle(rect);
                }
                moving_overlay = false;
            }
            else if (moving_overlay)
            {
                if ((state&base_window::RIGHT) && (state&base_window::SHIFT) && !hidden && enabled)
                {
                    // map point(x,y) into the image coordinate space.
                    point p = point(x,y) - total_rect().tl_corner();
                    if (zoom_in_scale != 1)
                    {
                        if (moving_what == MOVING_PART)
                            p = p/(double)zoom_in_scale-dpoint(0.5,0.5);
                        else
                            p = p/(double)zoom_in_scale;
                    }
                    else if (zoom_out_scale != 1)
                    {
                        p = p*(double)zoom_out_scale;
                    }

                    if (moving_what == MOVING_PART)
                    {
                        if (overlay_rects[moving_rect].parts[moving_part_name] != p)
                        {
                            overlay_rects[moving_rect].parts[moving_part_name] = p;
                            parent.invalidate_rectangle(rect);
                            if (event_handler.is_set())
                                event_handler();
                        }
                    }
                    else
                    {
                        rectangle original = overlay_rects[moving_rect].rect;
                        if (moving_what == MOVING_RECT_LEFT)
                            overlay_rects[moving_rect].rect.left() = std::min(p.x(), overlay_rects[moving_rect].rect.right());
                        else if (moving_what == MOVING_RECT_RIGHT)
                            overlay_rects[moving_rect].rect.right() = std::max(p.x()-1, overlay_rects[moving_rect].rect.left());
                        else if (moving_what == MOVING_RECT_TOP)
                            overlay_rects[moving_rect].rect.top() = std::min(p.y(), overlay_rects[moving_rect].rect.bottom());
                        else
                            overlay_rects[moving_rect].rect.bottom() = std::max(p.y()-1, overlay_rects[moving_rect].rect.top());

                        if (original != overlay_rects[moving_rect].rect)
                        {
                            parent.invalidate_rectangle(rect);
                            if (event_handler.is_set())
                                event_handler();
                        }
                    }
                }
                else
                {
                    moving_overlay = false;
                }
            }
        }

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

        void image_display::
        zoom_in (
        )
        {
            auto_mutex M(m);
            if (zoom_in_scale < 100 && zoom_out_scale == 1)
            {
                const point mouse_loc(lastx, lasty);
                // the pixel in img that the mouse is over
                const point pix_loc = (mouse_loc - total_rect().tl_corner())/zoom_in_scale;

                zoom_in_scale = zoom_in_scale*10/9 + 1;

                set_total_rect_size(img.nc()*zoom_in_scale, img.nr()*zoom_in_scale);

                // make is to the pixel under the mouse doesn't move while we zoom
                const point delta = total_rect().tl_corner() - (mouse_loc - pix_loc*zoom_in_scale);
                scroll_to_rect(translate_rect(display_rect(), delta));
            }
            else if (zoom_out_scale != 1)
            {
                const point mouse_loc(lastx, lasty);
                // the pixel in img that the mouse is over
                const point pix_loc = (mouse_loc - total_rect().tl_corner())*zoom_out_scale;

                zoom_out_scale = zoom_out_scale*9/10;
                if (zoom_out_scale == 0)
                    zoom_out_scale = 1;

                set_total_rect_size(img.nc()/zoom_out_scale, img.nr()/zoom_out_scale);

                // make is to the pixel under the mouse doesn't move while we zoom
                const point delta = total_rect().tl_corner() - (mouse_loc - pix_loc/zoom_out_scale);
                scroll_to_rect(translate_rect(display_rect(), delta));
            }
        }

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

        void image_display::
        on_wheel_up (
            unsigned long state
        )
        {
            // disable mouse wheel if the user is drawing a rectangle
            if (drawing_rect)
                return;

            // if CONTROL is not being held down
            if ((state & base_window::CONTROL) == 0)
            {
                scrollable_region::on_wheel_up(state);
                return;
            }

            if (rect.contains(lastx,lasty) == false || hidden || !enabled)
                return;

            zoom_in();
        }

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

        void image_display::
        zoom_out (
        )
        {
            auto_mutex M(m);
            if (zoom_in_scale != 1)
            {
                const point mouse_loc(lastx, lasty);
                // the pixel in img that the mouse is over
                const point pix_loc = (mouse_loc - total_rect().tl_corner())/zoom_in_scale;

                zoom_in_scale = zoom_in_scale*9/10;
                if (zoom_in_scale == 0)
                    zoom_in_scale = 1;

                set_total_rect_size(img.nc()*zoom_in_scale, img.nr()*zoom_in_scale);

                // make is to the pixel under the mouse doesn't move while we zoom
                const point delta = total_rect().tl_corner() - (mouse_loc - pix_loc*zoom_in_scale);
                scroll_to_rect(translate_rect(display_rect(), delta));
            }
            else if (std::max(img.nr(), img.nc())/zoom_out_scale > 10)
            {
                const point mouse_loc(lastx, lasty);
                // the pixel in img that the mouse is over
                const point pix_loc = (mouse_loc - total_rect().tl_corner())*zoom_out_scale;

                zoom_out_scale = zoom_out_scale*10/9 + 1;

                set_total_rect_size(img.nc()/zoom_out_scale, img.nr()/zoom_out_scale);

                // make is to the pixel under the mouse doesn't move while we zoom
                const point delta = total_rect().tl_corner() - (mouse_loc - pix_loc/zoom_out_scale);
                scroll_to_rect(translate_rect(display_rect(), delta));
            }
        }

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

        void image_display::
        on_wheel_down (
            unsigned long state
        )
        {
            // disable mouse wheel if the user is drawing a rectangle
            if (drawing_rect)
                return;

            // if CONTROL is not being held down
            if ((state & base_window::CONTROL) == 0)
            {
                scrollable_region::on_wheel_down(state);
                return;
            }

            if (rect.contains(lastx,lasty) == false || hidden || !enabled)
                return;

            zoom_out();
        }

    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------
    //         image_window member functions
    // ----------------------------------------------------------------------------------------
    // ----------------------------------------------------------------------------------------

        image_window::
        image_window(
        ) :
            gui_img(*this),
            window_has_closed(false),
            have_last_click(false),
            mouse_btn(0),
            clicked_signaler(this->wm),
            tie_input_events(false)
        {

            gui_img.set_image_clicked_handler(*this, &image_window::on_image_clicked);
            gui_img.disable_overlay_editing();
            // show this window on the screen
            show();
        }

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

        image_window::
        ~image_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();
        }

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

        base_window::on_close_return_code image_window::
        on_window_close(
        )
        {
            window_has_closed = true;
            clicked_signaler.broadcast();
            return base_window::CLOSE_WINDOW;
        }

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

        bool image_window::
        get_next_keypress (
            unsigned long& key,
            bool& is_printable,
            unsigned long& state
        )
        {
            auto_mutex lock(wm);
            while (have_last_keypress == false && !window_has_closed &&
                (have_last_click == false || !tie_input_events))
            {
                clicked_signaler.wait();
            }

            if (window_has_closed)
                return false;

            if (have_last_keypress)
            {
                // Mark that we are taking the key click so the next call to get_next_keypress()
                // will have to wait for another click.
                have_last_keypress = false;
                key = next_key;
                is_printable = next_is_printable;
                state = next_state;
                return true;
            }
            else
            {
                key = 0;
                is_printable = true;
                return false;
            }
        }

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

        void image_window::
        on_keydown (
            unsigned long key,
            bool is_printable,
            unsigned long state
        )
        {
            dlib::drawable_window::on_keydown(key,is_printable,state);

            have_last_keypress = true;
            next_key = key;
            next_is_printable = is_printable;
            next_state = state;
            clicked_signaler.signal();
        }

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

        void image_window::
        tie_events (
        )
        {
            auto_mutex lock(wm);
            tie_input_events = true;
        }

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

        void image_window::
        untie_events (
        )
        {
            auto_mutex lock(wm);
            tie_input_events = false;
        }

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

        bool image_window::
        events_tied (
        ) const
        {
            auto_mutex lock(wm);
            return tie_input_events;
        }

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

        bool image_window::
        get_next_double_click (
            point& p,
            unsigned long& mouse_button
        )
        {
            p = point(-1,-1);

            auto_mutex lock(wm);
            while (have_last_click == false && !window_has_closed &&
                (have_last_keypress==false || !tie_input_events))
            {
                clicked_signaler.wait();
            }

            if (window_has_closed)
                return false;

            if (have_last_click)
            {
                // Mark that we are taking the point click so the next call to
                // get_next_double_click() will have to wait for another click.
                have_last_click = false;
                mouse_button = mouse_btn;
                p = last_clicked_point;
                return true;
            }
            else
            {
                return false;
            }
        }

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

        void image_window::
        on_image_clicked (
            const point& p,
            bool is_double_click,
            unsigned long btn
        )
        {
            if (is_double_click)
            {
                have_last_click = true;
                last_clicked_point = p;
                mouse_btn = btn;
                clicked_signaler.signal();
            }
        }

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

        void image_window::
        add_overlay (
            const overlay_rect& overlay
        )
        {
            gui_img.add_overlay(overlay);
        }

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

        void image_window::
        add_overlay (
            const overlay_line& overlay
        )
        {
            gui_img.add_overlay(overlay);
        }

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

        void image_window::
        add_overlay (
            const overlay_circle& overlay
        )
        {
            gui_img.add_overlay(overlay);
        }

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

        void image_window::
        add_overlay (
            const std::vector<overlay_rect>& overlay
        )
        {
            gui_img.add_overlay(overlay);
        }

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

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

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

        void image_window::
        add_overlay (
            const std::vector<overlay_circle>& overlay
        )
        {
            gui_img.add_overlay(overlay);
        }

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

        void image_window::
        clear_overlay (
        )
        {
            gui_img.clear_overlay();
        }

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

        void image_window::
        on_window_resized(
        )
        {
            drawable_window::on_window_resized();
            unsigned long width, height;
            get_size(width,height);
            gui_img.set_size(width, height);

        }

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

    }

    #endif // DLIB_WIDGETs_CPP_