4184 lines
118 KiB
C++
4184 lines
118 KiB
C++
// Copyright (C) 2005 Davis E. King (davis@dlib.net), Keita Mochizuki
|
|
// License: Boost Software License See LICENSE.txt for the full license.
|
|
|
|
#ifndef DLIB_WIDGETs_
|
|
#define DLIB_WIDGETs_
|
|
|
|
#include <cctype>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "../algs.h"
|
|
#include "widgets_abstract.h"
|
|
#include "drawable.h"
|
|
#include "../gui_core.h"
|
|
#include "fonts.h"
|
|
#include "../timer.h"
|
|
#include "base_widgets.h"
|
|
#include "../member_function_pointer.h"
|
|
#include "../array.h"
|
|
#include "../array2d.h"
|
|
#include "../sequence.h"
|
|
#include "../dir_nav.h"
|
|
#include "../queue.h"
|
|
#include "style.h"
|
|
#include "../string.h"
|
|
#include "../misc_api.h"
|
|
#include "../any.h"
|
|
#include "../image_processing/full_object_detection.h"
|
|
#include "../geometry/line.h"
|
|
|
|
#ifdef _MSC_VER
|
|
// This #pragma directive is also located in the algs.h file but for whatever
|
|
// reason visual studio 9 just ignores it when it is only there.
|
|
|
|
// this is to disable the "'this' : used in base member initializer list"
|
|
// warning you get from some of the GUI objects since all the objects
|
|
// require that their parent class be passed into their constructor.
|
|
// In this case though it is totally safe so it is ok to disable this warning.
|
|
#pragma warning(disable : 4355)
|
|
#endif
|
|
|
|
namespace dlib
|
|
{
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class label
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class label : public drawable
|
|
{
|
|
public:
|
|
label(
|
|
drawable_window& w
|
|
) :
|
|
drawable(w),
|
|
text_color_(0,0,0)
|
|
{
|
|
enable_events();
|
|
}
|
|
|
|
~label()
|
|
{ disable_events(); parent.invalidate_rectangle(rect); }
|
|
|
|
void set_text (
|
|
const std::string& text
|
|
);
|
|
|
|
void set_text (
|
|
const std::wstring& text
|
|
);
|
|
|
|
void set_text (
|
|
const dlib::ustring& text
|
|
);
|
|
|
|
const std::string text (
|
|
) const;
|
|
|
|
const std::wstring wtext (
|
|
) const;
|
|
|
|
const dlib::ustring utext (
|
|
) const;
|
|
|
|
void set_text_color (
|
|
const rgb_pixel color
|
|
);
|
|
|
|
const rgb_pixel text_color (
|
|
) const;
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
private:
|
|
dlib::ustring text_;
|
|
rgb_pixel text_color_;
|
|
|
|
|
|
// restricted functions
|
|
label(label&); // copy constructor
|
|
label& operator=(label&); // assignment operator
|
|
|
|
protected:
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class toggle_button
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class toggle_button : public button_action
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
- checked == false
|
|
|
|
CONVENTION
|
|
- is_checked() == checked
|
|
!*/
|
|
|
|
public:
|
|
|
|
toggle_button(
|
|
drawable_window& w
|
|
) :
|
|
button_action(w),
|
|
btn_tooltip(w),
|
|
checked(false)
|
|
{
|
|
style.reset(new toggle_button_style_default());
|
|
enable_events();
|
|
}
|
|
|
|
~toggle_button() { disable_events(); parent.invalidate_rectangle(rect); }
|
|
|
|
void set_name (
|
|
const std::string& name
|
|
);
|
|
|
|
void set_name (
|
|
const std::wstring& name
|
|
);
|
|
|
|
void set_name (
|
|
const dlib::ustring& name
|
|
);
|
|
|
|
void set_size (
|
|
unsigned long width_,
|
|
unsigned long height_
|
|
);
|
|
|
|
void set_tooltip_text (
|
|
const std::string& text
|
|
);
|
|
|
|
void set_tooltip_text (
|
|
const std::wstring& text
|
|
);
|
|
|
|
void set_tooltip_text (
|
|
const ustring& text
|
|
);
|
|
|
|
const std::string tooltip_text (
|
|
) const;
|
|
|
|
const std::wstring tooltip_wtext (
|
|
) const;
|
|
|
|
const dlib::ustring tooltip_utext (
|
|
) const;
|
|
|
|
bool is_checked (
|
|
) const;
|
|
|
|
const std::string name (
|
|
) const;
|
|
|
|
const std::wstring wname (
|
|
) const;
|
|
|
|
const dlib::ustring uname (
|
|
) const;
|
|
|
|
void set_checked (
|
|
);
|
|
|
|
void set_unchecked (
|
|
);
|
|
|
|
void show (
|
|
);
|
|
|
|
void hide (
|
|
);
|
|
|
|
void enable (
|
|
);
|
|
|
|
void disable (
|
|
);
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
void set_pos (
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
template <
|
|
typename style_type
|
|
>
|
|
void set_style (
|
|
const style_type& style_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
style.reset(new style_type(style_));
|
|
rect = move_rect(style->get_min_size(name_,*mfont), rect.left(), rect.top());
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_click_handler (
|
|
T& object,
|
|
void (T::*event_handler_)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
event_handler = make_mfp(object,event_handler_);
|
|
event_handler_self.clear();
|
|
}
|
|
|
|
void set_click_handler (
|
|
const any_function<void()>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
event_handler = event_handler_;
|
|
event_handler_self.clear();
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_click_handler (
|
|
T& object,
|
|
void (T::*event_handler_)(toggle_button&)
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
event_handler_self = make_mfp(object,event_handler_);
|
|
event_handler.clear();
|
|
}
|
|
|
|
void set_sourced_click_handler (
|
|
const any_function<void(toggle_button&)>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
event_handler_self = event_handler_;
|
|
event_handler.clear();
|
|
}
|
|
|
|
private:
|
|
|
|
// restricted functions
|
|
toggle_button(toggle_button&); // copy constructor
|
|
toggle_button& operator=(toggle_button&); // assignment operator
|
|
|
|
dlib::ustring name_;
|
|
tooltip btn_tooltip;
|
|
bool checked;
|
|
|
|
any_function<void()> event_handler;
|
|
any_function<void(toggle_button&)> event_handler_self;
|
|
|
|
std::unique_ptr<toggle_button_style> style;
|
|
|
|
protected:
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const { style->draw_toggle_button(c,rect,enabled,*mfont,lastx,lasty,name_,is_depressed(),checked); }
|
|
|
|
void on_button_up (
|
|
bool mouse_over
|
|
);
|
|
|
|
void on_mouse_over (
|
|
){ if (style->redraw_on_mouse_over()) parent.invalidate_rectangle(rect); }
|
|
|
|
void on_mouse_not_over (
|
|
){ if (style->redraw_on_mouse_over()) parent.invalidate_rectangle(rect); }
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class text_field
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class text_field : public drawable
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
text_color_ == rgb_pixel(0,0,0)
|
|
bg_color_ == rgb_pixel(255,255,255)
|
|
cursor_pos == 0
|
|
text_width == 0
|
|
text_ == ""
|
|
has_focus == false
|
|
cursor_visible == false
|
|
recent_movement == false
|
|
highlight_start == 0
|
|
highlight_end == -1
|
|
shift_pos == -1
|
|
text_pos == 0
|
|
|
|
CONVENTION
|
|
- cursor_pos == the position of the cursor in the string text_. The
|
|
cursor appears before the letter text_[cursor_pos]
|
|
- cursor_x == the x coordinate of the cursor relative to the left side
|
|
of rect. i.e. the number of pixels that separate the cursor from the
|
|
left side of the text_field.
|
|
- has_focus == true if this text field has keyboard input focus
|
|
- cursor_visible == true if the cursor should be painted
|
|
- text_ == text()
|
|
- text_pos == the index of the first letter in text_ that appears in
|
|
this text field.
|
|
- text_width == the width of text_[text_pos] though text_[text.size()-1]
|
|
|
|
- if (has_focus && the user has recently moved the cursor) then
|
|
- recent_movement == true
|
|
- else
|
|
- recent_movement == false
|
|
|
|
- if (highlight_start <= highlight_end) then
|
|
- text[highlight_start] though text[highlight_end] should be
|
|
highlighted
|
|
|
|
- if (shift_pos != -1) then
|
|
- has_focus == true
|
|
- the shift key is being held down or the left mouse button is
|
|
being held down.
|
|
- shift_pos == the position of the cursor when the shift or mouse key
|
|
was first pressed.
|
|
|
|
- text_color() == text_color_
|
|
- background_color() == bg_color_
|
|
!*/
|
|
|
|
public:
|
|
text_field(
|
|
drawable_window& w
|
|
) :
|
|
drawable(w,MOUSE_CLICK | KEYBOARD_EVENTS | MOUSE_MOVE | STRING_PUT),
|
|
text_color_(0,0,0),
|
|
bg_color_(255,255,255),
|
|
text_width(0),
|
|
text_pos(0),
|
|
recent_movement(false),
|
|
has_focus(false),
|
|
cursor_visible(false),
|
|
cursor_pos(0),
|
|
highlight_start(0),
|
|
highlight_end(-1),
|
|
shift_pos(-1),
|
|
t(*this,&text_field::timer_action),
|
|
right_click_menu(w)
|
|
{
|
|
style.reset(new text_field_style_default());
|
|
rect.set_bottom(mfont->height()+ (style->get_padding(*mfont))*2);
|
|
rect.set_right((style->get_padding(*mfont))*2);
|
|
cursor_x = style->get_padding(*mfont);
|
|
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Cut",*this,&text_field::on_cut,'t'));
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Copy",*this,&text_field::on_copy,'C'));
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Paste",*this,&text_field::on_paste,'P'));
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Delete",*this,&text_field::on_delete_selected,'D'));
|
|
right_click_menu.menu().add_menu_item(menu_item_separator());
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Select All",*this,&text_field::on_select_all,'A'));
|
|
|
|
right_click_menu.set_rect(get_text_rect());
|
|
enable_events();
|
|
|
|
t.set_delay_time(500);
|
|
}
|
|
|
|
~text_field (
|
|
)
|
|
{
|
|
disable_events();
|
|
parent.invalidate_rectangle(rect);
|
|
t.stop_and_wait();
|
|
}
|
|
|
|
template <
|
|
typename style_type
|
|
>
|
|
void set_style (
|
|
const style_type& style_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
style.reset(new style_type(style_));
|
|
// call this just so that this widget redraws itself with the new style
|
|
set_main_font(mfont);
|
|
}
|
|
|
|
void set_text (
|
|
const std::string& text_
|
|
);
|
|
|
|
void set_text (
|
|
const std::wstring& text_
|
|
);
|
|
|
|
void give_input_focus (
|
|
);
|
|
|
|
bool has_input_focus (
|
|
) const;
|
|
|
|
void select_all_text (
|
|
);
|
|
|
|
void set_text (
|
|
const dlib::ustring& text_
|
|
);
|
|
|
|
const std::string text (
|
|
) const;
|
|
|
|
const std::wstring wtext (
|
|
) const;
|
|
|
|
const dlib::ustring utext (
|
|
) const;
|
|
|
|
void set_text_color (
|
|
const rgb_pixel color
|
|
);
|
|
|
|
const rgb_pixel text_color (
|
|
) const;
|
|
|
|
void set_background_color (
|
|
const rgb_pixel color
|
|
);
|
|
|
|
const rgb_pixel background_color (
|
|
) const;
|
|
|
|
void set_width (
|
|
unsigned long width
|
|
);
|
|
|
|
void set_pos (
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
int next_free_user_event_number (
|
|
) const
|
|
{
|
|
return drawable::next_free_user_event_number()+1;
|
|
}
|
|
|
|
void disable (
|
|
);
|
|
|
|
void enable (
|
|
);
|
|
|
|
void hide (
|
|
);
|
|
|
|
void show (
|
|
);
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_text_modified_handler (
|
|
T& object,
|
|
void (T::*event_handler)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
text_modified_handler = make_mfp(object,event_handler);
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_enter_key_handler (
|
|
T& object,
|
|
void (T::*event_handler)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
enter_key_handler = make_mfp(object,event_handler);
|
|
}
|
|
|
|
void set_text_modified_handler (
|
|
const any_function<void()>& event_handler
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
text_modified_handler = event_handler;
|
|
}
|
|
|
|
void set_enter_key_handler (
|
|
const any_function<void()>& event_handler
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
enter_key_handler = event_handler;
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_focus_lost_handler (
|
|
T& object,
|
|
void (T::*event_handler)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
focus_lost_handler = make_mfp(object,event_handler);
|
|
}
|
|
|
|
void set_focus_lost_handler (
|
|
const any_function<void()>& event_handler
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
focus_lost_handler = event_handler;
|
|
}
|
|
|
|
private:
|
|
|
|
void on_cut (
|
|
);
|
|
|
|
void on_copy (
|
|
);
|
|
|
|
void on_paste (
|
|
);
|
|
|
|
void on_select_all (
|
|
);
|
|
|
|
void on_delete_selected (
|
|
);
|
|
|
|
void on_text_is_selected (
|
|
);
|
|
|
|
void on_no_text_selected (
|
|
);
|
|
|
|
void on_user_event (
|
|
int num
|
|
)
|
|
{
|
|
// ignore this user event if it isn't for us
|
|
if (num != drawable::next_free_user_event_number())
|
|
return;
|
|
|
|
if (recent_movement == false)
|
|
{
|
|
cursor_visible = !cursor_visible;
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
else
|
|
{
|
|
if (cursor_visible == false)
|
|
{
|
|
cursor_visible = true;
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
recent_movement = false;
|
|
}
|
|
}
|
|
|
|
void timer_action (
|
|
) { parent.trigger_user_event(this,drawable::next_free_user_event_number()); }
|
|
/*!
|
|
ensures
|
|
- flips the state of cursor_visible
|
|
!*/
|
|
|
|
void move_cursor (
|
|
unsigned long pos
|
|
);
|
|
/*!
|
|
requires
|
|
- pos <= text_.size()
|
|
ensures
|
|
- moves the cursor to the position given by pos and moves the text
|
|
in the text box if necessary
|
|
- if the position changes then the parent window will be updated
|
|
!*/
|
|
|
|
rectangle get_text_rect (
|
|
) const;
|
|
/*!
|
|
ensures
|
|
- returns the rectangle that should contain the text in this widget
|
|
!*/
|
|
|
|
dlib::ustring text_;
|
|
rgb_pixel text_color_;
|
|
rgb_pixel bg_color_;
|
|
|
|
unsigned long text_width;
|
|
unsigned long text_pos;
|
|
|
|
|
|
bool recent_movement;
|
|
bool has_focus;
|
|
bool cursor_visible;
|
|
long cursor_pos;
|
|
unsigned long cursor_x;
|
|
|
|
// this tells you what part of the text is highlighted
|
|
long highlight_start;
|
|
long highlight_end;
|
|
long shift_pos;
|
|
any_function<void()> text_modified_handler;
|
|
any_function<void()> enter_key_handler;
|
|
any_function<void()> focus_lost_handler;
|
|
|
|
std::unique_ptr<text_field_style> style;
|
|
|
|
timer<text_field> t;
|
|
|
|
popup_menu_region right_click_menu;
|
|
|
|
// restricted functions
|
|
text_field(text_field&); // copy constructor
|
|
text_field& operator=(text_field&); // assignment operator
|
|
|
|
|
|
protected:
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
);
|
|
|
|
void on_mouse_up (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_mouse_move (
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_keydown (
|
|
unsigned long key,
|
|
bool is_printable,
|
|
unsigned long state
|
|
);
|
|
|
|
void on_string_put (
|
|
const std::wstring &str
|
|
);
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class text_box
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class text_box : public scrollable_region
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
text_color_ == rgb_pixel(0,0,0)
|
|
bg_color_ == rgb_pixel(255,255,255)
|
|
cursor_pos == 0
|
|
text_ == ""
|
|
has_focus == false
|
|
cursor_visible == false
|
|
recent_movement == false
|
|
highlight_start == 0
|
|
highlight_end == -1
|
|
shift_pos == -1
|
|
|
|
CONVENTION
|
|
- cursor_pos == the position of the cursor in the string text_. The
|
|
cursor appears before the letter text_[cursor_pos]
|
|
- cursor_rect == The rectangle that should be drawn for the cursor.
|
|
The position is relative to total_rect().
|
|
- has_focus == true if this text field has keyboard input focus
|
|
- cursor_visible == true if the cursor should be painted
|
|
- text_ == text()
|
|
|
|
- if (has_focus && the user has recently moved the cursor) then
|
|
- recent_movement == true
|
|
- else
|
|
- recent_movement == false
|
|
|
|
- if (highlight_start <= highlight_end) then
|
|
- text[highlight_start] though text[highlight_end] should be
|
|
highlighted
|
|
|
|
- if (shift_pos != -1) then
|
|
- has_focus == true
|
|
- the shift key is being held down or the left mouse button is
|
|
being held down.
|
|
- shift_pos == the position of the cursor when the shift or mouse key
|
|
was first pressed.
|
|
|
|
- text_color() == text_color_
|
|
- background_color() == bg_color_
|
|
!*/
|
|
|
|
public:
|
|
text_box(
|
|
drawable_window& w
|
|
) :
|
|
scrollable_region(w,MOUSE_CLICK | KEYBOARD_EVENTS | MOUSE_MOVE | STRING_PUT),
|
|
text_color_(0,0,0),
|
|
bg_color_(255,255,255),
|
|
recent_movement(false),
|
|
has_focus(false),
|
|
cursor_visible(false),
|
|
cursor_pos(0),
|
|
highlight_start(0),
|
|
highlight_end(-1),
|
|
shift_pos(-1),
|
|
t(*this,&text_box::timer_action),
|
|
right_click_menu(w)
|
|
{
|
|
style.reset(new text_box_style_default());
|
|
|
|
const long padding = static_cast<long>(style->get_padding(*mfont));
|
|
cursor_rect = mfont->compute_cursor_rect(rectangle(padding,padding,1000000,1000000), text_, 0);
|
|
|
|
adjust_total_rect();
|
|
|
|
set_vertical_mouse_wheel_scroll_increment(mfont->height());
|
|
set_horizontal_mouse_wheel_scroll_increment(mfont->height());
|
|
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Cut",*this,&text_box::on_cut,'t'));
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Copy",*this,&text_box::on_copy,'C'));
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Paste",*this,&text_box::on_paste,'P'));
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Delete",*this,&text_box::on_delete_selected,'D'));
|
|
right_click_menu.menu().add_menu_item(menu_item_separator());
|
|
right_click_menu.menu().add_menu_item(menu_item_text("Select All",*this,&text_box::on_select_all,'A'));
|
|
|
|
right_click_menu.set_rect(get_text_rect());
|
|
|
|
set_size(100,100);
|
|
|
|
enable_events();
|
|
|
|
t.set_delay_time(500);
|
|
}
|
|
|
|
~text_box (
|
|
)
|
|
{
|
|
disable_events();
|
|
parent.invalidate_rectangle(rect);
|
|
t.stop_and_wait();
|
|
}
|
|
|
|
template <
|
|
typename style_type
|
|
>
|
|
void set_style (
|
|
const style_type& style_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
style.reset(new style_type(style_));
|
|
|
|
scrollable_region::set_style(style_.get_scrollable_region_style());
|
|
// call this just so that this widget redraws itself with the new style
|
|
set_main_font(mfont);
|
|
}
|
|
|
|
void set_text (
|
|
const std::string& text_
|
|
);
|
|
|
|
void set_text (
|
|
const std::wstring& text_
|
|
);
|
|
|
|
void set_text (
|
|
const dlib::ustring& text_
|
|
);
|
|
|
|
const std::string text (
|
|
) const;
|
|
|
|
const std::wstring wtext (
|
|
) const;
|
|
|
|
const dlib::ustring utext (
|
|
) const;
|
|
|
|
void set_text_color (
|
|
const rgb_pixel color
|
|
);
|
|
|
|
const rgb_pixel text_color (
|
|
) const;
|
|
|
|
void set_background_color (
|
|
const rgb_pixel color
|
|
);
|
|
|
|
const rgb_pixel background_color (
|
|
) const;
|
|
|
|
void set_size (
|
|
unsigned long width,
|
|
unsigned long height
|
|
);
|
|
|
|
void set_pos (
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
int next_free_user_event_number (
|
|
) const
|
|
{
|
|
return scrollable_region::next_free_user_event_number()+1;
|
|
}
|
|
|
|
void disable (
|
|
);
|
|
|
|
void enable (
|
|
);
|
|
|
|
void hide (
|
|
);
|
|
|
|
void show (
|
|
);
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_text_modified_handler (
|
|
T& object,
|
|
void (T::*event_handler)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
text_modified_handler = make_mfp(object,event_handler);
|
|
}
|
|
|
|
void set_text_modified_handler (
|
|
const any_function<void()>& event_handler
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
text_modified_handler = event_handler;
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_enter_key_handler (
|
|
T& object,
|
|
void (T::*event_handler)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
enter_key_handler = make_mfp(object,event_handler);
|
|
}
|
|
|
|
void set_enter_key_handler (
|
|
const any_function<void()>& event_handler
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
enter_key_handler = event_handler;
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_focus_lost_handler (
|
|
T& object,
|
|
void (T::*event_handler)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
focus_lost_handler = make_mfp(object,event_handler);
|
|
}
|
|
|
|
void set_focus_lost_handler (
|
|
const any_function<void()>& event_handler
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
focus_lost_handler = event_handler;
|
|
}
|
|
|
|
private:
|
|
|
|
void on_cut (
|
|
);
|
|
|
|
void on_copy (
|
|
);
|
|
|
|
void on_paste (
|
|
);
|
|
|
|
void on_select_all (
|
|
);
|
|
|
|
void on_delete_selected (
|
|
);
|
|
|
|
void on_text_is_selected (
|
|
);
|
|
|
|
void on_no_text_selected (
|
|
);
|
|
|
|
void on_user_event (
|
|
int num
|
|
)
|
|
{
|
|
// ignore this user event if it isn't for us
|
|
if (num != scrollable_region::next_free_user_event_number())
|
|
return;
|
|
|
|
if (recent_movement == false)
|
|
{
|
|
cursor_visible = !cursor_visible;
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
else
|
|
{
|
|
if (cursor_visible == false)
|
|
{
|
|
cursor_visible = true;
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
recent_movement = false;
|
|
}
|
|
}
|
|
|
|
// The reason for using user actions here rather than just having the timer just call
|
|
// what it needs directly is to avoid a potential deadlock during destruction of this widget.
|
|
void timer_action (
|
|
) { parent.trigger_user_event(this,scrollable_region::next_free_user_event_number()); }
|
|
/*!
|
|
ensures
|
|
- flips the state of cursor_visible
|
|
!*/
|
|
|
|
void move_cursor (
|
|
unsigned long pos
|
|
);
|
|
/*!
|
|
requires
|
|
- pos <= text_.size()
|
|
ensures
|
|
- moves the cursor to the position given by pos and moves the text
|
|
in the text box if necessary
|
|
- if the position changes then the parent window will be updated
|
|
!*/
|
|
|
|
rectangle get_text_rect (
|
|
) const;
|
|
/*!
|
|
ensures
|
|
- returns the rectangle that should contain the text in this widget
|
|
!*/
|
|
|
|
void adjust_total_rect (
|
|
);
|
|
/*!
|
|
ensures
|
|
- adjusts total_rect() so that it is big enough to contain the text
|
|
currently in this object.
|
|
!*/
|
|
|
|
dlib::ustring text_;
|
|
rgb_pixel text_color_;
|
|
rgb_pixel bg_color_;
|
|
|
|
|
|
|
|
bool recent_movement;
|
|
bool has_focus;
|
|
bool cursor_visible;
|
|
long cursor_pos;
|
|
rectangle cursor_rect;
|
|
|
|
// this tells you what part of the text is highlighted
|
|
long highlight_start;
|
|
long highlight_end;
|
|
long shift_pos;
|
|
any_function<void()> text_modified_handler;
|
|
any_function<void()> enter_key_handler;
|
|
any_function<void()> focus_lost_handler;
|
|
|
|
std::unique_ptr<text_box_style> style;
|
|
|
|
timer<text_box> t;
|
|
|
|
popup_menu_region right_click_menu;
|
|
|
|
// restricted functions
|
|
text_box(text_box&); // copy constructor
|
|
text_box& operator=(text_box&); // assignment operator
|
|
|
|
|
|
protected:
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
);
|
|
|
|
void on_mouse_up (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_mouse_move (
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_keydown (
|
|
unsigned long key,
|
|
bool is_printable,
|
|
unsigned long state
|
|
);
|
|
|
|
void on_string_put (
|
|
const std::wstring &str
|
|
);
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class check_box
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class check_box : public toggle_button
|
|
{
|
|
public:
|
|
check_box(
|
|
drawable_window& w
|
|
) : toggle_button(w)
|
|
{
|
|
set_style(toggle_button_style_check_box());
|
|
}
|
|
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class radio_button
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class radio_button : public toggle_button
|
|
{
|
|
public:
|
|
radio_button (
|
|
drawable_window& w
|
|
) : toggle_button(w)
|
|
{
|
|
set_style(toggle_button_style_radio_button());
|
|
}
|
|
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class tabbed_display
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class tabbed_display : public drawable
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
- tabs.size() == 0
|
|
- selected_tab_ == 0
|
|
|
|
CONVENTION
|
|
- number_of_tabs() == tabs.size()
|
|
- tab_name(idx) == tabs[idx]
|
|
- if (tabs.size() > 0) then
|
|
- selected_tab_ == the index of the tab that is currently selected
|
|
|
|
- for all valid i:
|
|
- tabs[i].width == mfont->compute_size(tabs[i].name)
|
|
- tabs[i].rect == the rectangle that defines where this tab is
|
|
- if (tabs[i].group != 0) then
|
|
- tabs[i].group == a pointer to the widget_group for this tab.
|
|
|
|
- left_pad == the amount of padding in a tab to the left of the name string.
|
|
- right_pad == the amount of padding in a tab to the right of the name string.
|
|
- top_pad == the amount of padding in a tab to the top of the name string.
|
|
- bottom_pad == the amount of padding in a tab to the bottom of the name string.
|
|
|
|
- if (event_handler.is_set()) then
|
|
- event_handler() is what is called to process click events
|
|
on this object.
|
|
!*/
|
|
|
|
public:
|
|
|
|
tabbed_display(
|
|
drawable_window& w
|
|
);
|
|
|
|
virtual ~tabbed_display(
|
|
);
|
|
|
|
void set_size (
|
|
unsigned long width,
|
|
unsigned long height
|
|
);
|
|
|
|
void set_number_of_tabs (
|
|
unsigned long num
|
|
);
|
|
|
|
unsigned long selected_tab (
|
|
) const;
|
|
|
|
unsigned long number_of_tabs (
|
|
) const;
|
|
|
|
const std::string tab_name (
|
|
unsigned long idx
|
|
) const;
|
|
|
|
const std::wstring tab_wname (
|
|
unsigned long idx
|
|
) const;
|
|
|
|
const dlib::ustring& tab_uname (
|
|
unsigned long idx
|
|
) const;
|
|
|
|
void set_tab_name (
|
|
unsigned long idx,
|
|
const std::string& new_name
|
|
);
|
|
|
|
void set_tab_name (
|
|
unsigned long idx,
|
|
const std::wstring& new_name
|
|
);
|
|
|
|
void set_tab_name (
|
|
unsigned long idx,
|
|
const dlib::ustring& new_name
|
|
);
|
|
|
|
void set_pos (
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_click_handler (
|
|
T& object,
|
|
void (T::*eh)(unsigned long new_idx,unsigned long old_idx)
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
event_handler = make_mfp(object,eh);
|
|
}
|
|
|
|
void set_click_handler (
|
|
const any_function<void(unsigned long,unsigned long)>& eh
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
event_handler = eh;
|
|
}
|
|
|
|
void set_tab_group (
|
|
unsigned long idx,
|
|
widget_group& group
|
|
);
|
|
|
|
void show (
|
|
);
|
|
|
|
void hide (
|
|
);
|
|
|
|
void enable (
|
|
);
|
|
|
|
void disable (
|
|
);
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
void fit_to_contents (
|
|
);
|
|
|
|
protected:
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
);
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
private:
|
|
void recompute_tabs (
|
|
);
|
|
/*!
|
|
ensures
|
|
- recomputes the rectangles for all the tabs and makes this object
|
|
wider if needed
|
|
!*/
|
|
|
|
void draw_tab (
|
|
const rectangle& tab,
|
|
const canvas& c
|
|
) const;
|
|
/*!
|
|
ensures
|
|
- draws the outline of a tab as given by the rectangle onto c
|
|
!*/
|
|
|
|
struct tab_data
|
|
{
|
|
tab_data() : width(0), group(0) {}
|
|
|
|
dlib::ustring name;
|
|
unsigned long width;
|
|
rectangle rect;
|
|
widget_group* group;
|
|
};
|
|
|
|
unsigned long selected_tab_;
|
|
|
|
array<tab_data> tabs;
|
|
|
|
const long left_pad;
|
|
const long right_pad;
|
|
const long top_pad;
|
|
const long bottom_pad;
|
|
|
|
any_function<void(unsigned long,unsigned long)> event_handler;
|
|
|
|
// restricted functions
|
|
tabbed_display(tabbed_display&); // copy constructor
|
|
tabbed_display& operator=(tabbed_display&); // assignment operator
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class named_rectangle
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class named_rectangle : public drawable
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
name == ""
|
|
|
|
CONVENTION
|
|
name_ == name()
|
|
!*/
|
|
|
|
public:
|
|
|
|
named_rectangle(
|
|
drawable_window& w
|
|
);
|
|
|
|
virtual ~named_rectangle(
|
|
);
|
|
|
|
void set_size (
|
|
unsigned long width,
|
|
unsigned long height
|
|
);
|
|
|
|
void set_name (
|
|
const std::string& name
|
|
);
|
|
|
|
void set_name (
|
|
const std::wstring& name
|
|
);
|
|
|
|
void set_name (
|
|
const dlib::ustring& name
|
|
);
|
|
|
|
const std::string name (
|
|
) const;
|
|
|
|
const std::wstring wname (
|
|
) const;
|
|
|
|
const dlib::ustring uname (
|
|
) const;
|
|
|
|
void wrap_around (
|
|
const rectangle& rect
|
|
);
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
protected:
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
private:
|
|
|
|
void make_name_fit_in_rect (
|
|
);
|
|
|
|
dlib::ustring name_;
|
|
unsigned long name_width;
|
|
unsigned long name_height;
|
|
|
|
// restricted functions
|
|
named_rectangle(named_rectangle&); // copy constructor
|
|
named_rectangle& operator=(named_rectangle&); // assignment operator
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class mouse_tracker
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class mouse_tracker : public draggable
|
|
{
|
|
|
|
public:
|
|
|
|
mouse_tracker(
|
|
drawable_window& w
|
|
);
|
|
|
|
~mouse_tracker(
|
|
);
|
|
|
|
void show (
|
|
);
|
|
|
|
void hide (
|
|
);
|
|
|
|
void enable (
|
|
);
|
|
|
|
void disable (
|
|
);
|
|
|
|
void set_pos (
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
protected:
|
|
|
|
void on_mouse_move (
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_drag (
|
|
);
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
);
|
|
|
|
|
|
private:
|
|
|
|
const long offset;
|
|
named_rectangle nr;
|
|
label x_label;
|
|
label y_label;
|
|
std::ostringstream sout;
|
|
|
|
long click_x, click_y;
|
|
|
|
// restricted functions
|
|
mouse_tracker(mouse_tracker&); // copy constructor
|
|
mouse_tracker& operator=(mouse_tracker&); // assignment operator
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// function message_box()
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
namespace message_box_helper
|
|
{
|
|
class box_win : public drawable_window
|
|
{
|
|
void initialize (
|
|
);
|
|
public:
|
|
box_win (
|
|
const std::string& title_,
|
|
const std::string& message_
|
|
);
|
|
|
|
box_win (
|
|
const std::wstring& title_,
|
|
const std::wstring& message_
|
|
);
|
|
|
|
box_win (
|
|
const dlib::ustring& title_,
|
|
const dlib::ustring& message_
|
|
);
|
|
|
|
~box_win (
|
|
);
|
|
|
|
void set_click_handler (
|
|
const any_function<void()>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(wm);
|
|
event_handler = event_handler_;
|
|
}
|
|
|
|
private:
|
|
|
|
static void deleter_thread (
|
|
void* param
|
|
);
|
|
|
|
void on_click (
|
|
);
|
|
|
|
on_close_return_code on_window_close (
|
|
);
|
|
|
|
const std::wstring title;
|
|
const std::wstring message;
|
|
label msg;
|
|
button btn_ok;
|
|
|
|
any_function<void()> event_handler;
|
|
};
|
|
|
|
class blocking_box_win : public drawable_window
|
|
{
|
|
void initialize (
|
|
);
|
|
|
|
public:
|
|
blocking_box_win (
|
|
const std::string& title_,
|
|
const std::string& message_
|
|
);
|
|
|
|
blocking_box_win (
|
|
const std::wstring& title_,
|
|
const std::wstring& message_
|
|
);
|
|
|
|
blocking_box_win (
|
|
const dlib::ustring& title_,
|
|
const dlib::ustring& message_
|
|
);
|
|
|
|
~blocking_box_win (
|
|
);
|
|
|
|
private:
|
|
|
|
void on_click (
|
|
);
|
|
|
|
const std::wstring title;
|
|
const std::wstring message;
|
|
label msg;
|
|
button btn_ok;
|
|
};
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void message_box (
|
|
const std::string& title,
|
|
const std::string& message,
|
|
T& object,
|
|
void (T::*event_handler)()
|
|
)
|
|
{
|
|
using namespace message_box_helper;
|
|
box_win* win = new box_win(title,message);
|
|
win->set_click_handler(make_mfp(object,event_handler));
|
|
}
|
|
|
|
inline void message_box (
|
|
const std::string& title,
|
|
const std::string& message,
|
|
const any_function<void()>& event_handler
|
|
)
|
|
{
|
|
using namespace message_box_helper;
|
|
box_win* win = new box_win(title,message);
|
|
win->set_click_handler(event_handler);
|
|
}
|
|
|
|
inline void message_box (
|
|
const std::string& title,
|
|
const std::string& message
|
|
)
|
|
{
|
|
using namespace message_box_helper;
|
|
new box_win(title,message);
|
|
}
|
|
|
|
inline void message_box_blocking (
|
|
const std::string& title,
|
|
const std::string& message
|
|
)
|
|
{
|
|
using namespace message_box_helper;
|
|
blocking_box_win w(title,message);
|
|
w.wait_until_closed();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class list_box
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
namespace list_box_helper{
|
|
template <typename S = std::string>
|
|
class list_box : public scrollable_region,
|
|
public enumerable<const S>
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
- ms_enabled == false
|
|
- items.size() == 0
|
|
- last_selected = 0
|
|
|
|
CONVENTION
|
|
- size() == items.size()
|
|
- (*this)[i] == items[i].name
|
|
- is_selected(i) == items[i].is_selected
|
|
- ms_enabled == multiple_select_enabled()
|
|
|
|
- items[i].width == the width of items[i].name as given by font::compute_size()
|
|
- items[i].height == the height of items[i].name as given by font::compute_size()
|
|
|
|
- last_selected == the last item the user selected
|
|
!*/
|
|
|
|
public:
|
|
|
|
list_box(
|
|
drawable_window& w
|
|
);
|
|
|
|
~list_box(
|
|
);
|
|
|
|
bool is_selected (
|
|
unsigned long index
|
|
) const;
|
|
|
|
void select (
|
|
unsigned long index
|
|
);
|
|
|
|
void unselect (
|
|
unsigned long index
|
|
);
|
|
|
|
template <
|
|
typename style_type
|
|
>
|
|
void set_style (
|
|
const style_type& style_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
style.reset(new style_type(style_));
|
|
scrollable_region::set_style(style_.get_scrollable_region_style());
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
|
|
template <typename T>
|
|
void get_selected (
|
|
T& list
|
|
) const
|
|
{
|
|
auto_mutex M(m);
|
|
list.clear();
|
|
for (unsigned long i = 0; i < items.size(); ++i)
|
|
{
|
|
if (items[i].is_selected)
|
|
{
|
|
unsigned long idx = i;
|
|
list.enqueue(idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void load (
|
|
const T& list
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
items.clear();
|
|
unsigned long i = 0;
|
|
items.set_max_size(list.size());
|
|
items.set_size(list.size());
|
|
list.reset();
|
|
unsigned long max_width = 0;
|
|
unsigned long total_height = 0;
|
|
while (list.move_next())
|
|
{
|
|
items[i].is_selected = false;
|
|
items[i].name = list.element();
|
|
mfont->compute_size(items[i].name,items[i].width, items[i].height);
|
|
|
|
if (items[i].width > max_width)
|
|
max_width = items[i].width;
|
|
total_height += items[i].height;
|
|
|
|
++i;
|
|
}
|
|
set_total_rect_size(max_width, total_height);
|
|
|
|
parent.invalidate_rectangle(rect);
|
|
last_selected = 0;
|
|
}
|
|
|
|
const S& operator[] (
|
|
unsigned long index
|
|
) const;
|
|
|
|
bool multiple_select_enabled (
|
|
) const;
|
|
|
|
void enable_multiple_select (
|
|
);
|
|
|
|
void disable_multiple_select (
|
|
);
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_double_click_handler (
|
|
T& object,
|
|
void (T::*eh)(unsigned long index)
|
|
) { auto_mutex M(m); event_handler = make_mfp(object,eh); }
|
|
|
|
void set_double_click_handler (
|
|
const any_function<void(unsigned long)>& eh
|
|
) { auto_mutex M(m); event_handler = eh; }
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_click_handler (
|
|
T& object,
|
|
void (T::*eh)(unsigned long index)
|
|
) { auto_mutex M(m); single_click_event_handler = make_mfp(object,eh); }
|
|
|
|
void set_click_handler (
|
|
const any_function<void(unsigned long)>& eh
|
|
) { auto_mutex M(m); single_click_event_handler = eh; }
|
|
|
|
bool at_start (
|
|
) const;
|
|
|
|
void reset (
|
|
) const;
|
|
|
|
bool current_element_valid (
|
|
) const;
|
|
|
|
const S& element (
|
|
) const;
|
|
|
|
const S& element (
|
|
);
|
|
|
|
bool move_next (
|
|
) const;
|
|
|
|
size_t size (
|
|
) const;
|
|
|
|
unsigned long get_selected (
|
|
) const;
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
private:
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
);
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
template <typename SS>
|
|
struct data
|
|
{
|
|
SS name;
|
|
bool is_selected;
|
|
unsigned long width;
|
|
unsigned long height;
|
|
};
|
|
|
|
bool ms_enabled;
|
|
array<data<S> > items;
|
|
any_function<void(unsigned long)> event_handler;
|
|
any_function<void(unsigned long)> single_click_event_handler;
|
|
unsigned long last_selected;
|
|
|
|
std::unique_ptr<list_box_style> style;
|
|
|
|
// restricted functions
|
|
list_box(list_box&); // copy constructor
|
|
list_box& operator=(list_box&); // assignment operator
|
|
};
|
|
}
|
|
typedef list_box_helper::list_box<std::string> list_box;
|
|
typedef list_box_helper::list_box<std::wstring> wlist_box;
|
|
typedef list_box_helper::list_box<dlib::ustring> ulist_box;
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// function open_file_box()
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
namespace open_file_box_helper
|
|
{
|
|
class box_win : public drawable_window
|
|
{
|
|
public:
|
|
box_win (
|
|
const std::string& title,
|
|
bool has_text_field = false
|
|
);
|
|
|
|
~box_win (
|
|
);
|
|
|
|
void set_click_handler (
|
|
const any_function<void(const std::string&)>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(wm);
|
|
event_handler = event_handler_;
|
|
}
|
|
|
|
private:
|
|
|
|
void set_sizes(
|
|
);
|
|
|
|
void on_window_resized (
|
|
);
|
|
|
|
void deleter_thread (
|
|
);
|
|
|
|
void enter_folder (
|
|
const std::string& folder_name
|
|
);
|
|
|
|
void on_dirs_click (
|
|
unsigned long idx
|
|
);
|
|
|
|
void on_files_click (
|
|
unsigned long idx
|
|
);
|
|
|
|
void on_files_double_click (
|
|
unsigned long
|
|
);
|
|
|
|
void on_cancel_click (
|
|
);
|
|
|
|
void on_open_click (
|
|
);
|
|
|
|
void on_path_button_click (
|
|
toggle_button& btn
|
|
);
|
|
|
|
bool set_dir (
|
|
const std::string& dir
|
|
);
|
|
|
|
void on_root_click (
|
|
);
|
|
|
|
on_close_return_code on_window_close (
|
|
);
|
|
|
|
label lbl_dirs;
|
|
label lbl_files;
|
|
label lbl_file_name;
|
|
list_box lb_dirs;
|
|
list_box lb_files;
|
|
button btn_ok;
|
|
button btn_cancel;
|
|
toggle_button btn_root;
|
|
text_field tf_file_name;
|
|
std::string path;
|
|
std::string prefix;
|
|
int cur_dir;
|
|
|
|
any_function<void(const std::string&)> event_handler;
|
|
sequence<std::unique_ptr<toggle_button> >::kernel_2a_c sob;
|
|
};
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void open_file_box (
|
|
T& object,
|
|
void (T::*event_handler)(const std::string&)
|
|
)
|
|
{
|
|
using namespace open_file_box_helper;
|
|
box_win* win = new box_win("Open File",true);
|
|
win->set_click_handler(make_mfp(object,event_handler));
|
|
}
|
|
|
|
inline void open_file_box (
|
|
const any_function<void(const std::string&)>& event_handler
|
|
)
|
|
{
|
|
using namespace open_file_box_helper;
|
|
box_win* win = new box_win("Open File",true);
|
|
win->set_click_handler(event_handler);
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void open_existing_file_box (
|
|
T& object,
|
|
void (T::*event_handler)(const std::string&)
|
|
)
|
|
{
|
|
using namespace open_file_box_helper;
|
|
box_win* win = new box_win("Open File");
|
|
win->set_click_handler(make_mfp(object,event_handler));
|
|
}
|
|
|
|
inline void open_existing_file_box (
|
|
const any_function<void(const std::string&)>& event_handler
|
|
)
|
|
{
|
|
using namespace open_file_box_helper;
|
|
box_win* win = new box_win("Open File");
|
|
win->set_click_handler(event_handler);
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void save_file_box (
|
|
T& object,
|
|
void (T::*event_handler)(const std::string&)
|
|
)
|
|
{
|
|
using namespace open_file_box_helper;
|
|
box_win* win = new box_win("Save File",true);
|
|
win->set_click_handler(make_mfp(object,event_handler));
|
|
}
|
|
|
|
inline void save_file_box (
|
|
const any_function<void(const std::string&)>& event_handler
|
|
)
|
|
{
|
|
using namespace open_file_box_helper;
|
|
box_win* win = new box_win("Save File",true);
|
|
win->set_click_handler(event_handler);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class menu_bar
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class menu_bar : public drawable
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
- menus.size() == 0
|
|
- open_menu == 0
|
|
|
|
CONVENTION
|
|
- size() == menus.size()
|
|
- all menu data is stored in menus
|
|
- menus[x].name == the name of the xth menu
|
|
- if (menus[x].underline_pos != std::string::npos) then
|
|
- menus[x].underline_pos == the position of the character in the
|
|
menu name that should be underlined
|
|
- menus[x].underline_p1 != menus[x].underline_p2
|
|
and these two points define the underline bar
|
|
- else
|
|
- menus[x].underline_p1 == menus[x].underline_p2
|
|
- menus[x].menu == menu(x)
|
|
- menus[x].rect == the rectangle in which menus[x].name is drawn
|
|
- menus[x].bgrect == the rectangle for the xth menu button
|
|
|
|
- if (there is an open menu on the screen) then
|
|
- open_menu == the index of the open menu from menus
|
|
- else
|
|
- open_menu == menus.size()
|
|
!*/
|
|
|
|
public:
|
|
menu_bar(
|
|
drawable_window& w
|
|
);
|
|
|
|
~menu_bar();
|
|
|
|
// this function does nothing
|
|
void set_pos(long,long){}
|
|
|
|
void set_main_font (
|
|
const std::shared_ptr<font>& f
|
|
);
|
|
|
|
void set_number_of_menus (
|
|
unsigned long num
|
|
);
|
|
|
|
unsigned long number_of_menus (
|
|
) const;
|
|
|
|
void set_menu_name (
|
|
unsigned long idx,
|
|
const std::string name,
|
|
char underline_ch = '\0'
|
|
);
|
|
|
|
void set_menu_name (
|
|
unsigned long idx,
|
|
const std::wstring name,
|
|
char underline_ch = '\0'
|
|
);
|
|
|
|
void set_menu_name (
|
|
unsigned long idx,
|
|
const dlib::ustring name,
|
|
char underline_ch = '\0'
|
|
);
|
|
|
|
const std::string menu_name (
|
|
unsigned long idx
|
|
) const;
|
|
|
|
const std::wstring menu_wname (
|
|
unsigned long idx
|
|
) const;
|
|
|
|
const dlib::ustring menu_uname (
|
|
unsigned long idx
|
|
) const;
|
|
|
|
popup_menu& menu (
|
|
unsigned long idx
|
|
);
|
|
|
|
const popup_menu& menu (
|
|
unsigned long idx
|
|
) const;
|
|
|
|
protected:
|
|
|
|
void on_window_resized (
|
|
);
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
void on_window_moved (
|
|
);
|
|
|
|
void on_focus_lost (
|
|
);
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long ,
|
|
long x,
|
|
long y,
|
|
bool
|
|
);
|
|
|
|
void on_mouse_move (
|
|
unsigned long ,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_keydown (
|
|
unsigned long key,
|
|
bool is_printable,
|
|
unsigned long state
|
|
);
|
|
|
|
private:
|
|
|
|
void show_menu (
|
|
unsigned long i
|
|
);
|
|
|
|
void hide_menu (
|
|
);
|
|
|
|
void on_popup_hide (
|
|
);
|
|
|
|
void compute_menu_geometry (
|
|
);
|
|
|
|
void adjust_position (
|
|
);
|
|
|
|
struct menu_data
|
|
{
|
|
menu_data():underline_pos(dlib::ustring::npos){}
|
|
|
|
dlib::ustring name;
|
|
dlib::ustring::size_type underline_pos;
|
|
popup_menu menu;
|
|
rectangle rect;
|
|
rectangle bgrect;
|
|
point underline_p1;
|
|
point underline_p2;
|
|
};
|
|
|
|
array<menu_data> menus;
|
|
unsigned long open_menu;
|
|
|
|
// restricted functions
|
|
menu_bar(menu_bar&); // copy constructor
|
|
menu_bar& operator=(menu_bar&); // assignment operator
|
|
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class directed_graph_drawer
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
template <typename graph_type>
|
|
class directed_graph_drawer : public zoomable_region
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
- edge_selected == false
|
|
- mouse_drag == false
|
|
- selected_node == 0
|
|
- graph_.number_of_nodes() == 0
|
|
- external_graph.number_of_nodes() == 0
|
|
- radius == 25
|
|
- last_mouse_click_in_display == false
|
|
|
|
CONVENTION
|
|
- radius == the radius of the nodes when they aren't zoomed
|
|
- external_graph and graph_ have the same graph structure
|
|
- external_graph == graph()
|
|
- external_graph.node(i) == graph_node(i)
|
|
|
|
- if (one of the nodes is selected) then
|
|
- selected_node < graph_.number_of_nodes()
|
|
- graph_.node(selected_node) == the selected node
|
|
- else
|
|
- selected_node == graph_.number_of_nodes()
|
|
|
|
- if (the user is dragging a node with the mouse) then
|
|
- mouse_drag == true
|
|
- drag_offset == the vector from the mouse position to the
|
|
center of the node
|
|
- else
|
|
- mouse_drag == false
|
|
|
|
- if (the user has selected an edge) then
|
|
- edge_selected == true
|
|
- the parent node is graph_.node(selected_edge_parent)
|
|
- the child node is graph_.node(selected_edge_parent)
|
|
- else
|
|
- edge_selected == false
|
|
|
|
- for all valid i:
|
|
- graph_.node(i).data.p == the center of the node in graph space
|
|
- graph_.node(i).data.name == node_label(i)
|
|
- graph_.node(i).data.color == node_color(i)
|
|
- graph_.node(i).data.str_rect == a rectangle sized to contain graph_.node(i).data.name
|
|
|
|
- if (the last mouse click in our parent window as in our display_rect_ ) then
|
|
- last_mouse_click_in_display == true
|
|
- else
|
|
- last_mouse_click_in_display == false
|
|
!*/
|
|
|
|
public:
|
|
directed_graph_drawer (
|
|
drawable_window& w
|
|
) :
|
|
zoomable_region(w,MOUSE_CLICK | MOUSE_WHEEL | KEYBOARD_EVENTS),
|
|
radius(25),
|
|
edge_selected(false),
|
|
last_mouse_click_in_display(false)
|
|
{
|
|
mouse_drag = false;
|
|
selected_node = 0;
|
|
|
|
// Whenever you make your own drawable (or inherit from draggable or button_action)
|
|
// you have to remember to call this function to enable the events. The idea
|
|
// here is that you can perform whatever setup you need to do to get your
|
|
// object into a valid state without needing to worry about event handlers
|
|
// triggering before you are ready.
|
|
enable_events();
|
|
}
|
|
|
|
~directed_graph_drawer (
|
|
)
|
|
{
|
|
// Disable all further events for this drawable object. We have to do this
|
|
// because we don't want draw() events coming to this object while or after
|
|
// it has been destructed.
|
|
disable_events();
|
|
|
|
// Tell the parent window to redraw its area that previously contained this
|
|
// drawable object.
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
|
|
void clear_graph (
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
graph_.clear();
|
|
external_graph.clear();
|
|
parent.invalidate_rectangle(display_rect());
|
|
}
|
|
|
|
const typename graph_type::node_type& graph_node (
|
|
unsigned long i
|
|
) const
|
|
{
|
|
DLIB_ASSERT ( i < number_of_nodes() ,
|
|
"\tgraph_type::node_type& directed_graph_drawer::graph_node(i)"
|
|
<< "\n\ti: " << i
|
|
<< "\n\tnumber_of_nodes(): " << number_of_nodes()
|
|
);
|
|
return external_graph.node(i);
|
|
}
|
|
|
|
typename graph_type::node_type& graph_node (
|
|
unsigned long i
|
|
)
|
|
{
|
|
DLIB_ASSERT ( i < number_of_nodes() ,
|
|
"\tgraph_type::node_type& directed_graph_drawer::graph_node(i)"
|
|
<< "\n\ti: " << i
|
|
<< "\n\tnumber_of_nodes(): " << number_of_nodes()
|
|
);
|
|
return external_graph.node(i);
|
|
}
|
|
|
|
const graph_type& graph (
|
|
) const
|
|
{
|
|
return external_graph;
|
|
}
|
|
|
|
void save_graph (
|
|
std::ostream& out
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
serialize(external_graph, out);
|
|
serialize(graph_, out);
|
|
parent.invalidate_rectangle(display_rect());
|
|
}
|
|
|
|
void load_graph (
|
|
std::istream& in
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
deserialize(external_graph, in);
|
|
deserialize(graph_, in);
|
|
parent.invalidate_rectangle(display_rect());
|
|
}
|
|
|
|
unsigned long number_of_nodes (
|
|
) const
|
|
{
|
|
auto_mutex M(m);
|
|
return graph_.number_of_nodes();
|
|
}
|
|
|
|
void set_node_label (
|
|
unsigned long i,
|
|
const std::string& label
|
|
)
|
|
{
|
|
set_node_label(i, convert_mbstring_to_wstring(label));
|
|
}
|
|
|
|
void set_node_label (
|
|
unsigned long i,
|
|
const std::wstring& label
|
|
)
|
|
{
|
|
set_node_label(i, convert_wstring_to_utf32(label));
|
|
}
|
|
|
|
void set_node_label (
|
|
unsigned long i,
|
|
const dlib::ustring& label
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
DLIB_ASSERT ( i < number_of_nodes() ,
|
|
"\tvoid directed_graph_drawer::set_node_label(i,label)"
|
|
<< "\n\ti: " << i
|
|
<< "\n\tlabel: " << narrow(label)
|
|
<< "\n\tnumber_of_nodes(): " << number_of_nodes()
|
|
);
|
|
graph_.node(i).data.name = label.c_str();
|
|
unsigned long width, height;
|
|
mfont->compute_size(label,width,height);
|
|
graph_.node(i).data.str_rect = rectangle(width,height);
|
|
parent.invalidate_rectangle(display_rect());
|
|
}
|
|
|
|
void set_node_color (
|
|
unsigned long i,
|
|
rgb_pixel color
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
DLIB_ASSERT ( i < number_of_nodes() ,
|
|
"\tvoid directed_graph_drawer::set_node_color(i,label)"
|
|
<< "\n\ti: " << i
|
|
<< "\n\tnumber_of_nodes(): " << number_of_nodes()
|
|
);
|
|
graph_.node(i).data.color = color;
|
|
parent.invalidate_rectangle(display_rect());
|
|
}
|
|
|
|
rgb_pixel node_color (
|
|
unsigned long i
|
|
) const
|
|
{
|
|
auto_mutex M(m);
|
|
DLIB_ASSERT ( i < number_of_nodes() ,
|
|
"\trgb_pixel directed_graph_drawer::node_color(i)"
|
|
<< "\n\ti: " << i
|
|
<< "\n\tnumber_of_nodes(): " << number_of_nodes()
|
|
);
|
|
return graph_.node(i).data.color;
|
|
}
|
|
|
|
const std::string node_label (
|
|
unsigned long i
|
|
) const
|
|
{
|
|
auto_mutex M(m);
|
|
DLIB_ASSERT ( i < number_of_nodes() ,
|
|
"\tconst std::ustring directed_graph_drawer::node_label(i)"
|
|
<< "\n\ti: " << i
|
|
<< "\n\tnumber_of_nodes(): " << number_of_nodes()
|
|
);
|
|
return narrow(graph_.node(i).data.name);
|
|
}
|
|
|
|
const std::wstring node_wlabel (
|
|
unsigned long i
|
|
) const
|
|
{
|
|
return convert_utf32_to_wstring(node_ulabel(i));
|
|
}
|
|
|
|
const dlib::ustring node_ulabel (
|
|
unsigned long i
|
|
) const
|
|
{
|
|
auto_mutex M(m);
|
|
DLIB_ASSERT ( i < number_of_nodes() ,
|
|
"\tconst std::ustring directed_graph_drawer::node_label(i)"
|
|
<< "\n\ti: " << i
|
|
<< "\n\tnumber_of_nodes(): " << number_of_nodes()
|
|
);
|
|
return graph_.node(i).data.name.c_str();
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_node_selected_handler (
|
|
T& object,
|
|
void (T::*event_handler_)(unsigned long)
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
node_selected_handler = make_mfp(object,event_handler_);
|
|
}
|
|
|
|
void set_node_selected_handler (
|
|
const any_function<void(unsigned long)>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
node_selected_handler = event_handler_;
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_node_deselected_handler (
|
|
T& object,
|
|
void (T::*event_handler_)(unsigned long)
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
node_deselected_handler = make_mfp(object,event_handler_);
|
|
}
|
|
|
|
void set_node_deselected_handler (
|
|
const any_function<void(unsigned long)>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
node_deselected_handler = event_handler_;
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_node_deleted_handler (
|
|
T& object,
|
|
void (T::*event_handler_)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
node_deleted_handler = make_mfp(object,event_handler_);
|
|
}
|
|
|
|
void set_node_deleted_handler (
|
|
const any_function<void()>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
node_deleted_handler = event_handler_;
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_graph_modified_handler (
|
|
T& object,
|
|
void (T::*event_handler_)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
graph_modified_handler = make_mfp(object,event_handler_);
|
|
}
|
|
|
|
void set_graph_modified_handler (
|
|
const any_function<void()>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
graph_modified_handler = event_handler_;
|
|
}
|
|
|
|
protected:
|
|
|
|
void on_keydown (
|
|
unsigned long key,
|
|
bool ,
|
|
unsigned long
|
|
)
|
|
{
|
|
// ignore all keyboard input if the last thing the user clicked on
|
|
// wasn't the display area
|
|
if (last_mouse_click_in_display == false)
|
|
return;
|
|
|
|
// if a node is selected
|
|
if (selected_node != graph_.number_of_nodes())
|
|
{
|
|
// deselect the node if the user hits escape
|
|
if (key == base_window::KEY_ESC)
|
|
{
|
|
parent.invalidate_rectangle(display_rect());
|
|
if (node_deselected_handler.is_set())
|
|
node_deselected_handler(selected_node);
|
|
selected_node = graph_.number_of_nodes();
|
|
}
|
|
|
|
// delete the node if the user hits delete
|
|
if (key == base_window::KEY_DELETE || key == base_window::KEY_BACKSPACE)
|
|
{
|
|
parent.invalidate_rectangle(display_rect());
|
|
graph_.remove_node(selected_node);
|
|
external_graph.remove_node(selected_node);
|
|
selected_node = graph_.number_of_nodes();
|
|
mouse_drag = false;
|
|
if (graph_modified_handler.is_set())
|
|
graph_modified_handler();
|
|
if (node_deleted_handler.is_set())
|
|
node_deleted_handler();
|
|
}
|
|
}
|
|
|
|
// if an edge is selected
|
|
if (edge_selected)
|
|
{
|
|
// deselect the node if the user hits escape
|
|
if (key == base_window::KEY_ESC)
|
|
{
|
|
parent.invalidate_rectangle(display_rect());
|
|
edge_selected = false;
|
|
}
|
|
|
|
// delete the node if the user hits delete
|
|
if (key == base_window::KEY_DELETE || key == base_window::KEY_BACKSPACE)
|
|
{
|
|
parent.invalidate_rectangle(display_rect());
|
|
graph_.remove_edge(selected_edge_parent, selected_edge_child);
|
|
external_graph.remove_edge(selected_edge_parent, selected_edge_child);
|
|
edge_selected = false;
|
|
|
|
if (graph_modified_handler.is_set())
|
|
graph_modified_handler();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void on_mouse_move (
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
)
|
|
{
|
|
if (mouse_drag)
|
|
{
|
|
const point p(nearest_point(display_rect(),point(x,y)));
|
|
|
|
point center = drag_offset + p;
|
|
graph_.node(selected_node).data.p = gui_to_graph_space(center);
|
|
parent.invalidate_rectangle(display_rect());
|
|
}
|
|
else
|
|
{
|
|
zoomable_region::on_mouse_move(state,x,y);
|
|
}
|
|
|
|
// check if the mouse isn't being dragged anymore
|
|
if ((state & base_window::LEFT) == 0)
|
|
{
|
|
mouse_drag = false;
|
|
}
|
|
}
|
|
|
|
void on_mouse_up (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
)
|
|
{
|
|
mouse_drag = false;
|
|
zoomable_region::on_mouse_up(btn,state,x,y);
|
|
}
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
)
|
|
{
|
|
bool redraw = false;
|
|
|
|
if (display_rect().contains(x,y) &&
|
|
(btn == base_window::RIGHT || btn == base_window::LEFT) &&
|
|
(state & base_window::SHIFT) == 0 )
|
|
{
|
|
// start out saying no edge is selected
|
|
if (edge_selected)
|
|
{
|
|
edge_selected = false;
|
|
redraw = true;
|
|
}
|
|
|
|
bool click_hit_node = false;
|
|
dlib::vector<double,2> p(gui_to_graph_space(point(x,y)));
|
|
// check if this click is on an existing node
|
|
for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
|
|
{
|
|
dlib::vector<double,2> n(graph_.node(i).data.p);
|
|
if ((p-n).length() < radius)
|
|
{
|
|
click_hit_node = true;
|
|
point center = graph_to_gui_space(graph_.node(i).data.p);
|
|
mouse_drag = true;
|
|
drag_offset = center - point(x,y);
|
|
|
|
// only do something if the click isn't on the currently
|
|
// selected node
|
|
if (selected_node != i)
|
|
{
|
|
// send out the deselected event if appropriate
|
|
if (selected_node != graph_.number_of_nodes() && node_deselected_handler.is_set())
|
|
node_deselected_handler(selected_node);
|
|
|
|
selected_node = i;
|
|
redraw = true;
|
|
if (node_selected_handler.is_set())
|
|
node_selected_handler(selected_node);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if the click didn't hit any node then make sure nothing is selected
|
|
if (click_hit_node == false && selected_node != graph_.number_of_nodes())
|
|
{
|
|
if (node_deselected_handler.is_set())
|
|
node_deselected_handler(selected_node);
|
|
selected_node = graph_.number_of_nodes();
|
|
redraw = true;
|
|
}
|
|
|
|
|
|
// check if this click is on an edge if we didn't click on a node
|
|
if (click_hit_node == false)
|
|
{
|
|
for (unsigned long n = 0; n < graph_.number_of_nodes() && edge_selected == false; ++n)
|
|
{
|
|
const dlib::vector<double,2> parent_center(graph_to_gui_space(graph_.node(n).data.p));
|
|
for (unsigned long e = 0; e < graph_.node(n).number_of_children() && edge_selected == false; ++e)
|
|
{
|
|
const dlib::vector<double,2> child_center(graph_to_gui_space(graph_.node(n).child(e).data.p));
|
|
|
|
rectangle area;
|
|
area += parent_center;
|
|
area += child_center;
|
|
// if the point(x,y) is between the two nodes then lets consider it further
|
|
if (area.contains(point(x,y)))
|
|
{
|
|
p = point(x,y);
|
|
const dlib::vector<double> z(0,0,1);
|
|
// find the distance from the line between the two nodes
|
|
const dlib::vector<double,2> perpendicular(z.cross(parent_center-child_center).normalize());
|
|
double distance = std::abs((child_center-p).dot(perpendicular));
|
|
if (distance < 8)
|
|
{
|
|
edge_selected = true;
|
|
selected_edge_parent = n;
|
|
selected_edge_child = graph_.node(n).child(e).index();
|
|
redraw = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// if the click didn't land on any node then add a new one if this was
|
|
// a right mouse button click
|
|
if (click_hit_node == false && btn == base_window::RIGHT)
|
|
{
|
|
const unsigned long n = graph_.add_node();
|
|
external_graph.add_node();
|
|
|
|
graph_.node(n).data.p = gui_to_graph_space(point(x,y));
|
|
|
|
redraw = true;
|
|
selected_node = n;
|
|
mouse_drag = false;
|
|
if (graph_modified_handler.is_set())
|
|
graph_modified_handler();
|
|
|
|
if (node_selected_handler.is_set())
|
|
node_selected_handler(selected_node);
|
|
|
|
}
|
|
else if (selected_node == graph_.number_of_nodes())
|
|
{
|
|
// in this case the click landed in the white area between nodes
|
|
zoomable_region::on_mouse_down( btn, state, x, y, is_double_click);
|
|
}
|
|
}
|
|
|
|
// If the user is shift clicking with the mouse then see if we
|
|
// should add a new edge.
|
|
if (display_rect().contains(x,y) &&
|
|
btn == base_window::LEFT &&
|
|
(state & base_window::SHIFT) &&
|
|
selected_node != graph_.number_of_nodes() )
|
|
{
|
|
dlib::vector<double,2> p(gui_to_graph_space(point(x,y)));
|
|
// check if this click is on an existing node
|
|
for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
|
|
{
|
|
dlib::vector<double,2> n(graph_.node(i).data.p);
|
|
if ((p-n).length() < radius)
|
|
{
|
|
// add the edge if it doesn't already exist and isn't an edge back to
|
|
// the same node
|
|
if (graph_.has_edge(selected_node,i) == false && selected_node != i &&
|
|
graph_.has_edge(i, selected_node) == false)
|
|
{
|
|
graph_.add_edge(selected_node,i);
|
|
external_graph.add_edge(selected_node,i);
|
|
redraw = true;
|
|
|
|
if (graph_modified_handler.is_set())
|
|
graph_modified_handler();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (redraw)
|
|
parent.invalidate_rectangle(display_rect());
|
|
|
|
|
|
if (display_rect().contains(x,y) == false)
|
|
last_mouse_click_in_display = false;
|
|
else
|
|
last_mouse_click_in_display = true;
|
|
}
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const
|
|
{
|
|
zoomable_region::draw(c);
|
|
|
|
rectangle area = c.intersect(display_rect());
|
|
if (area.is_empty() == true)
|
|
return;
|
|
|
|
|
|
if (enabled)
|
|
fill_rect(c,display_rect(),255);
|
|
else
|
|
fill_rect(c,display_rect(),128);
|
|
|
|
|
|
const unsigned long rad = static_cast<unsigned long>(radius*zoom_scale());
|
|
point center;
|
|
|
|
|
|
// first draw all the edges
|
|
for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
|
|
{
|
|
center = graph_to_gui_space(graph_.node(i).data.p);
|
|
const rectangle circle_area(centered_rect(center,2*(rad+8),2*(rad+8)));
|
|
|
|
// draw lines to all this node's parents
|
|
const dlib::vector<double> z(0,0,1);
|
|
for (unsigned long j = 0; j < graph_.node(i).number_of_parents(); ++j)
|
|
{
|
|
point p(graph_to_gui_space(graph_.node(i).parent(j).data.p));
|
|
|
|
rgb_pixel color(0,0,0);
|
|
// if this is the selected edge then draw it with red instead of black
|
|
if (edge_selected && selected_edge_child == i && selected_edge_parent == graph_.node(i).parent(j).index())
|
|
{
|
|
color.red = 255;
|
|
// we need to be careful when drawing this line to not draw it over the node dots since it
|
|
// has a different color from them and would look weird
|
|
dlib::vector<double,2> v(p-center);
|
|
v = v.normalize()*rad;
|
|
draw_line(c,center+v,p-v ,color, area);
|
|
}
|
|
else
|
|
{
|
|
draw_line(c,center,p ,color, area);
|
|
}
|
|
|
|
|
|
// draw the triangle pointing to this node
|
|
if (area.intersect(circle_area).is_empty() == false)
|
|
{
|
|
dlib::vector<double,2> v(p-center);
|
|
v = v.normalize();
|
|
|
|
dlib::vector<double,2> cross = z.cross(v).normalize();
|
|
dlib::vector<double,2> r(center + v*rad);
|
|
for (double i = 0; i < 8*zoom_scale(); i += 0.1)
|
|
draw_line(c,(r+v*i)+cross*i, (r+v*i)-cross*i,color,area);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// now draw all the node dots
|
|
for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
|
|
{
|
|
center = graph_to_gui_space(graph_.node(i).data.p);
|
|
const rectangle circle_area(centered_rect(center,2*(rad+8),2*(rad+8)));
|
|
|
|
// draw the actual dot for this node
|
|
if (area.intersect(circle_area).is_empty()==false)
|
|
{
|
|
rgb_alpha_pixel color;
|
|
assign_pixel(color, graph_.node(i).data.color);
|
|
// this node is in area so lets draw it and all of its edges as well
|
|
draw_solid_circle(c,center,rad-3,color,area);
|
|
color.alpha = 240;
|
|
draw_circle(c,center,rad-3,color,area);
|
|
color.alpha = 200;
|
|
draw_circle(c,center,rad-2.5,color,area);
|
|
color.alpha = 160;
|
|
draw_circle(c,center,rad-2.0,color,area);
|
|
color.alpha = 120;
|
|
draw_circle(c,center,rad-1.5,color,area);
|
|
color.alpha = 80;
|
|
draw_circle(c,center,rad-1.0,color,area);
|
|
color.alpha = 40;
|
|
draw_circle(c,center,rad-0.5,color,area);
|
|
|
|
}
|
|
|
|
|
|
if (i == selected_node)
|
|
draw_circle(c,center,rad+5,rgb_pixel(0,0,255),area);
|
|
}
|
|
|
|
|
|
// now draw all the strings last
|
|
for (unsigned long i = 0; i < graph_.number_of_nodes(); ++i)
|
|
{
|
|
center = graph_to_gui_space(graph_.node(i).data.p);
|
|
rectangle circle_area(centered_rect(center,2*rad+3,2*rad+3));
|
|
if (area.intersect(circle_area).is_empty()==false)
|
|
{
|
|
rgb_pixel color = graph_.node(i).data.color;
|
|
// invert this color
|
|
color.red = 255-color.red;
|
|
color.green = 255-color.green;
|
|
color.blue = 255-color.blue;
|
|
sout << i;
|
|
unsigned long width, height;
|
|
mfont->compute_size(sout.str(),width,height);
|
|
rectangle str_rect(centered_rect(center, width,height));
|
|
if (circle_area.contains(str_rect))
|
|
{
|
|
mfont->draw_string(c,str_rect,sout.str(),color,0,std::string::npos,area);
|
|
|
|
// draw the label for this node if it isn't empty
|
|
if(graph_.node(i).data.name.size() > 0)
|
|
{
|
|
rectangle str_rect(graph_.node(i).data.str_rect);
|
|
str_rect = centered_rect(center.x(), center.y()-rad-mfont->height(), str_rect.width(), str_rect.height());
|
|
mfont->draw_string(c,str_rect,graph_.node(i).data.name,0,0,std::string::npos,area);
|
|
}
|
|
}
|
|
sout.str("");
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
|
|
struct data
|
|
{
|
|
data() : color(0,0,0) {}
|
|
vector<double> p;
|
|
dlib::ustring name;
|
|
rectangle str_rect;
|
|
rgb_pixel color;
|
|
};
|
|
|
|
friend void serialize(const data& item, std::ostream& out)
|
|
{
|
|
serialize(item.p, out);
|
|
serialize(item.name, out);
|
|
serialize(item.str_rect, out);
|
|
serialize(item.color, out);
|
|
}
|
|
|
|
friend void deserialize(data& item, std::istream& in)
|
|
{
|
|
deserialize(item.p, in);
|
|
deserialize(item.name, in);
|
|
deserialize(item.str_rect, in);
|
|
deserialize(item.color, in);
|
|
}
|
|
|
|
mutable std::ostringstream sout;
|
|
|
|
const double radius;
|
|
unsigned long selected_node;
|
|
bool mouse_drag; // true if the user is dragging a node
|
|
point drag_offset;
|
|
|
|
bool edge_selected;
|
|
unsigned long selected_edge_parent;
|
|
unsigned long selected_edge_child;
|
|
|
|
any_function<void(unsigned long)> node_selected_handler;
|
|
any_function<void(unsigned long)> node_deselected_handler;
|
|
any_function<void()> node_deleted_handler;
|
|
any_function<void()> graph_modified_handler;
|
|
|
|
graph_type external_graph;
|
|
// rebind the graph_ type to make us a graph_ of data structs
|
|
typename graph_type::template rebind<data,char, typename graph_type::mem_manager_type>::other graph_;
|
|
|
|
bool last_mouse_click_in_display;
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
// class text_grid
|
|
// ----------------------------------------------------------------------------------------
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class text_grid : public scrollable_region
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
- has_focus == false
|
|
- vertical_scroll_increment() == 10
|
|
- horizontal_scroll_increment() == 10
|
|
- border_color_ == rgb_pixel(128,128,128)
|
|
|
|
CONVENTION
|
|
- grid.nr() == row_height.size()
|
|
- grid.nc() == col_width.size()
|
|
- border_color() == border_color_
|
|
- text(r,c) == grid[r][c].text
|
|
- text_color(r,c) == grid[r][c].text_color
|
|
- background_color(r,c) == grid[r][c].bg_color
|
|
|
|
- if (the user has clicked on this widget and caused one of the
|
|
boxes to have input focus) then
|
|
- has_focus == true
|
|
- grid[active_row][active_col] == the active text box
|
|
- cursor_pos == the position of the cursor in the above box
|
|
- if (the cursor should be displayed) then
|
|
- show_cursor == true
|
|
- else
|
|
- show_cursor == false
|
|
- else
|
|
- has_focus == false
|
|
!*/
|
|
|
|
public:
|
|
text_grid (
|
|
drawable_window& w
|
|
);
|
|
|
|
~text_grid (
|
|
);
|
|
|
|
void set_grid_size (
|
|
unsigned long rows,
|
|
unsigned long cols
|
|
);
|
|
|
|
unsigned long number_of_columns (
|
|
) const;
|
|
|
|
unsigned long number_of_rows (
|
|
) const;
|
|
|
|
int next_free_user_event_number (
|
|
) const;
|
|
|
|
rgb_pixel border_color (
|
|
) const;
|
|
|
|
void set_border_color (
|
|
rgb_pixel color
|
|
);
|
|
|
|
const std::string text (
|
|
unsigned long row,
|
|
unsigned long col
|
|
) const;
|
|
|
|
const std::wstring wtext (
|
|
unsigned long row,
|
|
unsigned long col
|
|
) const;
|
|
|
|
const dlib::ustring utext (
|
|
unsigned long row,
|
|
unsigned long col
|
|
) const;
|
|
|
|
void set_text (
|
|
unsigned long row,
|
|
unsigned long col,
|
|
const std::string& str
|
|
);
|
|
|
|
void set_text (
|
|
unsigned long row,
|
|
unsigned long col,
|
|
const std::wstring& str
|
|
);
|
|
|
|
void set_text (
|
|
unsigned long row,
|
|
unsigned long col,
|
|
const dlib::ustring& str
|
|
);
|
|
|
|
const rgb_pixel text_color (
|
|
unsigned long row,
|
|
unsigned long col
|
|
) const;
|
|
|
|
void set_text_color (
|
|
unsigned long row,
|
|
unsigned long col,
|
|
const rgb_pixel color
|
|
);
|
|
|
|
const rgb_pixel background_color (
|
|
unsigned long row,
|
|
unsigned long col
|
|
) const;
|
|
|
|
void set_background_color (
|
|
unsigned long row,
|
|
unsigned long col,
|
|
const rgb_pixel color
|
|
);
|
|
|
|
bool is_editable (
|
|
unsigned long row,
|
|
unsigned long col
|
|
) const;
|
|
|
|
void set_editable (
|
|
unsigned long row,
|
|
unsigned long col,
|
|
bool editable
|
|
);
|
|
|
|
void set_column_width (
|
|
unsigned long col,
|
|
unsigned long width
|
|
);
|
|
|
|
void set_row_height (
|
|
unsigned long row,
|
|
unsigned long height
|
|
);
|
|
|
|
void disable (
|
|
);
|
|
|
|
void hide (
|
|
);
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_text_modified_handler (
|
|
T& object,
|
|
void (T::*eh)(unsigned long, unsigned long)
|
|
) { text_modified_handler = make_mfp(object,eh); }
|
|
|
|
void set_text_modified_handler (
|
|
const any_function<void(unsigned long, unsigned long)>& eh
|
|
) { text_modified_handler = eh; }
|
|
|
|
private:
|
|
|
|
void on_user_event (
|
|
int num
|
|
);
|
|
|
|
void timer_action (
|
|
);
|
|
/*!
|
|
ensures
|
|
- flips the state of show_cursor
|
|
!*/
|
|
|
|
void compute_bg_rects (
|
|
);
|
|
|
|
void compute_total_rect (
|
|
);
|
|
|
|
void on_keydown (
|
|
unsigned long key,
|
|
bool is_printable,
|
|
unsigned long state
|
|
);
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
);
|
|
|
|
void on_mouse_up (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_focus_lost (
|
|
);
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
rectangle get_text_rect (
|
|
unsigned long row,
|
|
unsigned long col
|
|
) const;
|
|
|
|
rectangle get_bg_rect (
|
|
unsigned long row,
|
|
unsigned long col
|
|
) const;
|
|
|
|
struct data_type
|
|
{
|
|
data_type(): text_color(0,0,0), bg_color(255,255,255),
|
|
first(0), is_editable(true)
|
|
{}
|
|
|
|
dlib::ustring text;
|
|
rgb_pixel text_color;
|
|
rgb_pixel bg_color;
|
|
rectangle bg_rect;
|
|
dlib::ustring::size_type first;
|
|
bool is_editable;
|
|
};
|
|
|
|
void drop_input_focus (
|
|
);
|
|
|
|
void move_cursor (
|
|
long row,
|
|
long col,
|
|
long new_cursor_pos
|
|
);
|
|
|
|
array2d<data_type> grid;
|
|
array<unsigned long> col_width;
|
|
array<unsigned long> row_height;
|
|
bool has_focus;
|
|
long active_col;
|
|
long active_row;
|
|
long cursor_pos;
|
|
bool show_cursor;
|
|
bool recent_cursor_move;
|
|
timer<text_grid> cursor_timer;
|
|
rgb_pixel border_color_;
|
|
any_function<void(unsigned long, unsigned long)> text_modified_handler;
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class image_display : public scrollable_region
|
|
{
|
|
/*!
|
|
INITIAL VALUE
|
|
- img.size() == 0
|
|
- overlay_rects.size() == 0
|
|
- overlay_lines.size() == 0
|
|
- drawing_rect == false
|
|
- rect_is_selected == false
|
|
|
|
CONVENTION
|
|
- img == the image this object displays
|
|
- overlay_rects == the overlay rectangles this object displays
|
|
- overlay_lines == the overlay lines this object displays
|
|
|
|
- if (drawing_rect) then
|
|
- the user is drawing a rectangle on the screen and is
|
|
thus holding down CTRL and the left mouse button.
|
|
- rect_anchor == the point on the screen where the user
|
|
clicked to begin drawing the rectangle.
|
|
- rect_to_draw == the rectangle which should appear on the screen.
|
|
|
|
- if (rect_is_selected) then
|
|
- selected_rect == the index in overlay_rects of the user selected
|
|
rectangle.
|
|
- last_right_click_pos == the last place we saw the user right click
|
|
the mouse.
|
|
- parts_menu.is_enabled() == true
|
|
- if (it is actually a part of this rect that is selected) then
|
|
- selected_part_name == the name of the part in overlay_rects[selected_rect].parts
|
|
that is selected.
|
|
- else
|
|
- selected_part_name.size() == 0
|
|
- else
|
|
- parts_menu.is_enabled() == false
|
|
- selected_part_name.size() == 0
|
|
|
|
- if (moving_overlay) then
|
|
- moving_rect == the index in overlay_rects that the move applies to.
|
|
- if (moving_what == MOVING_PART) then
|
|
- moving_part_name == the name of the part in
|
|
overlay_rects[moving_rect] that is being moved around with the
|
|
mouse.
|
|
- else
|
|
- moving_what will tell us which side of the rectangle in
|
|
overlay_rects[moving_rect] is being moved by the mouse.
|
|
!*/
|
|
|
|
public:
|
|
|
|
image_display(
|
|
drawable_window& w
|
|
);
|
|
|
|
~image_display(
|
|
);
|
|
|
|
template <
|
|
typename image_type
|
|
>
|
|
void set_image (
|
|
const image_type& new_img
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
|
|
// if the new image has a different size when compared to the previous image
|
|
// then we should readjust the total rectangle size.
|
|
if (num_rows(new_img) != img.nr() || num_columns(new_img) != img.nc())
|
|
{
|
|
if (zoom_in_scale != 1)
|
|
set_total_rect_size(num_columns(new_img)*zoom_in_scale, num_rows(new_img)*zoom_in_scale);
|
|
else
|
|
set_total_rect_size(num_columns(new_img)/zoom_out_scale, num_rows(new_img)/zoom_out_scale);
|
|
}
|
|
else
|
|
{
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
|
|
highlighted_rect = std::numeric_limits<unsigned long>::max();
|
|
rect_is_selected = false;
|
|
parts_menu.disable();
|
|
assign_image_scaled(img,new_img);
|
|
}
|
|
|
|
virtual void set_pos (
|
|
long x,
|
|
long y
|
|
)
|
|
{
|
|
auto_mutex lock(m);
|
|
scrollable_region::set_pos(x,y);
|
|
parts_menu.set_rect(rect);
|
|
}
|
|
|
|
virtual void set_size (
|
|
unsigned long width,
|
|
unsigned long height
|
|
)
|
|
{
|
|
auto_mutex lock(m);
|
|
scrollable_region::set_size(width,height);
|
|
parts_menu.set_rect(rect);
|
|
}
|
|
|
|
struct overlay_rect
|
|
{
|
|
overlay_rect() :crossed_out(false) { assign_pixel(color, 0);}
|
|
|
|
template <typename pixel_type>
|
|
overlay_rect(const rectangle& r, pixel_type p)
|
|
: rect(r),crossed_out(false) { assign_pixel(color, p); }
|
|
|
|
template <typename pixel_type>
|
|
overlay_rect(const rectangle& r, pixel_type p, const std::string& l)
|
|
: rect(r),label(l),crossed_out(false) { assign_pixel(color, p); }
|
|
|
|
template <typename pixel_type>
|
|
overlay_rect(const rectangle& r, pixel_type p, const std::string& l, const std::map<std::string,point>& parts_)
|
|
: rect(r),label(l),parts(parts_),crossed_out(false) { assign_pixel(color, p); }
|
|
|
|
rectangle rect;
|
|
rgb_alpha_pixel color;
|
|
std::string label;
|
|
std::map<std::string,point> parts;
|
|
bool crossed_out;
|
|
};
|
|
|
|
struct overlay_line
|
|
{
|
|
overlay_line() { assign_pixel(color, 0);}
|
|
|
|
template <typename pixel_type>
|
|
overlay_line(const dpoint& p1_, const dpoint& p2_, pixel_type p)
|
|
: p1(p1_), p2(p2_) { assign_pixel(color, p); }
|
|
|
|
dpoint p1;
|
|
dpoint p2;
|
|
rgb_alpha_pixel color;
|
|
};
|
|
|
|
struct overlay_circle
|
|
{
|
|
overlay_circle():radius(0) { assign_pixel(color, 0);}
|
|
|
|
template <typename pixel_type>
|
|
overlay_circle(const point& center_, const double radius_, pixel_type p)
|
|
: center(center_), radius(radius_) { assign_pixel(color, p); }
|
|
|
|
template <typename pixel_type>
|
|
overlay_circle(const point& center_, const double radius_, pixel_type p, const std::string& l)
|
|
: center(center_), radius(radius_), label(l) { assign_pixel(color, p); }
|
|
|
|
point center;
|
|
double radius;
|
|
rgb_alpha_pixel color;
|
|
std::string label;
|
|
};
|
|
|
|
void add_overlay (
|
|
const overlay_rect& overlay
|
|
);
|
|
|
|
void add_overlay (
|
|
const overlay_line& overlay
|
|
);
|
|
|
|
void add_overlay (
|
|
const overlay_circle& overlay
|
|
);
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_rect>& overlay
|
|
);
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_line>& overlay
|
|
);
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_circle>& overlay
|
|
);
|
|
|
|
void clear_overlay (
|
|
);
|
|
|
|
rectangle get_image_display_rect (
|
|
) const;
|
|
|
|
std::vector<overlay_rect> get_overlay_rects (
|
|
) const;
|
|
|
|
void set_default_overlay_rect_label (
|
|
const std::string& label
|
|
);
|
|
|
|
std::string get_default_overlay_rect_label (
|
|
) const;
|
|
|
|
void set_default_overlay_rect_color (
|
|
const rgb_alpha_pixel& color
|
|
);
|
|
|
|
rgb_alpha_pixel get_default_overlay_rect_color (
|
|
) const;
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_overlay_rects_changed_handler (
|
|
T& object,
|
|
void (T::*event_handler_)()
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
event_handler = make_mfp(object,event_handler_);
|
|
}
|
|
|
|
void set_overlay_rects_changed_handler (
|
|
const any_function<void()>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
event_handler = event_handler_;
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_overlay_rect_selected_handler (
|
|
T& object,
|
|
void (T::*event_handler_)(const overlay_rect& orect)
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
orect_selected_event_handler = make_mfp(object,event_handler_);
|
|
}
|
|
|
|
void set_overlay_rect_selected_handler (
|
|
const any_function<void(const overlay_rect& orect)>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
orect_selected_event_handler = event_handler_;
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_image_clicked_handler (
|
|
T& object,
|
|
void (T::*event_handler_)(const point& p, bool is_double_click, unsigned long btn)
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
image_clicked_handler = make_mfp(object,event_handler_);
|
|
}
|
|
|
|
void set_image_clicked_handler (
|
|
const any_function<void(const point& p, bool is_double_click, unsigned long btn)>& event_handler_
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
image_clicked_handler = event_handler_;
|
|
}
|
|
|
|
void add_labelable_part_name (
|
|
const std::string& name
|
|
);
|
|
|
|
void clear_labelable_part_names (
|
|
);
|
|
|
|
void enable_overlay_editing (
|
|
) { auto_mutex M(m); overlay_editing_enabled = true; }
|
|
|
|
void disable_overlay_editing (
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
overlay_editing_enabled = false;
|
|
rect_is_selected = false;
|
|
drawing_rect = false;
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
|
|
bool overlay_editing_is_enabled (
|
|
) const { auto_mutex M(m); return overlay_editing_enabled; }
|
|
|
|
void zoom_in (
|
|
);
|
|
|
|
void zoom_out (
|
|
);
|
|
|
|
private:
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
void on_wheel_up (
|
|
unsigned long state
|
|
);
|
|
|
|
void on_wheel_down (
|
|
unsigned long state
|
|
);
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
);
|
|
|
|
void on_mouse_up (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_mouse_move (
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
void on_keydown (
|
|
unsigned long key,
|
|
bool is_printable,
|
|
unsigned long state
|
|
);
|
|
|
|
void on_part_add (
|
|
const std::string& part_name
|
|
);
|
|
|
|
rectangle get_rect_on_screen (
|
|
unsigned long idx
|
|
) const;
|
|
|
|
rectangle get_rect_on_screen (
|
|
rectangle orect
|
|
) const;
|
|
|
|
rgb_alpha_pixel invert_pixel (const rgb_alpha_pixel& p) const
|
|
{ return rgb_alpha_pixel(255-p.red, 255-p.green, 255-p.blue, p.alpha); }
|
|
|
|
virtual int next_free_user_event_number (
|
|
) const { return scrollable_region::next_free_user_event_number()+1; }
|
|
// The reason for using user actions here rather than just having the timer just call
|
|
// what it needs directly is to avoid a potential deadlock during destruction of this widget.
|
|
void timer_event_unhighlight_rect()
|
|
{
|
|
highlight_timer.stop();
|
|
parent.trigger_user_event(this,scrollable_region::next_free_user_event_number());
|
|
}
|
|
void on_user_event (int num)
|
|
{
|
|
// ignore this user event if it isn't for us
|
|
if (num != scrollable_region::next_free_user_event_number())
|
|
return;
|
|
if (highlighted_rect < overlay_rects.size())
|
|
{
|
|
highlighted_rect = std::numeric_limits<unsigned long>::max();
|
|
parent.invalidate_rectangle(rect);
|
|
}
|
|
}
|
|
|
|
|
|
array2d<rgb_alpha_pixel> img;
|
|
|
|
|
|
std::vector<overlay_rect> overlay_rects;
|
|
std::vector<overlay_line> overlay_lines;
|
|
std::vector<overlay_circle> overlay_circles;
|
|
|
|
long zoom_in_scale;
|
|
long zoom_out_scale;
|
|
bool drawing_rect;
|
|
point rect_anchor;
|
|
rectangle rect_to_draw;
|
|
bool rect_is_selected;
|
|
std::string selected_part_name;
|
|
unsigned long selected_rect;
|
|
rgb_alpha_pixel default_rect_color;
|
|
std::string default_rect_label;
|
|
any_function<void()> event_handler;
|
|
any_function<void(const overlay_rect& orect)> orect_selected_event_handler;
|
|
any_function<void(const point& p, bool is_double_click, unsigned long btn)> image_clicked_handler;
|
|
popup_menu_region parts_menu;
|
|
point last_right_click_pos;
|
|
const double part_width;
|
|
std::set<std::string> part_names;
|
|
bool overlay_editing_enabled;
|
|
timer<image_display> highlight_timer;
|
|
unsigned long highlighted_rect;
|
|
bool holding_shift_key;
|
|
|
|
bool moving_overlay;
|
|
unsigned long moving_rect;
|
|
enum {
|
|
MOVING_RECT_LEFT,
|
|
MOVING_RECT_TOP,
|
|
MOVING_RECT_RIGHT,
|
|
MOVING_RECT_BOTTOM,
|
|
MOVING_PART
|
|
} moving_what;
|
|
std::string moving_part_name;
|
|
|
|
// restricted functions
|
|
image_display(image_display&); // copy constructor
|
|
image_display& operator=(image_display&); // assignment operator
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class perspective_display : public drawable, noncopyable
|
|
{
|
|
public:
|
|
|
|
perspective_display(
|
|
drawable_window& w
|
|
);
|
|
|
|
~perspective_display(
|
|
);
|
|
|
|
virtual void set_size (
|
|
unsigned long width,
|
|
unsigned long height
|
|
);
|
|
|
|
struct overlay_line
|
|
{
|
|
overlay_line() { assign_pixel(color, 0);}
|
|
|
|
overlay_line(const vector<double>& p1_, const vector<double>& p2_)
|
|
: p1(p1_), p2(p2_) { assign_pixel(color, 255); }
|
|
|
|
template <typename pixel_type>
|
|
overlay_line(const vector<double>& p1_, const vector<double>& p2_, pixel_type p)
|
|
: p1(p1_), p2(p2_) { assign_pixel(color, p); }
|
|
|
|
vector<double> p1;
|
|
vector<double> p2;
|
|
rgb_pixel color;
|
|
};
|
|
|
|
struct overlay_dot
|
|
{
|
|
overlay_dot() { assign_pixel(color, 0);}
|
|
|
|
overlay_dot(const vector<double>& p_)
|
|
: p(p_) { assign_pixel(color, 255); }
|
|
|
|
template <typename pixel_type>
|
|
overlay_dot(const vector<double>& p_, pixel_type color_)
|
|
: p(p_) { assign_pixel(color, color_); }
|
|
|
|
vector<double> p;
|
|
rgb_pixel color;
|
|
};
|
|
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_line>& overlay
|
|
);
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_dot>& overlay
|
|
);
|
|
|
|
void clear_overlay (
|
|
);
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_dot_double_clicked_handler (
|
|
T& object,
|
|
void (T::*event_handler_)(const vector<double>&)
|
|
)
|
|
{
|
|
auto_mutex M(m);
|
|
dot_clicked_event_handler = make_mfp(object,event_handler_);
|
|
}
|
|
|
|
void set_dot_double_clicked_handler (
|
|
const any_function<void(const vector<double>&)>& event_handler_
|
|
);
|
|
|
|
private:
|
|
|
|
void draw (
|
|
const canvas& c
|
|
) const;
|
|
|
|
void on_wheel_up (
|
|
unsigned long state
|
|
);
|
|
|
|
void on_wheel_down (
|
|
unsigned long state
|
|
);
|
|
|
|
void on_mouse_down (
|
|
unsigned long btn,
|
|
unsigned long state,
|
|
long x,
|
|
long y,
|
|
bool is_double_click
|
|
);
|
|
|
|
void on_mouse_move (
|
|
unsigned long state,
|
|
long x,
|
|
long y
|
|
);
|
|
|
|
static bool compare_second (
|
|
const std::pair<overlay_dot,float>& a,
|
|
const std::pair<overlay_dot,float>& b
|
|
) { return a.second < b.second; }
|
|
|
|
|
|
point last;
|
|
std::vector<overlay_line> overlay_lines;
|
|
std::vector<overlay_dot> overlay_dots;
|
|
|
|
camera_transform tform;
|
|
vector<double> sum_pts;
|
|
vector<double> max_pts;
|
|
any_function<void(const vector<double>&)> dot_clicked_event_handler;
|
|
mutable array2d<float> depth;
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class perspective_window : public drawable_window, noncopyable
|
|
{
|
|
public:
|
|
|
|
typedef perspective_display::overlay_line overlay_line;
|
|
typedef perspective_display::overlay_dot overlay_dot;
|
|
|
|
perspective_window(
|
|
) : disp(*this)
|
|
{
|
|
set_size(100,100);
|
|
on_window_resized();
|
|
show();
|
|
}
|
|
|
|
perspective_window(
|
|
const std::vector<dlib::vector<double> >& point_cloud
|
|
) :
|
|
disp(*this)
|
|
{
|
|
set_size(100,100);
|
|
on_window_resized();
|
|
add_overlay(point_cloud);
|
|
show();
|
|
}
|
|
|
|
perspective_window(
|
|
const std::vector<dlib::vector<double> >& point_cloud,
|
|
const std::string& title
|
|
) :
|
|
disp(*this)
|
|
{
|
|
set_size(100,100);
|
|
on_window_resized();
|
|
add_overlay(point_cloud);
|
|
set_title(title);
|
|
show();
|
|
}
|
|
|
|
~perspective_window(
|
|
)
|
|
{
|
|
// You should always call close_window() in the destructor of window
|
|
// objects to ensure that no events will be sent to this window while
|
|
// it is being destructed.
|
|
close_window();
|
|
}
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_line>& overlay
|
|
)
|
|
{
|
|
disp.add_overlay(overlay);
|
|
}
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_dot>& overlay
|
|
)
|
|
{
|
|
disp.add_overlay(overlay);
|
|
}
|
|
|
|
void clear_overlay (
|
|
)
|
|
{
|
|
disp.clear_overlay();
|
|
}
|
|
|
|
template <typename pixel_type>
|
|
void add_overlay(const vector<double>& p1, const vector<double>& p2, pixel_type p)
|
|
{
|
|
add_overlay(std::vector<overlay_line>(1,overlay_line(p1,p2,p)));
|
|
}
|
|
|
|
void add_overlay(const std::vector<dlib::vector<double> >& d)
|
|
{
|
|
add_overlay(d, 255);
|
|
}
|
|
|
|
template <typename pixel_type>
|
|
void add_overlay(const std::vector<dlib::vector<double> >& d, pixel_type p)
|
|
{
|
|
std::vector<overlay_dot> temp;
|
|
temp.resize(d.size());
|
|
for (unsigned long i = 0; i < temp.size(); ++i)
|
|
temp[i] = overlay_dot(d[i], p);
|
|
|
|
add_overlay(temp);
|
|
}
|
|
|
|
template <
|
|
typename T
|
|
>
|
|
void set_dot_double_clicked_handler (
|
|
T& object,
|
|
void (T::*event_handler_)(const vector<double>&)
|
|
)
|
|
{
|
|
disp.set_dot_double_clicked_handler(object,event_handler_);
|
|
}
|
|
|
|
void set_dot_double_clicked_handler (
|
|
const any_function<void(const vector<double>&)>& event_handler_
|
|
)
|
|
{
|
|
disp.set_dot_double_clicked_handler(event_handler_);
|
|
}
|
|
|
|
private:
|
|
|
|
void on_window_resized(
|
|
)
|
|
{
|
|
drawable_window::on_window_resized();
|
|
unsigned long width, height;
|
|
get_size(width,height);
|
|
disp.set_pos(0,0);
|
|
disp.set_size(width, height);
|
|
}
|
|
|
|
perspective_display disp;
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
class image_window : public drawable_window
|
|
{
|
|
public:
|
|
|
|
typedef image_display::overlay_rect overlay_rect;
|
|
typedef image_display::overlay_line overlay_line;
|
|
typedef image_display::overlay_circle overlay_circle;
|
|
|
|
image_window(
|
|
);
|
|
|
|
template < typename image_type >
|
|
image_window(
|
|
const image_type& img
|
|
) :
|
|
gui_img(*this),
|
|
window_has_closed(false),
|
|
have_last_click(false),
|
|
mouse_btn(0),
|
|
clicked_signaler(this->wm),
|
|
have_last_keypress(false),
|
|
tie_input_events(false)
|
|
{
|
|
gui_img.set_image_clicked_handler(*this, &image_window::on_image_clicked);
|
|
gui_img.disable_overlay_editing();
|
|
set_image(img);
|
|
show();
|
|
}
|
|
|
|
template < typename image_type >
|
|
image_window(
|
|
const image_type& img,
|
|
const std::string& title
|
|
) :
|
|
gui_img(*this),
|
|
window_has_closed(false),
|
|
have_last_click(false),
|
|
mouse_btn(0),
|
|
clicked_signaler(this->wm),
|
|
have_last_keypress(false),
|
|
tie_input_events(false)
|
|
{
|
|
gui_img.set_image_clicked_handler(*this, &image_window::on_image_clicked);
|
|
gui_img.disable_overlay_editing();
|
|
set_image(img);
|
|
set_title(title);
|
|
show();
|
|
}
|
|
|
|
|
|
~image_window(
|
|
);
|
|
|
|
template < typename image_type >
|
|
void set_image (
|
|
const image_type& img
|
|
)
|
|
{
|
|
const unsigned long padding = scrollable_region_style_default().get_border_size();
|
|
auto_mutex M(wm);
|
|
gui_img.set_image(img);
|
|
|
|
// Only ever mess with the size of the window if the user is giving us an image
|
|
// that is a different size. Otherwise we assume that they will have already
|
|
// sized the window to whatever they feel is reasonable for an image of the
|
|
// current size.
|
|
if (previous_image_size != get_rect(img))
|
|
{
|
|
const rectangle r = gui_img.get_image_display_rect();
|
|
if (image_rect != r)
|
|
{
|
|
// set the size of this window to match the size of the input image
|
|
set_size(r.width()+padding*2,r.height()+padding*2);
|
|
|
|
// call this to make sure everything else is setup properly
|
|
on_window_resized();
|
|
|
|
image_rect = r;
|
|
}
|
|
previous_image_size = get_rect(img);
|
|
}
|
|
}
|
|
|
|
void add_overlay (
|
|
const overlay_rect& overlay
|
|
);
|
|
|
|
template <typename pixel_type>
|
|
void add_overlay(const rectangle& r, pixel_type p)
|
|
{ add_overlay(image_display::overlay_rect(r,p)); }
|
|
|
|
void add_overlay(const rectangle& r)
|
|
{ add_overlay(image_display::overlay_rect(r,rgb_pixel(255,0,0))); }
|
|
|
|
template <typename pixel_type>
|
|
void add_overlay(const rectangle& r, pixel_type p, const std::string& l)
|
|
{ add_overlay(image_display::overlay_rect(r,p,l)); }
|
|
|
|
template <typename pixel_type>
|
|
void add_overlay(const std::vector<rectangle>& r, pixel_type p)
|
|
{
|
|
std::vector<overlay_rect> temp;
|
|
temp.resize(r.size());
|
|
for (unsigned long i = 0; i < temp.size(); ++i)
|
|
temp[i] = overlay_rect(r[i], p);
|
|
|
|
add_overlay(temp);
|
|
}
|
|
|
|
void add_overlay(const std::vector<rectangle>& r)
|
|
{ add_overlay(r, rgb_pixel(255,0,0)); }
|
|
|
|
void add_overlay(
|
|
const full_object_detection& object,
|
|
const std::vector<std::string>& part_names
|
|
)
|
|
{
|
|
|
|
add_overlay(overlay_rect(object.get_rect(), rgb_pixel(255,0,0)));
|
|
|
|
std::vector<overlay_circle> temp;
|
|
temp.reserve(object.num_parts());
|
|
for (unsigned long i = 0; i < object.num_parts(); ++i)
|
|
{
|
|
if (object.part(i) != OBJECT_PART_NOT_PRESENT)
|
|
{
|
|
if (i < part_names.size())
|
|
temp.push_back(overlay_circle(object.part(i), 7, rgb_pixel(0,255,0), part_names[i]));
|
|
else
|
|
temp.push_back(overlay_circle(object.part(i), 7, rgb_pixel(0,255,0)));
|
|
}
|
|
}
|
|
|
|
add_overlay(temp);
|
|
}
|
|
|
|
void add_overlay(
|
|
const full_object_detection& object
|
|
)
|
|
{
|
|
std::vector<std::string> part_names;
|
|
add_overlay(object, part_names);
|
|
}
|
|
|
|
void add_overlay(
|
|
const std::vector<full_object_detection>& objects,
|
|
const std::vector<std::string>& part_names
|
|
)
|
|
{
|
|
std::vector<overlay_rect> rtemp;
|
|
rtemp.reserve(objects.size());
|
|
for (unsigned long i = 0; i < objects.size(); ++i)
|
|
{
|
|
rtemp.push_back(overlay_rect(objects[i].get_rect(), rgb_pixel(255,0,0)));
|
|
}
|
|
|
|
add_overlay(rtemp);
|
|
|
|
std::vector<overlay_circle> temp;
|
|
|
|
for (unsigned long i = 0; i < objects.size(); ++i)
|
|
{
|
|
for (unsigned long j = 0; j < objects[i].num_parts(); ++j)
|
|
{
|
|
if (objects[i].part(j) != OBJECT_PART_NOT_PRESENT)
|
|
{
|
|
if (j < part_names.size())
|
|
temp.push_back(overlay_circle(objects[i].part(j), 7, rgb_pixel(0,255,0),part_names[j]));
|
|
else
|
|
temp.push_back(overlay_circle(objects[i].part(j), 7, rgb_pixel(0,255,0)));
|
|
}
|
|
}
|
|
}
|
|
|
|
add_overlay(temp);
|
|
}
|
|
|
|
void add_overlay(
|
|
const std::vector<full_object_detection>& objects
|
|
)
|
|
{
|
|
std::vector<std::string> part_names;
|
|
add_overlay(objects, part_names);
|
|
}
|
|
|
|
void add_overlay (
|
|
const overlay_line& overlay
|
|
);
|
|
|
|
template <typename pixel_type>
|
|
void add_overlay(const line& l, pixel_type p)
|
|
{
|
|
add_overlay(image_display::overlay_line(l.p1(),l.p2(),p));
|
|
}
|
|
|
|
void add_overlay(const line& l)
|
|
{
|
|
add_overlay(l, rgb_pixel(255,0,0));
|
|
}
|
|
|
|
void add_overlay (
|
|
const overlay_circle& overlay
|
|
);
|
|
|
|
template <typename pixel_type>
|
|
void add_overlay(const point& p1, const point& p2, pixel_type p)
|
|
{ add_overlay(image_display::overlay_line(p1,p2,p)); }
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_rect>& overlay
|
|
);
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_line>& overlay
|
|
);
|
|
|
|
void add_overlay (
|
|
const std::vector<overlay_circle>& overlay
|
|
);
|
|
|
|
void clear_overlay (
|
|
);
|
|
|
|
bool get_next_double_click (
|
|
point& p,
|
|
unsigned long& mouse_button
|
|
);
|
|
|
|
void tie_events (
|
|
);
|
|
|
|
void untie_events (
|
|
);
|
|
|
|
bool events_tied (
|
|
) const;
|
|
|
|
bool get_next_double_click (
|
|
point& p
|
|
)
|
|
{
|
|
unsigned long mouse_button;
|
|
return get_next_double_click(p, mouse_button);
|
|
}
|
|
|
|
bool get_next_keypress (
|
|
unsigned long& key,
|
|
bool& is_printable,
|
|
unsigned long& state
|
|
);
|
|
|
|
bool get_next_keypress (
|
|
unsigned long& key,
|
|
bool& is_printable
|
|
)
|
|
{
|
|
unsigned long state;
|
|
return get_next_keypress(key,is_printable,state);
|
|
}
|
|
|
|
private:
|
|
|
|
virtual base_window::on_close_return_code on_window_close(
|
|
);
|
|
|
|
void on_window_resized(
|
|
);
|
|
|
|
void on_image_clicked (
|
|
const point& p,
|
|
bool is_double_click,
|
|
unsigned long btn
|
|
);
|
|
|
|
virtual void on_keydown (
|
|
unsigned long key,
|
|
bool is_printable,
|
|
unsigned long state
|
|
);
|
|
|
|
// restricted functions
|
|
image_window(image_window&);
|
|
image_window& operator= (image_window&);
|
|
|
|
image_display gui_img;
|
|
rectangle image_rect;
|
|
rectangle previous_image_size;
|
|
bool window_has_closed;
|
|
bool have_last_click;
|
|
point last_clicked_point;
|
|
unsigned long mouse_btn;
|
|
rsignaler clicked_signaler;
|
|
|
|
bool have_last_keypress;
|
|
unsigned long next_key;
|
|
bool next_is_printable;
|
|
unsigned long next_state;
|
|
bool tie_input_events;
|
|
};
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
|
|
}
|
|
|
|
#ifdef NO_MAKEFILE
|
|
#include "widgets.cpp"
|
|
#endif
|
|
|
|
#endif // DLIB_WIDGETs_
|
|
|