// Copyright (C) 2005 Davis E. King (davis@dlib.net), Keita Mochizuki // License: Boost Software License See LICENSE.txt for the full license. #ifndef DLIB_BASE_WIDGETs_ #define DLIB_BASE_WIDGETs_ #include <cctype> #include <memory> #include "base_widgets_abstract.h" #include "drawable.h" #include "../gui_core.h" #include "../algs.h" #include "../member_function_pointer.h" #include "../timer.h" #include "../map.h" #include "../set.h" #include "../array2d.h" #include "../pixel.h" #include "../image_transforms/assign_image.h" #include "../array.h" #include "style.h" #include "../unicode.h" #include "../any.h" namespace dlib { // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class draggable // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class draggable : public drawable { /*! INITIAL VALUE - drag == false CONVENTION - if (the user is holding the left button down over this object) then - drag == true - x == the x position of the mouse relative to the upper left corner of this object. - y == the y position of the mouse relative to the upper left corner of this object. - else - drag == false !*/ public: draggable( drawable_window& w, unsigned long events = 0 ) : drawable(w,events | MOUSE_MOVE | MOUSE_CLICK), drag(false) {} virtual ~draggable( ) = 0; rectangle draggable_area ( ) const { auto_mutex M(m); return area; } void set_draggable_area ( const rectangle& area_ ) { auto_mutex M(m); area = area_; } protected: bool is_being_dragged ( ) const { return drag; } virtual void on_drag ( ){} virtual void on_drag_stop ( ){} void on_mouse_move ( unsigned long state, long x, long y ); void on_mouse_down ( unsigned long btn, unsigned long , long x, long y, bool ); void on_mouse_up ( unsigned long btn, unsigned long state, long x, long y ); private: rectangle area; bool drag; long x, y; // restricted functions draggable(draggable&); // copy constructor draggable& operator=(draggable&); // assignment operator }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class mouse_over_event // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class mouse_over_event : public drawable { /*! INITIAL VALUE - is_mouse_over_ == false CONVENTION - is_mouse_over_ == is_mouse_over() !*/ public: mouse_over_event( drawable_window& w, unsigned long events = 0 ) : drawable(w,events | MOUSE_MOVE), is_mouse_over_(false) {} virtual ~mouse_over_event( ) = 0; int next_free_user_event_number() const { return drawable::next_free_user_event_number()+1; } protected: bool is_mouse_over ( ) const; virtual void on_mouse_over ( ){} virtual void on_mouse_not_over ( ){} void on_mouse_leave ( ); void on_mouse_move ( unsigned long state, long x, long y ); void on_user_event ( int num ); private: mutable bool is_mouse_over_; // restricted functions mouse_over_event(mouse_over_event&); // copy constructor mouse_over_event& operator=(mouse_over_event&); // assignment operator }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class button_action // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class button_action : public mouse_over_event { /*! INITIAL VALUE - is_depressed_ == false - seen_click == false CONVENTION - is_depressed_ == is_depressed() - if (the user has clicked the button but hasn't yet released the left mouse button) then - seen_click == true - else - seen_click == false !*/ public: button_action( drawable_window& w, unsigned long events = 0 ) : mouse_over_event(w,events | MOUSE_MOVE | MOUSE_CLICK), is_depressed_(false), seen_click(false) {} virtual ~button_action( ) = 0; int next_free_user_event_number() const { return mouse_over_event::next_free_user_event_number()+1; } protected: bool is_depressed ( ) const; virtual void on_button_down ( ){} virtual void on_button_up ( bool ){} void on_mouse_not_over ( ); void on_mouse_down ( unsigned long btn, unsigned long , long x, long y, bool ); void on_mouse_move ( unsigned long state, long x, long y ); void on_mouse_up ( unsigned long btn, unsigned long, long x, long y ); private: mutable bool is_depressed_; bool seen_click; void on_user_event ( int num ); // restricted functions button_action(button_action&); // copy constructor button_action& operator=(button_action&); // assignment operator }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class widget_group // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class widget_group : public drawable { /*! INITIAL VALUE widgets.size() == 0 CONVENTION - widgets contains all the drawable objects and their relative positions that are in *this. - wg_widgets contains pointers to just the widgets that happen to be widget_group objects. !*/ struct relpos { unsigned long x; unsigned long y; }; public: widget_group( drawable_window& w ) : drawable(w) { rect = rectangle(0,0,-1,-1); enable_events();} virtual ~widget_group( ){ disable_events(); } void empty ( ); void add ( drawable& widget, unsigned long x, unsigned long y ); void add ( widget_group& widget, unsigned long x, unsigned long y ); bool is_member ( const drawable& widget ) const; void remove ( const drawable& widget ); unsigned long size ( ) const; void set_pos ( long x, long y ); void set_z_order ( long order ); void show ( ); void hide ( ); void enable ( ); void disable ( ); void fit_to_contents ( ); protected: // this object doesn't draw anything but also isn't abstract void draw ( const canvas& ) const {} private: map<drawable*,relpos>::kernel_1a_c widgets; set<widget_group*>::kernel_1a_c wg_widgets; // restricted functions widget_group(widget_group&); // copy constructor widget_group& operator=(widget_group&); // assignment operator }; // ---------------------------------------------------------------------------------------- class image_widget : public draggable { /*! INITIAL VALUE - img.size() == 0 CONVENTION - img == the image this object displays !*/ public: image_widget( drawable_window& w ): draggable(w) { enable_events(); } ~image_widget( ) { disable_events(); parent.invalidate_rectangle(rect); } template < typename image_type > void set_image ( const image_type& new_img ) { auto_mutex M(m); assign_image_scaled(img,new_img); rectangle old(rect); rect.set_right(rect.left()+img.nc()-1); rect.set_bottom(rect.top()+img.nr()-1); parent.invalidate_rectangle(rect+old); } private: void draw ( const canvas& c ) const { rectangle area = rect.intersect(c); if (area.is_empty()) return; draw_image(c, point(rect.left(),rect.top()), img); } array2d<rgb_alpha_pixel> img; // restricted functions image_widget(image_widget&); // copy constructor image_widget& operator=(image_widget&); // assignment operator }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class tooltip // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class tooltip : public mouse_over_event { /*! INITIAL VALUE - stuff.get() == 0 - events_are_enabled() == false CONVENTION - if (events_are_enabled() == true) then - stuff.get() != 0 !*/ public: tooltip( drawable_window& w ) : mouse_over_event(w,MOUSE_CLICK) {} ~tooltip( ){ disable_events();} void set_size ( unsigned long width, unsigned long height ) { auto_mutex M(m); rect = resize_rect(rect,width,height); } void set_text ( const std::string& str ) { set_text(convert_mbstring_to_wstring(str)); } void set_text ( const std::wstring& str ) { set_text(convert_wstring_to_utf32(str)); } void set_text ( const ustring& str ) { auto_mutex M(m); if (!stuff) { stuff.reset(new data(*this)); enable_events(); } stuff->win.set_text(str); } const std::string text ( ) const { return convert_wstring_to_mbstring(wtext()); } const std::wstring wtext ( ) const { return convert_utf32_to_wstring(utext()); } const dlib::ustring utext ( ) const { auto_mutex M(m); dlib::ustring temp; if (stuff) { temp = stuff->win.text; } return temp.c_str(); } void hide ( ) { auto_mutex M(m); mouse_over_event::hide(); if (stuff) { stuff->tt_timer.stop(); stuff->win.hide(); } } void disable ( ) { auto_mutex M(m); mouse_over_event::disable(); if (stuff) { stuff->tt_timer.stop(); stuff->win.hide(); } } protected: void on_mouse_over() { stuff->x = lastx; stuff->y = lasty; stuff->tt_timer.start(); } void on_mouse_not_over () { stuff->tt_timer.stop(); stuff->win.hide(); } void on_mouse_down ( unsigned long btn, unsigned long state, long x, long y, bool is_double_click ) { mouse_over_event::on_mouse_down(btn,state,x,y,is_double_click); stuff->tt_timer.stop(); stuff->win.hide(); } void draw ( const canvas& ) const{} private: class tooltip_window : public base_window { public: tooltip_window (const std::shared_ptr<font>& f) : base_window(false,true), pad(3), mfont(f) { } ustring text; rectangle rect_all; rectangle rect_text; const unsigned long pad; const std::shared_ptr<font> mfont; void set_text ( const std::string& str ) { set_text(convert_mbstring_to_wstring(str)); } void set_text ( const std::wstring& str ) { set_text(convert_wstring_to_utf32(str)); } void set_text ( const dlib::ustring& str ) { text = str.c_str(); unsigned long width, height; mfont->compute_size(text,width,height); set_size(width+pad*2, height+pad*2); rect_all.set_left(0); rect_all.set_top(0); rect_all.set_right(width+pad*2-1); rect_all.set_bottom(height+pad*2-1); rect_text = move_rect(rectangle(width,height),pad,pad); } void paint(const canvas& c) { c.fill(255,255,150); draw_rectangle(c, rect_all); mfont->draw_string(c,rect_text,text); } }; void show_tooltip ( ) { auto_mutex M(m); long x, y; // if the mouse has moved since we started the timer then // keep waiting until the user stops moving it if (lastx != stuff->x || lasty != stuff->y) { stuff->x = lastx; stuff->y = lasty; return; } unsigned long display_width, display_height; // stop the timer stuff->tt_timer.stop(); parent.get_pos(x,y); x += lastx+15; y += lasty+15; // make sure the tooltip isn't going to be off the screen parent.get_display_size(display_width, display_height); rectangle wrect(move_rect(stuff->win.rect_all,x,y)); rectangle srect(display_width, display_height); if (srect.contains(wrect) == false) { rectangle temp(srect.intersect(wrect)); x -= wrect.width()-temp.width(); y -= wrect.height()-temp.height(); } stuff->win.set_pos(x,y); stuff->win.show(); } // put all this stuff in data so we can arrange to only // construct it when someone is actually using the tooltip widget // rather than just instantiating it. struct data { data( tooltip& self ) : x(-1), y(-1), win(self.mfont), tt_timer(self,&tooltip::show_tooltip) { tt_timer.set_delay_time(400); } long x, y; tooltip_window win; timer<tooltip> tt_timer; }; friend struct data; std::unique_ptr<data> stuff; // restricted functions tooltip(tooltip&); // copy constructor tooltip& operator=(tooltip&); // assignment operator }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class button // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class button : public button_action { public: button( drawable_window& w ) : button_action(w), btn_tooltip(w) { style.reset(new button_style_default()); enable_events(); } ~button() { disable_events(); parent.invalidate_rectangle(style->get_invalidation_rect(rect)); } 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 set_tooltip_text ( const std::string& text ); void set_tooltip_text ( const std::wstring& text ); void set_tooltip_text ( const dlib::ustring& text ); void set_pos( long x, long y ); const std::string tooltip_text ( ) const; const std::wstring tooltip_wtext ( ) const; const dlib::ustring tooltip_utext ( ) const; void set_main_font ( const std::shared_ptr<font>& f ); void show ( ); void hide ( ); void enable ( ); void disable ( ); 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(style->get_invalidation_rect(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_)(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(button&)>& event_handler_ ) { auto_mutex M(m); event_handler_self = event_handler_; event_handler.clear(); } bool is_depressed ( ) const { auto_mutex M(m); return button_action::is_depressed(); } template < typename T > void set_button_down_handler ( T& object, void (T::*event_handler)() ) { auto_mutex M(m); button_down_handler = make_mfp(object,event_handler); } void set_button_down_handler ( const any_function<void()>& event_handler ) { auto_mutex M(m); button_down_handler = event_handler; } template < typename T > void set_button_up_handler ( T& object, void (T::*event_handler)(bool mouse_over) ) { auto_mutex M(m); button_up_handler = make_mfp(object,event_handler); } void set_button_up_handler ( const any_function<void(bool)>& event_handler ) { auto_mutex M(m); button_up_handler = event_handler; } template < typename T > void set_button_down_handler ( T& object, void (T::*event_handler)(button&) ) { auto_mutex M(m); button_down_handler_self = make_mfp(object,event_handler); } void set_sourced_button_down_handler ( const any_function<void(button&)>& event_handler ) { auto_mutex M(m); button_down_handler_self = event_handler; } template < typename T > void set_button_up_handler ( T& object, void (T::*event_handler)(bool mouse_over, button&) ) { auto_mutex M(m); button_up_handler_self = make_mfp(object,event_handler); } void set_sourced_button_up_handler ( const any_function<void(bool,button&)>& event_handler ) { auto_mutex M(m); button_up_handler_self = event_handler; } private: // restricted functions button(button&); // copy constructor button& operator=(button&); // assignment operator dlib::ustring name_; tooltip btn_tooltip; any_function<void()> event_handler; any_function<void(button&)> event_handler_self; any_function<void()> button_down_handler; any_function<void(bool)> button_up_handler; any_function<void(button&)> button_down_handler_self; any_function<void(bool,button&)> button_up_handler_self; std::unique_ptr<button_style> style; protected: void draw ( const canvas& c ) const { style->draw_button(c,rect,enabled,*mfont,lastx,lasty,name_,is_depressed()); } void on_button_up ( bool mouse_over ); void on_button_down ( ); void on_mouse_over ( ){ if (style->redraw_on_mouse_over()) parent.invalidate_rectangle(style->get_invalidation_rect(rect)); } void on_mouse_not_over ( ){ if (style->redraw_on_mouse_over()) parent.invalidate_rectangle(style->get_invalidation_rect(rect)); } }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class scroll_bar // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class scroll_bar : public drawable { /*! INITIAL VALUE - ori == a value given by the constructor - style == a scroll_bar_style_default object - pos == 0 - max_pos == 0 - js == 10 CONVENTION - ori == orientation() - b1 == the button that is near the 0 end of the scroll bar - b2 == the button that is near the max_pos() end of the scroll bar - max_pos == max_slider_pos() - pos == slider_pos() - js == jump_size() !*/ public: enum bar_orientation { HORIZONTAL, VERTICAL }; scroll_bar( drawable_window& w, bar_orientation orientation_ ); virtual ~scroll_bar( ); bar_orientation orientation ( ) const; void set_length ( unsigned long length ); long max_slider_pos ( ) const; void set_max_slider_pos ( long mpos ); void set_slider_pos ( long pos ); long slider_pos ( ) const; template < typename T > void set_scroll_handler ( T& object, void (T::*eh)() ) { auto_mutex M(m); scroll_handler = make_mfp(object,eh); } void set_scroll_handler ( const any_function<void()>& eh ) { auto_mutex M(m); scroll_handler = eh; } void set_pos ( long x, long y ); void enable ( ) { auto_mutex M(m); if (!hidden) show_slider(); if (max_pos != 0) { b1.enable(); b2.enable(); } drawable::enable(); } void disable ( ) { auto_mutex M(m); hide_slider(); b1.disable(); b2.disable(); drawable::disable(); } void hide ( ) { auto_mutex M(m); hide_slider(); top_filler.hide(); bottom_filler.hide(); b1.hide(); b2.hide(); drawable::hide(); } void show ( ) { auto_mutex M(m); b1.show(); b2.show(); drawable::show(); top_filler.show(); if (enabled) show_slider(); } void set_z_order ( long order ) { auto_mutex M(m); slider.set_z_order(order); top_filler.set_z_order(order); bottom_filler.set_z_order(order); b1.set_z_order(order); b2.set_z_order(order); drawable::set_z_order(order); } void set_jump_size ( long js ); long jump_size ( ) const; template < typename style_type > void set_style ( const style_type& style_ ) { auto_mutex M(m); style.reset(new style_type(style_)); if (ori == HORIZONTAL) { b1.set_style(style_.get_left_button_style()); b2.set_style(style_.get_right_button_style()); set_length(rect.width()); } else { b1.set_style(style_.get_up_button_style()); b2.set_style(style_.get_down_button_style()); set_length(rect.height()); } } private: void hide_slider ( ); /*! ensures - hides the slider and makes any other changes needed so that the scroll_bar still looks right. !*/ void show_slider ( ); /*! ensures - shows the slider and makes any other changes needed so that the scroll_bar still looks right. !*/ void on_slider_drag ( ); /*! requires - is called whenever the user drags the slider !*/ void draw ( const canvas& c ) const; void b1_down ( ); void b1_up ( bool mouse_over ); void b2_down ( ); void b2_up ( bool mouse_over ); void top_filler_down ( ); void top_filler_up ( bool mouse_over ); void bottom_filler_down ( ); void bottom_filler_up ( bool mouse_over ); void on_user_event ( int i ); void delayed_set_slider_pos ( unsigned long dpos ); void b1_down_t ( ); void b2_down_t ( ); void top_filler_down_t ( ); void bottom_filler_down_t ( ); friend class filler; class filler : public button_action { friend class scroll_bar; public: filler ( drawable_window& w, scroll_bar& object, void (scroll_bar::*down)(), void (scroll_bar::*up)(bool) ): button_action(w), my_scroll_bar(object) { bup = make_mfp(object,up); bdown = make_mfp(object,down); enable_events(); } ~filler ( ) { disable_events(); } void set_size ( unsigned long width, unsigned long height ) { rectangle old(rect); const unsigned long x = rect.left(); const unsigned long y = rect.top(); rect.set_right(x+width-1); rect.set_bottom(y+height-1); parent.invalidate_rectangle(rect+old); } private: void draw ( const canvas& c ) const { my_scroll_bar.style->draw_scroll_bar_background(c,rect,enabled,lastx,lasty,is_depressed()); } void on_button_down ( ) { bdown(); } void on_button_up ( bool mouse_over ) { bup(mouse_over); } scroll_bar& my_scroll_bar; any_function<void()> bdown; any_function<void(bool)> bup; }; friend class slider_class; class slider_class : public draggable { friend class scroll_bar; public: slider_class ( drawable_window& w, scroll_bar& object, void (scroll_bar::*handler)() ) : draggable(w, MOUSE_MOVE), mouse_in_widget(false), my_scroll_bar(object) { callback = make_mfp(object,handler); enable_events(); } ~slider_class ( ) { disable_events(); } void set_size ( unsigned long width, unsigned long height ) { rectangle old(rect); const unsigned long x = rect.left(); const unsigned long y = rect.top(); rect.set_right(x+width-1); rect.set_bottom(y+height-1); parent.invalidate_rectangle(rect+old); } private: virtual void on_mouse_move ( unsigned long state, long x, long y ) { draggable::on_mouse_move(state,x,y); if (!hidden && my_scroll_bar.style->redraw_on_mouse_over_slider()) { if (rect.contains(x,y) && !mouse_in_widget) { mouse_in_widget = true; parent.invalidate_rectangle(rect); } else if (rect.contains(x,y) == false && mouse_in_widget) { mouse_in_widget = false; parent.invalidate_rectangle(rect); } } } void on_mouse_leave ( ) { if (mouse_in_widget && my_scroll_bar.style->redraw_on_mouse_over_slider()) { mouse_in_widget = false; parent.invalidate_rectangle(rect); } } void on_drag_stop ( ) { if (my_scroll_bar.style->redraw_on_mouse_over_slider()) parent.invalidate_rectangle(rect); } void on_drag ( ) { callback(); } void draw ( const canvas& c ) const { my_scroll_bar.style->draw_scroll_bar_slider(c,rect,enabled,lastx,lasty, is_being_dragged()); } bool mouse_in_widget; scroll_bar& my_scroll_bar; any_function<void()> callback; }; void adjust_fillers ( ); /*! ensures - top_filler and bottom_filler appear in their correct positions relative to the current positions of the slider and the b1 and b2 buttons !*/ unsigned long get_slider_size ( ) const; /*! ensures - returns the length in pixels the slider should have based on the current state of this scroll bar !*/ button b1, b2; slider_class slider; bar_orientation ori; filler top_filler, bottom_filler; any_function<void()> scroll_handler; long pos; long max_pos; long js; timer<scroll_bar> b1_timer; timer<scroll_bar> b2_timer; timer<scroll_bar> top_filler_timer; timer<scroll_bar> bottom_filler_timer; long delayed_pos; std::unique_ptr<scroll_bar_style> style; // restricted functions scroll_bar(scroll_bar&); // copy constructor scroll_bar& operator=(scroll_bar&); // assignment operator }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class popup_menu // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class menu_item { public: virtual ~menu_item() {} virtual rectangle get_left_size ( ) const { return rectangle(); } virtual rectangle get_middle_size ( ) const = 0; virtual rectangle get_right_size ( ) const { return rectangle(); } virtual unichar get_hot_key ( ) const { return 0; } virtual void draw_background ( const canvas& , const rectangle& , const bool , const bool ) const {} virtual void draw_left ( const canvas& , const rectangle& , const bool , const bool ) const {} virtual void draw_middle ( const canvas& , const rectangle& , const bool , const bool ) const = 0; virtual void draw_right ( const canvas& , const rectangle& , const bool , const bool ) const {} virtual void on_click ( ) const {} virtual bool has_click_event ( ) const { return false; } }; // ---------------------------------------------------------------------------------------- class menu_item_submenu : public menu_item { void initialize ( unichar hk ) { const dlib::ustring &str = text; if (hk != 0) { std::string::size_type pos = str.find_first_of(hk); if (pos != std::string::npos) { // now compute the location of the underline bar rectangle r1 = f->compute_cursor_rect( rectangle(100000,100000), str, pos); rectangle r2 = f->compute_cursor_rect( rectangle(100000,100000), str, pos+1); underline_p1.x() = r1.left()+1; underline_p2.x() = r2.left()-1; underline_p1.y() = r1.bottom()-f->height()+f->ascender()+2; underline_p2.y() = r2.bottom()-f->height()+f->ascender()+2; } } } public: menu_item_submenu ( const std::string& str, unichar hk = 0 ) : text(convert_wstring_to_utf32(convert_mbstring_to_wstring(str))), f(default_font::get_font()), hotkey(hk) { initialize(hk); } menu_item_submenu ( const std::wstring& str, unichar hk = 0 ) : text(convert_wstring_to_utf32(str)), f(default_font::get_font()), hotkey(hk) { initialize(hk); } menu_item_submenu ( const dlib::ustring& str, unichar hk = 0 ) : text(str), f(default_font::get_font()), hotkey(hk) { initialize(hk); } virtual unichar get_hot_key ( ) const { return hotkey; } virtual rectangle get_middle_size ( ) const { unsigned long width, height; f->compute_size(text,width,height); return rectangle(width+30,height); } virtual rectangle get_right_size ( ) const { return rectangle(15, 5); } virtual void draw_background ( const canvas& c, const rectangle& rect, const bool enabled, const bool is_selected ) const { if (c.intersect(rect).is_empty()) return; if (enabled && is_selected) { fill_rect_with_vertical_gradient(c, rect,rgb_alpha_pixel(0,200,0,100), rgb_alpha_pixel(0,0,0,100)); draw_rectangle(c, rect,rgb_alpha_pixel(0,0,0,100)); } } virtual void draw_right ( const canvas& c, const rectangle& rect, const bool enabled, const bool ) const { if (c.intersect(rect).is_empty()) return; unsigned char color = 0; if (enabled == false) color = 128; long x, y; x = rect.right() - 7; y = rect.top() + rect.height()/2; for ( unsigned long i = 0; i < 5; ++i) draw_line (c, point(x - i, y + i), point(x - i, y - i), color); } virtual void draw_middle ( const canvas& c, const rectangle& rect, const bool enabled, const bool ) const { if (c.intersect(rect).is_empty()) return; if (enabled) { f->draw_string(c,rect,text); } else { f->draw_string(c,rect,text,128); } if (underline_p1 != underline_p2) { point base(rect.left(),rect.top()); draw_line(c, base+underline_p1, base+underline_p2); } } private: dlib::ustring text; const std::shared_ptr<font> f; any_function<void()> action; unichar hotkey; point underline_p1; point underline_p2; }; // ---------------------------------------------------------------------------------------- class menu_item_text : public menu_item { void initialize ( const any_function<void()>& event_handler_, unichar hk ) { dlib::ustring &str = text; action = event_handler_; if (hk != 0) { std::string::size_type pos = str.find_first_of(hk); if (pos != std::string::npos) { // now compute the location of the underline bar rectangle r1 = f->compute_cursor_rect( rectangle(100000,100000), str, pos); rectangle r2 = f->compute_cursor_rect( rectangle(100000,100000), str, pos+1); underline_p1.x() = r1.left()+1; underline_p2.x() = r2.left()-1; underline_p1.y() = r1.bottom()-f->height()+f->ascender()+2; underline_p2.y() = r2.bottom()-f->height()+f->ascender()+2; } } } public: template <typename T> menu_item_text ( const std::string& str, T& object, void (T::*event_handler_)(), unichar hk = 0 ) : text(convert_wstring_to_utf32(convert_mbstring_to_wstring(str))), f(default_font::get_font()), hotkey(hk) { initialize(make_mfp(object, event_handler_), hk); } menu_item_text ( const std::string& str, const any_function<void()>& event_handler_, unichar hk = 0 ) : text(convert_wstring_to_utf32(convert_mbstring_to_wstring(str))), f(default_font::get_font()), hotkey(hk) { initialize(event_handler_, hk); } template <typename T> menu_item_text ( const std::wstring& str, T& object, void (T::*event_handler_)(), unichar hk = 0 ) : text(convert_wstring_to_utf32(str)), f(default_font::get_font()), hotkey(hk) { initialize(make_mfp(object, event_handler_), hk); } menu_item_text ( const std::wstring& str, const any_function<void()>& event_handler_, unichar hk = 0 ) : text(convert_wstring_to_utf32(str)), f(default_font::get_font()), hotkey(hk) { initialize(event_handler_, hk); } template <typename T> menu_item_text ( const dlib::ustring& str, T& object, void (T::*event_handler_)(), unichar hk = 0 ) : text(str), f(default_font::get_font()), hotkey(hk) { initialize(make_mfp(object, event_handler_), hk); } menu_item_text ( const dlib::ustring& str, const any_function<void()>& event_handler_, unichar hk = 0 ) : text(str), f(default_font::get_font()), hotkey(hk) { initialize(event_handler_, hk); } virtual unichar get_hot_key ( ) const { return hotkey; } virtual rectangle get_middle_size ( ) const { unsigned long width, height; f->compute_size(text,width,height); return rectangle(width,height); } virtual void draw_background ( const canvas& c, const rectangle& rect, const bool enabled, const bool is_selected ) const { if (c.intersect(rect).is_empty()) return; if (enabled && is_selected) { fill_rect_with_vertical_gradient(c, rect,rgb_alpha_pixel(0,200,0,100), rgb_alpha_pixel(0,0,0,100)); draw_rectangle(c, rect,rgb_alpha_pixel(0,0,0,100)); } } virtual void draw_middle ( const canvas& c, const rectangle& rect, const bool enabled, const bool ) const { if (c.intersect(rect).is_empty()) return; unsigned char color = 0; if (enabled == false) color = 128; f->draw_string(c,rect,text,color); if (underline_p1 != underline_p2) { point base(rect.left(),rect.top()); draw_line(c, base+underline_p1, base+underline_p2, color); } } virtual void on_click ( ) const { action(); } virtual bool has_click_event ( ) const { return true; } private: dlib::ustring text; const std::shared_ptr<font> f; any_function<void()> action; unichar hotkey; point underline_p1; point underline_p2; }; // ---------------------------------------------------------------------------------------- class menu_item_separator : public menu_item { public: virtual rectangle get_middle_size ( ) const { return rectangle(10,4); } virtual void draw_background ( const canvas& c, const rectangle& rect, const bool , const bool ) const { if (c.intersect(rect).is_empty()) return; point p1(rect.left(),rect.top()+rect.height()/2-1); point p2(rect.right(),rect.top()+rect.height()/2-1); point p3(rect.left(),rect.top()+rect.height()/2); point p4(rect.right(),rect.top()+rect.height()/2); draw_line(c, p1,p2,128); draw_line(c, p3,p4,255); } virtual void draw_middle ( const canvas& , const rectangle& , const bool , const bool ) const { } }; // ---------------------------------------------------------------------------------------- class popup_menu : public base_window { /*! INITIAL VALUE - pad == 2 - item_pad == 3 - cur_rect == rectangle(pad,pad,pad-1,pad-1) - left_width == 0 - middle_width == 0 - selected_item == 0 - submenu_open == false - items.size() == 0 - item_enabled.size() == 0 - left_rects.size() == 0 - middle_rects.size() == 0 - right_rects.size() == 0 - line_rects.size() == 0 - submenus.size() == 0 - hide_handlers.size() == 0 CONVENTION - pad = 2 - item_pad = 3 - all of the following arrays have the same size: - items.size() - item_enabled.size() - left_rects.size() - middle_rects.size() - right_rects.size() - line_rects.size() - submenus.size() - win_rect == a rectangle that is the exact size of this window and with its upper left corner at (0,0) - cur_rect == the rect inside which all the menu items are drawn - if (a menu_item is supposed to be selected) then - selected_item == the index in menus of the menu_item - else - selected_item == submenus.size() - if (there is a selected submenu and it is currently open) then - submenu_open == true - else - submenu_open == false - for all valid i: - items[i] == a pointer to the ith menu_item - item_enabled[i] == true if the ith menu_item is enabled, false otherwise - left_rects[i] == the left rectangle for the ith menu item - middle_rects[i] == the middle rectangle for the ith menu item - right_rects[i] == the right rectangle for the ith menu item - line_rects[i] == the rectangle for the entire line on which the ith menu item appears. - if (submenus[i] != 0) then - the ith menu item has a submenu and it is pointed to by submenus[i] - hide_handlers == an array of all the on_hide events registered for this popup_menu !*/ public: popup_menu ( ); template < typename menu_item_type > unsigned long add_menu_item ( const menu_item_type& new_item ) { auto_mutex M(wm); bool t = true; std::unique_ptr<menu_item> item(new menu_item_type(new_item)); items.push_back(item); item_enabled.push_back(t); // figure out how big the window should be now and what not rectangle left = new_item.get_left_size(); rectangle middle = new_item.get_middle_size(); rectangle right = new_item.get_right_size(); bool recalc_rect_positions = false; const rectangle all = left+middle+right; // make sure left_width contains the max of all the left rectangles if (left.width() > left_width) { left_width = left.width(); recalc_rect_positions = true; } // make sure middle_width contains the max of all the middle rectangles if (middle.width() > middle_width) { middle_width = middle.width(); recalc_rect_positions = true; } // make the current rectangle wider if necessary if (cur_rect.width() < left_width + middle_width + right.width() + 2*item_pad) { cur_rect = resize_rect_width(cur_rect, left_width + middle_width + right.width() + 2*item_pad); recalc_rect_positions = true; } const long y = cur_rect.bottom()+1 + item_pad; const long x = cur_rect.left() + item_pad; // make the current rectangle taller to account for this new menu item cur_rect.set_bottom(cur_rect.bottom()+all.height() + 2*item_pad); // adjust all the saved rectangles since the width of the window changed // or left_width changed if (recalc_rect_positions) { long y = cur_rect.top() + item_pad; for (unsigned long i = 0; i < left_rects.size(); ++i) { middle_rects[i] = move_rect(middle_rects[i], x+left_width, y); right_rects[i] = move_rect(right_rects[i], x+cur_rect.width()-right_rects[i].width()-item_pad, y); line_rects[i] = resize_rect_width(line_rects[i], cur_rect.width()); y += line_rects[i].height(); } } // save the rectangles for later use. Also position them at the // right spots left = move_rect(left,x,y); middle = move_rect(middle,x+left_width,y); right = move_rect(right,x+cur_rect.width()-right.width()-item_pad,y); rectangle line(move_rect(rectangle(cur_rect.width(),all.height()+2*item_pad), x-item_pad, y-item_pad)); // make sure the left, middle, and right rectangles are centered in the // line. if (left.height() < all.height()) left = translate_rect(left,0, (all.height()-left.height())/2); if (middle.height() < all.height()) middle = translate_rect(middle,0, (all.height()-middle.height())/2); if (right.height() < all.height()) right = translate_rect(right,0, (all.height()-right.height())/2); left_rects.push_back(left); middle_rects.push_back(middle); right_rects.push_back(right); line_rects.push_back(line); popup_menu* junk = 0; submenus.push_back(junk); win_rect.set_right(cur_rect.right()+pad); win_rect.set_bottom(cur_rect.bottom()+pad); set_size(win_rect.width(),win_rect.height()); // make it so that nothing is selected selected_item = submenus.size(); return items.size()-1; } template < typename menu_item_type > unsigned long add_submenu ( const menu_item_type& new_item, popup_menu& submenu ) { auto_mutex M(wm); submenus[add_menu_item(new_item)] = &submenu; submenu.set_on_hide_handler(*this,&popup_menu::on_submenu_hide); return items.size()-1; } void enable_menu_item ( unsigned long idx ); void disable_menu_item ( unsigned long idx ); unsigned long size ( ) const; void clear ( ); void show ( ); void hide ( ); template <typename T> void set_on_hide_handler ( T& object, void (T::*event_handler)() ) { auto_mutex M(wm); member_function_pointer<> temp; temp.set(object,event_handler); // if this handler isn't already registered then add it bool found_handler = false; for (unsigned long i = 0; i < hide_handlers.size(); ++i) { if (hide_handlers[i] == temp) { found_handler = true; break; } } if (found_handler == false) { hide_handlers.push_back(temp); } } void select_first_item ( ); bool forwarded_on_keydown ( unsigned long key, bool is_printable, unsigned long state ); private: void on_submenu_hide ( ); void on_window_resized( ); void on_mouse_up ( unsigned long btn, unsigned long, long x, long y ); void on_mouse_move ( unsigned long state, long x, long y ); void close_submenu ( ); bool display_selected_submenu ( ); /*! ensures - if (submenus[selected_item] isn't null) then - displays the selected submenu - returns true - else - returns false !*/ void on_mouse_leave ( ); void paint ( const canvas& c ); const long pad; const long item_pad; rectangle cur_rect; rectangle win_rect; unsigned long left_width; unsigned long middle_width; array<std::unique_ptr<menu_item> > items; array<bool> item_enabled; array<rectangle> left_rects; array<rectangle> middle_rects; array<rectangle> right_rects; array<rectangle> line_rects; array<popup_menu*> submenus; unsigned long selected_item; bool submenu_open; array<member_function_pointer<> > hide_handlers; // restricted functions popup_menu(popup_menu&); // copy constructor popup_menu& operator=(popup_menu&); // assignment operator }; // ---------------------------------------------------------------------------------------- class zoomable_region : public drawable { /*! INITIAL VALUE - min_scale == 0.15 - max_scale == 1.0 - zoom_increment_ == 0.02 - scale == 1.0 - mouse_drag_screen == false CONVENTION - zoom_increment() == zoom_increment_ - min_zoom_scale() == min_scale - max_zoom_scale() == max_scale - zoom_scale() == scale - if (the user is currently dragging the graph around via the mouse) then - mouse_drag_screen == true - else - mouse_drag_screen == false - max_graph_point() == lr_point - display_rect() == display_rect_ - gui_to_graph_space(point(display_rect.left(),display_rect.top())) == gr_orig !*/ public: zoomable_region ( drawable_window& w, unsigned long events = 0 ); virtual ~zoomable_region ( )= 0; virtual 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_)); hsb.set_style(style_.get_horizontal_scroll_bar_style()); vsb.set_style(style_.get_vertical_scroll_bar_style()); // do this just so that everything gets redrawn right set_size(rect.width(), rect.height()); } void set_zoom_increment ( double zi ); double zoom_increment ( ) const; void set_max_zoom_scale ( double ms ); void set_min_zoom_scale ( double ms ); double min_zoom_scale ( ) const; double max_zoom_scale ( ) const; virtual void set_size ( unsigned long width, unsigned long height ); void show ( ); void hide ( ); void enable ( ); void disable ( ); void set_z_order ( long order ); protected: virtual void on_view_changed () {} point graph_to_gui_space ( const vector<double,2>& p ) const; vector<double,2> gui_to_graph_space ( const point& p ) const; point max_graph_point ( ) const; rectangle display_rect ( ) const; double zoom_scale ( ) const; void set_zoom_scale ( double new_scale ); void center_display_at_graph_point ( const vector<double,2>& p ); // ----------- event handlers --------------- void on_wheel_down ( unsigned long state ); void on_wheel_up ( unsigned long state ); void on_mouse_move ( unsigned long state, long x, long y ); void on_mouse_up ( unsigned long btn, unsigned long state, long x, long y ); 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 on_h_scroll ( ); void on_v_scroll ( ); void redraw_graph ( ); void adjust_origin ( const point& gui_p, const vector<double,2>& graph_p ); /*! ensures - adjusts gr_orig so that we are as close to the following as possible: - graph_to_gui_space(graph_p) == gui_p - gui_to_graph_space(gui_p) == graph_p !*/ vector<double,2> gr_orig; // point in graph space such that it's gui space point is the upper left of display_rect_ vector<double,2> lr_point; // point in graph space such that it is at the lower right corner of the screen at max zoom mutable std::ostringstream sout; double scale; // 0 < scale <= 1 double min_scale; double max_scale; double zoom_increment_; rectangle display_rect_; bool mouse_drag_screen; // true if the user is dragging the white background area vector<double,2> drag_screen_point; // the starting point the mouse was at in graph space for the background area drag scroll_bar vsb; scroll_bar hsb; std::unique_ptr<scrollable_region_style> style; // restricted functions zoomable_region(zoomable_region&); // copy constructor zoomable_region& operator=(zoomable_region&); // assignment operator }; // ---------------------------------------------------------------------------------------- class scrollable_region : public drawable { /*! INITIAL VALUE - hscroll_bar_inc == 1 - vscroll_bar_inc == 1 - h_wheel_scroll_bar_inc == 1 - v_wheel_scroll_bar_inc == 1 - mouse_drag_enabled_ == false - user_is_dragging_mouse == false CONVENTION - mouse_drag_enabled() == mouse_drag_enabled_ - horizontal_scroll_increment() == hscroll_bar_inc - vertical_scroll_increment() == vscroll_bar_inc - horizontal_mouse_wheel_scroll_increment() == h_wheel_scroll_bar_inc - vertical_mouse_wheel_scroll_increment() == v_wheel_scroll_bar_inc - vertical_scroll_pos() == vsb.slider_pos() - horizontal_scroll_pos() == hsb.slider_pos() - total_rect() == total_rect_ - display_rect() == display_rect_ - if (the user is currently dragging the total_rect around with a mouse drag) then - user_is_dragging_mouse == true - drag_origin == the point the mouse was at, with respect to total_rect, when the dragging started - else - user_is_dragging_mouse == false !*/ public: scrollable_region ( drawable_window& w, unsigned long events = 0 ); virtual ~scrollable_region ( ) = 0; template < typename style_type > void set_style ( const style_type& style_ ) { auto_mutex M(m); style.reset(new style_type(style_)); hsb.set_style(style_.get_horizontal_scroll_bar_style()); vsb.set_style(style_.get_vertical_scroll_bar_style()); // do this just so that everything gets redrawn right set_size(rect.width(), rect.height()); } void show ( ); void hide ( ); void enable ( ); void disable ( ); void set_z_order ( long order ); virtual void set_size ( unsigned long width, unsigned long height ); unsigned long horizontal_mouse_wheel_scroll_increment ( ) const; unsigned long vertical_mouse_wheel_scroll_increment ( ) const; void set_horizontal_mouse_wheel_scroll_increment ( unsigned long inc ); void set_vertical_mouse_wheel_scroll_increment ( unsigned long inc ); unsigned long horizontal_scroll_increment ( ) const; unsigned long vertical_scroll_increment ( ) const; void set_horizontal_scroll_increment ( unsigned long inc ); void set_vertical_scroll_increment ( unsigned long inc ); long horizontal_scroll_pos ( ) const; long vertical_scroll_pos ( ) const; void set_horizontal_scroll_pos ( long pos ); void set_vertical_scroll_pos ( long pos ); virtual void set_pos ( long x, long y ); bool mouse_drag_enabled ( ) const; void enable_mouse_drag ( ); void disable_mouse_drag ( ); protected: virtual void on_view_changed () {} const rectangle& display_rect ( ) const; void set_total_rect_size ( unsigned long width, unsigned long height ); const rectangle& total_rect ( ) const; void scroll_to_rect ( const rectangle& r_ ); void on_wheel_down ( unsigned long state ); void on_mouse_move ( unsigned long state, long x, long y ); 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_wheel_up ( unsigned long state ); void draw ( const canvas& c ) const; private: bool need_h_scroll ( ) const; bool need_v_scroll ( ) const; void on_h_scroll ( ); void on_v_scroll ( ); rectangle total_rect_; rectangle display_rect_; scroll_bar hsb; scroll_bar vsb; unsigned long hscroll_bar_inc; unsigned long vscroll_bar_inc; unsigned long h_wheel_scroll_bar_inc; unsigned long v_wheel_scroll_bar_inc; bool mouse_drag_enabled_; bool user_is_dragging_mouse; point drag_origin; std::unique_ptr<scrollable_region_style> style; }; // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- // class popup_menu_region // ---------------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------------- class popup_menu_region : public drawable { /*! CONVENTION popup_menu_visible() == popup_menu_shown !*/ public: popup_menu_region( drawable_window& w ); virtual ~popup_menu_region( ); void set_size ( unsigned long width, unsigned long height ); void set_rect ( const rectangle& new_rect ); popup_menu& menu ( ); void hide ( ); void disable ( ); bool popup_menu_visible ( ) const { auto_mutex M(m); return popup_menu_shown; } protected: void on_keydown ( unsigned long key, bool is_printable, unsigned long state ); void on_focus_lost ( ); void on_focus_gained ( ); void on_window_moved( ); void on_mouse_down ( unsigned long btn, unsigned long state, long x, long y, bool is_double_click ); void on_menu_becomes_hidden ( ); void draw ( const canvas& ) const; private: popup_menu menu_; bool popup_menu_shown; // restricted functions popup_menu_region(popup_menu_region&); // copy constructor popup_menu_region& operator=(popup_menu_region&); // assignment operator }; // ---------------------------------------------------------------------------------------- } #ifdef NO_MAKEFILE #include "base_widgets.cpp" #endif #endif // DLIB_BASE_WIDGETs_