summaryrefslogtreecommitdiffstats
path: root/ml/dlib/dlib/gui_widgets/base_widgets.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-03-09 13:19:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-03-09 13:20:02 +0000
commit58daab21cd043e1dc37024a7f99b396788372918 (patch)
tree96771e43bb69f7c1c2b0b4f7374cb74d7866d0cb /ml/dlib/dlib/gui_widgets/base_widgets.cpp
parentReleasing debian version 1.43.2-1. (diff)
downloadnetdata-58daab21cd043e1dc37024a7f99b396788372918.tar.xz
netdata-58daab21cd043e1dc37024a7f99b396788372918.zip
Merging upstream version 1.44.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ml/dlib/dlib/gui_widgets/base_widgets.cpp')
-rw-r--r--ml/dlib/dlib/gui_widgets/base_widgets.cpp3343
1 files changed, 3343 insertions, 0 deletions
diff --git a/ml/dlib/dlib/gui_widgets/base_widgets.cpp b/ml/dlib/dlib/gui_widgets/base_widgets.cpp
new file mode 100644
index 000000000..2f2eb8e9c
--- /dev/null
+++ b/ml/dlib/dlib/gui_widgets/base_widgets.cpp
@@ -0,0 +1,3343 @@
+// Copyright (C) 2005 Davis E. King (davis@dlib.net), Keita Mochizuki
+// License: Boost Software License See LICENSE.txt for the full license.
+#ifndef DLIB_BASE_WIDGETs_CPP_
+#define DLIB_BASE_WIDGETs_CPP_
+
+#include <iostream>
+#include <memory>
+
+#include "base_widgets.h"
+#include "../assert.h"
+
+namespace dlib
+{
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+ // button object methods
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ void 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(style->get_invalidation_rect(rect+old));
+ btn_tooltip.set_size(width,height);
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ show (
+ )
+ {
+ button_action::show();
+ btn_tooltip.show();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ hide (
+ )
+ {
+ button_action::hide();
+ btn_tooltip.hide();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ enable (
+ )
+ {
+ button_action::enable();
+ btn_tooltip.enable();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ disable (
+ )
+ {
+ button_action::disable();
+ btn_tooltip.disable();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ set_tooltip_text (
+ const std::string& text
+ )
+ {
+ btn_tooltip.set_text(text);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ set_tooltip_text (
+ const std::wstring& text
+ )
+ {
+ btn_tooltip.set_text(text);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ set_tooltip_text (
+ const ustring& text
+ )
+ {
+ btn_tooltip.set_text(text);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ const std::string button::
+ tooltip_text (
+ ) const
+ {
+ return btn_tooltip.text();
+ }
+
+ const std::wstring button::
+ tooltip_wtext (
+ ) const
+ {
+ return btn_tooltip.wtext();
+ }
+
+ const dlib::ustring button::
+ tooltip_utext (
+ ) const
+ {
+ return btn_tooltip.utext();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ set_main_font (
+ const std::shared_ptr<font>& f
+ )
+ {
+ auto_mutex M(m);
+ mfont = f;
+ set_name(name_);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ set_pos (
+ long x,
+ long y
+ )
+ {
+ auto_mutex M(m);
+ button_action::set_pos(x,y);
+ btn_tooltip.set_pos(x,y);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ set_name (
+ const std::string& name
+ )
+ {
+ set_name(convert_mbstring_to_wstring(name));
+ }
+
+ void button::
+ set_name (
+ const std::wstring& name
+ )
+ {
+ set_name(convert_wstring_to_utf32(name));
+ }
+
+ void button::
+ set_name (
+ const 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(style->get_invalidation_rect(rect+old));
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ const std::string button::
+ name (
+ ) const
+ {
+ auto_mutex M(m);
+ std::string temp = convert_wstring_to_mbstring(wname());
+ // do this to get rid of any reference counting that may be present in
+ // the std::string implementation.
+ char c = temp[0];
+ temp[0] = c;
+ return temp;
+ }
+
+ const std::wstring button::
+ wname (
+ ) const
+ {
+ auto_mutex M(m);
+ std::wstring temp = convert_utf32_to_wstring(uname());
+ // do this to get rid of any reference counting that may be present in
+ // the std::wstring implementation.
+ wchar_t w = temp[0];
+ temp[0] = w;
+ return temp;
+ }
+
+ const dlib::ustring 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 dlib::ustring implementation.
+ temp[0] = name_[0];
+ return temp;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ on_button_up (
+ bool mouse_over
+ )
+ {
+ if (mouse_over)
+ {
+ // this is a valid button click
+ if (event_handler.is_set())
+ event_handler();
+ if (event_handler_self.is_set())
+ event_handler_self(*this);
+ }
+ if (button_up_handler.is_set())
+ button_up_handler(mouse_over);
+ if (button_up_handler_self.is_set())
+ button_up_handler_self(mouse_over,*this);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button::
+ on_button_down (
+ )
+ {
+ if (button_down_handler.is_set())
+ button_down_handler();
+ if (button_down_handler_self.is_set())
+ button_down_handler_self(*this);
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+ // draggable object methods
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ draggable::~draggable() {}
+
+// ----------------------------------------------------------------------------------------
+
+ void draggable::
+ on_mouse_move (
+ unsigned long state,
+ long x,
+ long y
+ )
+ {
+ if (drag && (state & base_window::LEFT) && enabled && !hidden)
+ {
+ // the user is trying to drag this object. we should calculate the new
+ // x and y positions for the upper left corner of this object's rectangle
+
+ long new_x = x - this->x;
+ long new_y = y - this->y;
+
+ // make sure these points are inside the draggable area.
+ if (new_x < area.left())
+ new_x = area.left();
+ if (new_x + static_cast<long>(rect.width()) - 1 > area.right())
+ new_x = area.right() - rect.width() + 1;
+
+ if (new_y + static_cast<long>(rect.height()) - 1 > area.bottom())
+ new_y = area.bottom() - rect.height() + 1;
+ if (new_y < area.top())
+ new_y = area.top();
+
+ // now make the new rectangle for this object
+ rectangle new_rect(
+ new_x,
+ new_y,
+ new_x + rect.width() - 1,
+ new_y + rect.height() - 1
+ );
+
+ // only do anything if this is a new rectangle and it is inside area
+ if (new_rect != rect && area.intersect(new_rect) == new_rect)
+ {
+ parent.invalidate_rectangle(new_rect + rect);
+ rect = new_rect;
+
+ // call the on_drag() event handler
+ on_drag();
+ }
+ }
+ else
+ {
+ drag = false;
+ on_drag_stop();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void draggable::
+ on_mouse_up (
+ unsigned long ,
+ unsigned long state,
+ long ,
+ long
+ )
+ {
+ if (drag && (state & base_window::LEFT) == 0)
+ {
+ drag = false;
+ on_drag_stop();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void draggable::
+ on_mouse_down (
+ unsigned long btn,
+ unsigned long ,
+ long x,
+ long y,
+ bool
+ )
+ {
+ if (enabled && !hidden && rect.contains(x,y) && btn == base_window::LEFT)
+ {
+ drag = true;
+ this->x = x - rect.left();
+ this->y = y - rect.top();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+ // mouse_over_event object methods
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ mouse_over_event::~mouse_over_event() {}
+
+// ----------------------------------------------------------------------------------------
+
+ void mouse_over_event::
+ on_mouse_leave (
+ )
+ {
+ if (is_mouse_over_)
+ {
+ is_mouse_over_ = false;
+ on_mouse_not_over();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void mouse_over_event::
+ on_mouse_move (
+ unsigned long ,
+ long x,
+ long y
+ )
+ {
+ if (rect.contains(x,y) == false)
+ {
+ if (is_mouse_over_)
+ {
+ is_mouse_over_ = false;
+ on_mouse_not_over();
+ }
+ }
+ else if (is_mouse_over_ == false)
+ {
+ is_mouse_over_ = true;
+ if (enabled && !hidden)
+ on_mouse_over();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ bool mouse_over_event::
+ is_mouse_over (
+ ) const
+ {
+ // check if the mouse is still really over this button
+ if (is_mouse_over_ && rect.contains(lastx,lasty) == false)
+ {
+ // trigger a user event to call on_mouse_not_over() and repaint this object.
+ // we must do this in another event because someone might call is_mouse_over()
+ // from draw() and you don't want this function to end up calling
+ // parent.invalidate_rectangle(). It would lead to draw() being called over
+ // and over.
+ parent.trigger_user_event((void*)this,drawable::next_free_user_event_number());
+ return false;
+ }
+
+ return is_mouse_over_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void mouse_over_event::
+ on_user_event (
+ int num
+ )
+ {
+ if (is_mouse_over_ && num == drawable::next_free_user_event_number())
+ {
+ is_mouse_over_ = false;
+ on_mouse_not_over();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+ // button_action object methods
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ button_action::~button_action() {}
+
+// ----------------------------------------------------------------------------------------
+
+ void button_action::
+ on_mouse_down (
+ unsigned long btn,
+ unsigned long ,
+ long x,
+ long y,
+ bool
+ )
+ {
+ if (enabled && !hidden && btn == base_window::LEFT && rect.contains(x,y))
+ {
+ is_depressed_ = true;
+ seen_click = true;
+ parent.invalidate_rectangle(rect);
+ on_button_down();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button_action::
+ on_mouse_not_over (
+ )
+ {
+ if (is_depressed_)
+ {
+ is_depressed_ = false;
+ parent.invalidate_rectangle(rect);
+ on_button_up(false);
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button_action::
+ on_mouse_move (
+ unsigned long state,
+ long x,
+ long y
+ )
+ {
+ // forward event to the parent class so it can do it's thing as well as us
+ mouse_over_event::on_mouse_move(state,x,y);
+
+ if (enabled == false || hidden == true)
+ return;
+
+
+ if ((state & base_window::LEFT) == 0)
+ {
+ seen_click = false;
+ if (is_depressed_)
+ {
+ is_depressed_ = false;
+ parent.invalidate_rectangle(rect);
+ on_button_up(false);
+ }
+
+ // the left button isn't down so we don't care about anything else
+ return;
+ }
+
+ if (rect.contains(x,y) == false)
+ {
+ if (is_depressed_)
+ {
+ is_depressed_ = false;
+ parent.invalidate_rectangle(rect);
+ on_button_up(false);
+ }
+ }
+ else if (is_depressed_ == false && seen_click)
+ {
+ is_depressed_ = true;
+ parent.invalidate_rectangle(rect);
+ on_button_down();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button_action::
+ on_mouse_up (
+ unsigned long btn,
+ unsigned long,
+ long x,
+ long y
+ )
+ {
+ if (enabled && !hidden && btn == base_window::LEFT)
+ {
+ if (is_depressed_)
+ {
+ is_depressed_ = false;
+ parent.invalidate_rectangle(rect);
+
+ if (rect.contains(x,y))
+ {
+ on_button_up(true);
+ }
+ else
+ {
+ on_button_up(false);
+ }
+ }
+ else if (seen_click && rect.contains(x,y))
+ {
+ // this case here covers the unlikly event that you click on a button,
+ // move the mouse off the button and then move it back very quickly and
+ // release the mouse button. It is possible that this mouse up event
+ // will occurr before any mouse move event so you might not have set
+ // that the button is depressed yet.
+
+ // So we should say that this triggers an on_button_down() event and
+ // then an on_button_up(true) event.
+
+ parent.invalidate_rectangle(rect);
+
+ on_button_down();
+ on_button_up(true);
+ }
+
+ seen_click = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ bool button_action::
+ is_depressed (
+ ) const
+ {
+ // check if the mouse is still really over this button
+ if (enabled && !hidden && is_depressed_ && rect.contains(lastx,lasty) == false)
+ {
+ // trigger a user event to call on_button_up() and repaint this object.
+ // we must do this in another event because someone might call is_depressed()
+ // from draw() and you don't want this function to end up calling
+ // parent.invalidate_rectangle(). It would lead to draw() being called over
+ // and over.
+ parent.trigger_user_event((void*)this,mouse_over_event::next_free_user_event_number());
+ return false;
+ }
+
+ return is_depressed_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void button_action::
+ on_user_event (
+ int num
+ )
+ {
+ // forward event to the parent class so it can do it's thing as well as us
+ mouse_over_event::on_user_event(num);
+
+ if (is_depressed_ && num == mouse_over_event::next_free_user_event_number())
+ {
+ is_depressed_ = false;
+ parent.invalidate_rectangle(rect);
+ on_button_up(false);
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+ // scroll_bar object methods
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ scroll_bar::
+ scroll_bar(
+ drawable_window& w,
+ bar_orientation orientation
+ ) :
+ drawable(w),
+ b1(w),
+ b2(w),
+ slider(w,*this,&scroll_bar::on_slider_drag),
+ ori(orientation),
+ top_filler(w,*this,&scroll_bar::top_filler_down,&scroll_bar::top_filler_up),
+ bottom_filler(w,*this,&scroll_bar::bottom_filler_down,&scroll_bar::bottom_filler_up),
+ pos(0),
+ max_pos(0),
+ js(10),
+ b1_timer(*this,&scroll_bar::b1_down_t),
+ b2_timer(*this,&scroll_bar::b2_down_t),
+ top_filler_timer(*this,&scroll_bar::top_filler_down_t),
+ bottom_filler_timer(*this,&scroll_bar::bottom_filler_down_t)
+ {
+ set_style(scroll_bar_style_default());
+
+ // don't show the slider when there is no place it can move.
+ slider.hide();
+
+ set_length(100);
+
+ b1.set_button_down_handler(*this,&scroll_bar::b1_down);
+ b2.set_button_down_handler(*this,&scroll_bar::b2_down);
+
+ b1.set_button_up_handler(*this,&scroll_bar::b1_up);
+ b2.set_button_up_handler(*this,&scroll_bar::b2_up);
+ b1.disable();
+ b2.disable();
+ enable_events();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ scroll_bar::
+ ~scroll_bar(
+ )
+ {
+ disable_events();
+ parent.invalidate_rectangle(rect);
+ // wait for all the timers to be stopped
+ b1_timer.stop_and_wait();
+ b2_timer.stop_and_wait();
+ top_filler_timer.stop_and_wait();
+ bottom_filler_timer.stop_and_wait();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ scroll_bar::bar_orientation scroll_bar::
+ orientation (
+ ) const
+ {
+ auto_mutex M(m);
+ return ori;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ set_length (
+ unsigned long length
+ )
+ {
+ auto_mutex M(m);
+ // make the min length be at least 1
+ if (length == 0)
+ {
+ length = 1;
+ }
+
+
+ parent.invalidate_rectangle(rect);
+
+ if (ori == HORIZONTAL)
+ {
+ rect.set_right(rect.left() + length - 1);
+ rect.set_bottom(rect.top() + style->get_width() - 1);
+
+ const long btn_size = style->get_button_length(rect.width(), max_pos);
+
+ b1.set_size(btn_size,style->get_width());
+ b2.set_size(btn_size,style->get_width());
+
+ slider.set_size(get_slider_size(),style->get_width());
+ }
+ else
+ {
+ rect.set_right(rect.left() + style->get_width() - 1);
+ rect.set_bottom(rect.top() + length - 1);
+
+ const long btn_size = style->get_button_length(rect.height(), max_pos);
+
+ b1.set_size(style->get_width(),btn_size);
+ b2.set_size(style->get_width(),btn_size);
+
+ slider.set_size(style->get_width(),get_slider_size());
+ }
+
+ // call this to put everything is in the right spot.
+ set_pos (rect.left(),rect.top());
+
+ if ((b2.get_rect().top() - b1.get_rect().bottom() - 1 <= 8 && ori == VERTICAL) ||
+ (b2.get_rect().left() - b1.get_rect().right() - 1 <= 8 && ori == HORIZONTAL) ||
+ max_pos == 0)
+ {
+ hide_slider();
+ }
+ else if (enabled && !hidden)
+ {
+ show_slider();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ set_pos (
+ long x,
+ long y
+ )
+ {
+ auto_mutex M(m);
+ drawable::set_pos(x,y);
+
+ b1.set_pos(rect.left(),rect.top());
+ if (ori == HORIZONTAL)
+ {
+ // make the b2 button appear at the end of the scroll_bar
+ b2.set_pos(rect.right()-b2.get_rect().width() + 1,rect.top());
+
+ if (max_pos != 0)
+ {
+ double range = b2.get_rect().left() - b1.get_rect().right() - slider.get_rect().width() - 1;
+ double slider_pos = pos;
+ slider_pos /= max_pos;
+ slider_pos *= range;
+ slider.set_pos(
+ static_cast<long>(slider_pos)+rect.left() + b1.get_rect().width(),
+ rect.top()
+ );
+
+ // move the draggable area for the slider to the new location
+ rectangle area = rect;
+ area.set_left(area.left() + style->get_width());
+ area.set_right(area.right() - style->get_width());
+ slider.set_draggable_area(area);
+
+ }
+
+
+ }
+ else
+ {
+ // make the b2 button appear at the end of the scroll_bar
+ b2.set_pos(rect.left(), rect.bottom() - b2.get_rect().height() + 1);
+
+ if (max_pos != 0)
+ {
+ double range = b2.get_rect().top() - b1.get_rect().bottom() - slider.get_rect().height() - 1;
+ double slider_pos = pos;
+ slider_pos /= max_pos;
+ slider_pos *= range;
+ slider.set_pos(
+ rect.left(),
+ static_cast<long>(slider_pos) + rect.top() + b1.get_rect().height()
+ );
+
+ // move the draggable area for the slider to the new location
+ rectangle area = rect;
+ area.set_top(area.top() + style->get_width());
+ area.set_bottom(area.bottom() - style->get_width());
+ slider.set_draggable_area(area);
+ }
+ }
+
+ adjust_fillers();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ unsigned long scroll_bar::
+ get_slider_size (
+ ) const
+ {
+ if (ori == HORIZONTAL)
+ return style->get_slider_length(rect.width(),max_pos);
+ else
+ return style->get_slider_length(rect.height(),max_pos);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ adjust_fillers (
+ )
+ {
+ rectangle top(rect), bottom(rect);
+
+ if (ori == HORIZONTAL)
+ {
+ if (slider.is_hidden())
+ {
+ top.set_left(b1.get_rect().right()+1);
+ top.set_right(b2.get_rect().left()-1);
+ bottom.set_left(1);
+ bottom.set_right(-1);
+ }
+ else
+ {
+ top.set_left(b1.get_rect().right()+1);
+ top.set_right(slider.get_rect().left()-1);
+ bottom.set_left(slider.get_rect().right()+1);
+ bottom.set_right(b2.get_rect().left()-1);
+ }
+ }
+ else
+ {
+ if (slider.is_hidden())
+ {
+ top.set_top(b1.get_rect().bottom()+1);
+ top.set_bottom(b2.get_rect().top()-1);
+ bottom.set_top(1);
+ bottom.set_bottom(-1);
+ }
+ else
+ {
+ top.set_top(b1.get_rect().bottom()+1);
+ top.set_bottom(slider.get_rect().top()-1);
+ bottom.set_top(slider.get_rect().bottom()+1);
+ bottom.set_bottom(b2.get_rect().top()-1);
+ }
+ }
+
+ top_filler.rect = top;
+ bottom_filler.rect = bottom;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ hide_slider (
+ )
+ {
+ rectangle top(rect), bottom(rect);
+ slider.hide();
+ top_filler.disable();
+ bottom_filler.disable();
+ bottom_filler.hide();
+ if (ori == HORIZONTAL)
+ {
+ top.set_left(b1.get_rect().right()+1);
+ top.set_right(b2.get_rect().left()-1);
+ }
+ else
+ {
+ top.set_top(b1.get_rect().bottom()+1);
+ top.set_bottom(b2.get_rect().top()-1);
+ }
+ top_filler.rect = top;
+ bottom_filler.rect = bottom;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ show_slider (
+ )
+ {
+ if ((b2.get_rect().top() - b1.get_rect().bottom() - 1 <= 8 && ori == VERTICAL) ||
+ (b2.get_rect().left() - b1.get_rect().right() - 1 <= 8 && ori == HORIZONTAL) ||
+ max_pos == 0)
+ return;
+
+ rectangle top(rect), bottom(rect);
+ slider.show();
+ top_filler.enable();
+ bottom_filler.enable();
+ bottom_filler.show();
+ if (ori == HORIZONTAL)
+ {
+ top.set_left(b1.get_rect().right()+1);
+ top.set_right(slider.get_rect().left()-1);
+ bottom.set_left(slider.get_rect().right()+1);
+ bottom.set_right(b2.get_rect().left()-1);
+ }
+ else
+ {
+ top.set_top(b1.get_rect().bottom()+1);
+ top.set_bottom(slider.get_rect().top()-1);
+ bottom.set_top(slider.get_rect().bottom()+1);
+ bottom.set_bottom(b2.get_rect().top()-1);
+ }
+ top_filler.rect = top;
+ bottom_filler.rect = bottom;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ long scroll_bar::
+ max_slider_pos (
+ ) const
+ {
+ auto_mutex M(m);
+ return max_pos;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ set_max_slider_pos (
+ long mpos
+ )
+ {
+ auto_mutex M(m);
+ max_pos = mpos;
+ if (pos > mpos)
+ pos = mpos;
+
+ if (ori == HORIZONTAL)
+ set_length(rect.width());
+ else
+ set_length(rect.height());
+
+ if (mpos != 0 && enabled)
+ {
+ b1.enable();
+ b2.enable();
+ }
+ else
+ {
+ b1.disable();
+ b2.disable();
+ }
+
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ set_slider_pos (
+ long pos
+ )
+ {
+ auto_mutex M(m);
+ if (pos < 0)
+ pos = 0;
+ if (pos > max_pos)
+ pos = max_pos;
+
+ this->pos = pos;
+
+ // move the slider object to its new position
+ set_pos(rect.left(),rect.top());
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ long scroll_bar::
+ slider_pos (
+ ) const
+ {
+ auto_mutex M(m);
+ return pos;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ on_slider_drag (
+ )
+ {
+ if (ori == HORIZONTAL)
+ {
+ double slider_pos = slider.get_rect().left() - b1.get_rect().right() - 1;
+ double range = b2.get_rect().left() - b1.get_rect().right() - slider.get_rect().width() - 1;
+ slider_pos /= range;
+ slider_pos *= max_pos;
+ pos = static_cast<unsigned long>(slider_pos);
+ }
+ else
+ {
+ double slider_pos = slider.get_rect().top() - b1.get_rect().bottom() - 1;
+ double range = b2.get_rect().top() - b1.get_rect().bottom() - slider.get_rect().height() - 1;
+ slider_pos /= range;
+ slider_pos *= max_pos;
+ pos = static_cast<unsigned long>(slider_pos);
+ }
+
+ adjust_fillers();
+
+ if (scroll_handler.is_set())
+ scroll_handler();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ draw (
+ const canvas&
+ ) const
+ {
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ b1_down (
+ )
+ {
+ if (pos != 0)
+ {
+ set_slider_pos(pos-1);
+ if (scroll_handler.is_set())
+ scroll_handler();
+
+ if (b1_timer.delay_time() == 1000)
+ b1_timer.set_delay_time(500);
+ else
+ b1_timer.set_delay_time(50);
+ b1_timer.start();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ b1_up (
+ bool
+ )
+ {
+ b1_timer.stop();
+ b1_timer.set_delay_time(1000);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ b2_down (
+ )
+ {
+ if (pos != max_pos)
+ {
+ set_slider_pos(pos+1);
+ if (scroll_handler.is_set())
+ scroll_handler();
+
+ if (b2_timer.delay_time() == 1000)
+ b2_timer.set_delay_time(500);
+ else
+ b2_timer.set_delay_time(50);
+ b2_timer.start();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ b2_up (
+ bool
+ )
+ {
+ b2_timer.stop();
+ b2_timer.set_delay_time(1000);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ top_filler_down (
+ )
+ {
+ // ignore this if the mouse is now outside this object. This could happen
+ // since the timers are also calling this function.
+ if (top_filler.rect.contains(lastx,lasty) == false)
+ {
+ top_filler_up(false);
+ return;
+ }
+
+ if (pos != 0)
+ {
+ if (pos < js)
+ {
+ // if there is less than jump_size() space left then jump the remaining
+ // amount.
+ delayed_set_slider_pos(0);
+ }
+ else
+ {
+ delayed_set_slider_pos(pos-js);
+ }
+
+ if (top_filler_timer.delay_time() == 1000)
+ top_filler_timer.set_delay_time(500);
+ else
+ top_filler_timer.set_delay_time(50);
+ top_filler_timer.start();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ top_filler_up (
+ bool
+ )
+ {
+ top_filler_timer.stop();
+ top_filler_timer.set_delay_time(1000);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ bottom_filler_down (
+ )
+ {
+ // ignore this if the mouse is now outside this object. This could happen
+ // since the timers are also calling this function.
+ if (bottom_filler.rect.contains(lastx,lasty) == false)
+ {
+ bottom_filler_up(false);
+ return;
+ }
+
+ if (pos != max_pos)
+ {
+ if (max_pos - pos < js)
+ {
+ // if there is less than jump_size() space left then jump the remaining
+ // amount.
+ delayed_set_slider_pos(max_pos);
+ }
+ else
+ {
+ delayed_set_slider_pos(pos+js);
+ }
+
+ if (bottom_filler_timer.delay_time() == 1000)
+ bottom_filler_timer.set_delay_time(500);
+ else
+ bottom_filler_timer.set_delay_time(50);
+ bottom_filler_timer.start();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ bottom_filler_up (
+ bool
+ )
+ {
+ bottom_filler_timer.stop();
+ bottom_filler_timer.set_delay_time(1000);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ set_jump_size (
+ long js_
+ )
+ {
+ auto_mutex M(m);
+ if (js_ < 1)
+ js = 1;
+ else
+ js = js_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ long scroll_bar::
+ jump_size (
+ ) const
+ {
+ auto_mutex M(m);
+ return js;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ on_user_event (
+ int i
+ )
+ {
+ switch (i)
+ {
+ case 0:
+ b1_down();
+ break;
+ case 1:
+ b2_down();
+ break;
+ case 2:
+ top_filler_down();
+ break;
+ case 3:
+ bottom_filler_down();
+ break;
+ case 4:
+ // if the position we are supposed to switch the slider too isn't
+ // already set
+ if (delayed_pos != pos)
+ {
+ set_slider_pos(delayed_pos);
+ if (scroll_handler.is_set())
+ scroll_handler();
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ delayed_set_slider_pos (
+ unsigned long dpos
+ )
+ {
+ delayed_pos = dpos;
+ parent.trigger_user_event(this,4);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ b1_down_t (
+ )
+ {
+ parent.trigger_user_event(this,0);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ b2_down_t (
+ )
+ {
+ parent.trigger_user_event(this,1);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ top_filler_down_t (
+ )
+ {
+ parent.trigger_user_event(this,2);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scroll_bar::
+ bottom_filler_down_t (
+ )
+ {
+ parent.trigger_user_event(this,3);
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+// widget_group object methods
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ empty (
+ )
+ {
+ auto_mutex M(m);
+ widgets.clear();
+ wg_widgets.clear();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ add (
+ drawable& widget,
+ unsigned long x,
+ unsigned long y
+ )
+ {
+ auto_mutex M(m);
+ drawable* w = &widget;
+ relpos rp;
+ rp.x = x;
+ rp.y = y;
+ if (widgets.is_in_domain(w))
+ {
+ widgets[w].x = x;
+ widgets[w].y = y;
+ }
+ else
+ {
+ widgets.add(w,rp);
+ }
+ if (is_hidden())
+ widget.hide();
+ else
+ widget.show();
+
+ if (is_enabled())
+ widget.enable();
+ else
+ widget.disable();
+
+ widget.set_z_order(z_order());
+ widget.set_pos(x+rect.left(),y+rect.top());
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ add (
+ widget_group& widget,
+ unsigned long x,
+ unsigned long y
+ )
+ {
+ auto_mutex M(m);
+ drawable& w = widget;
+ add(w, x, y);
+
+ widget_group* wg = &widget;
+ wg_widgets.add(wg);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ bool widget_group::
+ is_member (
+ const drawable& widget
+ ) const
+ {
+ auto_mutex M(m);
+ drawable* w = const_cast<drawable*>(&widget);
+ return widgets.is_in_domain(w);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ remove (
+ const drawable& widget
+ )
+ {
+ auto_mutex M(m);
+ drawable* w = const_cast<drawable*>(&widget);
+ if (widgets.is_in_domain(w))
+ {
+ widgets.destroy(w);
+
+ // check if we also have an entry in the wg_widgets set and if
+ // so then remove that too
+ widget_group* wg = reinterpret_cast<widget_group*>(w);
+ if (wg_widgets.is_member(wg))
+ {
+ wg_widgets.destroy(wg);
+ }
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ size_t widget_group::
+ size (
+ ) const
+ {
+ auto_mutex M(m);
+ return widgets.size();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ set_pos (
+ long x,
+ long y
+ )
+ {
+ auto_mutex M(m);
+ widgets.reset();
+ while (widgets.move_next())
+ {
+ const unsigned long rx = widgets.element().value().x;
+ const unsigned long ry = widgets.element().value().y;
+ widgets.element().key()->set_pos(x+rx,y+ry);
+ }
+ drawable::set_pos(x,y);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ set_z_order (
+ long order
+ )
+ {
+ auto_mutex M(m);
+ widgets.reset();
+ while (widgets.move_next())
+ widgets.element().key()->set_z_order(order);
+ drawable::set_z_order(order);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ show (
+ )
+ {
+ auto_mutex M(m);
+ widgets.reset();
+ while (widgets.move_next())
+ widgets.element().key()->show();
+ drawable::show();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ hide (
+ )
+ {
+ auto_mutex M(m);
+ widgets.reset();
+ while (widgets.move_next())
+ widgets.element().key()->hide();
+ drawable::hide();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ enable (
+ )
+ {
+ auto_mutex M(m);
+ widgets.reset();
+ while (widgets.move_next())
+ widgets.element().key()->enable();
+ drawable::enable();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ disable ()
+ {
+ auto_mutex M(m);
+ widgets.reset();
+ while (widgets.move_next())
+ widgets.element().key()->disable();
+ drawable::disable();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void widget_group::
+ fit_to_contents (
+ )
+ {
+ auto_mutex M(m);
+
+ // call fit_to_contents on all the widget_groups we contain
+ wg_widgets.reset();
+ while (wg_widgets.move_next())
+ wg_widgets.element()->fit_to_contents();
+
+ // now accumulate a rectangle that contains everything in this widget_group
+ rectangle r;
+ widgets.reset();
+ while (widgets.move_next())
+ r = r + widgets.element().key()->get_rect();
+
+ if (r.is_empty())
+ {
+ // make sure it is still empty after we set it at the correct position
+ r.set_right(rect.left()-1);
+ r.set_bottom(rect.top()-1);
+ }
+
+ r.set_left(rect.left());
+ r.set_top(rect.top());
+ rect = r;
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+// class popup_menu
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ popup_menu::
+ popup_menu (
+ ) :
+ base_window(false,true),
+ pad(2),
+ item_pad(3),
+ cur_rect(pad,pad,pad-1,pad-1),
+ left_width(0),
+ middle_width(0),
+ selected_item(0),
+ submenu_open(false)
+ {
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ enable_menu_item (
+ unsigned long idx
+ )
+ {
+ DLIB_ASSERT ( idx < size() ,
+ "\tvoid popup_menu::enable_menu_item()"
+ << "\n\tidx: " << idx
+ << "\n\tsize(): " << size()
+ );
+ auto_mutex M(wm);
+ item_enabled[idx] = true;
+ invalidate_rectangle(cur_rect);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ disable_menu_item (
+ unsigned long idx
+ )
+ {
+ DLIB_ASSERT ( idx < size() ,
+ "\tvoid popup_menu::enable_menu_item()"
+ << "\n\tidx: " << idx
+ << "\n\tsize(): " << size()
+ );
+ auto_mutex M(wm);
+ item_enabled[idx] = false;
+ invalidate_rectangle(cur_rect);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ size_t popup_menu::
+ size (
+ ) const
+ {
+ auto_mutex M(wm);
+ return items.size();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ clear (
+ )
+ {
+ auto_mutex M(wm);
+ hide();
+ cur_rect = rectangle(pad,pad,pad-1,pad-1);
+ win_rect = rectangle();
+ left_width = 0;
+ middle_width = 0;
+ items.clear();
+ item_enabled.clear();
+ left_rects.clear();
+ middle_rects.clear();
+ right_rects.clear();
+ line_rects.clear();
+ submenus.clear();
+ selected_item = 0;
+ submenu_open = false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ show (
+ )
+ {
+ auto_mutex M(wm);
+ selected_item = submenus.size();
+ base_window::show();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ hide (
+ )
+ {
+ auto_mutex M(wm);
+ // hide ourselves
+ close_submenu();
+ selected_item = submenus.size();
+ base_window::hide();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ select_first_item (
+ )
+ {
+ auto_mutex M(wm);
+ close_submenu();
+ selected_item = items.size();
+ for (unsigned long i = 0; i < items.size(); ++i)
+ {
+ if ((items[i]->has_click_event() || submenus[i]) && item_enabled[i])
+ {
+ selected_item = i;
+ break;
+ }
+ }
+ invalidate_rectangle(cur_rect);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ bool popup_menu::
+ forwarded_on_keydown (
+ unsigned long key,
+ bool is_printable,
+ unsigned long state
+ )
+ {
+ auto_mutex M(wm);
+ // do nothing if this popup menu is empty
+ if (items.size() == 0)
+ return false;
+
+
+ // check if the selected item is a submenu
+ if (selected_item != submenus.size() && submenus[selected_item] != 0 && submenu_open)
+ {
+ // send the key to the submenu and return if that menu used the key
+ if (submenus[selected_item]->forwarded_on_keydown(key,is_printable,state) == true)
+ return true;
+ }
+
+ if (key == KEY_UP)
+ {
+ for (unsigned long i = 0; i < items.size(); ++i)
+ {
+ selected_item = (selected_item + items.size() - 1)%items.size();
+ // only stop looking if this one is enabled and has a click event or is a submenu
+ if (item_enabled[selected_item] && (items[selected_item]->has_click_event() || submenus[selected_item]) )
+ break;
+ }
+ invalidate_rectangle(cur_rect);
+ return true;
+ }
+ else if (key == KEY_DOWN)
+ {
+ for (unsigned long i = 0; i < items.size(); ++i)
+ {
+ selected_item = (selected_item + 1)%items.size();
+ // only stop looking if this one is enabled and has a click event or is a submenu
+ if (item_enabled[selected_item] && (items[selected_item]->has_click_event() || submenus[selected_item]))
+ break;
+ }
+ invalidate_rectangle(cur_rect);
+ return true;
+ }
+ else if (key == KEY_RIGHT && submenu_open == false && display_selected_submenu())
+ {
+ submenus[selected_item]->select_first_item();
+ return true;
+ }
+ else if (key == KEY_LEFT && selected_item != submenus.size() &&
+ submenus[selected_item] != 0 && submenu_open)
+ {
+ close_submenu();
+ return true;
+ }
+ else if (key == '\n')
+ {
+ if (selected_item != submenus.size() && (items[selected_item]->has_click_event() || submenus[selected_item]))
+ {
+ const long idx = selected_item;
+ // only hide this popup window if this isn't a submenu
+ if (submenus[idx] == 0)
+ {
+ hide();
+ hide_handlers.reset();
+ while (hide_handlers.move_next())
+ hide_handlers.element()();
+ }
+ else
+ {
+ display_selected_submenu();
+ submenus[idx]->select_first_item();
+ }
+ items[idx]->on_click();
+ return true;
+ }
+ }
+ else if (is_printable)
+ {
+ // check if there is a hotkey for this key
+ for (unsigned long i = 0; i < items.size(); ++i)
+ {
+ if (std::tolower(key) == std::tolower(items[i]->get_hot_key()) &&
+ (items[i]->has_click_event() || submenus[i]) && item_enabled[i] )
+ {
+ // only hide this popup window if this isn't a submenu
+ if (submenus[i] == 0)
+ {
+ hide();
+ hide_handlers.reset();
+ while (hide_handlers.move_next())
+ hide_handlers.element()();
+ }
+ else
+ {
+ if (selected_item != items.size())
+ invalidate_rectangle(line_rects[selected_item]);
+
+ selected_item = i;
+ display_selected_submenu();
+ invalidate_rectangle(line_rects[i]);
+ submenus[i]->select_first_item();
+ }
+ items[i]->on_click();
+ }
+ }
+
+ // always say we use a printable key for hotkeys
+ return true;
+ }
+
+ return false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ on_submenu_hide (
+ )
+ {
+ hide();
+ hide_handlers.reset();
+ while (hide_handlers.move_next())
+ hide_handlers.element()();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ on_window_resized(
+ )
+ {
+ invalidate_rectangle(win_rect);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ on_mouse_up (
+ unsigned long btn,
+ unsigned long,
+ long x,
+ long y
+ )
+ {
+ if (cur_rect.contains(x,y) && btn == LEFT)
+ {
+ // figure out which item this was on
+ for (unsigned long i = 0; i < items.size(); ++i)
+ {
+ if (line_rects[i].contains(x,y) && item_enabled[i] && items[i]->has_click_event())
+ {
+ // only hide this popup window if this isn't a submenu
+ if (submenus[i] == 0)
+ {
+ hide();
+ hide_handlers.reset();
+ while (hide_handlers.move_next())
+ hide_handlers.element()();
+ }
+ items[i]->on_click();
+ break;
+ }
+ }
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ on_mouse_move (
+ unsigned long ,
+ long x,
+ long y
+ )
+ {
+ if (cur_rect.contains(x,y))
+ {
+ // check if the mouse is still in the same rect it was in last time
+ rectangle last_rect;
+ if (selected_item != submenus.size())
+ {
+ last_rect = line_rects[selected_item];
+ }
+
+ // if the mouse isn't in the same rectangle any more
+ if (last_rect.contains(x,y) == false)
+ {
+ if (selected_item != submenus.size())
+ {
+ invalidate_rectangle(last_rect);
+ close_submenu();
+ selected_item = submenus.size();
+ }
+
+
+ // figure out if we should redraw any menu items
+ for (unsigned long i = 0; i < items.size(); ++i)
+ {
+ if (items[i]->has_click_event() || submenus[i])
+ {
+ if (line_rects[i].contains(x,y))
+ {
+ selected_item = i;
+ break;
+ }
+ }
+ }
+
+ // if we found a rectangle that contains the mouse then
+ // tell it to redraw itself
+ if (selected_item != submenus.size())
+ {
+ display_selected_submenu();
+ invalidate_rectangle(line_rects[selected_item]);
+ }
+ }
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ close_submenu (
+ )
+ {
+ if (selected_item != submenus.size() && submenus[selected_item] && submenu_open)
+ {
+ submenus[selected_item]->hide();
+ submenu_open = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ bool popup_menu::
+ display_selected_submenu (
+ )
+ {
+ // show the submenu if one exists
+ if (selected_item != submenus.size() && submenus[selected_item])
+ {
+ long wx, wy;
+ get_pos(wx,wy);
+ wx += line_rects[selected_item].right();
+ wy += line_rects[selected_item].top();
+ submenus[selected_item]->set_pos(wx+1,wy-2);
+ submenus[selected_item]->show();
+ submenu_open = true;
+ return true;
+ }
+ return false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ on_mouse_leave (
+ )
+ {
+ if (selected_item != submenus.size())
+ {
+ // only unhighlight a menu item if it isn't a submenu item
+ if (submenus[selected_item] == 0)
+ {
+ invalidate_rectangle(line_rects[selected_item]);
+ selected_item = submenus.size();
+ }
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu::
+ paint (
+ const canvas& c
+ )
+ {
+ c.fill(200,200,200);
+ draw_rectangle(c, win_rect);
+ for (unsigned long i = 0; i < items.size(); ++i)
+ {
+ bool is_selected = false;
+ if (selected_item != submenus.size() && i == selected_item &&
+ item_enabled[i])
+ is_selected = true;
+
+ items[i]->draw_background(c,line_rects[i], item_enabled[i], is_selected);
+ items[i]->draw_left(c,left_rects[i], item_enabled[i], is_selected);
+ items[i]->draw_middle(c,middle_rects[i], item_enabled[i], is_selected);
+ items[i]->draw_right(c,right_rects[i], item_enabled[i], is_selected);
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+// class zoomable_region
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ zoomable_region::
+ zoomable_region (
+ drawable_window& w,
+ unsigned long events
+ ) :
+ drawable(w,MOUSE_CLICK | MOUSE_WHEEL | MOUSE_MOVE | events),
+ min_scale(0.15),
+ max_scale(1.0),
+ zoom_increment_(0.90),
+ vsb(w, scroll_bar::VERTICAL),
+ hsb(w, scroll_bar::HORIZONTAL)
+ {
+ scale = 1;
+ mouse_drag_screen = false;
+ style.reset(new scrollable_region_style_default());
+
+ hsb.set_scroll_handler(*this,&zoomable_region::on_h_scroll);
+ vsb.set_scroll_handler(*this,&zoomable_region::on_v_scroll);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ zoomable_region::
+ ~zoomable_region()
+ {
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ set_pos (
+ long x,
+ long y
+ )
+ {
+ auto_mutex M(m);
+ drawable::set_pos(x,y);
+ const long border_size = style->get_border_size();
+ vsb.set_pos(rect.right()-border_size+1-vsb.width(),rect.top()+border_size);
+ hsb.set_pos(rect.left()+border_size,rect.bottom()-border_size+1-hsb.height());
+
+ display_rect_ = rectangle(rect.left()+border_size,
+ rect.top()+border_size,
+ rect.right()-border_size-vsb.width(),
+ rect.bottom()-border_size-hsb.height());
+
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ set_zoom_increment (
+ double zi
+ )
+ {
+ DLIB_ASSERT(0.0 < zi && zi < 1.0,
+ "\tvoid zoomable_region::set_zoom_increment(zi)"
+ << "\n\t the zoom increment must be between 0 and 1"
+ << "\n\t zi: " << zi
+ << "\n\t this: " << this
+ );
+
+ auto_mutex M(m);
+ zoom_increment_ = zi;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ double zoomable_region::
+ zoom_increment (
+ ) const
+ {
+ auto_mutex M(m);
+ return zoom_increment_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ set_max_zoom_scale (
+ double ms
+ )
+ {
+ DLIB_ASSERT(ms > 0,
+ "\tvoid zoomable_region::set_max_zoom_scale(ms)"
+ << "\n\t the max zoom scale must be greater than 0"
+ << "\n\t ms: " << ms
+ << "\n\t this: " << this
+ );
+
+ auto_mutex M(m);
+ max_scale = ms;
+ if (scale > ms)
+ {
+ scale = max_scale;
+ lr_point = gui_to_graph_space(point(display_rect_.right(),display_rect_.bottom()));
+ redraw_graph();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ set_min_zoom_scale (
+ double ms
+ )
+ {
+ DLIB_ASSERT(ms > 0,
+ "\tvoid zoomable_region::set_min_zoom_scale(ms)"
+ << "\n\t the min zoom scale must be greater than 0"
+ << "\n\t ms: " << ms
+ << "\n\t this: " << this
+ );
+
+ auto_mutex M(m);
+ min_scale = ms;
+
+ if (scale < ms)
+ {
+ scale = min_scale;
+ }
+
+ // just call set_size so that everything gets redrawn right
+ set_size(rect.width(), rect.height());
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ double zoomable_region::
+ min_zoom_scale (
+ ) const
+ {
+ auto_mutex M(m);
+ return min_scale;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ double zoomable_region::
+ max_zoom_scale (
+ ) const
+ {
+ auto_mutex M(m);
+ return max_scale;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ set_size (
+ unsigned long width,
+ unsigned long height
+ )
+ {
+ auto_mutex M(m);
+ rectangle old(rect);
+ const long border_size = style->get_border_size();
+ rect = resize_rect(rect,width,height);
+ vsb.set_pos(rect.right()-border_size+1-vsb.width(), rect.top()+border_size);
+ hsb.set_pos(rect.left()+border_size, rect.bottom()-border_size+1-hsb.height());
+
+ display_rect_ = rectangle(rect.left()+border_size,
+ rect.top()+border_size,
+ rect.right()-border_size-vsb.width(),
+ rect.bottom()-border_size-hsb.height());
+ vsb.set_length(display_rect_.height());
+ hsb.set_length(display_rect_.width());
+ parent.invalidate_rectangle(rect+old);
+
+ const double old_scale = scale;
+ const vector<double,2> old_gr_orig(gr_orig);
+ scale = min_scale;
+ gr_orig = vector<double,2>(0,0);
+ lr_point = gui_to_graph_space(point(display_rect_.right(),display_rect_.bottom()));
+ scale = old_scale;
+
+ // call adjust_origin() so that the scroll bars get their max slider positions
+ // setup right
+ const point rect_corner(display_rect_.left(), display_rect_.top());
+ adjust_origin(rect_corner, old_gr_orig);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ show (
+ )
+ {
+ auto_mutex M(m);
+ drawable::show();
+ hsb.show();
+ vsb.show();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ hide (
+ )
+ {
+ auto_mutex M(m);
+ drawable::hide();
+ hsb.hide();
+ vsb.hide();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ enable (
+ )
+ {
+ auto_mutex M(m);
+ drawable::enable();
+ hsb.enable();
+ vsb.enable();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ disable (
+ )
+ {
+ auto_mutex M(m);
+ drawable::disable();
+ hsb.disable();
+ vsb.disable();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ set_z_order (
+ long order
+ )
+ {
+ auto_mutex M(m);
+ drawable::set_z_order(order);
+ hsb.set_z_order(order);
+ vsb.set_z_order(order);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ point zoomable_region::
+ graph_to_gui_space (
+ const vector<double,2>& p
+ ) const
+ {
+ const point rect_corner(display_rect_.left(), display_rect_.top());
+ return (p - gr_orig)*scale + rect_corner;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ vector<double,2> zoomable_region::
+ gui_to_graph_space (
+ const point& p
+ ) const
+ {
+ const point rect_corner(display_rect_.left(), display_rect_.top());
+ return (p - rect_corner)/scale + gr_orig;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ point zoomable_region::
+ max_graph_point (
+ ) const
+ {
+ return lr_point;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ rectangle zoomable_region::
+ display_rect (
+ ) const
+ {
+ return display_rect_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ double zoomable_region::
+ zoom_scale (
+ ) const
+ {
+ return scale;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ set_zoom_scale (
+ double new_scale
+ )
+ {
+ // if new_scale isn't in the right range then put it back in range before we do the
+ // rest of this function
+ if (!(min_scale <= new_scale && new_scale <= max_scale))
+ {
+ if (new_scale > max_scale)
+ new_scale = max_scale;
+ else
+ new_scale = min_scale;
+ }
+
+ // find the point in the center of the graph area
+ point center((display_rect_.left()+display_rect_.right())/2, (display_rect_.top()+display_rect_.bottom())/2);
+ point graph_p(gui_to_graph_space(center));
+ scale = new_scale;
+ adjust_origin(center, graph_p);
+ redraw_graph();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ center_display_at_graph_point (
+ const vector<double,2>& p
+ )
+ {
+ // find the point in the center of the graph area
+ point center((display_rect_.left()+display_rect_.right())/2, (display_rect_.top()+display_rect_.bottom())/2);
+ adjust_origin(center, p);
+ redraw_graph();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ on_wheel_down (
+ unsigned long
+ )
+ {
+ // zoom out
+ if (enabled && !hidden && scale > min_scale && display_rect_.contains(lastx,lasty))
+ {
+ point gui_p(lastx,lasty);
+ point graph_p(gui_to_graph_space(gui_p));
+ const double old_scale = scale;
+ scale *= zoom_increment_;
+ if (scale < min_scale)
+ scale = min_scale;
+ redraw_graph();
+ adjust_origin(gui_p, graph_p);
+
+ if (scale != old_scale)
+ on_view_changed();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ on_wheel_up (
+ unsigned long
+ )
+ {
+ // zoom in
+ if (enabled && !hidden && scale < max_scale && display_rect_.contains(lastx,lasty))
+ {
+ point gui_p(lastx,lasty);
+ point graph_p(gui_to_graph_space(gui_p));
+ const double old_scale = scale;
+ scale /= zoom_increment_;
+ if (scale > max_scale)
+ scale = max_scale;
+ redraw_graph();
+ adjust_origin(gui_p, graph_p);
+
+ if (scale != old_scale)
+ on_view_changed();
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ on_mouse_move (
+ unsigned long state,
+ long x,
+ long y
+ )
+ {
+ if (enabled && !hidden && mouse_drag_screen)
+ {
+ adjust_origin(point(x,y), drag_screen_point);
+ redraw_graph();
+ on_view_changed();
+ }
+
+ // check if the mouse isn't being dragged anymore
+ if ((state & base_window::LEFT) == 0)
+ {
+ mouse_drag_screen = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ on_mouse_up (
+ unsigned long ,
+ unsigned long ,
+ long ,
+ long
+ )
+ {
+ mouse_drag_screen = false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ on_mouse_down (
+ unsigned long btn,
+ unsigned long ,
+ long x,
+ long y,
+ bool
+ )
+ {
+ if (enabled && !hidden && display_rect_.contains(x,y) && btn == base_window::LEFT)
+ {
+ mouse_drag_screen = true;
+ drag_screen_point = gui_to_graph_space(point(x,y));
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ draw (
+ const canvas& c
+ ) const
+ {
+ style->draw_scrollable_region_border(c, rect, enabled);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ on_h_scroll (
+ )
+ {
+ gr_orig.x() = hsb.slider_pos();
+ redraw_graph();
+
+ on_view_changed();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ on_v_scroll (
+ )
+ {
+ gr_orig.y() = vsb.slider_pos();
+ redraw_graph();
+
+ on_view_changed();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ redraw_graph (
+ )
+ {
+ parent.invalidate_rectangle(display_rect_);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void zoomable_region::
+ adjust_origin (
+ const point& gui_p,
+ const vector<double,2>& graph_p
+ )
+ {
+ const point rect_corner(display_rect_.left(), display_rect_.top());
+ const dlib::vector<double,2> v(gui_p - rect_corner);
+ gr_orig = graph_p - v/scale;
+
+
+ // make sure the origin isn't outside the point (0,0)
+ if (gr_orig.x() < 0)
+ gr_orig.x() = 0;
+ if (gr_orig.y() < 0)
+ gr_orig.y() = 0;
+
+ // make sure the lower right corner of the display_rect_ doesn't map to a point beyond lr_point
+ point lr_rect_corner(display_rect_.right(), display_rect_.bottom());
+ point p = graph_to_gui_space(lr_point);
+ vector<double,2> lr_rect_corner_graph_space(gui_to_graph_space(lr_rect_corner));
+ vector<double,2> delta(lr_point - lr_rect_corner_graph_space);
+ if (lr_rect_corner.x() > p.x())
+ {
+ gr_orig.x() += delta.x();
+ }
+
+ if (lr_rect_corner.y() > p.y())
+ {
+ gr_orig.y() += delta.y();
+ }
+
+
+ const vector<double,2> ul_rect_corner_graph_space(gui_to_graph_space(rect_corner));
+ lr_rect_corner_graph_space = gui_to_graph_space(lr_rect_corner);
+ // now adjust the scroll bars
+
+ hsb.set_max_slider_pos((unsigned long)std::max(lr_point.x()-(lr_rect_corner_graph_space.x()-ul_rect_corner_graph_space.x()),0.0));
+ vsb.set_max_slider_pos((unsigned long)std::max(lr_point.y()-(lr_rect_corner_graph_space.y()-ul_rect_corner_graph_space.y()),0.0));
+ // adjust slider position now.
+ hsb.set_slider_pos(static_cast<long>(ul_rect_corner_graph_space.x()));
+ vsb.set_slider_pos(static_cast<long>(ul_rect_corner_graph_space.y()));
+
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+// class scrollable_region
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ scrollable_region::
+ scrollable_region (
+ drawable_window& w,
+ unsigned long events
+ ) :
+ drawable(w, MOUSE_WHEEL|events|MOUSE_CLICK|MOUSE_MOVE),
+ hsb(w,scroll_bar::HORIZONTAL),
+ vsb(w,scroll_bar::VERTICAL),
+ hscroll_bar_inc(1),
+ vscroll_bar_inc(1),
+ h_wheel_scroll_bar_inc(1),
+ v_wheel_scroll_bar_inc(1),
+ mouse_drag_enabled_(false),
+ user_is_dragging_mouse(false)
+ {
+ style.reset(new scrollable_region_style_default());
+
+ hsb.set_scroll_handler(*this,&scrollable_region::on_h_scroll);
+ vsb.set_scroll_handler(*this,&scrollable_region::on_v_scroll);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ scrollable_region::
+ ~scrollable_region (
+ )
+ {
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ show (
+ )
+ {
+ auto_mutex M(m);
+ drawable::show();
+ if (need_h_scroll())
+ hsb.show();
+ if (need_v_scroll())
+ vsb.show();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ hide (
+ )
+ {
+ auto_mutex M(m);
+ drawable::hide();
+ hsb.hide();
+ vsb.hide();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ enable (
+ )
+ {
+ auto_mutex M(m);
+ drawable::enable();
+ hsb.enable();
+ vsb.enable();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ disable (
+ )
+ {
+ auto_mutex M(m);
+ drawable::disable();
+ hsb.disable();
+ vsb.disable();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_z_order (
+ long order
+ )
+ {
+ auto_mutex M(m);
+ drawable::set_z_order(order);
+ hsb.set_z_order(order);
+ vsb.set_z_order(order);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_size (
+ unsigned long width,
+ unsigned long height
+ )
+ {
+ auto_mutex M(m);
+ rectangle old(rect);
+ rect = resize_rect(rect,width,height);
+ vsb.set_pos(rect.right()-style->get_border_size()-vsb.width()+1, rect.top()+style->get_border_size());
+ hsb.set_pos(rect.left()+style->get_border_size(), rect.bottom()-style->get_border_size()-hsb.height()+1);
+
+ // adjust the display_rect_
+ if (need_h_scroll() && need_v_scroll())
+ {
+ // both scroll bars aren't hidden
+ if (!hidden)
+ {
+ vsb.show();
+ hsb.show();
+ }
+ display_rect_ = rectangle( rect.left()+style->get_border_size(),
+ rect.top()+style->get_border_size(),
+ rect.right()-style->get_border_size()-vsb.width(),
+ rect.bottom()-style->get_border_size()-hsb.height());
+
+ // figure out how many scroll bar positions there should be
+ unsigned long hdelta = total_rect_.width()-display_rect_.width();
+ unsigned long vdelta = total_rect_.height()-display_rect_.height();
+ hdelta = (hdelta+hscroll_bar_inc-1)/hscroll_bar_inc;
+ vdelta = (vdelta+vscroll_bar_inc-1)/vscroll_bar_inc;
+
+ hsb.set_max_slider_pos(hdelta);
+ vsb.set_max_slider_pos(vdelta);
+
+ vsb.set_jump_size((display_rect_.height()+vscroll_bar_inc-1)/vscroll_bar_inc/2+1);
+ hsb.set_jump_size((display_rect_.width()+hscroll_bar_inc-1)/hscroll_bar_inc/2+1);
+ }
+ else if (need_h_scroll())
+ {
+ // only hsb is hidden
+ if (!hidden)
+ {
+ hsb.show();
+ vsb.hide();
+ }
+ display_rect_ = rectangle( rect.left()+style->get_border_size(),
+ rect.top()+style->get_border_size(),
+ rect.right()-style->get_border_size(),
+ rect.bottom()-style->get_border_size()-hsb.height());
+
+ // figure out how many scroll bar positions there should be
+ unsigned long hdelta = total_rect_.width()-display_rect_.width();
+ hdelta = (hdelta+hscroll_bar_inc-1)/hscroll_bar_inc;
+
+ hsb.set_max_slider_pos(hdelta);
+ vsb.set_max_slider_pos(0);
+
+ hsb.set_jump_size((display_rect_.width()+hscroll_bar_inc-1)/hscroll_bar_inc/2+1);
+ }
+ else if (need_v_scroll())
+ {
+ // only vsb is hidden
+ if (!hidden)
+ {
+ hsb.hide();
+ vsb.show();
+ }
+ display_rect_ = rectangle( rect.left()+style->get_border_size(),
+ rect.top()+style->get_border_size(),
+ rect.right()-style->get_border_size()-vsb.width(),
+ rect.bottom()-style->get_border_size());
+
+ unsigned long vdelta = total_rect_.height()-display_rect_.height();
+ vdelta = (vdelta+vscroll_bar_inc-1)/vscroll_bar_inc;
+
+ hsb.set_max_slider_pos(0);
+ vsb.set_max_slider_pos(vdelta);
+
+ vsb.set_jump_size((display_rect_.height()+vscroll_bar_inc-1)/vscroll_bar_inc/2+1);
+ }
+ else
+ {
+ // both are hidden
+ if (!hidden)
+ {
+ hsb.hide();
+ vsb.hide();
+ }
+ display_rect_ = rectangle( rect.left()+style->get_border_size(),
+ rect.top()+style->get_border_size(),
+ rect.right()-style->get_border_size(),
+ rect.bottom()-style->get_border_size());
+
+ hsb.set_max_slider_pos(0);
+ vsb.set_max_slider_pos(0);
+ }
+
+ vsb.set_length(display_rect_.height());
+ hsb.set_length(display_rect_.width());
+
+ // adjust the total_rect_ position by trigging the scroll events
+ on_h_scroll();
+ on_v_scroll();
+
+ parent.invalidate_rectangle(rect+old);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ unsigned long scrollable_region::
+ horizontal_mouse_wheel_scroll_increment (
+ ) const
+ {
+ auto_mutex M(m);
+ return h_wheel_scroll_bar_inc;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ unsigned long scrollable_region::
+ vertical_mouse_wheel_scroll_increment (
+ ) const
+ {
+ auto_mutex M(m);
+ return v_wheel_scroll_bar_inc;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_horizontal_mouse_wheel_scroll_increment (
+ unsigned long inc
+ )
+ {
+ auto_mutex M(m);
+ h_wheel_scroll_bar_inc = inc;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_vertical_mouse_wheel_scroll_increment (
+ unsigned long inc
+ )
+ {
+ auto_mutex M(m);
+ v_wheel_scroll_bar_inc = inc;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ unsigned long scrollable_region::
+ horizontal_scroll_increment (
+ ) const
+ {
+ auto_mutex M(m);
+ return hscroll_bar_inc;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ unsigned long scrollable_region::
+ vertical_scroll_increment (
+ ) const
+ {
+ auto_mutex M(m);
+ return vscroll_bar_inc;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_horizontal_scroll_increment (
+ unsigned long inc
+ )
+ {
+ auto_mutex M(m);
+ hscroll_bar_inc = inc;
+ // call set_size to reset the scroll bars
+ set_size(rect.width(),rect.height());
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_vertical_scroll_increment (
+ unsigned long inc
+ )
+ {
+ auto_mutex M(m);
+ vscroll_bar_inc = inc;
+ // call set_size to reset the scroll bars
+ set_size(rect.width(),rect.height());
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ long scrollable_region::
+ horizontal_scroll_pos (
+ ) const
+ {
+ auto_mutex M(m);
+ return hsb.slider_pos();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ long scrollable_region::
+ vertical_scroll_pos (
+ ) const
+ {
+ auto_mutex M(m);
+ return vsb.slider_pos();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_horizontal_scroll_pos (
+ long pos
+ )
+ {
+ auto_mutex M(m);
+
+ hsb.set_slider_pos(pos);
+ on_h_scroll();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_vertical_scroll_pos (
+ long pos
+ )
+ {
+ auto_mutex M(m);
+
+ vsb.set_slider_pos(pos);
+ on_v_scroll();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_pos (
+ long x,
+ long y
+ )
+ {
+ auto_mutex M(m);
+ drawable::set_pos(x,y);
+ vsb.set_pos(rect.right()-style->get_border_size()-vsb.width()+1, rect.top()+style->get_border_size());
+ hsb.set_pos(rect.left()+style->get_border_size(), rect.bottom()-style->get_border_size()-hsb.height()+1);
+
+ const long delta_x = total_rect_.left() - display_rect_.left();
+ const long delta_y = total_rect_.top() - display_rect_.top();
+
+ display_rect_ = move_rect(display_rect_, rect.left()+style->get_border_size(), rect.top()+style->get_border_size());
+
+ total_rect_ = move_rect(total_rect_, display_rect_.left()+delta_x, display_rect_.top()+delta_y);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ bool scrollable_region::
+ mouse_drag_enabled (
+ ) const
+ {
+ auto_mutex M(m);
+ return mouse_drag_enabled_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ enable_mouse_drag (
+ )
+ {
+ auto_mutex M(m);
+ mouse_drag_enabled_ = true;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ disable_mouse_drag (
+ )
+ {
+ auto_mutex M(m);
+ mouse_drag_enabled_ = false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ const rectangle& scrollable_region::
+ display_rect (
+ ) const
+ {
+ return display_rect_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ set_total_rect_size (
+ unsigned long width,
+ unsigned long height
+ )
+ {
+ DLIB_ASSERT((width > 0 && height > 0) || (width == 0 && height == 0),
+ "\tvoid scrollable_region::set_total_rect_size(width,height)"
+ << "\n\twidth and height must be > 0 or both == 0"
+ << "\n\twidth: " << width
+ << "\n\theight: " << height
+ << "\n\tthis: " << this
+ );
+
+ total_rect_ = move_rect(rectangle(width,height),
+ display_rect_.left()-static_cast<long>(hsb.slider_pos()),
+ display_rect_.top()-static_cast<long>(vsb.slider_pos()));
+
+ // call this just to reconfigure the scroll bars
+ set_size(rect.width(),rect.height());
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ const rectangle& scrollable_region::
+ total_rect (
+ ) const
+ {
+ return total_rect_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ scroll_to_rect (
+ const rectangle& r_
+ )
+ {
+ const rectangle r(total_rect_.intersect(r_));
+ const rectangle old(total_rect_);
+ // adjust the horizontal scroll bar so that r fits as best as possible
+ if (r.left() < display_rect_.left())
+ {
+ long distance = (r.left()-total_rect_.left())/hscroll_bar_inc;
+ hsb.set_slider_pos(distance);
+ }
+ else if (r.right() > display_rect_.right())
+ {
+ long distance = (r.right()-total_rect_.left()-display_rect_.width()+hscroll_bar_inc)/hscroll_bar_inc;
+ hsb.set_slider_pos(distance);
+ }
+
+ // adjust the vertical scroll bar so that r fits as best as possible
+ if (r.top() < display_rect_.top())
+ {
+ long distance = (r.top()-total_rect_.top())/vscroll_bar_inc;
+ vsb.set_slider_pos(distance);
+ }
+ else if (r.bottom() > display_rect_.bottom())
+ {
+ long distance = (r.bottom()-total_rect_.top()-display_rect_.height()+vscroll_bar_inc)/vscroll_bar_inc;
+ vsb.set_slider_pos(distance);
+ }
+
+
+ // adjust total_rect_ so that it matches where the scroll bars are now
+ total_rect_ = move_rect(total_rect_,
+ display_rect_.left()-hscroll_bar_inc*hsb.slider_pos(),
+ display_rect_.top()-vscroll_bar_inc*vsb.slider_pos());
+
+ // only redraw if we actually changed something
+ if (total_rect_ != old)
+ {
+ parent.invalidate_rectangle(display_rect_);
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ on_wheel_down (
+ unsigned long
+ )
+ {
+ if (rect.contains(lastx,lasty) && enabled && !hidden)
+ {
+ if (need_v_scroll())
+ {
+ long pos = vsb.slider_pos();
+ vsb.set_slider_pos(pos+(long)v_wheel_scroll_bar_inc);
+ on_v_scroll();
+ }
+ else if (need_h_scroll())
+ {
+ long pos = hsb.slider_pos();
+ hsb.set_slider_pos(pos+(long)h_wheel_scroll_bar_inc);
+ on_h_scroll();
+ }
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ on_mouse_move (
+ unsigned long state,
+ long x,
+ long y
+ )
+ {
+ if (enabled && !hidden && user_is_dragging_mouse && state==base_window::LEFT)
+ {
+ point current_delta = point(x,y) - point(total_rect().left(), total_rect().top());
+ rectangle new_rect(translate_rect(display_rect(), drag_origin - current_delta));
+ new_rect = centered_rect(new_rect, new_rect.width()-hscroll_bar_inc, new_rect.height()-vscroll_bar_inc);
+ scroll_to_rect(new_rect);
+ on_view_changed();
+ }
+ else
+ {
+ user_is_dragging_mouse = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ on_mouse_down (
+ unsigned long btn,
+ unsigned long ,
+ long x,
+ long y,
+ bool
+ )
+ {
+ if (mouse_drag_enabled_ && enabled && !hidden && display_rect().contains(x,y) && (btn==base_window::LEFT))
+ {
+ drag_origin = point(x,y) - point(total_rect().left(), total_rect().top());
+ user_is_dragging_mouse = true;
+ }
+ else
+ {
+ user_is_dragging_mouse = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ on_mouse_up (
+ unsigned long ,
+ unsigned long ,
+ long ,
+ long
+ )
+ {
+ user_is_dragging_mouse = false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ on_wheel_up (
+ unsigned long
+ )
+ {
+ if (rect.contains(lastx,lasty) && enabled && !hidden)
+ {
+ if (need_v_scroll())
+ {
+ long pos = vsb.slider_pos();
+ vsb.set_slider_pos(pos-(long)v_wheel_scroll_bar_inc);
+ on_v_scroll();
+ }
+ else if (need_h_scroll())
+ {
+ long pos = hsb.slider_pos();
+ hsb.set_slider_pos(pos-(long)h_wheel_scroll_bar_inc);
+ on_h_scroll();
+ }
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ draw (
+ const canvas& c
+ ) const
+ {
+ style->draw_scrollable_region_border(c, rect, enabled);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ bool scrollable_region::
+ need_h_scroll (
+ ) const
+ {
+ if (total_rect_.width() > rect.width()-style->get_border_size()*2)
+ {
+ return true;
+ }
+ else
+ {
+ // check if we would need a vertical scroll bar and if adding one would make us need
+ // a horizontal one
+ if (total_rect_.height() > rect.height()-style->get_border_size()*2 &&
+ total_rect_.width() > rect.width()-style->get_border_size()*2-vsb.width())
+ return true;
+ else
+ return false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ bool scrollable_region::
+ need_v_scroll (
+ ) const
+ {
+ if (total_rect_.height() > rect.height()-style->get_border_size()*2)
+ {
+ return true;
+ }
+ else
+ {
+ // check if we would need a horizontal scroll bar and if adding one would make us need
+ // a vertical_scroll_pos one
+ if (total_rect_.width() > rect.width()-style->get_border_size()*2 &&
+ total_rect_.height() > rect.height()-style->get_border_size()*2-hsb.height())
+ return true;
+ else
+ return false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ on_h_scroll (
+ )
+ {
+ total_rect_ = move_rect(total_rect_, display_rect_.left()-hscroll_bar_inc*hsb.slider_pos(), total_rect_.top());
+ parent.invalidate_rectangle(display_rect_);
+ if (events_are_enabled())
+ on_view_changed();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void scrollable_region::
+ on_v_scroll (
+ )
+ {
+ total_rect_ = move_rect(total_rect_, total_rect_.left(), display_rect_.top()-vscroll_bar_inc*vsb.slider_pos());
+ parent.invalidate_rectangle(display_rect_);
+ if (events_are_enabled())
+ on_view_changed();
+ }
+
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+// class popup_menu_region
+// ----------------------------------------------------------------------------------------
+// ----------------------------------------------------------------------------------------
+
+ popup_menu_region::
+ popup_menu_region(
+ drawable_window& w
+ ) :
+ drawable(w,MOUSE_CLICK | KEYBOARD_EVENTS | FOCUS_EVENTS | WINDOW_MOVED),
+ popup_menu_shown(false)
+ {
+
+ menu_.set_on_hide_handler(*this,&popup_menu_region::on_menu_becomes_hidden);
+ enable_events();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ popup_menu_region::
+ ~popup_menu_region(
+ )
+ {
+ disable_events();
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ set_size (
+ unsigned long width,
+ unsigned long height
+ )
+ {
+ auto_mutex M(m);
+ rect = resize_rect(rect,width,height);
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ set_rect (
+ const rectangle& new_rect
+ )
+ {
+ auto_mutex M(m);
+ rect = new_rect;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ popup_menu& popup_menu_region::
+ menu (
+ )
+ {
+ return menu_;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ hide (
+ )
+ {
+ auto_mutex M(m);
+ drawable::hide();
+ menu_.hide();
+ popup_menu_shown = false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ disable (
+ )
+ {
+ auto_mutex M(m);
+ drawable::disable();
+ menu_.hide();
+ popup_menu_shown = false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ on_keydown (
+ unsigned long key,
+ bool is_printable,
+ unsigned long state
+ )
+ {
+ if (enabled && !hidden && popup_menu_shown)
+ {
+ menu_.forwarded_on_keydown(key, is_printable, state);
+ }
+ else if (popup_menu_shown)
+ {
+ menu_.hide();
+ popup_menu_shown = false;
+ }
+
+ if (key == (unsigned long)base_window::KEY_ESC)
+ {
+ menu_.hide();
+ popup_menu_shown = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ on_menu_becomes_hidden (
+ )
+ {
+ popup_menu_shown = false;
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ on_focus_lost (
+ )
+ {
+ if (popup_menu_shown)
+ {
+ menu_.hide();
+ popup_menu_shown = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ on_focus_gained (
+ )
+ {
+ if (popup_menu_shown)
+ {
+ menu_.hide();
+ popup_menu_shown = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ on_window_moved(
+ )
+ {
+ if (popup_menu_shown)
+ {
+ menu_.hide();
+ popup_menu_shown = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ on_mouse_down (
+ unsigned long btn,
+ unsigned long ,
+ long x,
+ long y,
+ bool
+ )
+ {
+ if (enabled && !hidden && rect.contains(x,y) && btn == base_window::RIGHT)
+ {
+ long orig_x, orig_y;
+ parent.get_pos(orig_x, orig_y);
+ menu_.set_pos(orig_x+x, orig_y+y);
+ menu_.show();
+ popup_menu_shown = true;
+ }
+ else if (popup_menu_shown)
+ {
+ menu_.hide();
+ popup_menu_shown = false;
+ }
+ }
+
+// ----------------------------------------------------------------------------------------
+
+ void popup_menu_region::
+ draw (
+ const canvas&
+ ) const
+ {
+ }
+
+// ----------------------------------------------------------------------------------------
+
+}
+
+#endif // DLIB_BASE_WIDGETs_CPP_
+