1 // Copyright (C) 2005 Davis E. King (davis@dlib.net), Keita Mochizuki 2 // License: Boost Software License See LICENSE.txt for the full license. 3 #ifndef DLIB_WIDGETs_CPP_ 4 #define DLIB_WIDGETs_CPP_ 5 6 #include <algorithm> 7 #include <memory> 8 9 #include "widgets.h" 10 #include "../string.h" 11 12 namespace dlib 13 { 14 15 // ---------------------------------------------------------------------------------------- 16 // ---------------------------------------------------------------------------------------- 17 // toggle_button object methods 18 // ---------------------------------------------------------------------------------------- 19 // ---------------------------------------------------------------------------------------- 20 21 void toggle_button:: set_size(unsigned long width,unsigned long height)22 set_size ( 23 unsigned long width, 24 unsigned long height 25 ) 26 { 27 auto_mutex M(m); 28 rectangle min_rect = style->get_min_size(name_,*mfont); 29 // only change the size if it isn't going to be too small to fit the name 30 if (height >= min_rect.height() && 31 width >= min_rect.width()) 32 { 33 rectangle old(rect); 34 rect = resize_rect(rect,width,height); 35 parent.invalidate_rectangle(rect+old); 36 btn_tooltip.set_size(width,height); 37 } 38 } 39 40 // ---------------------------------------------------------------------------------------- 41 42 void toggle_button:: set_checked()43 set_checked ( 44 ) 45 { 46 auto_mutex M(m); 47 checked = true; 48 parent.invalidate_rectangle(rect); 49 } 50 51 // ---------------------------------------------------------------------------------------- 52 53 void toggle_button:: set_unchecked()54 set_unchecked ( 55 ) 56 { 57 auto_mutex M(m); 58 checked = false; 59 parent.invalidate_rectangle(rect); 60 } 61 62 // ---------------------------------------------------------------------------------------- 63 64 bool toggle_button:: is_checked() const65 is_checked ( 66 ) const 67 { 68 auto_mutex M(m); 69 return checked; 70 } 71 72 // ---------------------------------------------------------------------------------------- 73 74 void toggle_button:: show()75 show ( 76 ) 77 { 78 button_action::show(); 79 btn_tooltip.show(); 80 } 81 82 // ---------------------------------------------------------------------------------------- 83 84 void toggle_button:: hide()85 hide ( 86 ) 87 { 88 button_action::hide(); 89 btn_tooltip.hide(); 90 } 91 92 // ---------------------------------------------------------------------------------------- 93 94 void toggle_button:: enable()95 enable ( 96 ) 97 { 98 button_action::enable(); 99 btn_tooltip.enable(); 100 } 101 102 // ---------------------------------------------------------------------------------------- 103 104 void toggle_button:: disable()105 disable ( 106 ) 107 { 108 button_action::disable(); 109 btn_tooltip.disable(); 110 } 111 112 // ---------------------------------------------------------------------------------------- 113 114 void toggle_button:: set_tooltip_text(const std::string & text)115 set_tooltip_text ( 116 const std::string& text 117 ) 118 { 119 btn_tooltip.set_text(text); 120 } 121 122 void toggle_button:: set_tooltip_text(const std::wstring & text)123 set_tooltip_text ( 124 const std::wstring& text 125 ) 126 { 127 btn_tooltip.set_text(text); 128 } 129 130 void toggle_button:: set_tooltip_text(const dlib::ustring & text)131 set_tooltip_text ( 132 const dlib::ustring& text 133 ) 134 { 135 btn_tooltip.set_text(text); 136 } 137 138 // ---------------------------------------------------------------------------------------- 139 140 const std::string toggle_button:: tooltip_text() const141 tooltip_text ( 142 ) const 143 { 144 return btn_tooltip.text(); 145 } 146 147 const std::wstring toggle_button:: tooltip_wtext() const148 tooltip_wtext ( 149 ) const 150 { 151 return btn_tooltip.wtext(); 152 } 153 154 const dlib::ustring toggle_button:: tooltip_utext() const155 tooltip_utext ( 156 ) const 157 { 158 return btn_tooltip.utext(); 159 } 160 161 // ---------------------------------------------------------------------------------------- 162 163 void toggle_button:: set_main_font(const std::shared_ptr<font> & f)164 set_main_font ( 165 const std::shared_ptr<font>& f 166 ) 167 { 168 auto_mutex M(m); 169 mfont = f; 170 set_name(name_); 171 } 172 173 // ---------------------------------------------------------------------------------------- 174 175 void toggle_button:: set_pos(long x,long y)176 set_pos ( 177 long x, 178 long y 179 ) 180 { 181 auto_mutex M(m); 182 button_action::set_pos(x,y); 183 btn_tooltip.set_pos(x,y); 184 } 185 186 // ---------------------------------------------------------------------------------------- 187 188 void toggle_button:: set_name(const std::string & name)189 set_name ( 190 const std::string& name 191 ) 192 { 193 set_name(convert_mbstring_to_wstring(name)); 194 } 195 196 void toggle_button:: set_name(const std::wstring & name)197 set_name ( 198 const std::wstring& name 199 ) 200 { 201 set_name(convert_wstring_to_utf32(name)); 202 } 203 204 void toggle_button:: set_name(const dlib::ustring & name)205 set_name ( 206 const dlib::ustring& name 207 ) 208 { 209 auto_mutex M(m); 210 name_ = name; 211 // do this to get rid of any reference counting that may be present in 212 // the std::string implementation. 213 name_[0] = name_[0]; 214 215 rectangle old(rect); 216 rect = move_rect(style->get_min_size(name,*mfont),rect.left(),rect.top()); 217 btn_tooltip.set_size(rect.width(),rect.height()); 218 219 parent.invalidate_rectangle(rect+old); 220 } 221 222 // ---------------------------------------------------------------------------------------- 223 224 const std::string toggle_button:: name() const225 name ( 226 ) const 227 { 228 return convert_wstring_to_mbstring(wname()); 229 } 230 231 const std::wstring toggle_button:: wname() const232 wname ( 233 ) const 234 { 235 return convert_utf32_to_wstring(uname()); 236 } 237 238 const dlib::ustring toggle_button:: uname() const239 uname ( 240 ) const 241 { 242 auto_mutex M(m); 243 dlib::ustring temp = name_; 244 // do this to get rid of any reference counting that may be present in 245 // the std::string implementation. 246 temp[0] = name_[0]; 247 return temp; 248 } 249 250 // ---------------------------------------------------------------------------------------- 251 252 void toggle_button:: on_button_up(bool mouse_over)253 on_button_up ( 254 bool mouse_over 255 ) 256 { 257 if (mouse_over) 258 { 259 checked = !checked; 260 // this is a valid toggle_button click 261 if (event_handler.is_set()) 262 event_handler(); 263 else if (event_handler_self.is_set()) 264 event_handler_self(*this); 265 } 266 } 267 268 // ---------------------------------------------------------------------------------------- 269 // ---------------------------------------------------------------------------------------- 270 // label object methods 271 // ---------------------------------------------------------------------------------------- 272 // ---------------------------------------------------------------------------------------- 273 274 void label:: draw(const canvas & c) const275 draw ( 276 const canvas& c 277 ) const 278 { 279 rectangle area = rect.intersect(c); 280 if (area.is_empty() || text_.size() == 0) 281 return; 282 283 using namespace std; 284 unsigned char r = text_color_.red; 285 unsigned char g = text_color_.green; 286 unsigned char b = text_color_.blue; 287 if (!enabled) 288 { 289 r = 128; 290 g = 128; 291 b = 128; 292 } 293 294 rectangle text_rect(rect); 295 296 string::size_type first, last; 297 first = 0; 298 last = text_.find_first_of('\n'); 299 mfont->draw_string(c,text_rect,text_,rgb_pixel(r,g,b),first,last); 300 301 while (last != string::npos) 302 { 303 first = last+1; 304 last = text_.find_first_of('\n',first); 305 text_rect.set_top(text_rect.top()+mfont->height()); 306 mfont->draw_string(c,text_rect,text_,rgb_pixel(r,g,b),first,last); 307 } 308 } 309 310 // ---------------------------------------------------------------------------------------- 311 312 void label:: set_main_font(const std::shared_ptr<font> & f)313 set_main_font ( 314 const std::shared_ptr<font>& f 315 ) 316 { 317 auto_mutex M(m); 318 mfont = f; 319 set_text(text_); 320 } 321 322 // ---------------------------------------------------------------------------------------- 323 324 325 void label:: set_text(const std::string & text)326 set_text ( 327 const std::string& text 328 ) 329 { 330 set_text(convert_mbstring_to_wstring(text)); 331 } 332 333 void label:: set_text(const std::wstring & text)334 set_text ( 335 const std::wstring& text 336 ) 337 { 338 set_text(convert_wstring_to_utf32(text)); 339 } 340 341 void label:: set_text(const dlib::ustring & text)342 set_text ( 343 const dlib::ustring& text 344 ) 345 { 346 using namespace std; 347 auto_mutex M(m); 348 text_ = text; 349 // do this to get rid of any reference counting that may be present in 350 // the std::string implementation. 351 text_[0] = text[0]; 352 353 rectangle old(rect); 354 355 unsigned long width; 356 unsigned long height; 357 mfont->compute_size(text,width,height); 358 359 rect.set_right(rect.left() + width - 1); 360 rect.set_bottom(rect.top() + height - 1); 361 362 parent.invalidate_rectangle(rect+old); 363 } 364 365 // ---------------------------------------------------------------------------------------- 366 367 const std::string label:: text() const368 text ( 369 ) const 370 { 371 return convert_wstring_to_mbstring(wtext()); 372 } 373 374 const std::wstring label:: wtext() const375 wtext ( 376 ) const 377 { 378 return convert_utf32_to_wstring(utext()); 379 } 380 381 const dlib::ustring label:: utext() const382 utext ( 383 ) const 384 { 385 auto_mutex M(m); 386 dlib::ustring temp = text_; 387 // do this to get rid of any reference counting that may be present in 388 // the std::string implementation. 389 temp[0] = text_[0]; 390 return temp; 391 } 392 393 // ---------------------------------------------------------------------------------------- 394 395 void label:: set_text_color(const rgb_pixel color)396 set_text_color ( 397 const rgb_pixel color 398 ) 399 { 400 m.lock(); 401 text_color_ = color; 402 parent.invalidate_rectangle(rect); 403 m.unlock(); 404 } 405 406 // ---------------------------------------------------------------------------------------- 407 408 const rgb_pixel label:: text_color() const409 text_color ( 410 ) const 411 { 412 auto_mutex M(m); 413 return text_color_; 414 } 415 416 // ---------------------------------------------------------------------------------------- 417 // ---------------------------------------------------------------------------------------- 418 // text_field object methods 419 // ---------------------------------------------------------------------------------------- 420 // ---------------------------------------------------------------------------------------- 421 422 rectangle text_field:: get_text_rect() const423 get_text_rect ( 424 ) const 425 { 426 // figure out where the text string should appear 427 unsigned long vertical_pad = (rect.height() - mfont->height())/2+1; 428 429 rectangle text_rect; 430 text_rect.set_left(rect.left()+style->get_padding(*mfont)); 431 text_rect.set_top(rect.top()+vertical_pad); 432 text_rect.set_right(rect.right()-style->get_padding(*mfont)); 433 text_rect.set_bottom(text_rect.top()+mfont->height()-1); 434 return text_rect; 435 } 436 437 // ---------------------------------------------------------------------------------------- 438 439 void text_field:: enable()440 enable ( 441 ) 442 { 443 drawable::enable(); 444 right_click_menu.enable(); 445 } 446 447 // ---------------------------------------------------------------------------------------- 448 449 void text_field:: give_input_focus()450 give_input_focus ( 451 ) 452 { 453 auto_mutex M(m); 454 has_focus = true; 455 cursor_visible = true; 456 parent.invalidate_rectangle(rect); 457 t.start(); 458 } 459 460 // ---------------------------------------------------------------------------------------- 461 462 bool text_field:: has_input_focus() const463 has_input_focus ( 464 ) const 465 { 466 auto_mutex M(m); 467 return has_focus; 468 } 469 470 // ---------------------------------------------------------------------------------------- 471 472 void text_field:: select_all_text()473 select_all_text ( 474 ) 475 { 476 auto_mutex M(m); 477 on_select_all(); 478 } 479 480 // ---------------------------------------------------------------------------------------- 481 482 void text_field:: on_cut()483 on_cut ( 484 ) 485 { 486 on_copy(); 487 on_delete_selected(); 488 } 489 490 // ---------------------------------------------------------------------------------------- 491 492 void text_field:: on_copy()493 on_copy ( 494 ) 495 { 496 if (highlight_start <= highlight_end) 497 { 498 put_on_clipboard(text_.substr(highlight_start, highlight_end-highlight_start+1)); 499 } 500 } 501 502 // ---------------------------------------------------------------------------------------- 503 504 void text_field:: on_paste()505 on_paste ( 506 ) 507 { 508 ustring temp_str; 509 get_from_clipboard(temp_str); 510 511 // If this is a multi line string then just take the first line. 512 ustring::size_type pos = temp_str.find_first_of('\n'); 513 if (pos != ustring::npos) 514 { 515 temp_str = temp_str.substr(0,pos); 516 } 517 518 if (highlight_start <= highlight_end) 519 { 520 text_ = text_.substr(0,highlight_start) + temp_str + 521 text_.substr(highlight_end+1,text_.size()-highlight_end-1); 522 move_cursor(highlight_start+temp_str.size()); 523 highlight_start = 0; 524 highlight_end = -1; 525 parent.invalidate_rectangle(rect); 526 on_no_text_selected(); 527 528 // send out the text modified event 529 if (text_modified_handler.is_set()) 530 text_modified_handler(); 531 } 532 else 533 { 534 text_ = text_.substr(0,cursor_pos) + temp_str + 535 text_.substr(cursor_pos,text_.size()-cursor_pos); 536 move_cursor(cursor_pos+temp_str.size()); 537 538 // send out the text modified event 539 if (temp_str.size() != 0 && text_modified_handler.is_set()) 540 text_modified_handler(); 541 } 542 } 543 544 // ---------------------------------------------------------------------------------------- 545 546 void text_field:: on_select_all()547 on_select_all ( 548 ) 549 { 550 move_cursor(static_cast<long>(text_.size())); 551 highlight_start = 0; 552 highlight_end = static_cast<long>(text_.size()-1); 553 if (highlight_start <= highlight_end) 554 on_text_is_selected(); 555 parent.invalidate_rectangle(rect); 556 } 557 558 // ---------------------------------------------------------------------------------------- 559 560 void text_field:: on_delete_selected()561 on_delete_selected ( 562 ) 563 { 564 if (highlight_start <= highlight_end) 565 { 566 text_ = text_.erase(highlight_start,highlight_end-highlight_start+1); 567 move_cursor(highlight_start); 568 highlight_start = 0; 569 highlight_end = -1; 570 571 on_no_text_selected(); 572 // send out the text modified event 573 if (text_modified_handler.is_set()) 574 text_modified_handler(); 575 576 parent.invalidate_rectangle(rect); 577 } 578 } 579 580 // ---------------------------------------------------------------------------------------- 581 582 void text_field:: on_text_is_selected()583 on_text_is_selected ( 584 ) 585 { 586 right_click_menu.menu().enable_menu_item(0); 587 right_click_menu.menu().enable_menu_item(1); 588 right_click_menu.menu().enable_menu_item(3); 589 } 590 591 // ---------------------------------------------------------------------------------------- 592 593 void text_field:: on_no_text_selected()594 on_no_text_selected ( 595 ) 596 { 597 right_click_menu.menu().disable_menu_item(0); 598 right_click_menu.menu().disable_menu_item(1); 599 right_click_menu.menu().disable_menu_item(3); 600 } 601 602 // ---------------------------------------------------------------------------------------- 603 604 void text_field:: show()605 show ( 606 ) 607 { 608 drawable::show(); 609 right_click_menu.show(); 610 } 611 612 // ---------------------------------------------------------------------------------------- 613 614 void text_field:: disable()615 disable ( 616 ) 617 { 618 auto_mutex M(m); 619 drawable::disable(); 620 t.stop(); 621 has_focus = false; 622 cursor_visible = false; 623 right_click_menu.disable(); 624 } 625 626 // ---------------------------------------------------------------------------------------- 627 628 void text_field:: hide()629 hide ( 630 ) 631 { 632 auto_mutex M(m); 633 drawable::hide(); 634 t.stop(); 635 has_focus = false; 636 cursor_visible = false; 637 } 638 639 // ---------------------------------------------------------------------------------------- 640 641 void text_field:: set_main_font(const std::shared_ptr<font> & f)642 set_main_font ( 643 const std::shared_ptr<font>& f 644 ) 645 { 646 auto_mutex M(m); 647 mfont = f; 648 // adjust the height of this text field so that it is appropriate for the current 649 // font size 650 rect.set_bottom(rect.top() + mfont->height()+ (style->get_padding(*mfont))*2); 651 set_text(text_); 652 right_click_menu.set_rect(get_text_rect()); 653 } 654 655 // ---------------------------------------------------------------------------------------- 656 657 void text_field:: draw(const canvas & c) const658 draw ( 659 const canvas& c 660 ) const 661 { 662 rectangle area = rect.intersect(c); 663 if (area.is_empty()) 664 return; 665 666 style->draw_text_field(c,rect,get_text_rect(), enabled, *mfont, text_, cursor_x, text_pos, 667 text_color_, bg_color_, has_focus, cursor_visible, highlight_start, 668 highlight_end); 669 } 670 671 // ---------------------------------------------------------------------------------------- 672 673 void text_field:: set_text(const std::string & text)674 set_text ( 675 const std::string& text 676 ) 677 { 678 set_text(convert_mbstring_to_wstring(text)); 679 } 680 681 void text_field:: set_text(const std::wstring & text)682 set_text ( 683 const std::wstring& text 684 ) 685 { 686 set_text(convert_wstring_to_utf32(text)); 687 } 688 689 void text_field:: set_text(const dlib::ustring & text)690 set_text ( 691 const dlib::ustring& text 692 ) 693 { 694 DLIB_ASSERT ( text.find_first_of('\n') == std::string::npos , 695 "\tvoid text_field::set_text()" 696 << "\n\ttext: " << narrow(text) ); 697 auto_mutex M(m); 698 // do this to get rid of any reference counting that may be present in 699 // the std::string implementation. 700 text_ = text.c_str(); 701 702 move_cursor(0); 703 704 highlight_start = 0; 705 highlight_end = -1; 706 707 parent.invalidate_rectangle(rect); 708 } 709 710 // ---------------------------------------------------------------------------------------- 711 712 const std::string text_field:: text() const713 text ( 714 ) const 715 { 716 std::string temp = convert_wstring_to_mbstring(wtext()); 717 return temp; 718 } 719 720 const std::wstring text_field:: wtext() const721 wtext ( 722 ) const 723 { 724 std::wstring temp = convert_utf32_to_wstring(utext()); 725 return temp; 726 } 727 728 const dlib::ustring text_field:: utext() const729 utext ( 730 ) const 731 { 732 auto_mutex M(m); 733 // do this to get rid of any reference counting that may be present in 734 // the dlib::ustring implementation. 735 dlib::ustring temp = text_.c_str(); 736 return temp; 737 } 738 739 // ---------------------------------------------------------------------------------------- 740 741 void text_field:: set_width(unsigned long width)742 set_width ( 743 unsigned long width 744 ) 745 { 746 auto_mutex M(m); 747 if (width < style->get_padding(*mfont)*2) 748 return; 749 750 rectangle old(rect); 751 752 rect.set_right(rect.left() + width - 1); 753 754 right_click_menu.set_rect(get_text_rect()); 755 parent.invalidate_rectangle(rect+old); 756 } 757 758 // ---------------------------------------------------------------------------------------- 759 760 void text_field:: set_pos(long x,long y)761 set_pos ( 762 long x, 763 long y 764 ) 765 { 766 drawable::set_pos(x,y); 767 right_click_menu.set_rect(get_text_rect()); 768 } 769 770 // ---------------------------------------------------------------------------------------- 771 772 void text_field:: set_background_color(const rgb_pixel color)773 set_background_color ( 774 const rgb_pixel color 775 ) 776 { 777 auto_mutex M(m); 778 bg_color_ = color; 779 parent.invalidate_rectangle(rect); 780 } 781 782 // ---------------------------------------------------------------------------------------- 783 784 const rgb_pixel text_field:: background_color() const785 background_color ( 786 ) const 787 { 788 auto_mutex M(m); 789 return bg_color_; 790 } 791 792 // ---------------------------------------------------------------------------------------- 793 794 void text_field:: set_text_color(const rgb_pixel color)795 set_text_color ( 796 const rgb_pixel color 797 ) 798 { 799 auto_mutex M(m); 800 text_color_ = color; 801 parent.invalidate_rectangle(rect); 802 } 803 804 // ---------------------------------------------------------------------------------------- 805 806 const rgb_pixel text_field:: text_color() const807 text_color ( 808 ) const 809 { 810 auto_mutex M(m); 811 return text_color_; 812 } 813 814 // ---------------------------------------------------------------------------------------- 815 816 void text_field:: on_mouse_move(unsigned long state,long x,long y)817 on_mouse_move ( 818 unsigned long state, 819 long x, 820 long y 821 ) 822 { 823 if (!enabled || hidden || !has_focus) 824 { 825 return; 826 } 827 828 if (state & base_window::LEFT) 829 { 830 if (highlight_start <= highlight_end) 831 { 832 if (highlight_start == cursor_pos) 833 shift_pos = highlight_end + 1; 834 else 835 shift_pos = highlight_start; 836 } 837 838 unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y,text_pos); 839 if (static_cast<long>(new_pos) != cursor_pos) 840 { 841 move_cursor(new_pos); 842 parent.invalidate_rectangle(rect); 843 } 844 } 845 else if (shift_pos != -1) 846 { 847 shift_pos = -1; 848 } 849 850 } 851 852 // ---------------------------------------------------------------------------------------- 853 854 void text_field:: on_mouse_up(unsigned long btn,unsigned long,long,long)855 on_mouse_up ( 856 unsigned long btn, 857 unsigned long, 858 long , 859 long 860 ) 861 { 862 if (!enabled || hidden) 863 return; 864 865 if (btn == base_window::LEFT) 866 shift_pos = -1; 867 } 868 869 // ---------------------------------------------------------------------------------------- 870 871 void text_field:: on_mouse_down(unsigned long btn,unsigned long state,long x,long y,bool double_clicked)872 on_mouse_down ( 873 unsigned long btn, 874 unsigned long state, 875 long x, 876 long y, 877 bool double_clicked 878 ) 879 { 880 using namespace std; 881 if (!enabled || hidden || btn != (unsigned long)base_window::LEFT) 882 return; 883 884 if (rect.contains(x,y)) 885 { 886 has_focus = true; 887 cursor_visible = true; 888 parent.invalidate_rectangle(rect); 889 t.start(); 890 891 if (double_clicked) 892 { 893 // highlight the double clicked word 894 string::size_type first, last; 895 const ustring ustr = convert_utf8_to_utf32(std::string(" \t\n")); 896 first = text_.substr(0,cursor_pos).find_last_of(ustr.c_str()); 897 last = text_.find_first_of(ustr.c_str(),cursor_pos); 898 long f = static_cast<long>(first); 899 long l = static_cast<long>(last); 900 if (first == string::npos) 901 f = -1; 902 if (last == string::npos) 903 l = static_cast<long>(text_.size()); 904 905 ++f; 906 --l; 907 908 move_cursor(l+1); 909 highlight_start = f; 910 highlight_end = l; 911 on_text_is_selected(); 912 } 913 else 914 { 915 if (state & base_window::SHIFT) 916 { 917 if (highlight_start <= highlight_end) 918 { 919 if (highlight_start == cursor_pos) 920 shift_pos = highlight_end + 1; 921 else 922 shift_pos = highlight_start; 923 } 924 else 925 { 926 shift_pos = cursor_pos; 927 } 928 } 929 930 bool at_end = false; 931 if (cursor_pos == 0 || cursor_pos == static_cast<long>(text_.size())) 932 at_end = true; 933 const long old_pos = cursor_pos; 934 935 unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y,text_pos); 936 if (static_cast<long>(new_pos) != cursor_pos) 937 { 938 move_cursor(new_pos); 939 parent.invalidate_rectangle(rect); 940 } 941 shift_pos = cursor_pos; 942 943 if (at_end && cursor_pos == old_pos) 944 { 945 highlight_start = 0; 946 highlight_end = -1; 947 on_no_text_selected(); 948 parent.invalidate_rectangle(rect); 949 } 950 } 951 952 } 953 else if (has_focus) 954 { 955 t.stop(); 956 has_focus = false; 957 cursor_visible = false; 958 shift_pos = -1; 959 highlight_start = 0; 960 highlight_end = -1; 961 on_no_text_selected(); 962 963 if (focus_lost_handler.is_set()) 964 focus_lost_handler(); 965 parent.invalidate_rectangle(rect); 966 } 967 } 968 969 // ---------------------------------------------------------------------------------------- 970 971 void text_field:: on_keydown(unsigned long key,bool is_printable,unsigned long state)972 on_keydown ( 973 unsigned long key, 974 bool is_printable, 975 unsigned long state 976 ) 977 { 978 // If the right click menu is up then we don't want to do anything with 979 // the keyboard ourselves. Let the popup menu use the keyboard for now. 980 if (right_click_menu.popup_menu_visible()) 981 return; 982 983 const ustring space_str = convert_utf8_to_utf32(std::string(" \t\n")); 984 const bool shift = (state&base_window::KBD_MOD_SHIFT) != 0; 985 const bool ctrl = (state&base_window::KBD_MOD_CONTROL) != 0; 986 if (has_focus && enabled && !hidden) 987 { 988 if (shift && is_printable == false) 989 { 990 if (shift_pos == -1) 991 { 992 if (highlight_start <= highlight_end) 993 { 994 if (highlight_start == cursor_pos) 995 shift_pos = highlight_end + 1; 996 else 997 shift_pos = highlight_start; 998 } 999 else 1000 { 1001 shift_pos = cursor_pos; 1002 } 1003 } 1004 } 1005 else 1006 { 1007 shift_pos = -1; 1008 } 1009 1010 if (key == base_window::KEY_LEFT || 1011 key == base_window::KEY_UP) 1012 { 1013 if (cursor_pos != 0) 1014 { 1015 unsigned long new_pos; 1016 if (ctrl) 1017 { 1018 // find the first non-whitespace to our left 1019 std::string::size_type pos = text_.find_last_not_of(space_str.c_str(),cursor_pos); 1020 if (pos != std::string::npos) 1021 { 1022 pos = text_.find_last_of(space_str.c_str(),pos); 1023 if (pos != std::string::npos) 1024 new_pos = static_cast<unsigned long>(pos); 1025 else 1026 new_pos = 0; 1027 } 1028 else 1029 { 1030 new_pos = 0; 1031 } 1032 } 1033 else 1034 { 1035 new_pos = cursor_pos-1; 1036 } 1037 1038 move_cursor(new_pos); 1039 } 1040 else if (shift_pos == -1) 1041 { 1042 highlight_start = 0; 1043 highlight_end = -1; 1044 on_no_text_selected(); 1045 parent.invalidate_rectangle(rect); 1046 } 1047 1048 } 1049 else if (key == base_window::KEY_RIGHT || 1050 key == base_window::KEY_DOWN) 1051 { 1052 if (cursor_pos != static_cast<long>(text_.size())) 1053 { 1054 unsigned long new_pos; 1055 if (ctrl) 1056 { 1057 // find the first non-whitespace to our left 1058 std::string::size_type pos = text_.find_first_not_of(space_str.c_str(),cursor_pos); 1059 if (pos != std::string::npos) 1060 { 1061 pos = text_.find_first_of(space_str.c_str(),pos); 1062 if (pos != std::string::npos) 1063 new_pos = static_cast<unsigned long>(pos+1); 1064 else 1065 new_pos = static_cast<unsigned long>(text_.size()); 1066 } 1067 else 1068 { 1069 new_pos = static_cast<unsigned long>(text_.size()); 1070 } 1071 } 1072 else 1073 { 1074 new_pos = cursor_pos+1; 1075 } 1076 1077 move_cursor(new_pos); 1078 } 1079 else if (shift_pos == -1) 1080 { 1081 highlight_start = 0; 1082 highlight_end = -1; 1083 on_no_text_selected(); 1084 parent.invalidate_rectangle(rect); 1085 } 1086 } 1087 else if (is_printable) 1088 { 1089 if (ctrl) 1090 { 1091 if (key == 'a') 1092 { 1093 on_select_all(); 1094 } 1095 else if (key == 'c') 1096 { 1097 on_copy(); 1098 } 1099 else if (key == 'v') 1100 { 1101 on_paste(); 1102 } 1103 else if (key == 'x') 1104 { 1105 on_cut(); 1106 } 1107 } 1108 else if (key != '\n') 1109 { 1110 if (highlight_start <= highlight_end) 1111 { 1112 text_ = text_.substr(0,highlight_start) + static_cast<unichar>(key) + 1113 text_.substr(highlight_end+1,text_.size()-highlight_end-1); 1114 move_cursor(highlight_start+1); 1115 highlight_start = 0; 1116 highlight_end = -1; 1117 on_no_text_selected(); 1118 parent.invalidate_rectangle(rect); 1119 } 1120 else 1121 { 1122 text_ = text_.substr(0,cursor_pos) + static_cast<unichar>(key) + 1123 text_.substr(cursor_pos,text_.size()-cursor_pos); 1124 move_cursor(cursor_pos+1); 1125 } 1126 unsigned long height; 1127 mfont->compute_size(text_,text_width,height,text_pos); 1128 1129 // send out the text modified event 1130 if (text_modified_handler.is_set()) 1131 text_modified_handler(); 1132 } 1133 else if (key == '\n') 1134 { 1135 if (enter_key_handler.is_set()) 1136 enter_key_handler(); 1137 } 1138 } 1139 else if (key == base_window::KEY_BACKSPACE) 1140 { 1141 // if something is highlighted then delete that 1142 if (highlight_start <= highlight_end) 1143 { 1144 on_delete_selected(); 1145 } 1146 else if (cursor_pos != 0) 1147 { 1148 text_ = text_.erase(cursor_pos-1,1); 1149 move_cursor(cursor_pos-1); 1150 1151 // send out the text modified event 1152 if (text_modified_handler.is_set()) 1153 text_modified_handler(); 1154 } 1155 else 1156 { 1157 // do this just so it repaints itself right 1158 move_cursor(cursor_pos); 1159 } 1160 unsigned long height; 1161 mfont->compute_size(text_,text_width,height,text_pos); 1162 parent.invalidate_rectangle(rect); 1163 } 1164 else if (key == base_window::KEY_DELETE) 1165 { 1166 // if something is highlighted then delete that 1167 if (highlight_start <= highlight_end) 1168 { 1169 on_delete_selected(); 1170 } 1171 else if (cursor_pos != static_cast<long>(text_.size())) 1172 { 1173 text_ = text_.erase(cursor_pos,1); 1174 1175 // send out the text modified event 1176 if (text_modified_handler.is_set()) 1177 text_modified_handler(); 1178 } 1179 else 1180 { 1181 // do this just so it repaints itself right 1182 move_cursor(cursor_pos); 1183 } 1184 parent.invalidate_rectangle(rect); 1185 1186 unsigned long height; 1187 mfont->compute_size(text_,text_width,height,text_pos); 1188 } 1189 else if (key == base_window::KEY_HOME) 1190 { 1191 move_cursor(0); 1192 if (shift_pos == -1) 1193 { 1194 highlight_start = 0; 1195 highlight_end = -1; 1196 on_no_text_selected(); 1197 parent.invalidate_rectangle(rect); 1198 } 1199 } 1200 else if (key == base_window::KEY_END) 1201 { 1202 move_cursor(static_cast<unsigned long>(text_.size())); 1203 if (shift_pos == -1) 1204 { 1205 highlight_start = 0; 1206 highlight_end = -1; 1207 on_no_text_selected(); 1208 parent.invalidate_rectangle(rect); 1209 } 1210 } 1211 cursor_visible = true; 1212 recent_movement = true; 1213 1214 } 1215 } 1216 1217 // ---------------------------------------------------------------------------------------- 1218 1219 void text_field:: on_string_put(const std::wstring & str)1220 on_string_put( 1221 const std::wstring &str 1222 ) 1223 { 1224 if (has_focus && enabled && !hidden){ 1225 ustring ustr = convert_wstring_to_utf32(str); 1226 if (highlight_start <= highlight_end) 1227 { 1228 text_ = text_.substr(0,highlight_start) + ustr + 1229 text_.substr(highlight_end+1,text_.size()-highlight_end-1); 1230 move_cursor(highlight_start+ustr.size()); 1231 highlight_start = 0; 1232 highlight_end = -1; 1233 on_no_text_selected(); 1234 parent.invalidate_rectangle(rect); 1235 } 1236 else 1237 { 1238 text_ = text_.substr(0,cursor_pos) + ustr + 1239 text_.substr(cursor_pos,text_.size()-cursor_pos); 1240 move_cursor(cursor_pos+ustr.size()); 1241 } 1242 unsigned long height; 1243 mfont->compute_size(text_,text_width,height,text_pos); 1244 1245 // send out the text modified event 1246 if (text_modified_handler.is_set()) 1247 text_modified_handler(); 1248 } 1249 } 1250 1251 // ---------------------------------------------------------------------------------------- 1252 1253 void text_field:: move_cursor(unsigned long pos)1254 move_cursor ( 1255 unsigned long pos 1256 ) 1257 { 1258 using namespace std; 1259 const long old_cursor_pos = cursor_pos; 1260 1261 if (text_pos >= pos) 1262 { 1263 // the cursor should go all the way to the left side of the text 1264 if (pos >= 6) 1265 text_pos = pos-6; 1266 else 1267 text_pos = 0; 1268 1269 cursor_pos = pos; 1270 unsigned long height; 1271 mfont->compute_size(text_,text_width,height,text_pos); 1272 1273 unsigned long width; 1274 unsigned long new_x = style->get_padding(*mfont); 1275 if (static_cast<long>(cursor_pos)-1 >= static_cast<long>(text_pos)) 1276 { 1277 mfont->compute_size(text_,width,height,text_pos,cursor_pos-1); 1278 if (cursor_pos != 0) 1279 new_x += width - mfont->right_overflow(); 1280 } 1281 1282 cursor_x = new_x; 1283 } 1284 else 1285 { 1286 unsigned long height; 1287 unsigned long width; 1288 mfont->compute_size(text_,width,height,text_pos,pos-1); 1289 1290 unsigned long new_x = style->get_padding(*mfont) + 1291 width - mfont->right_overflow(); 1292 1293 // move the text to the left if necessary 1294 if (new_x + 4 > rect.width()) 1295 { 1296 while (new_x > rect.width() - rect.width()/5) 1297 { 1298 new_x -= (*mfont)[text_[text_pos]].width(); 1299 ++text_pos; 1300 } 1301 } 1302 1303 cursor_x = new_x; 1304 cursor_pos = pos; 1305 mfont->compute_size(text_,text_width,height,text_pos); 1306 } 1307 1308 parent.set_im_pos(rect.left()+cursor_x, rect.top()); 1309 1310 if (old_cursor_pos != cursor_pos) 1311 { 1312 if (shift_pos != -1) 1313 { 1314 highlight_start = std::min(shift_pos,cursor_pos); 1315 highlight_end = std::max(shift_pos,cursor_pos)-1; 1316 } 1317 else 1318 { 1319 highlight_start = 0; 1320 highlight_end = -1; 1321 } 1322 1323 if (highlight_start > highlight_end) 1324 on_no_text_selected(); 1325 else 1326 on_text_is_selected(); 1327 1328 recent_movement = true; 1329 cursor_visible = true; 1330 parent.invalidate_rectangle(rect); 1331 } 1332 } 1333 1334 // ---------------------------------------------------------------------------------------- 1335 // ---------------------------------------------------------------------------------------- 1336 // tabbed_display object methods 1337 // ---------------------------------------------------------------------------------------- 1338 // ---------------------------------------------------------------------------------------- 1339 1340 tabbed_display:: tabbed_display(drawable_window & w)1341 tabbed_display( 1342 drawable_window& w 1343 ) : 1344 drawable(w,MOUSE_CLICK), 1345 selected_tab_(0), 1346 left_pad(6), 1347 right_pad(4), 1348 top_pad(3), 1349 bottom_pad(3) 1350 { 1351 rect = rectangle(0,0,40,mfont->height()+top_pad+bottom_pad); 1352 enable_events(); 1353 tabs.set_max_size(1); 1354 tabs.set_size(1); 1355 } 1356 1357 // ---------------------------------------------------------------------------------------- 1358 1359 tabbed_display:: ~tabbed_display()1360 ~tabbed_display( 1361 ) 1362 { 1363 disable_events(); 1364 parent.invalidate_rectangle(rect); 1365 } 1366 1367 // ---------------------------------------------------------------------------------------- 1368 1369 void tabbed_display:: set_pos(long x,long y)1370 set_pos ( 1371 long x, 1372 long y 1373 ) 1374 { 1375 auto_mutex M(m); 1376 // we have to adjust the positions of all the tab rectangles 1377 const long xdelta = rect.left() - x; 1378 const long ydelta = rect.top() - y; 1379 for (unsigned long i = 0; i < tabs.size(); ++i) 1380 { 1381 tabs[i].rect.set_left(tabs[i].rect.left()+xdelta); 1382 tabs[i].rect.set_right(tabs[i].rect.right()+xdelta); 1383 1384 tabs[i].rect.set_top(tabs[i].rect.top()+ydelta); 1385 tabs[i].rect.set_bottom(tabs[i].rect.bottom()+ydelta); 1386 1387 1388 // adjust the position of the group associated with this tab if it exists 1389 if (tabs[i].group) 1390 tabs[i].group->set_pos(x+3, y+mfont->height()+top_pad+bottom_pad+3); 1391 } 1392 drawable::set_pos(x,y); 1393 recompute_tabs(); 1394 } 1395 1396 // ---------------------------------------------------------------------------------------- 1397 1398 void tabbed_display:: fit_to_contents()1399 fit_to_contents ( 1400 ) 1401 { 1402 auto_mutex M(m); 1403 rectangle new_rect; 1404 point p(rect.left(),rect.top()); 1405 new_rect += p; 1406 1407 for (unsigned long i = 0; i < tabs.size(); ++i) 1408 { 1409 if (tabs[i].group) 1410 { 1411 tabs[i].group->fit_to_contents(); 1412 new_rect += tabs[i].group->get_rect(); 1413 } 1414 } 1415 1416 // and give the new rect an additional 4 pixels on the bottom and right sides 1417 // so that the contents to hit the edge of the tabbed display 1418 new_rect = resize_rect(new_rect, new_rect.width()+4, new_rect.height()+4); 1419 1420 parent.invalidate_rectangle(new_rect+rect); 1421 rect = new_rect; 1422 } 1423 1424 // ---------------------------------------------------------------------------------------- 1425 1426 void tabbed_display:: set_size(unsigned long width,unsigned long height)1427 set_size ( 1428 unsigned long width, 1429 unsigned long height 1430 ) 1431 { 1432 auto_mutex M(m); 1433 rectangle old(rect); 1434 const long x = rect.left(); 1435 const long y = rect.top(); 1436 rect.set_right(x+width-1); 1437 rect.set_bottom(y+height-1); 1438 1439 recompute_tabs(); 1440 1441 parent.invalidate_rectangle(rect+old); 1442 } 1443 1444 // ---------------------------------------------------------------------------------------- 1445 1446 void tabbed_display:: set_number_of_tabs(unsigned long num)1447 set_number_of_tabs ( 1448 unsigned long num 1449 ) 1450 { 1451 auto_mutex M(m); 1452 1453 DLIB_ASSERT ( num > 0 , 1454 "\tvoid tabbed_display::set_number_of_tabs()" 1455 << "\n\tnum: " << num ); 1456 1457 tabs.set_max_size(num); 1458 tabs.set_size(num); 1459 1460 selected_tab_ = 0; 1461 1462 recompute_tabs(); 1463 parent.invalidate_rectangle(rect); 1464 } 1465 1466 // ---------------------------------------------------------------------------------------- 1467 1468 unsigned long tabbed_display:: selected_tab() const1469 selected_tab ( 1470 ) const 1471 { 1472 auto_mutex M(m); 1473 return selected_tab_; 1474 } 1475 1476 unsigned long tabbed_display:: number_of_tabs() const1477 number_of_tabs ( 1478 ) const 1479 { 1480 auto_mutex M(m); 1481 return tabs.size(); 1482 } 1483 1484 // ---------------------------------------------------------------------------------------- 1485 1486 const std::string tabbed_display:: tab_name(unsigned long idx) const1487 tab_name ( 1488 unsigned long idx 1489 ) const 1490 { 1491 return convert_wstring_to_mbstring(tab_wname(idx)); 1492 } 1493 1494 const std::wstring tabbed_display:: tab_wname(unsigned long idx) const1495 tab_wname ( 1496 unsigned long idx 1497 ) const 1498 { 1499 return convert_utf32_to_wstring(tab_uname(idx)); 1500 } 1501 1502 const dlib::ustring& tabbed_display:: tab_uname(unsigned long idx) const1503 tab_uname ( 1504 unsigned long idx 1505 ) const 1506 { 1507 auto_mutex M(m); 1508 1509 DLIB_ASSERT ( idx < number_of_tabs() , 1510 "\tvoid tabbed_display::tab_name()" 1511 << "\n\tidx: " << idx 1512 << "\n\tnumber_of_tabs(): " << number_of_tabs() ); 1513 1514 return tabs[idx].name; 1515 } 1516 1517 // ---------------------------------------------------------------------------------------- 1518 1519 void tabbed_display:: set_tab_name(unsigned long idx,const std::string & new_name)1520 set_tab_name ( 1521 unsigned long idx, 1522 const std::string& new_name 1523 ) 1524 { 1525 set_tab_name(idx, convert_mbstring_to_wstring(new_name)); 1526 } 1527 1528 void tabbed_display:: set_tab_name(unsigned long idx,const std::wstring & new_name)1529 set_tab_name ( 1530 unsigned long idx, 1531 const std::wstring& new_name 1532 ) 1533 { 1534 set_tab_name(idx, convert_wstring_to_utf32(new_name)); 1535 } 1536 1537 void tabbed_display:: set_tab_name(unsigned long idx,const dlib::ustring & new_name)1538 set_tab_name ( 1539 unsigned long idx, 1540 const dlib::ustring& new_name 1541 ) 1542 { 1543 auto_mutex M(m); 1544 1545 1546 DLIB_ASSERT ( idx < number_of_tabs() , 1547 "\tvoid tabbed_display::set_tab_name()" 1548 << "\n\tidx: " << idx 1549 << "\n\tnumber_of_tabs(): " << number_of_tabs() ); 1550 1551 1552 tabs[idx].name = new_name; 1553 // do this so that there isn't any reference counting going on 1554 tabs[idx].name[0] = tabs[idx].name[0]; 1555 unsigned long height; 1556 mfont->compute_size(new_name,tabs[idx].width,height); 1557 1558 1559 recompute_tabs(); 1560 1561 parent.invalidate_rectangle(rect); 1562 } 1563 1564 // ---------------------------------------------------------------------------------------- 1565 1566 void tabbed_display:: on_mouse_down(unsigned long btn,unsigned long,long x,long y,bool)1567 on_mouse_down ( 1568 unsigned long btn, 1569 unsigned long, 1570 long x, 1571 long y, 1572 bool 1573 ) 1574 { 1575 if (rect.contains(x,y) && btn == base_window::LEFT && enabled && !hidden) 1576 { 1577 rectangle temp = rect; 1578 const long offset = mfont->height() + bottom_pad + top_pad; 1579 temp.set_bottom(rect.top()+offset); 1580 if (temp.contains(x,y)) 1581 { 1582 // now we have to figure out which tab was clicked 1583 for (unsigned long i = 0; i < tabs.size(); ++i) 1584 { 1585 if (selected_tab_ != i && tabs[i].rect.contains(x,y) && 1586 tabs[selected_tab_].rect.contains(x,y) == false) 1587 { 1588 unsigned long old_idx = selected_tab_; 1589 selected_tab_ = i; 1590 recompute_tabs(); 1591 parent.invalidate_rectangle(temp); 1592 1593 // adjust the widget_group objects for these tabs if they exist 1594 if (tabs[i].group) 1595 tabs[i].group->show(); 1596 if (tabs[old_idx].group) 1597 tabs[old_idx].group->hide(); 1598 1599 if (event_handler.is_set()) 1600 event_handler(i,old_idx); 1601 break; 1602 } 1603 } 1604 } 1605 } 1606 } 1607 1608 // ---------------------------------------------------------------------------------------- 1609 1610 void tabbed_display:: set_tab_group(unsigned long idx,widget_group & group)1611 set_tab_group ( 1612 unsigned long idx, 1613 widget_group& group 1614 ) 1615 { 1616 auto_mutex M(m); 1617 1618 DLIB_ASSERT ( idx < number_of_tabs() , 1619 "\tvoid tabbed_display::set_tab_group()" 1620 << "\n\tidx: " << idx 1621 << "\n\tnumber_of_tabs(): " << number_of_tabs() ); 1622 1623 1624 tabs[idx].group = &group; 1625 group.set_pos(rect.left()+3,rect.top()+mfont->height()+top_pad+bottom_pad+2); 1626 if (idx == selected_tab_) 1627 group.show(); 1628 else 1629 group.hide(); 1630 } 1631 1632 // ---------------------------------------------------------------------------------------- 1633 1634 void tabbed_display:: disable()1635 disable ( 1636 ) 1637 { 1638 auto_mutex M(m); 1639 if (tabs[selected_tab_].group) 1640 tabs[selected_tab_].group->disable(); 1641 drawable::disable(); 1642 } 1643 1644 // ---------------------------------------------------------------------------------------- 1645 1646 void tabbed_display:: enable()1647 enable ( 1648 ) 1649 { 1650 auto_mutex M(m); 1651 if (tabs[selected_tab_].group) 1652 tabs[selected_tab_].group->enable(); 1653 drawable::enable(); 1654 } 1655 1656 // ---------------------------------------------------------------------------------------- 1657 1658 void tabbed_display:: hide()1659 hide ( 1660 ) 1661 { 1662 auto_mutex M(m); 1663 if (tabs[selected_tab_].group) 1664 tabs[selected_tab_].group->hide(); 1665 drawable::hide(); 1666 } 1667 1668 // ---------------------------------------------------------------------------------------- 1669 1670 void tabbed_display:: show()1671 show ( 1672 ) 1673 { 1674 auto_mutex M(m); 1675 if (tabs[selected_tab_].group) 1676 tabs[selected_tab_].group->show(); 1677 drawable::show(); 1678 } 1679 1680 // ---------------------------------------------------------------------------------------- 1681 1682 void tabbed_display:: draw(const canvas & c) const1683 draw ( 1684 const canvas& c 1685 ) const 1686 { 1687 rectangle area = rect.intersect(c); 1688 if (area.is_empty()) 1689 return; 1690 1691 // draw the main border first 1692 rectangle main_box(rect.left(),rect.top()+mfont->height()+top_pad+bottom_pad,rect.right(),rect.bottom()); 1693 draw_button_up(c,main_box); 1694 draw_pixel(c,point(main_box.right()-1,main_box.top()),rgb_pixel(128,128,128)); 1695 1696 rgb_pixel color; 1697 if (enabled) 1698 { 1699 color.red = 0; 1700 color.green = 0; 1701 color.blue = 0; 1702 } 1703 else 1704 { 1705 color.red = 128; 1706 color.green = 128; 1707 color.blue = 128; 1708 } 1709 1710 // draw the tabs 1711 for (unsigned long i = 0; i < tabs.size(); ++i) 1712 { 1713 if (selected_tab_ != i) 1714 draw_tab(tabs[i].rect,c); 1715 1716 // draw the name string 1717 rectangle temp = tabs[i].rect; 1718 temp.set_top(temp.top()+top_pad); 1719 temp.set_bottom(temp.bottom()+bottom_pad); 1720 temp.set_left(temp.left()+left_pad); 1721 temp.set_right(temp.right()+right_pad); 1722 mfont->draw_string(c,temp,tabs[i].name,color); 1723 } 1724 draw_tab(tabs[selected_tab_].rect,c); 1725 draw_line(c, 1726 point(tabs[selected_tab_].rect.left()+1, 1727 tabs[selected_tab_].rect.bottom()), 1728 point(tabs[selected_tab_].rect.right()-2, 1729 tabs[selected_tab_].rect.bottom()), 1730 rgb_pixel(212,208,200)); 1731 } 1732 1733 // ---------------------------------------------------------------------------------------- 1734 1735 void tabbed_display:: draw_tab(const rectangle & tab,const canvas & c) const1736 draw_tab ( 1737 const rectangle& tab, 1738 const canvas& c 1739 ) const 1740 { 1741 const rgb_pixel white(255,255,255); 1742 const rgb_pixel background(212,208,200); 1743 const rgb_pixel dark_gray(64,64,64); 1744 const rgb_pixel gray(128,128,128); 1745 draw_line(c,point(tab.left(),tab.top()+2),point(tab.left(),tab.bottom()),white); 1746 draw_line(c,point(tab.left()+1,tab.top()+2),point(tab.left()+1,tab.bottom()),background); 1747 draw_line(c,point(tab.right(),tab.top()+2),point(tab.right(),tab.bottom()),dark_gray); 1748 draw_line(c,point(tab.right()-1,tab.top()+2),point(tab.right()-1,tab.bottom()),gray); 1749 draw_line(c,point(tab.left()+2,tab.top()),point(tab.right()-2,tab.top()),white); 1750 draw_pixel(c,point(tab.left()+1,tab.top()+1),white); 1751 draw_pixel(c,point(tab.right()-1,tab.top()+1),dark_gray); 1752 } 1753 1754 // ---------------------------------------------------------------------------------------- 1755 1756 void tabbed_display:: set_main_font(const std::shared_ptr<font> & f)1757 set_main_font ( 1758 const std::shared_ptr<font>& f 1759 ) 1760 { 1761 auto_mutex M(m); 1762 mfont = f; 1763 1764 for (unsigned long i = 0; i < tabs.size(); ++i) 1765 { 1766 unsigned long height; 1767 mfont->compute_size(tabs[i].name,tabs[i].width,height); 1768 } 1769 1770 recompute_tabs(); 1771 set_pos(rect.left(), rect.top()); 1772 1773 parent.invalidate_rectangle(rect); 1774 } 1775 1776 // ---------------------------------------------------------------------------------------- 1777 1778 void tabbed_display:: recompute_tabs()1779 recompute_tabs ( 1780 ) 1781 { 1782 const long offset = mfont->height() + bottom_pad + top_pad; 1783 1784 1785 // figure out the size and position of all the tabs 1786 rectangle sel_tab_rect, other_tab; 1787 sel_tab_rect.set_top(rect.top()); 1788 sel_tab_rect.set_bottom(rect.top()+offset); 1789 1790 other_tab.set_top(rect.top()+2); 1791 other_tab.set_bottom(rect.top()+offset-1); 1792 1793 long cur_x = rect.left(); 1794 for (unsigned long i = 0; i < tabs.size(); ++i) 1795 { 1796 const unsigned long str_width = tabs[i].width; 1797 if (selected_tab_ != i) 1798 { 1799 other_tab.set_left(cur_x); 1800 cur_x += left_pad + str_width + right_pad; 1801 other_tab.set_right(cur_x); 1802 tabs[i].rect = other_tab; 1803 ++cur_x; 1804 1805 } 1806 else 1807 { 1808 if (i != 0) 1809 sel_tab_rect.set_left(cur_x-2); 1810 else 1811 sel_tab_rect.set_left(cur_x); 1812 1813 cur_x += left_pad + str_width + right_pad; 1814 1815 if (i != tabs.size()-1) 1816 sel_tab_rect.set_right(cur_x+2); 1817 else 1818 sel_tab_rect.set_right(cur_x); 1819 ++cur_x; 1820 1821 tabs[i].rect = sel_tab_rect; 1822 } 1823 } 1824 1825 // make sure this object is wide enough 1826 const rectangle& last = tabs[tabs.size()-1].rect; 1827 const rectangle& first = tabs[0].rect; 1828 rect = last + rect + first; 1829 1830 } 1831 1832 // ---------------------------------------------------------------------------------------- 1833 // ---------------------------------------------------------------------------------------- 1834 // named_rectangle object methods 1835 // ---------------------------------------------------------------------------------------- 1836 // ---------------------------------------------------------------------------------------- 1837 1838 named_rectangle:: named_rectangle(drawable_window & w)1839 named_rectangle( 1840 drawable_window& w 1841 ) : 1842 drawable(w), 1843 name_width(0), 1844 name_height(0) 1845 { 1846 make_name_fit_in_rect(); 1847 enable_events(); 1848 } 1849 1850 // ---------------------------------------------------------------------------------------- 1851 1852 named_rectangle:: ~named_rectangle()1853 ~named_rectangle( 1854 ) 1855 { 1856 disable_events(); 1857 parent.invalidate_rectangle(rect); 1858 } 1859 1860 // ---------------------------------------------------------------------------------------- 1861 1862 void named_rectangle:: set_size(unsigned long width,unsigned long height)1863 set_size ( 1864 unsigned long width, 1865 unsigned long height 1866 ) 1867 { 1868 auto_mutex M(m); 1869 rectangle old(rect); 1870 const long x = rect.left(); 1871 const long y = rect.top(); 1872 rect.set_right(x+width-1); 1873 rect.set_bottom(y+height-1); 1874 1875 make_name_fit_in_rect(); 1876 parent.invalidate_rectangle(rect+old); 1877 } 1878 1879 // ---------------------------------------------------------------------------------------- 1880 1881 void named_rectangle:: wrap_around(const rectangle & r)1882 wrap_around ( 1883 const rectangle& r 1884 ) 1885 { 1886 auto_mutex M(m); 1887 rectangle old(rect); 1888 const unsigned long pad = name_height/2; 1889 1890 rect = rectangle(r.left()-pad, r.top()-name_height*4/3, r.right()+pad, r.bottom()+pad); 1891 1892 make_name_fit_in_rect(); 1893 parent.invalidate_rectangle(rect+old); 1894 } 1895 1896 // ---------------------------------------------------------------------------------------- 1897 1898 void named_rectangle:: set_main_font(const std::shared_ptr<font> & f)1899 set_main_font ( 1900 const std::shared_ptr<font>& f 1901 ) 1902 { 1903 auto_mutex M(m); 1904 mfont = f; 1905 mfont->compute_size(name_,name_width,name_height); 1906 make_name_fit_in_rect(); 1907 parent.invalidate_rectangle(rect); 1908 } 1909 1910 // ---------------------------------------------------------------------------------------- 1911 1912 void named_rectangle:: make_name_fit_in_rect()1913 make_name_fit_in_rect ( 1914 ) 1915 { 1916 // make sure the named rectangle is big enough to contain the name 1917 const unsigned long wtemp = mfont->height() + name_width; 1918 const unsigned long htemp = mfont->height() + name_height; 1919 if (rect.width() < wtemp) 1920 rect.set_right(rect.left() + wtemp - 1 ); 1921 if (rect.height() < htemp) 1922 rect.set_bottom(rect.bottom() + htemp - 1 ); 1923 } 1924 1925 // ---------------------------------------------------------------------------------------- 1926 1927 void named_rectangle:: set_name(const std::string & name)1928 set_name ( 1929 const std::string& name 1930 ) 1931 { 1932 set_name(convert_mbstring_to_wstring(name)); 1933 } 1934 1935 void named_rectangle:: set_name(const std::wstring & name)1936 set_name ( 1937 const std::wstring& name 1938 ) 1939 { 1940 set_name(convert_wstring_to_utf32(name)); 1941 } 1942 1943 void named_rectangle:: set_name(const dlib::ustring & name)1944 set_name ( 1945 const dlib::ustring& name 1946 ) 1947 { 1948 auto_mutex M(m); 1949 name_ = name.c_str(); 1950 mfont->compute_size(name_,name_width,name_height); 1951 1952 make_name_fit_in_rect(); 1953 parent.invalidate_rectangle(rect); 1954 } 1955 1956 // ---------------------------------------------------------------------------------------- 1957 1958 const std::string named_rectangle:: name() const1959 name ( 1960 ) const 1961 { 1962 return convert_wstring_to_mbstring(wname()); 1963 } 1964 1965 const std::wstring named_rectangle:: wname() const1966 wname ( 1967 ) const 1968 { 1969 return convert_utf32_to_wstring(uname()); 1970 } 1971 1972 const dlib::ustring named_rectangle:: uname() const1973 uname ( 1974 ) const 1975 { 1976 auto_mutex M(m); 1977 return dlib::ustring(name_.c_str()); 1978 } 1979 1980 // ---------------------------------------------------------------------------------------- 1981 1982 void named_rectangle:: draw(const canvas & c) const1983 draw ( 1984 const canvas& c 1985 ) const 1986 { 1987 rectangle area = rect.intersect(c); 1988 if (area.is_empty()) 1989 return; 1990 1991 const unsigned long gap = mfont->height()/2; 1992 rectangle strrect = rect; 1993 strrect.set_left(rect.left() + gap); 1994 1995 const unsigned long rtop = rect.top() + name_height/2; 1996 1997 const rgb_pixel white(255,255,255); 1998 const rgb_pixel gray(128,128,128); 1999 2000 mfont->draw_string(c,strrect,name_); 2001 draw_line(c,point(rect.left(), rtop), 2002 point(rect.left()+gap/2, rtop), gray); 2003 draw_line(c,point(rect.left(), rtop), 2004 point(rect.left(), rect.bottom()-1), gray); 2005 draw_line(c,point(rect.left(), rect.bottom()-1), 2006 point(rect.right()-1, rect.bottom()-1), gray); 2007 draw_line(c,point(rect.right()-1, rtop), 2008 point(rect.right()-1, rect.bottom()-2), gray); 2009 draw_line(c,point(strrect.left() + name_width + 2, rtop), 2010 point(rect.right()-1, rtop), gray); 2011 2012 draw_line(c,point(strrect.left() + name_width + 2, rtop+1), 2013 point( rect.right()-2, rtop+1), white); 2014 draw_line(c,point(rect.right(), rtop), 2015 point(rect.right(), rect.bottom()), white); 2016 draw_line(c,point(rect.left(), rect.bottom()), 2017 point(rect.right(), rect.bottom()), white); 2018 draw_line(c,point(rect.left()+1, rtop+1), 2019 point(rect.left()+1, rect.bottom()-2), white); 2020 draw_line(c,point(rect.left()+1, rtop+1), 2021 point(rect.left()+gap/2, rtop+1), white); 2022 } 2023 2024 // ---------------------------------------------------------------------------------------- 2025 // ---------------------------------------------------------------------------------------- 2026 // class mouse_tracker 2027 // ---------------------------------------------------------------------------------------- 2028 // ---------------------------------------------------------------------------------------- 2029 2030 mouse_tracker:: mouse_tracker(drawable_window & w)2031 mouse_tracker( 2032 drawable_window& w 2033 ) : 2034 draggable(w), 2035 offset(18), 2036 nr(w), 2037 x_label(w), 2038 y_label(w), 2039 click_x(-1), 2040 click_y(-1) 2041 { 2042 set_draggable_area(rectangle(0,0,500,500)); 2043 2044 2045 x_label.set_text("x: "); 2046 y_label.set_text("y: "); 2047 nr.set_name("mouse position"); 2048 2049 2050 x_label.set_pos(offset,offset); 2051 y_label.set_pos(x_label.get_rect().left(), x_label.get_rect().bottom()+3); 2052 2053 nr.wrap_around(x_label.get_rect() + y_label.get_rect()); 2054 rect = nr.get_rect(); 2055 2056 set_z_order(2000000000); 2057 x_label.set_z_order(2000000001); 2058 y_label.set_z_order(2000000001); 2059 nr.set_z_order(2000000001); 2060 2061 enable_events(); 2062 } 2063 2064 // ---------------------------------------------------------------------------------------- 2065 2066 mouse_tracker:: ~mouse_tracker()2067 ~mouse_tracker( 2068 ) 2069 { 2070 disable_events(); 2071 parent.invalidate_rectangle(rect); 2072 } 2073 2074 // ---------------------------------------------------------------------------------------- 2075 2076 void mouse_tracker:: set_main_font(const std::shared_ptr<font> & f)2077 set_main_font ( 2078 const std::shared_ptr<font>& f 2079 ) 2080 { 2081 auto_mutex M(m); 2082 nr.set_main_font(f); 2083 x_label.set_main_font(f); 2084 y_label.set_main_font(f); 2085 mfont = f; 2086 nr.wrap_around(x_label.get_rect() + y_label.get_rect()); 2087 rect = nr.get_rect(); 2088 } 2089 2090 // ---------------------------------------------------------------------------------------- 2091 2092 void mouse_tracker:: set_pos(long x,long y)2093 set_pos ( 2094 long x, 2095 long y 2096 ) 2097 { 2098 draggable::set_pos(x,y); 2099 nr.set_pos(x,y); 2100 x_label.set_pos(rect.left()+offset,rect.top()+offset); 2101 y_label.set_pos(x_label.get_rect().left(), x_label.get_rect().bottom()+3); 2102 } 2103 2104 // ---------------------------------------------------------------------------------------- 2105 2106 void mouse_tracker:: show()2107 show ( 2108 ) 2109 { 2110 draggable::show(); 2111 nr.show(); 2112 x_label.show(); 2113 y_label.show(); 2114 } 2115 2116 // ---------------------------------------------------------------------------------------- 2117 2118 void mouse_tracker:: hide()2119 hide ( 2120 ) 2121 { 2122 draggable::hide(); 2123 nr.hide(); 2124 x_label.hide(); 2125 y_label.hide(); 2126 } 2127 2128 // ---------------------------------------------------------------------------------------- 2129 2130 void mouse_tracker:: enable()2131 enable ( 2132 ) 2133 { 2134 draggable::enable(); 2135 nr.enable(); 2136 x_label.enable(); 2137 y_label.enable(); 2138 } 2139 2140 // ---------------------------------------------------------------------------------------- 2141 2142 void mouse_tracker:: disable()2143 disable ( 2144 ) 2145 { 2146 draggable::disable(); 2147 nr.disable(); 2148 x_label.disable(); 2149 y_label.disable(); 2150 } 2151 2152 // ---------------------------------------------------------------------------------------- 2153 2154 void mouse_tracker:: on_mouse_down(unsigned long btn,unsigned long state,long x,long y,bool double_clicked)2155 on_mouse_down ( 2156 unsigned long btn, 2157 unsigned long state, 2158 long x, 2159 long y, 2160 bool double_clicked 2161 ) 2162 { 2163 draggable::on_mouse_down(btn,state,x,y,double_clicked); 2164 if ((state & base_window::SHIFT) && (btn == base_window::LEFT) && enabled && !hidden) 2165 { 2166 parent.invalidate_rectangle(rectangle(x,y,x,y)); 2167 parent.invalidate_rectangle(rectangle(click_x,click_y,click_x,click_y)); 2168 click_x = x; 2169 click_y = y; 2170 2171 y_label.set_text("y: 0"); 2172 x_label.set_text("x: 0"); 2173 } 2174 } 2175 2176 // ---------------------------------------------------------------------------------------- 2177 2178 void mouse_tracker:: on_mouse_move(unsigned long state,long x,long y)2179 on_mouse_move ( 2180 unsigned long state, 2181 long x, 2182 long y 2183 ) 2184 { 2185 if (!hidden && enabled) 2186 { 2187 parent.invalidate_rectangle(rect); 2188 draggable::on_mouse_move(state,x,y); 2189 2190 long dx = 0; 2191 long dy = 0; 2192 if (click_x != -1) 2193 dx = click_x; 2194 if (click_y != -1) 2195 dy = click_y; 2196 2197 sout.str(""); 2198 sout << "y: " << y - dy; 2199 y_label.set_text(sout.str()); 2200 2201 sout.str(""); 2202 sout << "x: " << x - dx; 2203 x_label.set_text(sout.str()); 2204 } 2205 } 2206 2207 // ---------------------------------------------------------------------------------------- 2208 2209 void mouse_tracker:: on_drag()2210 on_drag ( 2211 ) 2212 { 2213 nr.set_pos(rect.left(),rect.top()); 2214 x_label.set_pos(rect.left()+offset,rect.top()+offset); 2215 y_label.set_pos(x_label.get_rect().left(), x_label.get_rect().bottom()+3); 2216 2217 long x = 0; 2218 long y = 0; 2219 if (click_x != -1) 2220 x = click_x; 2221 if (click_y != -1) 2222 y = click_y; 2223 2224 sout.str(""); 2225 sout << "y: " << lasty - y; 2226 y_label.set_text(sout.str()); 2227 2228 sout.str(""); 2229 sout << "x: " << lastx - x; 2230 x_label.set_text(sout.str()); 2231 } 2232 2233 // ---------------------------------------------------------------------------------------- 2234 2235 void mouse_tracker:: draw(const canvas & c) const2236 draw ( 2237 const canvas& c 2238 ) const 2239 { 2240 fill_rect(c, rect,rgb_pixel(212,208,200)); 2241 draw_pixel(c, point(click_x,click_y),rgb_pixel(255,0,0)); 2242 } 2243 2244 // ---------------------------------------------------------------------------------------- 2245 // ---------------------------------------------------------------------------------------- 2246 // class list_box 2247 // ---------------------------------------------------------------------------------------- 2248 // ---------------------------------------------------------------------------------------- 2249 2250 namespace list_box_helper{ 2251 template <typename S> 2252 list_box<S>:: list_box(drawable_window & w)2253 list_box( 2254 drawable_window& w 2255 ) : 2256 scrollable_region(w,MOUSE_WHEEL|MOUSE_CLICK), 2257 ms_enabled(false), 2258 last_selected(0) 2259 { 2260 set_vertical_scroll_increment(mfont->height()); 2261 set_horizontal_scroll_increment(mfont->height()); 2262 2263 style.reset(new list_box_style_default()); 2264 enable_events(); 2265 } 2266 2267 // ---------------------------------------------------------------------------------------- 2268 2269 template <typename S> 2270 list_box<S>:: ~list_box()2271 ~list_box( 2272 ) 2273 { 2274 disable_events(); 2275 parent.invalidate_rectangle(rect); 2276 } 2277 2278 // ---------------------------------------------------------------------------------------- 2279 2280 template <typename S> 2281 void list_box<S>:: set_main_font(const std::shared_ptr<font> & f)2282 set_main_font ( 2283 const std::shared_ptr<font>& f 2284 ) 2285 { 2286 auto_mutex M(m); 2287 mfont = f; 2288 // recompute the sizes of all the items 2289 for (unsigned long i = 0; i < items.size(); ++i) 2290 { 2291 mfont->compute_size(items[i].name,items[i].width, items[i].height); 2292 } 2293 set_vertical_scroll_increment(mfont->height()); 2294 parent.invalidate_rectangle(rect); 2295 } 2296 2297 // ---------------------------------------------------------------------------------------- 2298 2299 template <typename S> 2300 bool list_box<S>:: is_selected(unsigned long index) const2301 is_selected ( 2302 unsigned long index 2303 ) const 2304 { 2305 auto_mutex M(m); 2306 DLIB_ASSERT ( index < size() , 2307 "\tbool list_box::is_selected(index)" 2308 << "\n\tindex: " << index 2309 << "\n\tsize(): " << size() ); 2310 2311 return items[index].is_selected; 2312 } 2313 2314 // ---------------------------------------------------------------------------------------- 2315 2316 template <typename S> 2317 void list_box<S>:: select(unsigned long index)2318 select ( 2319 unsigned long index 2320 ) 2321 { 2322 auto_mutex M(m); 2323 DLIB_ASSERT ( index < size() , 2324 "\tvoid list_box::select(index)" 2325 << "\n\tindex: " << index 2326 << "\n\tsize(): " << size() ); 2327 2328 last_selected = index; 2329 items[index].is_selected = true; 2330 parent.invalidate_rectangle(rect); 2331 } 2332 2333 // ---------------------------------------------------------------------------------------- 2334 2335 template <typename S> 2336 void list_box<S>:: unselect(unsigned long index)2337 unselect ( 2338 unsigned long index 2339 ) 2340 { 2341 auto_mutex M(m); 2342 DLIB_ASSERT ( index < size() , 2343 "\tvoid list_box::unselect(index)" 2344 << "\n\tindex: " << index 2345 << "\n\tsize(): " << size() ); 2346 items[index].is_selected = false; 2347 parent.invalidate_rectangle(rect); 2348 } 2349 2350 // ---------------------------------------------------------------------------------------- 2351 2352 template <typename S> operator [](unsigned long index) const2353 const S& list_box<S>::operator [] ( 2354 unsigned long index 2355 ) const 2356 { 2357 auto_mutex M(m); 2358 DLIB_ASSERT ( index < size() , 2359 "\tconst std::string& list_box::operator[](index)" 2360 << "\n\tindex: " << index 2361 << "\n\tsize(): " << size() ); 2362 return items[index].name; 2363 } 2364 2365 // ---------------------------------------------------------------------------------------- 2366 2367 template <typename S> 2368 bool list_box<S>:: multiple_select_enabled() const2369 multiple_select_enabled ( 2370 ) const 2371 { 2372 auto_mutex M(m); 2373 return ms_enabled; 2374 } 2375 2376 // ---------------------------------------------------------------------------------------- 2377 2378 template <typename S> 2379 void list_box<S>:: enable_multiple_select()2380 enable_multiple_select ( 2381 ) 2382 { 2383 auto_mutex M(m); 2384 ms_enabled = true; 2385 } 2386 2387 // ---------------------------------------------------------------------------------------- 2388 2389 template <typename S> 2390 void list_box<S>:: disable_multiple_select()2391 disable_multiple_select ( 2392 ) 2393 { 2394 auto_mutex M(m); 2395 ms_enabled = false; 2396 } 2397 2398 // ---------------------------------------------------------------------------------------- 2399 2400 template <typename S> 2401 bool list_box<S>:: at_start() const2402 at_start ( 2403 ) const 2404 { 2405 auto_mutex M(m); 2406 return items.at_start(); 2407 } 2408 2409 // ---------------------------------------------------------------------------------------- 2410 2411 template <typename S> 2412 void list_box<S>:: reset() const2413 reset ( 2414 ) const 2415 { 2416 auto_mutex M(m); 2417 items.reset(); 2418 } 2419 2420 // ---------------------------------------------------------------------------------------- 2421 2422 template <typename S> 2423 bool list_box<S>:: current_element_valid() const2424 current_element_valid ( 2425 ) const 2426 { 2427 auto_mutex M(m); 2428 return items.current_element_valid(); 2429 } 2430 2431 // ---------------------------------------------------------------------------------------- 2432 2433 template <typename S> 2434 const S &list_box<S>:: element() const2435 element ( 2436 ) const 2437 { 2438 auto_mutex M(m); 2439 DLIB_ASSERT ( current_element_valid() , 2440 "\tconst std::string& list_box::element()" 2441 ); 2442 return items.element().name; 2443 } 2444 2445 // ---------------------------------------------------------------------------------------- 2446 2447 template <typename S> 2448 const S &list_box<S>:: element()2449 element ( 2450 ) 2451 { 2452 auto_mutex M(m); 2453 DLIB_ASSERT ( current_element_valid() , 2454 "\tconst std::string& list_box::element()" 2455 ); 2456 return items.element().name; 2457 } 2458 2459 // ---------------------------------------------------------------------------------------- 2460 2461 template <typename S> 2462 bool list_box<S>:: move_next() const2463 move_next ( 2464 ) const 2465 { 2466 auto_mutex M(m); 2467 return items.move_next(); 2468 } 2469 2470 // ---------------------------------------------------------------------------------------- 2471 2472 template <typename S> 2473 size_t list_box<S>:: size() const2474 size ( 2475 ) const 2476 { 2477 auto_mutex M(m); 2478 return items.size(); 2479 } 2480 2481 // ---------------------------------------------------------------------------------------- 2482 2483 template <typename S> 2484 void list_box<S>:: draw(const canvas & c) const2485 draw ( 2486 const canvas& c 2487 ) const 2488 { 2489 scrollable_region::draw(c); 2490 2491 rectangle area = display_rect().intersect(c); 2492 if (area.is_empty()) 2493 return; 2494 2495 style->draw_list_box_background(c, display_rect(), enabled); 2496 2497 long y = total_rect().top(); 2498 for (unsigned long i = 0; i < items.size(); ++i) 2499 { 2500 if (y+(long)items[i].height <= area.top()) 2501 { 2502 y += items[i].height; 2503 continue; 2504 } 2505 2506 rectangle r(total_rect().left(), y, display_rect().right(), y+items[i].height-1); 2507 2508 style->draw_list_box_item(c,r, display_rect(), enabled, *mfont, items[i].name, items[i].is_selected); 2509 2510 2511 y += items[i].height; 2512 2513 if (y > area.bottom()) 2514 break; 2515 } 2516 } 2517 2518 // ---------------------------------------------------------------------------------------- 2519 2520 template <typename S> 2521 void list_box<S>:: on_mouse_down(unsigned long btn,unsigned long state,long x,long y,bool is_double_click)2522 on_mouse_down ( 2523 unsigned long btn, 2524 unsigned long state, 2525 long x, 2526 long y, 2527 bool is_double_click 2528 ) 2529 { 2530 if (display_rect().contains(x,y) && btn == base_window::LEFT && enabled && !hidden ) 2531 { 2532 if ( ms_enabled == false || 2533 ((!(state&base_window::CONTROL)) && !(state&base_window::SHIFT))) 2534 { 2535 items.reset(); 2536 while (items.move_next()) 2537 { 2538 items.element().is_selected = false; 2539 } 2540 } 2541 2542 y -= total_rect().top(); 2543 long h = 0; 2544 for (unsigned long i = 0; i < items.size(); ++i) 2545 { 2546 h += items[i].height; 2547 if (h >= y) 2548 { 2549 if (ms_enabled) 2550 { 2551 if (state&base_window::CONTROL) 2552 { 2553 items[i].is_selected = !items[i].is_selected; 2554 if (items[i].is_selected) 2555 last_selected = i; 2556 } 2557 else if (state&base_window::SHIFT) 2558 { 2559 // we want to select everything between (and including) the 2560 // current thing clicked and last_selected. 2561 const unsigned long first = std::min(i,last_selected); 2562 const unsigned long last = std::max(i,last_selected); 2563 for (unsigned long j = first; j <= last; ++j) 2564 items[j].is_selected = true; 2565 } 2566 else 2567 { 2568 items[i].is_selected = true; 2569 last_selected = i; 2570 if (is_double_click && event_handler.is_set()) 2571 event_handler(i); 2572 else if (single_click_event_handler.is_set()) 2573 single_click_event_handler(i); 2574 } 2575 } 2576 else 2577 { 2578 items[i].is_selected = true; 2579 last_selected = i; 2580 if (is_double_click && event_handler.is_set()) 2581 event_handler(i); 2582 else if (single_click_event_handler.is_set()) 2583 single_click_event_handler(i); 2584 } 2585 2586 break; 2587 } 2588 } 2589 2590 parent.invalidate_rectangle(rect); 2591 } 2592 } 2593 2594 // ---------------------------------------------------------------------------------------- 2595 2596 template <typename S> 2597 unsigned long list_box<S>:: get_selected() const2598 get_selected ( 2599 ) const 2600 { 2601 auto_mutex M(m); 2602 DLIB_ASSERT ( multiple_select_enabled() == false, 2603 "\tunsigned long list_box::get_selected()" 2604 ); 2605 for (unsigned long i = 0; i < items.size(); ++i) 2606 { 2607 if (items[i].is_selected) 2608 return i; 2609 } 2610 return items.size(); 2611 } 2612 // ---------------------------------------------------------------------------------------- 2613 2614 // making instance of template 2615 template class list_box<std::string>; 2616 template class list_box<std::wstring>; 2617 template class list_box<dlib::ustring>; 2618 } 2619 2620 // ---------------------------------------------------------------------------------------- 2621 // ---------------------------------------------------------------------------------------- 2622 // function message_box() 2623 // ---------------------------------------------------------------------------------------- 2624 // ---------------------------------------------------------------------------------------- 2625 2626 namespace message_box_helper 2627 { 2628 void box_win:: initialize()2629 initialize ( 2630 ) 2631 { 2632 msg.set_pos(20,20); 2633 msg.set_text(message); 2634 rectangle msg_rect = msg.get_rect(); 2635 btn_ok.set_name("OK"); 2636 btn_ok.set_size(60,btn_ok.height()); 2637 if (msg_rect.width() >= 60) 2638 btn_ok.set_pos(msg_rect.width()/2+msg_rect.left()-btn_ok.width()/2,msg_rect.bottom()+15); 2639 else 2640 btn_ok.set_pos(20,msg_rect.bottom()+15); 2641 btn_ok.set_click_handler(*this,&box_win::on_click); 2642 2643 rectangle size = btn_ok.get_rect() + msg_rect; 2644 set_size(size.right()+20,size.bottom()+20); 2645 2646 2647 show(); 2648 set_title(title); 2649 } 2650 2651 // ------------------------------------------------------------------------------------ 2652 2653 box_win:: box_win(const std::string & title_,const std::string & message_)2654 box_win ( 2655 const std::string& title_, 2656 const std::string& message_ 2657 ) : 2658 drawable_window(false), 2659 title(convert_mbstring_to_wstring(title_)), 2660 message(convert_mbstring_to_wstring(message_)), 2661 msg(*this), 2662 btn_ok(*this) 2663 { 2664 initialize(); 2665 } 2666 2667 // ------------------------------------------------------------------------------------ 2668 2669 box_win:: box_win(const std::wstring & title_,const std::wstring & message_)2670 box_win ( 2671 const std::wstring& title_, 2672 const std::wstring& message_ 2673 ) : 2674 drawable_window(false), 2675 title(title_), 2676 message(message_), 2677 msg(*this), 2678 btn_ok(*this) 2679 { 2680 initialize(); 2681 } 2682 2683 // ------------------------------------------------------------------------------------ 2684 2685 box_win:: box_win(const dlib::ustring & title_,const dlib::ustring & message_)2686 box_win ( 2687 const dlib::ustring& title_, 2688 const dlib::ustring& message_ 2689 ) : 2690 drawable_window(false), 2691 title(convert_utf32_to_wstring(title_)), 2692 message(convert_utf32_to_wstring(message_)), 2693 msg(*this), 2694 btn_ok(*this) 2695 { 2696 initialize(); 2697 } 2698 2699 // ------------------------------------------------------------------------------------ 2700 2701 box_win:: ~box_win()2702 ~box_win ( 2703 ) 2704 { 2705 close_window(); 2706 } 2707 2708 // ------------------------------------------------------------------------------------ 2709 2710 void box_win:: deleter_thread(void * param)2711 deleter_thread ( 2712 void* param 2713 ) 2714 { 2715 // The point of this extra event_handler stuff is to allow the user 2716 // to end the program from within the callback. So we want to destroy the 2717 // window *before* we call their callback. 2718 box_win& w = *static_cast<box_win*>(param); 2719 w.close_window(); 2720 any_function<void()> event_handler(w.event_handler); 2721 delete &w; 2722 if (event_handler.is_set()) 2723 event_handler(); 2724 } 2725 2726 // ------------------------------------------------------------------------------------ 2727 2728 void box_win:: on_click()2729 on_click ( 2730 ) 2731 { 2732 hide(); 2733 create_new_thread(&deleter_thread,this); 2734 } 2735 2736 // ------------------------------------------------------------------------------------ 2737 2738 base_window::on_close_return_code box_win:: on_window_close()2739 on_window_close ( 2740 ) 2741 { 2742 // The point of this extra event_handler stuff is to allow the user 2743 // to end the program within the callback. So we want to destroy the 2744 // window *before* we call their callback. 2745 any_function<void()> event_handler_copy(event_handler); 2746 delete this; 2747 if (event_handler_copy.is_set()) 2748 event_handler_copy(); 2749 return CLOSE_WINDOW; 2750 } 2751 2752 // ------------------------------------------------------------------------------------ 2753 // ------------------------------------------------------------------------------------ 2754 // ------------------------------------------------------------------------------------ 2755 2756 void blocking_box_win:: initialize()2757 initialize ( 2758 ) 2759 { 2760 msg.set_pos(20,20); 2761 msg.set_text(message); 2762 rectangle msg_rect = msg.get_rect(); 2763 btn_ok.set_name("OK"); 2764 btn_ok.set_size(60,btn_ok.height()); 2765 if (msg_rect.width() >= 60) 2766 btn_ok.set_pos(msg_rect.width()/2+msg_rect.left()-btn_ok.width()/2,msg_rect.bottom()+15); 2767 else 2768 btn_ok.set_pos(20,msg_rect.bottom()+15); 2769 btn_ok.set_click_handler(*this,&blocking_box_win::on_click); 2770 2771 rectangle size = btn_ok.get_rect() + msg_rect; 2772 set_size(size.right()+20,size.bottom()+20); 2773 2774 2775 set_title(title); 2776 show(); 2777 } 2778 2779 // ------------------------------------------------------------------------------------ 2780 2781 blocking_box_win:: blocking_box_win(const std::string & title_,const std::string & message_)2782 blocking_box_win ( 2783 const std::string& title_, 2784 const std::string& message_ 2785 ) : 2786 drawable_window(false), 2787 title(convert_mbstring_to_wstring(title_)), 2788 message(convert_mbstring_to_wstring(message_)), 2789 msg(*this), 2790 btn_ok(*this) 2791 { 2792 initialize(); 2793 } 2794 2795 // ------------------------------------------------------------------------------------ 2796 2797 blocking_box_win:: blocking_box_win(const std::wstring & title_,const std::wstring & message_)2798 blocking_box_win ( 2799 const std::wstring& title_, 2800 const std::wstring& message_ 2801 ) : 2802 drawable_window(false), 2803 title(title_), 2804 message(message_), 2805 msg(*this), 2806 btn_ok(*this) 2807 { 2808 initialize(); 2809 } 2810 2811 // ------------------------------------------------------------------------------------ 2812 2813 blocking_box_win:: blocking_box_win(const dlib::ustring & title_,const dlib::ustring & message_)2814 blocking_box_win ( 2815 const dlib::ustring& title_, 2816 const dlib::ustring& message_ 2817 ) : 2818 drawable_window(false), 2819 title(convert_utf32_to_wstring(title_)), 2820 message(convert_utf32_to_wstring(message_)), 2821 msg(*this), 2822 btn_ok(*this) 2823 { 2824 initialize(); 2825 } 2826 2827 // ------------------------------------------------------------------------------------ 2828 2829 blocking_box_win:: ~blocking_box_win()2830 ~blocking_box_win ( 2831 ) 2832 { 2833 close_window(); 2834 } 2835 2836 // ------------------------------------------------------------------------------------ 2837 2838 void blocking_box_win:: on_click()2839 on_click ( 2840 ) 2841 { 2842 close_window(); 2843 } 2844 2845 } 2846 2847 // ---------------------------------------------------------------------------------------- 2848 // ---------------------------------------------------------------------------------------- 2849 // function open_file_box() 2850 // ---------------------------------------------------------------------------------------- 2851 // ---------------------------------------------------------------------------------------- 2852 2853 namespace open_file_box_helper 2854 { 2855 box_win:: box_win(const std::string & title,bool has_text_field)2856 box_win ( 2857 const std::string& title, 2858 bool has_text_field 2859 ) : 2860 lbl_dirs(*this), 2861 lbl_files(*this), 2862 lbl_file_name(*this), 2863 lb_dirs(*this), 2864 lb_files(*this), 2865 btn_ok(*this), 2866 btn_cancel(*this), 2867 btn_root(*this), 2868 tf_file_name(*this) 2869 { 2870 if (has_text_field == false) 2871 { 2872 tf_file_name.hide(); 2873 lbl_file_name.hide(); 2874 } 2875 else 2876 { 2877 lbl_file_name.set_text("File: "); 2878 } 2879 2880 cur_dir = -1; 2881 set_size(500,300); 2882 2883 lbl_dirs.set_text("Directories:"); 2884 lbl_files.set_text("Files:"); 2885 btn_ok.set_name("Ok"); 2886 btn_cancel.set_name("Cancel"); 2887 btn_root.set_name("/"); 2888 2889 btn_root.set_click_handler(*this,&box_win::on_root_click); 2890 btn_cancel.set_click_handler(*this,&box_win::on_cancel_click); 2891 btn_ok.set_click_handler(*this,&box_win::on_open_click); 2892 lb_dirs.set_double_click_handler(*this,&box_win::on_dirs_click); 2893 lb_files.set_click_handler(*this,&box_win::on_files_click); 2894 lb_files.set_double_click_handler(*this,&box_win::on_files_double_click); 2895 2896 2897 btn_root.set_pos(5,5); 2898 2899 set_sizes(); 2900 set_title(title); 2901 2902 on_root_click(); 2903 2904 // make it so that the file box starts out in our current working 2905 // directory 2906 std::string full_name(get_current_dir()); 2907 2908 while (full_name.size() > 0) 2909 { 2910 std::string::size_type pos = full_name.find_first_of("\\/"); 2911 std::string left(full_name.substr(0,pos)); 2912 if (pos != std::string::npos) 2913 full_name = full_name.substr(pos+1); 2914 else 2915 full_name.clear(); 2916 2917 if (left.size() > 0) 2918 enter_folder(left); 2919 } 2920 2921 2922 show(); 2923 } 2924 2925 // ------------------------------------------------------------------------------------ 2926 2927 box_win:: ~box_win()2928 ~box_win ( 2929 ) 2930 { 2931 close_window(); 2932 } 2933 2934 // ------------------------------------------------------------------------------------ 2935 2936 void box_win:: set_sizes()2937 set_sizes( 2938 ) 2939 { 2940 unsigned long width, height; 2941 get_size(width,height); 2942 2943 2944 if (lbl_file_name.is_hidden()) 2945 { 2946 lbl_dirs.set_pos(0,btn_root.bottom()+5); 2947 lb_dirs.set_pos(0,lbl_dirs.bottom()); 2948 lb_dirs.set_size(width/2,height-lb_dirs.top()-btn_cancel.height()-10); 2949 2950 lbl_files.set_pos(lb_dirs.right(),btn_root.bottom()+5); 2951 lb_files.set_pos(lb_dirs.right(),lbl_files.bottom()); 2952 lb_files.set_size(width-lb_files.left(),height-lb_files.top()-btn_cancel.height()-10); 2953 2954 btn_ok.set_pos(width - btn_ok.width()-25,lb_files.bottom()+5); 2955 btn_cancel.set_pos(btn_ok.left() - btn_cancel.width()-5,lb_files.bottom()+5); 2956 } 2957 else 2958 { 2959 2960 lbl_dirs.set_pos(0,btn_root.bottom()+5); 2961 lb_dirs.set_pos(0,lbl_dirs.bottom()); 2962 lb_dirs.set_size(width/2,height-lb_dirs.top()-btn_cancel.height()-10-tf_file_name.height()); 2963 2964 lbl_files.set_pos(lb_dirs.right(),btn_root.bottom()+5); 2965 lb_files.set_pos(lb_dirs.right(),lbl_files.bottom()); 2966 lb_files.set_size(width-lb_files.left(),height-lb_files.top()-btn_cancel.height()-10-tf_file_name.height()); 2967 2968 lbl_file_name.set_pos(lb_files.left(), lb_files.bottom()+8); 2969 tf_file_name.set_pos(lbl_file_name.right(), lb_files.bottom()+5); 2970 tf_file_name.set_width(width-tf_file_name.left()-5); 2971 2972 btn_ok.set_pos(width - btn_ok.width()-25,tf_file_name.bottom()+5); 2973 btn_cancel.set_pos(btn_ok.left() - btn_cancel.width()-5,tf_file_name.bottom()+5); 2974 } 2975 2976 } 2977 2978 // ------------------------------------------------------------------------------------ 2979 2980 void box_win:: on_window_resized()2981 on_window_resized ( 2982 ) 2983 { 2984 set_sizes(); 2985 } 2986 2987 // ------------------------------------------------------------------------------------ 2988 2989 void box_win:: deleter_thread()2990 deleter_thread ( 2991 ) 2992 { 2993 close_window(); 2994 delete this; 2995 } 2996 2997 // ------------------------------------------------------------------------------------ 2998 2999 void box_win:: enter_folder(const std::string & folder_name)3000 enter_folder ( 3001 const std::string& folder_name 3002 ) 3003 { 3004 if (btn_root.is_checked()) 3005 btn_root.set_unchecked(); 3006 if (cur_dir != -1) 3007 sob[cur_dir]->set_unchecked(); 3008 3009 3010 const std::string old_path = path; 3011 const long old_cur_dir = cur_dir; 3012 3013 std::unique_ptr<toggle_button> new_btn(new toggle_button(*this)); 3014 new_btn->set_name(folder_name); 3015 new_btn->set_click_handler(*this,&box_win::on_path_button_click); 3016 3017 // remove any path buttons that won't be part of the path anymore 3018 if (sob.size()) 3019 { 3020 while (sob.size() > (unsigned long)(cur_dir+1)) 3021 { 3022 std::unique_ptr<toggle_button> junk; 3023 sob.remove(cur_dir+1,junk); 3024 } 3025 } 3026 3027 if (sob.size()) 3028 new_btn->set_pos(sob[sob.size()-1]->right()+5,sob[sob.size()-1]->top()); 3029 else 3030 new_btn->set_pos(btn_root.right()+5,btn_root.top()); 3031 3032 cur_dir = sob.size(); 3033 sob.add(sob.size(),new_btn); 3034 3035 path += folder_name + directory::get_separator(); 3036 if (set_dir(prefix + path) == false) 3037 { 3038 sob.remove(sob.size()-1,new_btn); 3039 path = old_path; 3040 cur_dir = old_cur_dir; 3041 } 3042 else 3043 { 3044 3045 sob[cur_dir]->set_checked(); 3046 } 3047 } 3048 3049 // ------------------------------------------------------------------------------------ 3050 3051 void box_win:: on_dirs_click(unsigned long idx)3052 on_dirs_click ( 3053 unsigned long idx 3054 ) 3055 { 3056 enter_folder(lb_dirs[idx]); 3057 } 3058 3059 // ------------------------------------------------------------------------------------ 3060 3061 void box_win:: on_files_click(unsigned long idx)3062 on_files_click ( 3063 unsigned long idx 3064 ) 3065 { 3066 if (tf_file_name.is_hidden() == false) 3067 { 3068 tf_file_name.set_text(lb_files[idx]); 3069 } 3070 } 3071 3072 // ------------------------------------------------------------------------------------ 3073 3074 void box_win:: on_files_double_click(unsigned long)3075 on_files_double_click ( 3076 unsigned long 3077 ) 3078 { 3079 on_open_click(); 3080 } 3081 3082 // ------------------------------------------------------------------------------------ 3083 3084 void box_win:: on_cancel_click()3085 on_cancel_click ( 3086 ) 3087 { 3088 hide(); 3089 create_new_thread<box_win,&box_win::deleter_thread>(*this); 3090 } 3091 3092 // ------------------------------------------------------------------------------------ 3093 3094 void box_win:: on_open_click()3095 on_open_click ( 3096 ) 3097 { 3098 if (lb_files.get_selected() != lb_files.size() || tf_file_name.text().size() > 0) 3099 { 3100 if (event_handler.is_set()) 3101 { 3102 if (tf_file_name.is_hidden()) 3103 event_handler(prefix + path + lb_files[lb_files.get_selected()]); 3104 else if (tf_file_name.text().size() > 0) 3105 event_handler(prefix + path + tf_file_name.text()); 3106 } 3107 hide(); 3108 create_new_thread<box_win,&box_win::deleter_thread>(*this); 3109 } 3110 } 3111 3112 // ------------------------------------------------------------------------------------ 3113 3114 void box_win:: on_path_button_click(toggle_button & btn)3115 on_path_button_click ( 3116 toggle_button& btn 3117 ) 3118 { 3119 if (btn_root.is_checked()) 3120 btn_root.set_unchecked(); 3121 if (cur_dir != -1) 3122 sob[cur_dir]->set_unchecked(); 3123 std::string new_path; 3124 3125 for (unsigned long i = 0; i < sob.size(); ++i) 3126 { 3127 new_path += sob[i]->name() + directory::get_separator(); 3128 if (sob[i].get() == &btn) 3129 { 3130 cur_dir = i; 3131 sob[i]->set_checked(); 3132 break; 3133 } 3134 } 3135 if (path != new_path) 3136 { 3137 path = new_path; 3138 set_dir(prefix+path); 3139 } 3140 } 3141 3142 // ------------------------------------------------------------------------------------ 3143 3144 struct case_insensitive_compare 3145 { operator ()dlib::open_file_box_helper::case_insensitive_compare3146 bool operator() ( 3147 const std::string& a, 3148 const std::string& b 3149 ) const 3150 { 3151 std::string::size_type i, size; 3152 size = std::min(a.size(),b.size()); 3153 for (i = 0; i < size; ++i) 3154 { 3155 if (std::tolower(a[i]) < std::tolower(b[i])) 3156 return true; 3157 else if (std::tolower(a[i]) > std::tolower(b[i])) 3158 return false; 3159 } 3160 if (a.size() < b.size()) 3161 return true; 3162 else 3163 return false; 3164 } 3165 }; 3166 3167 // ------------------------------------------------------------------------------------ 3168 3169 bool box_win:: set_dir(const std::string & dir)3170 set_dir ( 3171 const std::string& dir 3172 ) 3173 { 3174 try 3175 { 3176 directory d(dir); 3177 queue<directory>::kernel_1a_c qod; 3178 queue<file>::kernel_1a_c qof; 3179 queue<std::string>::sort_1a_c qos; 3180 d.get_dirs(qod); 3181 d.get_files(qof); 3182 3183 qod.reset(); 3184 while (qod.move_next()) 3185 { 3186 std::string temp = qod.element().name(); 3187 qos.enqueue(temp); 3188 } 3189 qos.sort(case_insensitive_compare()); 3190 lb_dirs.load(qos); 3191 qos.clear(); 3192 3193 qof.reset(); 3194 while (qof.move_next()) 3195 { 3196 std::string temp = qof.element().name(); 3197 qos.enqueue(temp); 3198 } 3199 qos.sort(case_insensitive_compare()); 3200 lb_files.load(qos); 3201 return true; 3202 } 3203 catch (directory::listing_error& ) 3204 { 3205 return false; 3206 } 3207 catch (directory::dir_not_found&) 3208 { 3209 return false; 3210 } 3211 } 3212 3213 // ------------------------------------------------------------------------------------ 3214 3215 void box_win:: on_root_click()3216 on_root_click ( 3217 ) 3218 { 3219 btn_root.set_checked(); 3220 if (cur_dir != -1) 3221 sob[cur_dir]->set_unchecked(); 3222 3223 queue<directory>::kernel_1a_c qod, qod2; 3224 queue<file>::kernel_1a_c qof; 3225 queue<std::string>::sort_1a_c qos; 3226 get_filesystem_roots(qod); 3227 path.clear(); 3228 cur_dir = -1; 3229 if (qod.size() == 1) 3230 { 3231 qod.current().get_files(qof); 3232 qod.current().get_dirs(qod2); 3233 prefix = qod.current().full_name(); 3234 3235 qod2.reset(); 3236 while (qod2.move_next()) 3237 { 3238 std::string temp = qod2.element().name(); 3239 qos.enqueue(temp); 3240 } 3241 qos.sort(case_insensitive_compare()); 3242 lb_dirs.load(qos); 3243 qos.clear(); 3244 3245 qof.reset(); 3246 while (qof.move_next()) 3247 { 3248 std::string temp = qof.element().name(); 3249 qos.enqueue(temp); 3250 } 3251 qos.sort(case_insensitive_compare()); 3252 lb_files.load(qos); 3253 } 3254 else 3255 { 3256 prefix.clear(); 3257 qod.reset(); 3258 while (qod.move_next()) 3259 { 3260 std::string temp = qod.element().full_name(); 3261 temp = temp.substr(0,temp.size()-1); 3262 qos.enqueue(temp); 3263 } 3264 qos.sort(case_insensitive_compare()); 3265 lb_dirs.load(qos); 3266 qos.clear(); 3267 lb_files.load(qos); 3268 } 3269 } 3270 3271 // ------------------------------------------------------------------------------------ 3272 3273 base_window::on_close_return_code box_win:: on_window_close()3274 on_window_close ( 3275 ) 3276 { 3277 delete this; 3278 return CLOSE_WINDOW; 3279 } 3280 3281 } 3282 3283 // ---------------------------------------------------------------------------------------- 3284 // ---------------------------------------------------------------------------------------- 3285 // class menu_bar 3286 // ---------------------------------------------------------------------------------------- 3287 // ---------------------------------------------------------------------------------------- 3288 3289 menu_bar:: menu_bar(drawable_window & w)3290 menu_bar( 3291 drawable_window& w 3292 ) : 3293 drawable(w, 0xFFFF), // listen for all events 3294 open_menu(0) 3295 { 3296 adjust_position(); 3297 enable_events(); 3298 } 3299 3300 // ---------------------------------------------------------------------------------------- 3301 3302 menu_bar:: ~menu_bar()3303 ~menu_bar() 3304 { 3305 disable_events(); 3306 parent.invalidate_rectangle(rect); 3307 } 3308 3309 // ---------------------------------------------------------------------------------------- 3310 3311 void menu_bar:: set_main_font(const std::shared_ptr<font> & f)3312 set_main_font ( 3313 const std::shared_ptr<font>& f 3314 ) 3315 { 3316 auto_mutex M(m); 3317 mfont = f; 3318 adjust_position(); 3319 compute_menu_geometry(); 3320 parent.invalidate_rectangle(rect); 3321 } 3322 3323 // ---------------------------------------------------------------------------------------- 3324 3325 void menu_bar:: set_number_of_menus(unsigned long num)3326 set_number_of_menus ( 3327 unsigned long num 3328 ) 3329 { 3330 auto_mutex M(m); 3331 menus.set_max_size(num); 3332 menus.set_size(num); 3333 open_menu = menus.size(); 3334 compute_menu_geometry(); 3335 3336 for (unsigned long i = 0; i < menus.size(); ++i) 3337 { 3338 menus[i].menu.set_on_hide_handler(*this,&menu_bar::on_popup_hide); 3339 } 3340 3341 parent.invalidate_rectangle(rect); 3342 } 3343 3344 // ---------------------------------------------------------------------------------------- 3345 3346 unsigned long menu_bar:: number_of_menus() const3347 number_of_menus ( 3348 ) const 3349 { 3350 auto_mutex M(m); 3351 return menus.size(); 3352 } 3353 3354 // ---------------------------------------------------------------------------------------- 3355 3356 void menu_bar:: set_menu_name(unsigned long idx,const std::string name,char underline_ch)3357 set_menu_name ( 3358 unsigned long idx, 3359 const std::string name, 3360 char underline_ch 3361 ) 3362 { 3363 set_menu_name(idx, convert_mbstring_to_wstring(name), underline_ch); 3364 } 3365 3366 // ---------------------------------------------------------------------------------------- 3367 3368 void menu_bar:: set_menu_name(unsigned long idx,const std::wstring name,char underline_ch)3369 set_menu_name ( 3370 unsigned long idx, 3371 const std::wstring name, 3372 char underline_ch 3373 ) 3374 { 3375 set_menu_name(idx, convert_wstring_to_utf32(name), underline_ch); 3376 } 3377 3378 // ---------------------------------------------------------------------------------------- 3379 3380 void menu_bar:: set_menu_name(unsigned long idx,const dlib::ustring name,char underline_ch)3381 set_menu_name ( 3382 unsigned long idx, 3383 const dlib::ustring name, 3384 char underline_ch 3385 ) 3386 { 3387 DLIB_ASSERT ( idx < number_of_menus() , 3388 "\tvoid menu_bar::set_menu_name()" 3389 << "\n\tidx: " << idx 3390 << "\n\tnumber_of_menus(): " << number_of_menus() 3391 ); 3392 auto_mutex M(m); 3393 menus[idx].name = name.c_str(); 3394 menus[idx].underline_pos = name.find_first_of(underline_ch); 3395 compute_menu_geometry(); 3396 parent.invalidate_rectangle(rect); 3397 } 3398 3399 // ---------------------------------------------------------------------------------------- 3400 3401 const std::string menu_bar:: menu_name(unsigned long idx) const3402 menu_name ( 3403 unsigned long idx 3404 ) const 3405 { 3406 return convert_wstring_to_mbstring(menu_wname(idx)); 3407 } 3408 3409 // ---------------------------------------------------------------------------------------- 3410 3411 const std::wstring menu_bar:: menu_wname(unsigned long idx) const3412 menu_wname ( 3413 unsigned long idx 3414 ) const 3415 { 3416 return convert_utf32_to_wstring(menu_uname(idx)); 3417 } 3418 3419 // ---------------------------------------------------------------------------------------- 3420 3421 const dlib::ustring menu_bar:: menu_uname(unsigned long idx) const3422 menu_uname ( 3423 unsigned long idx 3424 ) const 3425 { 3426 DLIB_ASSERT ( idx < number_of_menus() , 3427 "\tstd::string menu_bar::menu_name()" 3428 << "\n\tidx: " << idx 3429 << "\n\tnumber_of_menus(): " << number_of_menus() 3430 ); 3431 auto_mutex M(m); 3432 return menus[idx].name.c_str(); 3433 } 3434 3435 // ---------------------------------------------------------------------------------------- 3436 3437 popup_menu& menu_bar:: menu(unsigned long idx)3438 menu ( 3439 unsigned long idx 3440 ) 3441 { 3442 DLIB_ASSERT ( idx < number_of_menus() , 3443 "\tpopup_menu& menu_bar::menu()" 3444 << "\n\tidx: " << idx 3445 << "\n\tnumber_of_menus(): " << number_of_menus() 3446 ); 3447 auto_mutex M(m); 3448 return menus[idx].menu; 3449 } 3450 3451 // ---------------------------------------------------------------------------------------- 3452 3453 const popup_menu& menu_bar:: menu(unsigned long idx) const3454 menu ( 3455 unsigned long idx 3456 ) const 3457 { 3458 DLIB_ASSERT ( idx < number_of_menus() , 3459 "\tconst popup_menu& menu_bar::menu()" 3460 << "\n\tidx: " << idx 3461 << "\n\tnumber_of_menus(): " << number_of_menus() 3462 ); 3463 auto_mutex M(m); 3464 return menus[idx].menu; 3465 } 3466 3467 // ---------------------------------------------------------------------------------------- 3468 3469 void menu_bar:: on_window_resized()3470 on_window_resized ( 3471 ) 3472 { 3473 adjust_position(); 3474 hide_menu(); 3475 } 3476 3477 // ---------------------------------------------------------------------------------------- 3478 3479 void menu_bar:: draw(const canvas & c) const3480 draw ( 3481 const canvas& c 3482 ) const 3483 { 3484 rectangle area(rect.intersect(c)); 3485 if (area.is_empty()) 3486 return; 3487 3488 const unsigned char opacity = 40; 3489 fill_rect_with_vertical_gradient(c, rect,rgb_alpha_pixel(255,255,255,opacity), 3490 rgb_alpha_pixel(0,0,0,opacity)); 3491 3492 // first draw the border between the menu and the rest of the window 3493 draw_line(c, point(rect.left(),rect.bottom()-1), 3494 point(rect.right(),rect.bottom()-1), 100); 3495 draw_line(c, point(rect.left(),rect.bottom()), 3496 point(rect.right(),rect.bottom()), 255); 3497 3498 // now draw all the menu buttons 3499 for (unsigned long i = 0; i < menus.size(); ++i) 3500 { 3501 mfont->draw_string(c,menus[i].rect, menus[i].name ); 3502 if (menus[i].underline_p1 != menus[i].underline_p2) 3503 draw_line(c, menus[i].underline_p1, menus[i].underline_p2); 3504 3505 if (open_menu == i) 3506 { 3507 fill_rect_with_vertical_gradient(c, menus[i].bgrect,rgb_alpha_pixel(255,255,0,40), rgb_alpha_pixel(0,0,0,40)); 3508 } 3509 } 3510 } 3511 3512 // ---------------------------------------------------------------------------------------- 3513 3514 void menu_bar:: on_window_moved()3515 on_window_moved ( 3516 ) 3517 { 3518 hide_menu(); 3519 } 3520 3521 // ---------------------------------------------------------------------------------------- 3522 3523 void menu_bar:: on_focus_lost()3524 on_focus_lost ( 3525 ) 3526 { 3527 hide_menu(); 3528 } 3529 3530 // ---------------------------------------------------------------------------------------- 3531 3532 void menu_bar:: on_mouse_down(unsigned long btn,unsigned long,long x,long y,bool)3533 on_mouse_down ( 3534 unsigned long btn, 3535 unsigned long , 3536 long x, 3537 long y, 3538 bool 3539 ) 3540 { 3541 3542 if (rect.contains(x,y) == false || btn != (unsigned long)base_window::LEFT) 3543 { 3544 hide_menu(); 3545 return; 3546 } 3547 3548 unsigned long old_menu = menus.size(); 3549 3550 // if a menu is currently open then save its index 3551 if (open_menu != menus.size()) 3552 { 3553 old_menu = open_menu; 3554 hide_menu(); 3555 } 3556 3557 // figure out which menu should be open if any 3558 for (unsigned long i = 0; i < menus.size(); ++i) 3559 { 3560 if (menus[i].bgrect.contains(x,y)) 3561 { 3562 if (old_menu != i) 3563 show_menu(i); 3564 3565 break; 3566 } 3567 } 3568 3569 } 3570 3571 // ---------------------------------------------------------------------------------------- 3572 3573 void menu_bar:: on_mouse_move(unsigned long,long x,long y)3574 on_mouse_move ( 3575 unsigned long , 3576 long x, 3577 long y 3578 ) 3579 { 3580 // if the mouse is over the menu_bar and some menu is currently open 3581 if (rect.contains(x,y) && open_menu != menus.size()) 3582 { 3583 // if the mouse is still in the same rectangle then don't do anything 3584 if (menus[open_menu].bgrect.contains(x,y) == false) 3585 { 3586 // figure out which menu should be instead 3587 for (unsigned long i = 0; i < menus.size(); ++i) 3588 { 3589 if (menus[i].bgrect.contains(x,y)) 3590 { 3591 show_menu(i); 3592 break; 3593 } 3594 } 3595 3596 } 3597 } 3598 } 3599 3600 // ---------------------------------------------------------------------------------------- 3601 3602 void menu_bar:: on_keydown(unsigned long key,bool is_printable,unsigned long state)3603 on_keydown ( 3604 unsigned long key, 3605 bool is_printable, 3606 unsigned long state 3607 ) 3608 { 3609 if (state&base_window::KBD_MOD_ALT) 3610 { 3611 // check if the key matches any of our underlined keys 3612 for (unsigned long i = 0; i < menus.size(); ++i) 3613 { 3614 // if we have found a matching key 3615 if (is_printable && 3616 menus[i].underline_pos != std::string::npos && 3617 std::tolower(menus[i].name[menus[i].underline_pos]) == std::tolower(key)) 3618 { 3619 show_menu(i); 3620 menus[open_menu].menu.select_first_item(); 3621 return; 3622 } 3623 } 3624 } 3625 3626 if (open_menu != menus.size()) 3627 { 3628 unsigned long i = open_menu; 3629 // if the submenu doesn't use this key for something then we will 3630 if (menus[open_menu].menu.forwarded_on_keydown(key,is_printable,state) == false) 3631 { 3632 if (key == base_window::KEY_LEFT) 3633 { 3634 i = (i+menus.size()-1)%menus.size(); 3635 show_menu(i); 3636 menus[open_menu].menu.select_first_item(); 3637 } 3638 else if (key == base_window::KEY_RIGHT) 3639 { 3640 i = (i+1)%menus.size(); 3641 show_menu(i); 3642 menus[open_menu].menu.select_first_item(); 3643 } 3644 else if (key == base_window::KEY_ESC) 3645 { 3646 hide_menu(); 3647 } 3648 } 3649 } 3650 } 3651 3652 // ---------------------------------------------------------------------------------------- 3653 3654 void menu_bar:: show_menu(unsigned long i)3655 show_menu ( 3656 unsigned long i 3657 ) 3658 { 3659 rectangle temp; 3660 3661 // menu already open so do nothing 3662 if (i == open_menu) 3663 return; 3664 3665 // if a menu is currently open 3666 if (open_menu != menus.size()) 3667 { 3668 menus[open_menu].menu.hide(); 3669 temp = menus[open_menu].bgrect; 3670 } 3671 3672 // display the new menu 3673 open_menu = i; 3674 long wx, wy; 3675 parent.get_pos(wx,wy); 3676 wx += menus[i].bgrect.left(); 3677 wy += menus[i].bgrect.bottom()+1; 3678 menus[i].menu.set_pos(wx,wy); 3679 menus[i].menu.show(); 3680 parent.invalidate_rectangle(menus[i].bgrect+temp); 3681 } 3682 3683 // ---------------------------------------------------------------------------------------- 3684 3685 void menu_bar:: hide_menu()3686 hide_menu ( 3687 ) 3688 { 3689 // if a menu is currently open 3690 if (open_menu != menus.size()) 3691 { 3692 menus[open_menu].menu.hide(); 3693 parent.invalidate_rectangle(menus[open_menu].bgrect); 3694 open_menu = menus.size(); 3695 } 3696 } 3697 3698 // ---------------------------------------------------------------------------------------- 3699 3700 void menu_bar:: on_popup_hide()3701 on_popup_hide ( 3702 ) 3703 { 3704 // if a menu is currently open 3705 if (open_menu != menus.size()) 3706 { 3707 parent.invalidate_rectangle(menus[open_menu].bgrect); 3708 open_menu = menus.size(); 3709 } 3710 } 3711 3712 // ---------------------------------------------------------------------------------------- 3713 3714 void menu_bar:: compute_menu_geometry()3715 compute_menu_geometry ( 3716 ) 3717 { 3718 long x = 7; 3719 long bg_x = 0; 3720 for (unsigned long i = 0; i < menus.size(); ++i) 3721 { 3722 // compute the locations of the text rectangles 3723 menus[i].rect.set_top(5); 3724 menus[i].rect.set_left(x); 3725 menus[i].rect.set_bottom(rect.bottom()-2); 3726 3727 unsigned long width, height; 3728 mfont->compute_size(menus[i].name,width,height); 3729 menus[i].rect = resize_rect_width(menus[i].rect, width); 3730 x = menus[i].rect.right()+10; 3731 3732 menus[i].bgrect.set_top(0); 3733 menus[i].bgrect.set_left(bg_x); 3734 menus[i].bgrect.set_bottom(rect.bottom()-2); 3735 menus[i].bgrect.set_right(x-5); 3736 bg_x = menus[i].bgrect.right()+1; 3737 3738 if (menus[i].underline_pos != std::string::npos) 3739 { 3740 // now compute the location of the underline bar 3741 rectangle r1 = mfont->compute_cursor_rect( 3742 menus[i].rect, 3743 menus[i].name, 3744 menus[i].underline_pos); 3745 3746 rectangle r2 = mfont->compute_cursor_rect( 3747 menus[i].rect, 3748 menus[i].name, 3749 menus[i].underline_pos+1); 3750 3751 menus[i].underline_p1.x() = r1.left()+1; 3752 menus[i].underline_p2.x() = r2.left()-1; 3753 menus[i].underline_p1.y() = r1.bottom()-mfont->height()+mfont->ascender()+2; 3754 menus[i].underline_p2.y() = r2.bottom()-mfont->height()+mfont->ascender()+2; 3755 } 3756 else 3757 { 3758 // there is no underline in this case 3759 menus[i].underline_p1 = menus[i].underline_p2; 3760 } 3761 3762 } 3763 } 3764 3765 // ---------------------------------------------------------------------------------------- 3766 3767 void menu_bar:: adjust_position()3768 adjust_position ( 3769 ) 3770 { 3771 unsigned long width, height; 3772 rectangle old(rect); 3773 parent.get_size(width,height); 3774 rect.set_left(0); 3775 rect.set_top(0); 3776 rect = resize_rect(rect,width,mfont->height()+10); 3777 parent.invalidate_rectangle(old+rect); 3778 } 3779 3780 // ---------------------------------------------------------------------------------------- 3781 // ---------------------------------------------------------------------------------------- 3782 // class text_grid 3783 // ---------------------------------------------------------------------------------------- 3784 // ---------------------------------------------------------------------------------------- 3785 3786 text_grid:: text_grid(drawable_window & w)3787 text_grid ( 3788 drawable_window& w 3789 ) : 3790 scrollable_region(w, KEYBOARD_EVENTS | MOUSE_CLICK | FOCUS_EVENTS ), 3791 has_focus(false), 3792 cursor_timer(*this,&text_grid::timer_action), 3793 border_color_(128,128,128) 3794 { 3795 3796 cursor_timer.set_delay_time(500); 3797 set_vertical_scroll_increment(10); 3798 set_horizontal_scroll_increment(10); 3799 enable_events(); 3800 } 3801 3802 // ---------------------------------------------------------------------------------------- 3803 3804 text_grid:: ~text_grid()3805 ~text_grid ( 3806 ) 3807 { 3808 // Disable all further events for this drawable object. We have to do this 3809 // because we don't want draw() events coming to this object while or after 3810 // it has been destructed. 3811 disable_events(); 3812 3813 // wait for the timer to stop doing its thing 3814 cursor_timer.stop_and_wait(); 3815 // Tell the parent window to redraw its area that previously contained this 3816 // drawable object. 3817 parent.invalidate_rectangle(rect); 3818 } 3819 3820 // ---------------------------------------------------------------------------------------- 3821 3822 void text_grid:: set_grid_size(unsigned long rows,unsigned long cols)3823 set_grid_size ( 3824 unsigned long rows, 3825 unsigned long cols 3826 ) 3827 { 3828 auto_mutex M(m); 3829 row_height.set_max_size(rows); 3830 row_height.set_size(rows); 3831 3832 col_width.set_max_size(cols); 3833 col_width.set_size(cols); 3834 3835 grid.set_size(rows,cols); 3836 3837 for (unsigned long i = 0; i < row_height.size(); ++i) 3838 row_height[i] = (mfont->height()*3)/2; 3839 for (unsigned long i = 0; i < col_width.size(); ++i) 3840 col_width[i] = mfont->height()*5; 3841 3842 compute_total_rect(); 3843 compute_bg_rects(); 3844 } 3845 3846 // ---------------------------------------------------------------------------------------- 3847 3848 unsigned long text_grid:: number_of_columns() const3849 number_of_columns ( 3850 ) const 3851 { 3852 auto_mutex M(m); 3853 return grid.nc(); 3854 } 3855 3856 // ---------------------------------------------------------------------------------------- 3857 3858 unsigned long text_grid:: number_of_rows() const3859 number_of_rows ( 3860 ) const 3861 { 3862 auto_mutex M(m); 3863 return grid.nr(); 3864 } 3865 3866 // ---------------------------------------------------------------------------------------- 3867 3868 int text_grid:: next_free_user_event_number() const3869 next_free_user_event_number ( 3870 ) const 3871 { 3872 return scrollable_region::next_free_user_event_number()+1; 3873 } 3874 3875 // ---------------------------------------------------------------------------------------- 3876 3877 rgb_pixel text_grid:: border_color() const3878 border_color ( 3879 ) const 3880 { 3881 auto_mutex M(m); 3882 return border_color_; 3883 } 3884 3885 // ---------------------------------------------------------------------------------------- 3886 3887 void text_grid:: set_border_color(rgb_pixel color)3888 set_border_color ( 3889 rgb_pixel color 3890 ) 3891 { 3892 auto_mutex M(m); 3893 border_color_ = color; 3894 parent.invalidate_rectangle(rect); 3895 } 3896 3897 // ---------------------------------------------------------------------------------------- 3898 3899 const std::string text_grid:: text(unsigned long row,unsigned long col) const3900 text ( 3901 unsigned long row, 3902 unsigned long col 3903 ) const 3904 { 3905 return convert_wstring_to_mbstring(wtext(row, col)); 3906 } 3907 3908 // ---------------------------------------------------------------------------------------- 3909 3910 const std::wstring text_grid:: wtext(unsigned long row,unsigned long col) const3911 wtext ( 3912 unsigned long row, 3913 unsigned long col 3914 ) const 3915 { 3916 return convert_utf32_to_wstring(utext(row, col)); 3917 } 3918 3919 // ---------------------------------------------------------------------------------------- 3920 3921 const dlib::ustring text_grid:: utext(unsigned long row,unsigned long col) const3922 utext ( 3923 unsigned long row, 3924 unsigned long col 3925 ) const 3926 { 3927 auto_mutex M(m); 3928 DLIB_ASSERT ( row < number_of_rows() && col < number_of_columns(), 3929 "\tconst std::string text_grid::text(row,col)" 3930 << "\n\trow: " << row 3931 << "\n\tcol: " << col 3932 << "\n\tnumber_of_rows(): " << number_of_rows() 3933 << "\n\tnumber_of_columns(): " << number_of_columns() 3934 << "\n\tthis: " << this 3935 ); 3936 return grid[row][col].text.c_str(); 3937 } 3938 3939 // ---------------------------------------------------------------------------------------- 3940 3941 void text_grid:: set_text(unsigned long row,unsigned long col,const std::string & str)3942 set_text ( 3943 unsigned long row, 3944 unsigned long col, 3945 const std::string& str 3946 ) 3947 { 3948 set_text(row, col, convert_mbstring_to_wstring(str)); 3949 } 3950 3951 // ---------------------------------------------------------------------------------------- 3952 3953 void text_grid:: set_text(unsigned long row,unsigned long col,const std::wstring & str)3954 set_text ( 3955 unsigned long row, 3956 unsigned long col, 3957 const std::wstring& str 3958 ) 3959 { 3960 set_text(row, col, convert_wstring_to_utf32(str)); 3961 } 3962 3963 // ---------------------------------------------------------------------------------------- 3964 3965 void text_grid:: set_text(unsigned long row,unsigned long col,const dlib::ustring & str)3966 set_text ( 3967 unsigned long row, 3968 unsigned long col, 3969 const dlib::ustring& str 3970 ) 3971 { 3972 auto_mutex M(m); 3973 DLIB_ASSERT ( row < number_of_rows() && col < number_of_columns(), 3974 "\tvoid text_grid::set_text(row,col)" 3975 << "\n\trow: " << row 3976 << "\n\tcol: " << col 3977 << "\n\tnumber_of_rows(): " << number_of_rows() 3978 << "\n\tnumber_of_columns(): " << number_of_columns() 3979 << "\n\tthis: " << this 3980 ); 3981 grid[row][col].text = str.c_str(); 3982 parent.invalidate_rectangle(get_text_rect(row,col)); 3983 } 3984 3985 // ---------------------------------------------------------------------------------------- 3986 3987 const rgb_pixel text_grid:: text_color(unsigned long row,unsigned long col) const3988 text_color ( 3989 unsigned long row, 3990 unsigned long col 3991 ) const 3992 { 3993 auto_mutex M(m); 3994 DLIB_ASSERT ( row < number_of_rows() && col < number_of_columns(), 3995 "\tconst rgb_pixel text_grid::text_color(row,col)" 3996 << "\n\trow: " << row 3997 << "\n\tcol: " << col 3998 << "\n\tnumber_of_rows(): " << number_of_rows() 3999 << "\n\tnumber_of_columns(): " << number_of_columns() 4000 << "\n\tthis: " << this 4001 ); 4002 return grid[row][col].text_color; 4003 } 4004 4005 // ---------------------------------------------------------------------------------------- 4006 4007 void text_grid:: set_text_color(unsigned long row,unsigned long col,const rgb_pixel color)4008 set_text_color ( 4009 unsigned long row, 4010 unsigned long col, 4011 const rgb_pixel color 4012 ) 4013 { 4014 auto_mutex M(m); 4015 DLIB_ASSERT ( row < number_of_rows() && col < number_of_columns(), 4016 "\tvoid text_grid::set_text_color(row,col,color)" 4017 << "\n\trow: " << row 4018 << "\n\tcol: " << col 4019 << "\n\tnumber_of_rows(): " << number_of_rows() 4020 << "\n\tnumber_of_columns(): " << number_of_columns() 4021 << "\n\tthis: " << this 4022 ); 4023 grid[row][col].text_color = color; 4024 parent.invalidate_rectangle(get_text_rect(row,col)); 4025 } 4026 4027 // ---------------------------------------------------------------------------------------- 4028 4029 const rgb_pixel text_grid:: background_color(unsigned long row,unsigned long col) const4030 background_color ( 4031 unsigned long row, 4032 unsigned long col 4033 ) const 4034 { 4035 auto_mutex M(m); 4036 DLIB_ASSERT ( row < number_of_rows() && col < number_of_columns(), 4037 "\tconst rgb_pixel text_grid::background_color(row,col,color)" 4038 << "\n\trow: " << row 4039 << "\n\tcol: " << col 4040 << "\n\tnumber_of_rows(): " << number_of_rows() 4041 << "\n\tnumber_of_columns(): " << number_of_columns() 4042 << "\n\tthis: " << this 4043 ); 4044 return grid[row][col].bg_color; 4045 } 4046 4047 // ---------------------------------------------------------------------------------------- 4048 4049 void text_grid:: set_background_color(unsigned long row,unsigned long col,const rgb_pixel color)4050 set_background_color ( 4051 unsigned long row, 4052 unsigned long col, 4053 const rgb_pixel color 4054 ) 4055 { 4056 auto_mutex M(m); 4057 DLIB_ASSERT ( row < number_of_rows() && col < number_of_columns(), 4058 "\tvoid text_grid::set_background_color(row,col,color)" 4059 << "\n\trow: " << row 4060 << "\n\tcol: " << col 4061 << "\n\tnumber_of_rows(): " << number_of_rows() 4062 << "\n\tnumber_of_columns(): " << number_of_columns() 4063 << "\n\tthis: " << this 4064 ); 4065 grid[row][col].bg_color = color; 4066 parent.invalidate_rectangle(get_bg_rect(row,col)); 4067 } 4068 4069 // ---------------------------------------------------------------------------------------- 4070 4071 bool text_grid:: is_editable(unsigned long row,unsigned long col) const4072 is_editable ( 4073 unsigned long row, 4074 unsigned long col 4075 ) const 4076 { 4077 auto_mutex M(m); 4078 DLIB_ASSERT ( row < number_of_rows() && col < number_of_columns(), 4079 "\tbool text_grid::is_editable(row,col)" 4080 << "\n\trow: " << row 4081 << "\n\tcol: " << col 4082 << "\n\tnumber_of_rows(): " << number_of_rows() 4083 << "\n\tnumber_of_columns(): " << number_of_columns() 4084 << "\n\tthis: " << this 4085 ); 4086 return grid[row][col].is_editable; 4087 } 4088 4089 // ---------------------------------------------------------------------------------------- 4090 4091 void text_grid:: set_editable(unsigned long row,unsigned long col,bool editable)4092 set_editable ( 4093 unsigned long row, 4094 unsigned long col, 4095 bool editable 4096 ) 4097 { 4098 auto_mutex M(m); 4099 DLIB_ASSERT ( row < number_of_rows() && col < number_of_columns(), 4100 "\tvoid text_grid::set_editable(row,col,editable)" 4101 << "\n\trow: " << row 4102 << "\n\tcol: " << col 4103 << "\n\tnumber_of_rows(): " << number_of_rows() 4104 << "\n\tnumber_of_columns(): " << number_of_columns() 4105 << "\n\teditable: " << editable 4106 << "\n\tthis: " << this 4107 ); 4108 grid[row][col].is_editable = editable; 4109 if (has_focus && active_row == static_cast<long>(row) && active_col == static_cast<long>(col)) 4110 { 4111 drop_input_focus(); 4112 } 4113 } 4114 4115 // ---------------------------------------------------------------------------------------- 4116 4117 void text_grid:: set_column_width(unsigned long col,unsigned long width)4118 set_column_width ( 4119 unsigned long col, 4120 unsigned long width 4121 ) 4122 { 4123 auto_mutex M(m); 4124 DLIB_ASSERT ( col < number_of_columns(), 4125 "\tvoid text_grid::set_column_width(col,width)" 4126 << "\n\tcol: " << col 4127 << "\n\tnumber_of_columns(): " << number_of_columns() 4128 << "\n\twidth: " << width 4129 << "\n\tthis: " << this 4130 ); 4131 col_width[col] = width; 4132 compute_total_rect(); 4133 compute_bg_rects(); 4134 } 4135 4136 // ---------------------------------------------------------------------------------------- 4137 4138 void text_grid:: set_row_height(unsigned long row,unsigned long height)4139 set_row_height ( 4140 unsigned long row, 4141 unsigned long height 4142 ) 4143 { 4144 auto_mutex M(m); 4145 DLIB_ASSERT ( row < number_of_rows() , 4146 "\tvoid text_grid::set_row_height(row,height)" 4147 << "\n\trow: " << row 4148 << "\n\tnumber_of_rows(): " << number_of_rows() 4149 << "\n\theight: " << height 4150 << "\n\tthis: " << this 4151 ); 4152 row_height[row] = height; 4153 compute_total_rect(); 4154 compute_bg_rects(); 4155 } 4156 4157 // ---------------------------------------------------------------------------------------- 4158 4159 void text_grid:: disable()4160 disable ( 4161 ) 4162 { 4163 auto_mutex M(m); 4164 scrollable_region::disable(); 4165 drop_input_focus(); 4166 } 4167 4168 // ---------------------------------------------------------------------------------------- 4169 4170 void text_grid:: hide()4171 hide ( 4172 ) 4173 { 4174 auto_mutex M(m); 4175 scrollable_region::hide(); 4176 drop_input_focus(); 4177 } 4178 4179 // ---------------------------------------------------------------------------------------- 4180 4181 void text_grid:: on_user_event(int num)4182 on_user_event ( 4183 int num 4184 ) 4185 { 4186 // ignore this user event if it isn't for us 4187 if (num != scrollable_region::next_free_user_event_number()) 4188 return; 4189 4190 if (has_focus && !recent_cursor_move && enabled && !hidden) 4191 { 4192 show_cursor = !show_cursor; 4193 parent.invalidate_rectangle(get_text_rect(active_row,active_col)); 4194 } 4195 recent_cursor_move = false; 4196 } 4197 4198 // ---------------------------------------------------------------------------------------- 4199 4200 void text_grid:: timer_action()4201 timer_action ( 4202 ) 4203 { 4204 parent.trigger_user_event(this,scrollable_region::next_free_user_event_number()); 4205 } 4206 4207 // ---------------------------------------------------------------------------------------- 4208 4209 void text_grid:: compute_bg_rects()4210 compute_bg_rects ( 4211 ) 4212 { 4213 // loop over each element in the grid and figure out what its rectangle should be 4214 // with respect to the total_rect() 4215 point p1, p2; 4216 p1.y() = total_rect().top(); 4217 for (long row = 0; row < grid.nr(); ++row) 4218 { 4219 p1.x() = total_rect().left(); 4220 p2.y() = p1.y() + row_height[row]-1; 4221 for (long col = 0; col < grid.nc(); ++col) 4222 { 4223 // if this is the last box in this row make it super wide so that it always 4224 // goes to the end of the widget 4225 if (col+1 == grid.nc()) 4226 p2.x() = 1000000; 4227 else 4228 p2.x() = p1.x() + col_width[col]-1; 4229 4230 // at this point p1 is the upper left corner of this box and p2 is the 4231 // lower right corner of the box; 4232 rectangle bg_rect(p1); 4233 bg_rect += p2; 4234 4235 grid[row][col].bg_rect = translate_rect(bg_rect, -total_rect().left(), -total_rect().top()); 4236 4237 4238 p1.x() += 1 + col_width[col]; 4239 } 4240 p1.y() += 1 + row_height[row]; 4241 } 4242 } 4243 4244 // ---------------------------------------------------------------------------------------- 4245 4246 void text_grid:: compute_total_rect()4247 compute_total_rect ( 4248 ) 4249 { 4250 if (grid.size() == 0) 4251 { 4252 set_total_rect_size(0,0); 4253 } 4254 else 4255 { 4256 unsigned long width = col_width.size()-1; 4257 unsigned long height = row_height.size()-1; 4258 4259 for (unsigned long i = 0; i < col_width.size(); ++i) 4260 width += col_width[i]; 4261 for (unsigned long i = 0; i < row_height.size(); ++i) 4262 height += row_height[i]; 4263 4264 set_total_rect_size(width,height); 4265 } 4266 } 4267 4268 // ---------------------------------------------------------------------------------------- 4269 4270 void text_grid:: on_keydown(unsigned long key,bool is_printable,unsigned long state)4271 on_keydown ( 4272 unsigned long key, 4273 bool is_printable, 4274 unsigned long state 4275 ) 4276 { 4277 // ignore this event if we are disabled or hidden 4278 if (!enabled || hidden) 4279 return; 4280 4281 if (has_focus) 4282 { 4283 if (is_printable) 4284 { 4285 // if the user hit the tab key then jump to the next box 4286 if (key == '\t') 4287 { 4288 if (active_col+1 == grid.nc()) 4289 { 4290 if (active_row+1 == grid.nr()) 4291 move_cursor(0,0,0); 4292 else 4293 move_cursor(active_row+1,0,0); 4294 } 4295 else 4296 { 4297 move_cursor(active_row,active_col+1,0); 4298 } 4299 } 4300 if (key == '\n') 4301 { 4302 // ignore the enter key 4303 } 4304 else if (grid[active_row][active_col].is_editable) 4305 { 4306 // insert the key the user pressed into the string 4307 grid[active_row][active_col].text.insert(cursor_pos,1,static_cast<char>(key)); 4308 move_cursor(active_row,active_col,cursor_pos+1); 4309 4310 if (text_modified_handler.is_set()) 4311 text_modified_handler(active_row,active_col); 4312 } 4313 } 4314 else if ((state & base_window::KBD_MOD_CONTROL)) 4315 { 4316 if (key == base_window::KEY_LEFT) 4317 move_cursor(active_row,active_col-1,0); 4318 else if (key == base_window::KEY_RIGHT) 4319 move_cursor(active_row,active_col+1,0); 4320 else if (key == base_window::KEY_UP) 4321 move_cursor(active_row-1,active_col,0); 4322 else if (key == base_window::KEY_DOWN) 4323 move_cursor(active_row+1,active_col,0); 4324 else if (key == base_window::KEY_END) 4325 move_cursor(active_row,active_col,grid[active_row][active_col].text.size()); 4326 else if (key == base_window::KEY_HOME) 4327 move_cursor(active_row,active_col,0); 4328 } 4329 else 4330 { 4331 if (key == base_window::KEY_LEFT) 4332 move_cursor(active_row,active_col,cursor_pos-1); 4333 else if (key == base_window::KEY_RIGHT) 4334 move_cursor(active_row,active_col,cursor_pos+1); 4335 else if (key == base_window::KEY_UP) 4336 move_cursor(active_row-1,active_col,0); 4337 else if (key == base_window::KEY_DOWN) 4338 move_cursor(active_row+1,active_col,0); 4339 else if (key == base_window::KEY_END) 4340 move_cursor(active_row,active_col,grid[active_row][active_col].text.size()); 4341 else if (key == base_window::KEY_HOME) 4342 move_cursor(active_row,active_col,0); 4343 else if (key == base_window::KEY_BACKSPACE) 4344 { 4345 if (cursor_pos > 0 && grid[active_row][active_col].is_editable) 4346 { 4347 grid[active_row][active_col].text.erase( 4348 grid[active_row][active_col].text.begin()+cursor_pos-1, 4349 grid[active_row][active_col].text.begin()+cursor_pos); 4350 move_cursor(active_row,active_col,cursor_pos-1); 4351 4352 if (text_modified_handler.is_set()) 4353 text_modified_handler(active_row,active_col); 4354 } 4355 } 4356 else if (key == base_window::KEY_DELETE) 4357 { 4358 if (cursor_pos < static_cast<long>(grid[active_row][active_col].text.size()) && 4359 grid[active_row][active_col].is_editable) 4360 { 4361 grid[active_row][active_col].text.erase( 4362 grid[active_row][active_col].text.begin()+cursor_pos); 4363 move_cursor(active_row,active_col,cursor_pos); 4364 4365 if (text_modified_handler.is_set()) 4366 text_modified_handler(active_row,active_col); 4367 } 4368 } 4369 } 4370 } // if (has_focus) 4371 } 4372 4373 // ---------------------------------------------------------------------------------------- 4374 4375 void text_grid:: on_mouse_down(unsigned long btn,unsigned long state,long x,long y,bool is_double_click)4376 on_mouse_down ( 4377 unsigned long btn, 4378 unsigned long state, 4379 long x, 4380 long y, 4381 bool is_double_click 4382 ) 4383 { 4384 scrollable_region::on_mouse_down(btn, state, x, y, is_double_click); 4385 if (display_rect().contains(x,y) && enabled && !hidden) 4386 { 4387 // figure out which box this click landed in 4388 rectangle hit; 4389 4390 // find which column we hit 4391 unsigned long col = 0; 4392 long box_x = total_rect().left(); 4393 for (unsigned long i = 0; i < col_width.size(); ++i) 4394 { 4395 if (box_x <= x && (x < box_x+static_cast<long>(col_width[i]) || (i+1 == col_width.size()))) 4396 { 4397 col = i; 4398 hit.set_left(box_x); 4399 hit.set_right(box_x+col_width[i]-1); 4400 break; 4401 } 4402 else 4403 { 4404 box_x += col_width[i]+1; 4405 } 4406 } 4407 4408 // find which row we hit 4409 unsigned long row = 0; 4410 long box_y = total_rect().top(); 4411 for (unsigned long i = 0; i < row_height.size(); ++i) 4412 { 4413 if (box_y <= y && y < box_y+static_cast<long>(row_height[i])) 4414 { 4415 row = i; 4416 hit.set_top(box_y); 4417 hit.set_bottom(box_y+row_height[i]-1); 4418 break; 4419 } 4420 else 4421 { 4422 box_y += row_height[i]+1; 4423 } 4424 } 4425 4426 // if we hit a box 4427 if (hit.is_empty() == false) 4428 { 4429 move_cursor(row, 4430 col, 4431 mfont->compute_cursor_pos(get_text_rect(row,col), grid[row][col].text, x, y, grid[row][col].first) 4432 ); 4433 } 4434 else 4435 { 4436 drop_input_focus(); 4437 } 4438 } 4439 else 4440 { 4441 drop_input_focus(); 4442 } 4443 } 4444 4445 // ---------------------------------------------------------------------------------------- 4446 4447 void text_grid:: on_mouse_up(unsigned long btn,unsigned long state,long x,long y)4448 on_mouse_up ( 4449 unsigned long btn, 4450 unsigned long state, 4451 long x, 4452 long y 4453 ) 4454 { 4455 scrollable_region::on_mouse_up(btn, state, x, y); 4456 } 4457 4458 // ---------------------------------------------------------------------------------------- 4459 4460 void text_grid:: on_focus_lost()4461 on_focus_lost ( 4462 ) 4463 { 4464 drop_input_focus(); 4465 } 4466 4467 // ---------------------------------------------------------------------------------------- 4468 4469 void text_grid:: draw(const canvas & c) const4470 draw ( 4471 const canvas& c 4472 ) const 4473 { 4474 scrollable_region::draw(c); 4475 rectangle area = c.intersect(display_rect()); 4476 if (area.is_empty() == true) 4477 return; 4478 4479 if (enabled) 4480 fill_rect(c, area, 255); 4481 4482 // don't do anything if the grid is empty 4483 if (grid.size() == 0) 4484 return; 4485 4486 // draw all the vertical lines 4487 point p1, p2; 4488 p1.x() = p2.x() = total_rect().left(); 4489 p1.y() = total_rect().top(); 4490 p2.y() = total_rect().bottom(); 4491 for (unsigned long i = 0; i < col_width.size()-1; ++i) 4492 { 4493 p1.x() += col_width[i]; 4494 p2.x() += col_width[i]; 4495 if (enabled) 4496 draw_line(c,p1,p2,border_color_,area); 4497 else 4498 draw_line(c,p1,p2,128,area); 4499 p1.x() += 1; 4500 p2.x() += 1; 4501 } 4502 4503 // draw all the horizontal lines 4504 p1.y() = p2.y() = total_rect().top(); 4505 p1.x() = display_rect().left(); 4506 p2.x() = display_rect().right(); 4507 for (unsigned long i = 0; i < row_height.size(); ++i) 4508 { 4509 p1.y() += row_height[i]; 4510 p2.y() += row_height[i]; 4511 if (enabled) 4512 draw_line(c,p1,p2,border_color_,area); 4513 else 4514 draw_line(c,p1,p2,128,area); 4515 p1.y() += 1; 4516 p2.y() += 1; 4517 } 4518 4519 // draw the backgrounds and text for each box 4520 for (long row = 0; row < grid.nr(); ++row) 4521 { 4522 for (long col = 0; col < grid.nc(); ++col) 4523 { 4524 rectangle bg_rect(get_bg_rect(row,col)); 4525 4526 rectangle text_rect(get_text_rect(row,col)); 4527 4528 if (enabled) 4529 { 4530 fill_rect(c,bg_rect.intersect(area),grid[row][col].bg_color); 4531 4532 mfont->draw_string(c, 4533 text_rect, 4534 grid[row][col].text, 4535 grid[row][col].text_color, 4536 grid[row][col].first, 4537 std::string::npos, 4538 area); 4539 } 4540 else 4541 { 4542 mfont->draw_string(c, 4543 text_rect, 4544 grid[row][col].text, 4545 128, 4546 grid[row][col].first, 4547 std::string::npos, 4548 area); 4549 } 4550 4551 // if this box has input focus then draw it with a cursor 4552 if (has_focus && active_col == col && active_row == row && show_cursor) 4553 { 4554 rectangle cursor_rect = mfont->compute_cursor_rect(text_rect, 4555 grid[row][col].text, 4556 cursor_pos, 4557 grid[row][col].first); 4558 draw_rectangle(c,cursor_rect,0,area); 4559 } 4560 4561 } 4562 } 4563 4564 4565 } 4566 4567 // ---------------------------------------------------------------------------------------- 4568 4569 rectangle text_grid:: get_text_rect(unsigned long row,unsigned long col) const4570 get_text_rect ( 4571 unsigned long row, 4572 unsigned long col 4573 ) const 4574 { 4575 rectangle bg_rect(get_bg_rect(row,col)); 4576 long padding = (bg_rect.height() - mfont->height())/2 + (bg_rect.height() - mfont->height())%2; 4577 if (padding < 0) 4578 padding = 0; 4579 bg_rect.set_left(bg_rect.left()+padding); 4580 bg_rect.set_top(bg_rect.top()+padding); 4581 bg_rect.set_right(bg_rect.right()-padding); 4582 bg_rect.set_bottom(bg_rect.bottom()-padding); 4583 return bg_rect; 4584 } 4585 4586 // ---------------------------------------------------------------------------------------- 4587 4588 rectangle text_grid:: get_bg_rect(unsigned long row,unsigned long col) const4589 get_bg_rect ( 4590 unsigned long row, 4591 unsigned long col 4592 ) const 4593 { 4594 return translate_rect(grid[row][col].bg_rect, total_rect().left(), total_rect().top()); 4595 } 4596 4597 // ---------------------------------------------------------------------------------------- 4598 4599 void text_grid:: drop_input_focus()4600 drop_input_focus ( 4601 ) 4602 { 4603 if (has_focus) 4604 { 4605 parent.invalidate_rectangle(get_text_rect(active_row,active_col)); 4606 has_focus = false; 4607 show_cursor = false; 4608 cursor_timer.stop(); 4609 } 4610 } 4611 4612 // ---------------------------------------------------------------------------------------- 4613 4614 void text_grid:: move_cursor(long row,long col,long new_cursor_pos)4615 move_cursor ( 4616 long row, 4617 long col, 4618 long new_cursor_pos 4619 ) 4620 { 4621 // don't do anything if the grid is empty 4622 if (grid.size() == 0) 4623 { 4624 return; 4625 } 4626 4627 if (row < 0) 4628 row = 0; 4629 if (row >= grid.nr()) 4630 row = grid.nr()-1; 4631 if (col < 0) 4632 col = 0; 4633 if (col >= grid.nc()) 4634 col = grid.nc()-1; 4635 4636 if (new_cursor_pos < 0) 4637 { 4638 if (col == 0) 4639 { 4640 new_cursor_pos = 0; 4641 } 4642 else 4643 { 4644 --col; 4645 new_cursor_pos = grid[row][col].text.size(); 4646 } 4647 } 4648 4649 if (new_cursor_pos > static_cast<long>(grid[row][col].text.size())) 4650 { 4651 if (col+1 == grid.nc()) 4652 { 4653 new_cursor_pos = grid[row][col].text.size(); 4654 } 4655 else 4656 { 4657 ++col; 4658 new_cursor_pos = 0; 4659 } 4660 } 4661 4662 // if some other box had the input focus then redraw it 4663 if (has_focus && (active_row != row || active_col != col )) 4664 { 4665 parent.invalidate_rectangle(get_text_rect(active_row,active_col)); 4666 } 4667 4668 if (has_focus == false) 4669 { 4670 cursor_timer.start(); 4671 } 4672 4673 has_focus = true; 4674 recent_cursor_move = true; 4675 show_cursor = true; 4676 active_row = row; 4677 active_col = col; 4678 cursor_pos = new_cursor_pos; 4679 4680 // adjust the first character to draw so that the string is displayed well 4681 rectangle text_rect(get_text_rect(active_row,active_col)); 4682 rectangle cursor_rect = mfont->compute_cursor_rect(text_rect, 4683 grid[row][col].text, 4684 cursor_pos, 4685 grid[row][col].first); 4686 4687 // if the cursor rect is too far to the left of the string 4688 if (cursor_pos < static_cast<long>(grid[row][col].first)) 4689 { 4690 if (cursor_pos > 5) 4691 { 4692 grid[row][col].first = cursor_pos - 5; 4693 } 4694 else 4695 { 4696 grid[row][col].first = 0; 4697 } 4698 } 4699 // if the cursor rect is too far to the right of the string 4700 else if (cursor_rect.left() > text_rect.right()) 4701 { 4702 long distance = (cursor_rect.left() - text_rect.right()) + text_rect.width()/3; 4703 // find the letter that is distance pixels from the start of the string 4704 long sum = 0; 4705 for (unsigned long i = grid[row][col].first; i < grid[row][col].text.size(); ++i) 4706 { 4707 sum += (*mfont)[grid[row][col].text[i]].width(); 4708 if (sum >= distance) 4709 { 4710 grid[row][col].first = i; 4711 break; 4712 } 4713 } 4714 } 4715 4716 scroll_to_rect(get_bg_rect(row,col)); 4717 4718 // redraw our box 4719 parent.invalidate_rectangle(text_rect); 4720 4721 } 4722 4723 // ---------------------------------------------------------------------------------------- 4724 // ---------------------------------------------------------------------------------------- 4725 // text_field object methods 4726 // ---------------------------------------------------------------------------------------- 4727 // ---------------------------------------------------------------------------------------- 4728 4729 rectangle text_box:: get_text_rect() const4730 get_text_rect ( 4731 ) const 4732 { 4733 const unsigned long padding = style->get_padding(*mfont); 4734 4735 rectangle text_rect; 4736 text_rect.set_left(total_rect().left()+padding); 4737 text_rect.set_top(total_rect().top()+padding); 4738 text_rect.set_right(total_rect().right()-padding); 4739 text_rect.set_bottom(total_rect().bottom()-padding); 4740 return text_rect; 4741 } 4742 4743 // ---------------------------------------------------------------------------------------- 4744 4745 void text_box:: enable()4746 enable ( 4747 ) 4748 { 4749 scrollable_region::enable(); 4750 right_click_menu.enable(); 4751 } 4752 4753 // ---------------------------------------------------------------------------------------- 4754 4755 void text_box:: on_cut()4756 on_cut ( 4757 ) 4758 { 4759 on_copy(); 4760 on_delete_selected(); 4761 } 4762 4763 // ---------------------------------------------------------------------------------------- 4764 4765 void text_box:: on_copy()4766 on_copy ( 4767 ) 4768 { 4769 if (highlight_start <= highlight_end) 4770 { 4771 put_on_clipboard(text_.substr(highlight_start, highlight_end-highlight_start+1)); 4772 } 4773 } 4774 4775 // ---------------------------------------------------------------------------------------- 4776 4777 void text_box:: on_paste()4778 on_paste ( 4779 ) 4780 { 4781 ustring temp_str; 4782 get_from_clipboard(temp_str); 4783 4784 4785 if (highlight_start <= highlight_end) 4786 { 4787 text_ = text_.substr(0,highlight_start) + temp_str + 4788 text_.substr(highlight_end+1,text_.size()-highlight_end-1); 4789 move_cursor(highlight_start+temp_str.size()); 4790 highlight_start = 0; 4791 highlight_end = -1; 4792 parent.invalidate_rectangle(rect); 4793 on_no_text_selected(); 4794 4795 // send out the text modified event 4796 if (text_modified_handler.is_set()) 4797 text_modified_handler(); 4798 } 4799 else 4800 { 4801 text_ = text_.substr(0,cursor_pos) + temp_str + 4802 text_.substr(cursor_pos,text_.size()-cursor_pos); 4803 move_cursor(cursor_pos+temp_str.size()); 4804 4805 // send out the text modified event 4806 if (temp_str.size() != 0 && text_modified_handler.is_set()) 4807 text_modified_handler(); 4808 } 4809 4810 adjust_total_rect(); 4811 } 4812 4813 // ---------------------------------------------------------------------------------------- 4814 4815 void text_box:: on_select_all()4816 on_select_all ( 4817 ) 4818 { 4819 move_cursor(static_cast<long>(text_.size())); 4820 highlight_start = 0; 4821 highlight_end = static_cast<long>(text_.size()-1); 4822 if (highlight_start <= highlight_end) 4823 on_text_is_selected(); 4824 parent.invalidate_rectangle(rect); 4825 } 4826 4827 // ---------------------------------------------------------------------------------------- 4828 4829 void text_box:: on_delete_selected()4830 on_delete_selected ( 4831 ) 4832 { 4833 if (highlight_start <= highlight_end) 4834 { 4835 text_ = text_.erase(highlight_start,highlight_end-highlight_start+1); 4836 move_cursor(highlight_start); 4837 highlight_start = 0; 4838 highlight_end = -1; 4839 4840 on_no_text_selected(); 4841 // send out the text modified event 4842 if (text_modified_handler.is_set()) 4843 text_modified_handler(); 4844 4845 adjust_total_rect(); 4846 4847 parent.invalidate_rectangle(rect); 4848 } 4849 } 4850 4851 // ---------------------------------------------------------------------------------------- 4852 4853 void text_box:: on_text_is_selected()4854 on_text_is_selected ( 4855 ) 4856 { 4857 right_click_menu.menu().enable_menu_item(0); 4858 right_click_menu.menu().enable_menu_item(1); 4859 right_click_menu.menu().enable_menu_item(3); 4860 } 4861 4862 // ---------------------------------------------------------------------------------------- 4863 4864 void text_box:: on_no_text_selected()4865 on_no_text_selected ( 4866 ) 4867 { 4868 right_click_menu.menu().disable_menu_item(0); 4869 right_click_menu.menu().disable_menu_item(1); 4870 right_click_menu.menu().disable_menu_item(3); 4871 } 4872 4873 // ---------------------------------------------------------------------------------------- 4874 4875 void text_box:: show()4876 show ( 4877 ) 4878 { 4879 scrollable_region::show(); 4880 right_click_menu.show(); 4881 } 4882 4883 // ---------------------------------------------------------------------------------------- 4884 4885 void text_box:: disable()4886 disable ( 4887 ) 4888 { 4889 auto_mutex M(m); 4890 scrollable_region::disable(); 4891 t.stop(); 4892 has_focus = false; 4893 cursor_visible = false; 4894 right_click_menu.disable(); 4895 } 4896 4897 // ---------------------------------------------------------------------------------------- 4898 4899 void text_box:: hide()4900 hide ( 4901 ) 4902 { 4903 auto_mutex M(m); 4904 scrollable_region::hide(); 4905 t.stop(); 4906 has_focus = false; 4907 cursor_visible = false; 4908 } 4909 4910 // ---------------------------------------------------------------------------------------- 4911 4912 void text_box:: adjust_total_rect()4913 adjust_total_rect ( 4914 ) 4915 { 4916 const unsigned long padding = style->get_padding(*mfont); 4917 unsigned long text_width; 4918 unsigned long text_height; 4919 4920 mfont->compute_size(text_, text_width, text_height); 4921 4922 set_total_rect_size(text_width + padding*2, text_height + padding*2); 4923 } 4924 4925 // ---------------------------------------------------------------------------------------- 4926 4927 void text_box:: set_main_font(const std::shared_ptr<font> & f)4928 set_main_font ( 4929 const std::shared_ptr<font>& f 4930 ) 4931 { 4932 auto_mutex M(m); 4933 mfont = f; 4934 adjust_total_rect(); 4935 right_click_menu.set_rect(display_rect()); 4936 } 4937 4938 // ---------------------------------------------------------------------------------------- 4939 4940 void text_box:: draw(const canvas & c) const4941 draw ( 4942 const canvas& c 4943 ) const 4944 { 4945 scrollable_region::draw(c); 4946 rectangle area = rect.intersect(c); 4947 if (area.is_empty()) 4948 return; 4949 4950 const point origin(total_rect().left(), total_rect().top()); 4951 4952 style->draw_text_box(c,display_rect(),get_text_rect(), enabled, *mfont, text_, 4953 translate_rect(cursor_rect, origin), 4954 text_color_, bg_color_, has_focus, cursor_visible, highlight_start, 4955 highlight_end); 4956 } 4957 4958 // ---------------------------------------------------------------------------------------- 4959 4960 void text_box:: set_text(const std::string & text)4961 set_text ( 4962 const std::string& text 4963 ) 4964 { 4965 set_text(convert_mbstring_to_wstring(text)); 4966 } 4967 4968 void text_box:: set_text(const std::wstring & text)4969 set_text ( 4970 const std::wstring& text 4971 ) 4972 { 4973 set_text(convert_wstring_to_utf32(text)); 4974 } 4975 4976 void text_box:: set_text(const dlib::ustring & text)4977 set_text ( 4978 const dlib::ustring& text 4979 ) 4980 { 4981 auto_mutex M(m); 4982 // do this to get rid of any reference counting that may be present in 4983 // the std::string implementation. 4984 text_ = text.c_str(); 4985 4986 adjust_total_rect(); 4987 move_cursor(0); 4988 4989 highlight_start = 0; 4990 highlight_end = -1; 4991 } 4992 4993 // ---------------------------------------------------------------------------------------- 4994 4995 const std::string text_box:: text() const4996 text ( 4997 ) const 4998 { 4999 std::string temp = convert_wstring_to_mbstring(wtext()); 5000 return temp; 5001 } 5002 5003 const std::wstring text_box:: wtext() const5004 wtext ( 5005 ) const 5006 { 5007 std::wstring temp = convert_utf32_to_wstring(utext()); 5008 return temp; 5009 } 5010 5011 const dlib::ustring text_box:: utext() const5012 utext ( 5013 ) const 5014 { 5015 auto_mutex M(m); 5016 // do this to get rid of any reference counting that may be present in 5017 // the dlib::ustring implementation. 5018 dlib::ustring temp = text_.c_str(); 5019 return temp; 5020 } 5021 5022 // ---------------------------------------------------------------------------------------- 5023 5024 void text_box:: set_size(unsigned long width,unsigned long height)5025 set_size ( 5026 unsigned long width, 5027 unsigned long height 5028 ) 5029 { 5030 auto_mutex M(m); 5031 scrollable_region::set_size(width,height); 5032 right_click_menu.set_rect(display_rect()); 5033 } 5034 5035 // ---------------------------------------------------------------------------------------- 5036 5037 void text_box:: set_pos(long x,long y)5038 set_pos ( 5039 long x, 5040 long y 5041 ) 5042 { 5043 scrollable_region::set_pos(x,y); 5044 right_click_menu.set_rect(get_text_rect()); 5045 } 5046 5047 // ---------------------------------------------------------------------------------------- 5048 5049 void text_box:: set_background_color(const rgb_pixel color)5050 set_background_color ( 5051 const rgb_pixel color 5052 ) 5053 { 5054 auto_mutex M(m); 5055 bg_color_ = color; 5056 parent.invalidate_rectangle(rect); 5057 } 5058 5059 // ---------------------------------------------------------------------------------------- 5060 5061 const rgb_pixel text_box:: background_color() const5062 background_color ( 5063 ) const 5064 { 5065 auto_mutex M(m); 5066 return bg_color_; 5067 } 5068 5069 // ---------------------------------------------------------------------------------------- 5070 5071 void text_box:: set_text_color(const rgb_pixel color)5072 set_text_color ( 5073 const rgb_pixel color 5074 ) 5075 { 5076 auto_mutex M(m); 5077 text_color_ = color; 5078 parent.invalidate_rectangle(rect); 5079 } 5080 5081 // ---------------------------------------------------------------------------------------- 5082 5083 const rgb_pixel text_box:: text_color() const5084 text_color ( 5085 ) const 5086 { 5087 auto_mutex M(m); 5088 return text_color_; 5089 } 5090 5091 // ---------------------------------------------------------------------------------------- 5092 5093 void text_box:: on_mouse_move(unsigned long state,long x,long y)5094 on_mouse_move ( 5095 unsigned long state, 5096 long x, 5097 long y 5098 ) 5099 { 5100 if (!enabled || hidden || !has_focus) 5101 { 5102 return; 5103 } 5104 5105 if (state & base_window::LEFT) 5106 { 5107 if (highlight_start <= highlight_end) 5108 { 5109 if (highlight_start == cursor_pos) 5110 shift_pos = highlight_end + 1; 5111 else 5112 shift_pos = highlight_start; 5113 } 5114 5115 unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y); 5116 if (static_cast<long>(new_pos) != cursor_pos) 5117 { 5118 move_cursor(new_pos); 5119 parent.invalidate_rectangle(rect); 5120 } 5121 } 5122 else if (shift_pos != -1) 5123 { 5124 shift_pos = -1; 5125 } 5126 5127 } 5128 5129 // ---------------------------------------------------------------------------------------- 5130 5131 void text_box:: on_mouse_up(unsigned long btn,unsigned long,long,long)5132 on_mouse_up ( 5133 unsigned long btn, 5134 unsigned long, 5135 long , 5136 long 5137 ) 5138 { 5139 if (!enabled || hidden) 5140 return; 5141 5142 if (btn == base_window::LEFT) 5143 shift_pos = -1; 5144 } 5145 5146 // ---------------------------------------------------------------------------------------- 5147 5148 void text_box:: on_mouse_down(unsigned long btn,unsigned long state,long x,long y,bool double_clicked)5149 on_mouse_down ( 5150 unsigned long btn, 5151 unsigned long state, 5152 long x, 5153 long y, 5154 bool double_clicked 5155 ) 5156 { 5157 using namespace std; 5158 if (!enabled || hidden || btn != (unsigned long)base_window::LEFT) 5159 return; 5160 5161 if (display_rect().contains(x,y)) 5162 { 5163 has_focus = true; 5164 cursor_visible = true; 5165 parent.invalidate_rectangle(rect); 5166 t.start(); 5167 5168 5169 if (double_clicked) 5170 { 5171 // highlight the double clicked word 5172 string::size_type first, last; 5173 const ustring ustr = convert_utf8_to_utf32(std::string(" \t\n")); 5174 first = text_.substr(0,cursor_pos).find_last_of(ustr.c_str()); 5175 last = text_.find_first_of(ustr.c_str(),cursor_pos); 5176 long f = static_cast<long>(first); 5177 long l = static_cast<long>(last); 5178 if (first == string::npos) 5179 f = -1; 5180 if (last == string::npos) 5181 l = static_cast<long>(text_.size()); 5182 5183 ++f; 5184 --l; 5185 5186 move_cursor(l+1); 5187 highlight_start = f; 5188 highlight_end = l; 5189 on_text_is_selected(); 5190 } 5191 else 5192 { 5193 if (state & base_window::SHIFT) 5194 { 5195 if (highlight_start <= highlight_end) 5196 { 5197 if (highlight_start == cursor_pos) 5198 shift_pos = highlight_end + 1; 5199 else 5200 shift_pos = highlight_start; 5201 } 5202 else 5203 { 5204 shift_pos = cursor_pos; 5205 } 5206 } 5207 5208 bool at_end = false; 5209 if (cursor_pos == 0 || cursor_pos == static_cast<long>(text_.size())) 5210 at_end = true; 5211 const long old_pos = cursor_pos; 5212 5213 unsigned long new_pos = mfont->compute_cursor_pos(get_text_rect(),text_,x,y); 5214 move_cursor(new_pos); 5215 5216 shift_pos = cursor_pos; 5217 5218 if (at_end && cursor_pos == old_pos) 5219 { 5220 highlight_start = 0; 5221 highlight_end = -1; 5222 on_no_text_selected(); 5223 } 5224 } 5225 5226 } 5227 else if (has_focus && rect.contains(x,y) == false) 5228 { 5229 t.stop(); 5230 has_focus = false; 5231 cursor_visible = false; 5232 shift_pos = -1; 5233 highlight_start = 0; 5234 highlight_end = -1; 5235 on_no_text_selected(); 5236 5237 if (focus_lost_handler.is_set()) 5238 focus_lost_handler(); 5239 parent.invalidate_rectangle(rect); 5240 } 5241 else 5242 { 5243 has_focus = false; 5244 } 5245 } 5246 5247 // ---------------------------------------------------------------------------------------- 5248 5249 void text_box:: on_keydown(unsigned long key,bool is_printable,unsigned long state)5250 on_keydown ( 5251 unsigned long key, 5252 bool is_printable, 5253 unsigned long state 5254 ) 5255 { 5256 // If the right click menu is up then we don't want to do anything with 5257 // the keyboard ourselves. Let the popup menu use the keyboard for now. 5258 if (right_click_menu.popup_menu_visible()) 5259 return; 5260 5261 if (has_focus && enabled && !hidden) 5262 { 5263 const ustring space_str = convert_utf8_to_utf32(std::string(" \t\n")); 5264 const bool shift = (state&base_window::KBD_MOD_SHIFT) != 0; 5265 const bool ctrl = (state&base_window::KBD_MOD_CONTROL) != 0; 5266 5267 if (shift && is_printable == false) 5268 { 5269 if (shift_pos == -1) 5270 { 5271 if (highlight_start <= highlight_end) 5272 { 5273 if (highlight_start == cursor_pos) 5274 shift_pos = highlight_end + 1; 5275 else 5276 shift_pos = highlight_start; 5277 } 5278 else 5279 { 5280 shift_pos = cursor_pos; 5281 } 5282 } 5283 } 5284 else 5285 { 5286 shift_pos = -1; 5287 } 5288 5289 if (key == base_window::KEY_LEFT) 5290 { 5291 if (cursor_pos != 0) 5292 { 5293 unsigned long new_pos; 5294 if (ctrl) 5295 { 5296 // find the first non-whitespace to our left 5297 std::string::size_type pos = text_.find_last_not_of(space_str.c_str(),cursor_pos); 5298 if (pos != std::string::npos) 5299 { 5300 pos = text_.find_last_of(space_str.c_str(),pos); 5301 if (pos != std::string::npos) 5302 new_pos = static_cast<unsigned long>(pos); 5303 else 5304 new_pos = 0; 5305 } 5306 else 5307 { 5308 new_pos = 0; 5309 } 5310 } 5311 else 5312 { 5313 new_pos = cursor_pos-1; 5314 } 5315 5316 move_cursor(new_pos); 5317 } 5318 else if (shift_pos == -1) 5319 { 5320 highlight_start = 0; 5321 highlight_end = -1; 5322 on_no_text_selected(); 5323 parent.invalidate_rectangle(rect); 5324 } 5325 5326 } 5327 else if (key == base_window::KEY_RIGHT) 5328 { 5329 if (cursor_pos != static_cast<long>(text_.size())) 5330 { 5331 unsigned long new_pos; 5332 if (ctrl) 5333 { 5334 // find the first non-whitespace to our left 5335 std::string::size_type pos = text_.find_first_not_of(space_str.c_str(),cursor_pos); 5336 if (pos != std::string::npos) 5337 { 5338 pos = text_.find_first_of(space_str.c_str(),pos); 5339 if (pos != std::string::npos) 5340 new_pos = static_cast<unsigned long>(pos+1); 5341 else 5342 new_pos = static_cast<unsigned long>(text_.size()); 5343 } 5344 else 5345 { 5346 new_pos = static_cast<unsigned long>(text_.size()); 5347 } 5348 } 5349 else 5350 { 5351 new_pos = cursor_pos+1; 5352 } 5353 5354 move_cursor(new_pos); 5355 } 5356 else if (shift_pos == -1) 5357 { 5358 highlight_start = 0; 5359 highlight_end = -1; 5360 on_no_text_selected(); 5361 parent.invalidate_rectangle(rect); 5362 } 5363 } 5364 else if (key == base_window::KEY_UP) 5365 { 5366 if (ctrl) 5367 { 5368 move_cursor(0); 5369 } 5370 else 5371 { 5372 const point origin(total_rect().left(), total_rect().top()); 5373 // move the cursor so the position that is just a few pixels above 5374 // the current cursor_rect 5375 move_cursor(mfont->compute_cursor_pos( 5376 get_text_rect(), text_, cursor_rect.left()+origin.x(), 5377 cursor_rect.top()+origin.y()-mfont->height()/2)); 5378 5379 } 5380 5381 if (shift_pos == -1) 5382 { 5383 highlight_start = 0; 5384 highlight_end = -1; 5385 on_no_text_selected(); 5386 parent.invalidate_rectangle(rect); 5387 } 5388 } 5389 else if (key == base_window::KEY_DOWN) 5390 { 5391 if (ctrl) 5392 { 5393 move_cursor(static_cast<unsigned long>(text_.size())); 5394 } 5395 else 5396 { 5397 const point origin(total_rect().left(), total_rect().top()); 5398 // move the cursor so the position that is just a few pixels above 5399 // the current cursor_rect 5400 move_cursor(mfont->compute_cursor_pos( 5401 get_text_rect(), text_, cursor_rect.left()+origin.x(), 5402 cursor_rect.bottom()+origin.y()+mfont->height()/2)); 5403 } 5404 5405 if (shift_pos == -1) 5406 { 5407 highlight_start = 0; 5408 highlight_end = -1; 5409 on_no_text_selected(); 5410 parent.invalidate_rectangle(rect); 5411 } 5412 } 5413 else if (is_printable) 5414 { 5415 if (ctrl) 5416 { 5417 if (key == 'a') 5418 { 5419 on_select_all(); 5420 } 5421 else if (key == 'c') 5422 { 5423 on_copy(); 5424 } 5425 else if (key == 'v') 5426 { 5427 on_paste(); 5428 } 5429 else if (key == 'x') 5430 { 5431 on_cut(); 5432 } 5433 } 5434 else 5435 { 5436 if (highlight_start <= highlight_end) 5437 { 5438 text_ = text_.substr(0,highlight_start) + static_cast<unichar>(key) + 5439 text_.substr(highlight_end+1,text_.size()-highlight_end-1); 5440 5441 adjust_total_rect(); 5442 move_cursor(highlight_start+1); 5443 highlight_start = 0; 5444 highlight_end = -1; 5445 on_no_text_selected(); 5446 } 5447 else 5448 { 5449 text_ = text_.substr(0,cursor_pos) + static_cast<unichar>(key) + 5450 text_.substr(cursor_pos,text_.size()-cursor_pos); 5451 adjust_total_rect(); 5452 move_cursor(cursor_pos+1); 5453 } 5454 5455 // send out the text modified event 5456 if (text_modified_handler.is_set()) 5457 text_modified_handler(); 5458 5459 } 5460 5461 if (key == '\n') 5462 { 5463 if (enter_key_handler.is_set()) 5464 enter_key_handler(); 5465 } 5466 } 5467 else if (key == base_window::KEY_BACKSPACE) 5468 { 5469 // if something is highlighted then delete that 5470 if (highlight_start <= highlight_end) 5471 { 5472 on_delete_selected(); 5473 } 5474 else if (cursor_pos != 0) 5475 { 5476 text_ = text_.erase(cursor_pos-1,1); 5477 adjust_total_rect(); 5478 move_cursor(cursor_pos-1); 5479 5480 // send out the text modified event 5481 if (text_modified_handler.is_set()) 5482 text_modified_handler(); 5483 } 5484 else 5485 { 5486 // do this just so it repaints itself right 5487 move_cursor(cursor_pos); 5488 } 5489 5490 } 5491 else if (key == base_window::KEY_DELETE) 5492 { 5493 // if something is highlighted then delete that 5494 if (highlight_start <= highlight_end) 5495 { 5496 on_delete_selected(); 5497 } 5498 else if (cursor_pos != static_cast<long>(text_.size())) 5499 { 5500 text_ = text_.erase(cursor_pos,1); 5501 5502 adjust_total_rect(); 5503 // send out the text modified event 5504 if (text_modified_handler.is_set()) 5505 text_modified_handler(); 5506 } 5507 else 5508 { 5509 // do this just so it repaints itself right 5510 move_cursor(cursor_pos); 5511 } 5512 5513 } 5514 else if (key == base_window::KEY_HOME) 5515 { 5516 if (ctrl) 5517 { 5518 move_cursor(0); 5519 } 5520 else if (cursor_pos != 0) 5521 { 5522 // find the start of the current line 5523 ustring::size_type pos = text_.find_last_of('\n',cursor_pos-1); 5524 if (pos == ustring::npos) 5525 pos = 0; 5526 else 5527 pos += 1; 5528 move_cursor(static_cast<unsigned long>(pos)); 5529 5530 } 5531 5532 if (shift_pos == -1) 5533 { 5534 highlight_start = 0; 5535 highlight_end = -1; 5536 on_no_text_selected(); 5537 parent.invalidate_rectangle(rect); 5538 } 5539 } 5540 else if (key == base_window::KEY_END) 5541 { 5542 if (ctrl) 5543 { 5544 move_cursor(static_cast<unsigned long>(text_.size())); 5545 } 5546 { 5547 ustring::size_type pos = text_.find_first_of('\n',cursor_pos); 5548 if (pos == ustring::npos) 5549 pos = text_.size(); 5550 5551 move_cursor(static_cast<unsigned long>(pos)); 5552 } 5553 5554 if (shift_pos == -1) 5555 { 5556 highlight_start = 0; 5557 highlight_end = -1; 5558 on_no_text_selected(); 5559 parent.invalidate_rectangle(rect); 5560 } 5561 } 5562 else if (key == base_window::KEY_PAGE_DOWN || key == base_window::KEY_PAGE_UP) 5563 { 5564 long jump_size = display_rect().height() - 5565 std::min(mfont->height()*3, display_rect().height()/5); 5566 5567 // if we are supposed to page up then just jump in the other direction 5568 if (key == base_window::KEY_PAGE_UP) 5569 jump_size = -jump_size; 5570 5571 scroll_to_rect(translate_rect(display_rect(), point(0, jump_size ))); 5572 } 5573 5574 cursor_visible = true; 5575 recent_movement = true; 5576 5577 } 5578 } 5579 5580 // ---------------------------------------------------------------------------------------- 5581 5582 void text_box:: on_string_put(const std::wstring & str)5583 on_string_put( 5584 const std::wstring &str 5585 ) 5586 { 5587 if (has_focus && enabled && !hidden) 5588 { 5589 ustring ustr = convert_wstring_to_utf32(str); 5590 if (highlight_start <= highlight_end) 5591 { 5592 text_ = text_.substr(0,highlight_start) + ustr + 5593 text_.substr(highlight_end+1,text_.size()-highlight_end-1); 5594 5595 adjust_total_rect(); 5596 move_cursor(highlight_start+ustr.size()); 5597 highlight_start = 0; 5598 highlight_end = -1; 5599 on_no_text_selected(); 5600 } 5601 else 5602 { 5603 text_ = text_.substr(0,cursor_pos) + ustr + 5604 text_.substr(cursor_pos,text_.size()-cursor_pos); 5605 5606 adjust_total_rect(); 5607 move_cursor(cursor_pos+ustr.size()); 5608 } 5609 5610 5611 // send out the text modified event 5612 if (text_modified_handler.is_set()) 5613 text_modified_handler(); 5614 } 5615 } 5616 5617 // ---------------------------------------------------------------------------------------- 5618 5619 void text_box:: move_cursor(unsigned long pos)5620 move_cursor ( 5621 unsigned long pos 5622 ) 5623 { 5624 using namespace std; 5625 const long old_cursor_pos = cursor_pos; 5626 5627 5628 5629 // figure out where the cursor is supposed to be 5630 cursor_rect = mfont->compute_cursor_rect(get_text_rect(), text_, pos); 5631 const point origin(total_rect().left(), total_rect().top()); 5632 5633 5634 cursor_pos = pos; 5635 5636 5637 const unsigned long padding = style->get_padding(*mfont); 5638 5639 // now scroll us so that we can see the current cursor 5640 scroll_to_rect(centered_rect(cursor_rect, cursor_rect.width() + padding + 6, cursor_rect.height() + 1)); 5641 5642 // adjust the cursor_rect so that it is relative to the total_rect 5643 cursor_rect = translate_rect(cursor_rect, -origin); 5644 5645 parent.set_im_pos(cursor_rect.left(), cursor_rect.top()); 5646 5647 if (old_cursor_pos != cursor_pos) 5648 { 5649 if (shift_pos != -1) 5650 { 5651 highlight_start = std::min(shift_pos,cursor_pos); 5652 highlight_end = std::max(shift_pos,cursor_pos)-1; 5653 } 5654 5655 if (highlight_start > highlight_end) 5656 on_no_text_selected(); 5657 else 5658 on_text_is_selected(); 5659 5660 recent_movement = true; 5661 cursor_visible = true; 5662 parent.invalidate_rectangle(display_rect()); 5663 } 5664 5665 if (shift_pos == -1) 5666 { 5667 highlight_start = 0; 5668 highlight_end = -1; 5669 } 5670 } 5671 5672 // ---------------------------------------------------------------------------------------- 5673 // ---------------------------------------------------------------------------------------- 5674 // perspective_display member functions 5675 // ---------------------------------------------------------------------------------------- 5676 // ---------------------------------------------------------------------------------------- 5677 5678 perspective_display:: perspective_display(drawable_window & w)5679 perspective_display( 5680 drawable_window& w 5681 ) : 5682 drawable(w,MOUSE_MOVE|MOUSE_CLICK|MOUSE_WHEEL) 5683 { 5684 clear_overlay(); 5685 enable_events(); 5686 } 5687 5688 // ---------------------------------------------------------------------------------------- 5689 5690 perspective_display:: ~perspective_display()5691 ~perspective_display( 5692 ) 5693 { 5694 disable_events(); 5695 parent.invalidate_rectangle(rect); 5696 } 5697 5698 // ---------------------------------------------------------------------------------------- 5699 5700 void perspective_display:: set_size(unsigned long width,unsigned long height)5701 set_size ( 5702 unsigned long width, 5703 unsigned long height 5704 ) 5705 { 5706 auto_mutex lock(m); 5707 rectangle old(rect); 5708 rect = resize_rect(rect,width,height); 5709 tform = camera_transform(tform.get_camera_pos(), 5710 tform.get_camera_looking_at(), 5711 tform.get_camera_up_direction(), 5712 tform.get_camera_field_of_view(), 5713 std::min(rect.width(),rect.height())); 5714 parent.invalidate_rectangle(old+rect); 5715 } 5716 5717 // ---------------------------------------------------------------------------------------- 5718 5719 void perspective_display:: add_overlay(const std::vector<overlay_line> & overlay)5720 add_overlay ( 5721 const std::vector<overlay_line>& overlay 5722 ) 5723 { 5724 auto_mutex M(m); 5725 if (overlay.size() == 0) 5726 return; 5727 // push this new overlay into our overlay vector 5728 overlay_lines.insert(overlay_lines.end(), overlay.begin(), overlay.end()); 5729 5730 for (unsigned long i = 0; i < overlay.size(); ++i) 5731 { 5732 sum_pts += overlay[i].p1; 5733 sum_pts += overlay[i].p2; 5734 max_pts.x() = std::max(overlay[i].p1.x(), max_pts.x()); 5735 max_pts.x() = std::max(overlay[i].p2.x(), max_pts.x()); 5736 max_pts.y() = std::max(overlay[i].p1.y(), max_pts.y()); 5737 max_pts.y() = std::max(overlay[i].p2.y(), max_pts.y()); 5738 max_pts.z() = std::max(overlay[i].p1.z(), max_pts.z()); 5739 max_pts.z() = std::max(overlay[i].p2.z(), max_pts.z()); 5740 } 5741 5742 tform = camera_transform(max_pts, 5743 sum_pts/(overlay_lines.size()*2+overlay_dots.size()), 5744 vector<double>(0,0,1), 5745 tform.get_camera_field_of_view(), 5746 std::min(rect.width(),rect.height())); 5747 5748 5749 // make the parent window redraw us now that we changed the overlay 5750 parent.invalidate_rectangle(rect); 5751 } 5752 5753 // ---------------------------------------------------------------------------------------- 5754 5755 void perspective_display:: add_overlay(const std::vector<overlay_dot> & overlay)5756 add_overlay ( 5757 const std::vector<overlay_dot>& overlay 5758 ) 5759 { 5760 auto_mutex M(m); 5761 if (overlay.size() == 0) 5762 return; 5763 5764 for (unsigned long i = 0; i < overlay.size(); ++i) 5765 { 5766 overlay_dots.push_back(overlay[i]); 5767 5768 sum_pts += overlay[i].p; 5769 max_pts.x() = std::max(overlay[i].p.x(), max_pts.x()); 5770 max_pts.y() = std::max(overlay[i].p.y(), max_pts.y()); 5771 max_pts.z() = std::max(overlay[i].p.z(), max_pts.z()); 5772 } 5773 5774 tform = camera_transform(max_pts, 5775 sum_pts/(overlay_lines.size()*2+overlay_dots.size()), 5776 vector<double>(0,0,1), 5777 tform.get_camera_field_of_view(), 5778 std::min(rect.width(),rect.height())); 5779 5780 5781 // make the parent window redraw us now that we changed the overlay 5782 parent.invalidate_rectangle(rect); 5783 } 5784 5785 // ---------------------------------------------------------------------------------------- 5786 5787 void perspective_display:: clear_overlay()5788 clear_overlay ( 5789 ) 5790 { 5791 auto_mutex lock(m); 5792 overlay_dots.clear(); 5793 overlay_lines.clear(); 5794 sum_pts = vector<double>(); 5795 max_pts = vector<double>(-std::numeric_limits<double>::infinity(), 5796 -std::numeric_limits<double>::infinity(), 5797 -std::numeric_limits<double>::infinity()); 5798 5799 parent.invalidate_rectangle(rect); 5800 } 5801 5802 // ---------------------------------------------------------------------------------------- 5803 5804 void perspective_display:: set_dot_double_clicked_handler(const any_function<void (const vector<double> &)> & event_handler_)5805 set_dot_double_clicked_handler ( 5806 const any_function<void(const vector<double>&)>& event_handler_ 5807 ) 5808 { 5809 auto_mutex M(m); 5810 dot_clicked_event_handler = event_handler_; 5811 } 5812 5813 // ---------------------------------------------------------------------------------------- 5814 5815 void perspective_display:: draw(const canvas & c) const5816 draw ( 5817 const canvas& c 5818 ) const 5819 { 5820 if (depth.nr() < (long)c.height() || depth.nc() < (long)c.width()) 5821 depth.set_size(c.height(), c.width()); 5822 assign_all_pixels(depth, std::numeric_limits<float>::infinity()); 5823 5824 rectangle area = rect.intersect(c); 5825 fill_rect(c, area, 0); 5826 for (unsigned long i = 0; i < overlay_lines.size(); ++i) 5827 { 5828 draw_line(c, tform(overlay_lines[i].p1)+rect.tl_corner(), 5829 tform(overlay_lines[i].p2)+rect.tl_corner(), 5830 overlay_lines[i].color, 5831 area); 5832 } 5833 for (unsigned long i = 0; i < overlay_dots.size(); ++i) 5834 { 5835 double scale, distance; 5836 point p = tform(overlay_dots[i].p, scale, distance) + rect.tl_corner(); 5837 if (area.contains(p) && depth[p.y()-c.top()][p.x()-c.left()] > distance) 5838 { 5839 depth[p.y()-c.top()][p.x()-c.left()] = distance; 5840 assign_pixel(c[p.y()-c.top()][p.x()-c.left()], overlay_dots[i].color); 5841 } 5842 } 5843 5844 } 5845 5846 // ---------------------------------------------------------------------------------------- 5847 5848 void perspective_display:: on_wheel_up(unsigned long)5849 on_wheel_up ( 5850 unsigned long //state 5851 ) 5852 { 5853 if (rect.contains(lastx,lasty) == false || hidden || !enabled) 5854 return; 5855 5856 const double alpha = 0.10; 5857 const vector<double> delta = alpha*(tform.get_camera_pos() - tform.get_camera_looking_at()); 5858 tform = camera_transform( 5859 tform.get_camera_pos() - delta, 5860 tform.get_camera_looking_at(), 5861 tform.get_camera_up_direction(), 5862 tform.get_camera_field_of_view(), 5863 std::min(rect.width(),rect.height())); 5864 parent.invalidate_rectangle(rect); 5865 } 5866 5867 // ---------------------------------------------------------------------------------------- 5868 5869 void perspective_display:: on_wheel_down(unsigned long)5870 on_wheel_down ( 5871 unsigned long //state 5872 ) 5873 { 5874 if (rect.contains(lastx,lasty) == false || hidden || !enabled) 5875 return; 5876 5877 const double alpha = 0.10; 5878 const vector<double> delta = alpha*(tform.get_camera_pos() - tform.get_camera_looking_at()); 5879 tform = camera_transform( 5880 tform.get_camera_pos() + delta, 5881 tform.get_camera_looking_at(), 5882 tform.get_camera_up_direction(), 5883 tform.get_camera_field_of_view(), 5884 std::min(rect.width(),rect.height())); 5885 parent.invalidate_rectangle(rect); 5886 } 5887 5888 // ---------------------------------------------------------------------------------------- 5889 5890 void perspective_display:: on_mouse_down(unsigned long btn,unsigned long,long x,long y,bool is_double_click)5891 on_mouse_down ( 5892 unsigned long btn, 5893 unsigned long, //state 5894 long x, 5895 long y, 5896 bool is_double_click 5897 ) 5898 { 5899 if (btn == base_window::LEFT || btn == base_window::RIGHT) 5900 { 5901 last = point(x,y); 5902 } 5903 if (is_double_click && btn == base_window::LEFT && enabled && !hidden && overlay_dots.size() != 0) 5904 { 5905 double best_dist = std::numeric_limits<double>::infinity(); 5906 unsigned long best_idx = 0; 5907 const dpoint pp(x,y); 5908 for (unsigned long i = 0; i < overlay_dots.size(); ++i) 5909 { 5910 dpoint p = tform(overlay_dots[i].p) + rect.tl_corner(); 5911 double dist = length_squared(p-pp); 5912 if (dist < best_dist) 5913 { 5914 best_dist = dist; 5915 best_idx = i; 5916 } 5917 } 5918 if (dot_clicked_event_handler.is_set()) 5919 dot_clicked_event_handler(overlay_dots[best_idx].p); 5920 } 5921 } 5922 5923 // ---------------------------------------------------------------------------------------- 5924 5925 void perspective_display:: on_mouse_move(unsigned long state,long x,long y)5926 on_mouse_move ( 5927 unsigned long state, 5928 long x, 5929 long y 5930 ) 5931 { 5932 if (!enabled || hidden) 5933 return; 5934 5935 if (state == base_window::LEFT) 5936 { 5937 const point cur(x, y); 5938 dpoint delta = last-cur; 5939 last = cur; 5940 5941 const vector<double> radius = tform.get_camera_pos()-tform.get_camera_looking_at(); 5942 delta *= 2*pi*length(radius)/600.0; 5943 vector<double> tangent_x = tform.get_camera_up_direction().cross(radius).normalize(); 5944 vector<double> tangent_y = radius.cross(tangent_x).normalize(); 5945 vector<double> new_pos = tform.get_camera_pos() + tangent_x*delta.x() + tangent_y*-delta.y(); 5946 5947 // now make it have the correct radius relative to the looking at point. 5948 new_pos = (new_pos-tform.get_camera_looking_at()).normalize()*length(radius) + tform.get_camera_looking_at(); 5949 5950 tform = camera_transform(new_pos, 5951 tform.get_camera_looking_at(), 5952 tangent_y, 5953 tform.get_camera_field_of_view(), 5954 std::min(rect.width(),rect.height())); 5955 parent.invalidate_rectangle(rect); 5956 } 5957 else if (state == (base_window::LEFT|base_window::SHIFT) || 5958 state == base_window::RIGHT) 5959 { 5960 const point cur(x, y); 5961 dpoint delta = last-cur; 5962 last = cur; 5963 5964 const vector<double> radius = tform.get_camera_pos()-tform.get_camera_looking_at(); 5965 delta *= 2*pi*length(radius)/600.0; 5966 vector<double> tangent_x = tform.get_camera_up_direction().cross(radius).normalize(); 5967 vector<double> tangent_y = radius.cross(tangent_x).normalize(); 5968 5969 vector<double> offset = tangent_x*delta.x() + tangent_y*-delta.y(); 5970 5971 5972 tform = camera_transform( 5973 tform.get_camera_pos()+offset, 5974 tform.get_camera_looking_at()+offset, 5975 tform.get_camera_up_direction(), 5976 tform.get_camera_field_of_view(), 5977 std::min(rect.width(),rect.height())); 5978 parent.invalidate_rectangle(rect); 5979 } 5980 } 5981 5982 // ---------------------------------------------------------------------------------------- 5983 // ---------------------------------------------------------------------------------------- 5984 // image_display member functions 5985 // ---------------------------------------------------------------------------------------- 5986 // ---------------------------------------------------------------------------------------- 5987 5988 namespace impl 5989 { 5990 class image_display_functor 5991 { 5992 const std::string str; 5993 const member_function_pointer<const std::string&> mfp; 5994 public: image_display_functor(const std::string & str_,const member_function_pointer<const std::string &> & mfp_)5995 image_display_functor ( 5996 const std::string& str_, 5997 const member_function_pointer<const std::string&>& mfp_ 5998 ) : str(str_), 5999 mfp(mfp_) 6000 {} 6001 operator ()() const6002 void operator() ( 6003 ) const { mfp(str); } 6004 }; 6005 } 6006 6007 image_display:: image_display(drawable_window & w)6008 image_display( 6009 drawable_window& w 6010 ): 6011 scrollable_region(w,KEYBOARD_EVENTS), 6012 zoom_in_scale(1), 6013 zoom_out_scale(1), 6014 drawing_rect(true), 6015 rect_is_selected(false), 6016 selected_rect(0), 6017 default_rect_color(255,0,0,255), 6018 parts_menu(w), 6019 part_width(100), // "parts" circles are drawn 1.0/part_width size on the screen relative to the size of the bounding rectangle. 6020 overlay_editing_enabled(true), 6021 highlight_timer(*this, &image_display::timer_event_unhighlight_rect), 6022 highlighted_rect(std::numeric_limits<unsigned long>::max()), 6023 holding_shift_key(false) 6024 { 6025 enable_mouse_drag(); 6026 6027 highlight_timer.set_delay_time(250); 6028 set_horizontal_scroll_increment(1); 6029 set_vertical_scroll_increment(1); 6030 set_horizontal_mouse_wheel_scroll_increment(30); 6031 set_vertical_mouse_wheel_scroll_increment(30); 6032 6033 parts_menu.disable(); 6034 6035 6036 enable_events(); 6037 } 6038 6039 // ---------------------------------------------------------------------------------------- 6040 6041 void image_display:: on_part_add(const std::string & part_name)6042 on_part_add ( 6043 const std::string& part_name 6044 ) 6045 { 6046 if (!rect_is_selected) 6047 return; 6048 6049 const point loc = last_right_click_pos; 6050 6051 // Transform loc from gui window space into the space used by the overlay 6052 // rectangles (i.e. relative to the raw image) 6053 const point origin(total_rect().tl_corner()); 6054 point c1 = loc - origin; 6055 if (zoom_in_scale != 1) 6056 { 6057 c1 = c1/zoom_in_scale; 6058 } 6059 else if (zoom_out_scale != 1) 6060 { 6061 c1 = c1*zoom_out_scale; 6062 } 6063 6064 overlay_rects[selected_rect].parts[part_name] = c1; 6065 parent.invalidate_rectangle(rect); 6066 6067 if (event_handler.is_set()) 6068 event_handler(); 6069 } 6070 6071 // ---------------------------------------------------------------------------------------- 6072 6073 image_display:: ~image_display()6074 ~image_display( 6075 ) 6076 { 6077 highlight_timer.stop_and_wait(); 6078 disable_events(); 6079 parent.invalidate_rectangle(rect); 6080 } 6081 6082 // ---------------------------------------------------------------------------------------- 6083 6084 rectangle image_display:: get_image_display_rect() const6085 get_image_display_rect ( 6086 ) const 6087 { 6088 if (zoom_in_scale != 1) 6089 { 6090 return rectangle(0,0, img.nc()*zoom_in_scale-1, img.nr()*zoom_in_scale-1); 6091 } 6092 else if (zoom_out_scale != 1) 6093 { 6094 return rectangle(0,0, img.nc()/zoom_out_scale-1, img.nr()/zoom_out_scale-1); 6095 } 6096 else 6097 { 6098 return dlib::get_rect(img); 6099 } 6100 } 6101 6102 // ---------------------------------------------------------------------------------------- 6103 6104 void image_display:: add_overlay(const overlay_rect & overlay)6105 add_overlay ( 6106 const overlay_rect& overlay 6107 ) 6108 { 6109 auto_mutex M(m); 6110 // push this new overlay into our overlay vector 6111 overlay_rects.push_back(overlay); 6112 6113 // make the parent window redraw us now that we changed the overlay 6114 parent.invalidate_rectangle(rect); 6115 } 6116 6117 // ---------------------------------------------------------------------------------------- 6118 6119 void image_display:: add_overlay(const overlay_line & overlay)6120 add_overlay ( 6121 const overlay_line& overlay 6122 ) 6123 { 6124 auto_mutex M(m); 6125 6126 // push this new overlay into our overlay vector 6127 overlay_lines.push_back(overlay); 6128 6129 // make the parent window redraw us now that we changed the overlay 6130 parent.invalidate_rectangle(get_rect_on_screen(rectangle(overlay.p1, overlay.p2))); 6131 } 6132 6133 // ---------------------------------------------------------------------------------------- 6134 6135 void image_display:: add_overlay(const overlay_circle & overlay)6136 add_overlay ( 6137 const overlay_circle& overlay 6138 ) 6139 { 6140 auto_mutex M(m); 6141 6142 // push this new overlay into our overlay vector 6143 overlay_circles.push_back(overlay); 6144 6145 // make the parent window redraw us now that we changed the overlay 6146 parent.invalidate_rectangle(rect); 6147 } 6148 6149 // ---------------------------------------------------------------------------------------- 6150 6151 void image_display:: add_overlay(const std::vector<overlay_rect> & overlay)6152 add_overlay ( 6153 const std::vector<overlay_rect>& overlay 6154 ) 6155 { 6156 auto_mutex M(m); 6157 6158 // push this new overlay into our overlay vector 6159 overlay_rects.insert(overlay_rects.end(), overlay.begin(), overlay.end()); 6160 6161 // make the parent window redraw us now that we changed the overlay 6162 parent.invalidate_rectangle(rect); 6163 } 6164 6165 // ---------------------------------------------------------------------------------------- 6166 6167 void image_display:: add_overlay(const std::vector<overlay_line> & overlay)6168 add_overlay ( 6169 const std::vector<overlay_line>& overlay 6170 ) 6171 { 6172 auto_mutex M(m); 6173 6174 // push this new overlay into our overlay vector 6175 overlay_lines.insert(overlay_lines.end(), overlay.begin(), overlay.end()); 6176 6177 // make the parent window redraw us now that we changed the overlay 6178 parent.invalidate_rectangle(rect); 6179 } 6180 6181 // ---------------------------------------------------------------------------------------- 6182 6183 void image_display:: add_overlay(const std::vector<overlay_circle> & overlay)6184 add_overlay ( 6185 const std::vector<overlay_circle>& overlay 6186 ) 6187 { 6188 auto_mutex M(m); 6189 6190 // push this new overlay into our overlay vector 6191 overlay_circles.insert(overlay_circles.end(), overlay.begin(), overlay.end()); 6192 6193 // make the parent window redraw us now that we changed the overlay 6194 parent.invalidate_rectangle(rect); 6195 } 6196 6197 // ---------------------------------------------------------------------------------------- 6198 6199 void image_display:: clear_overlay()6200 clear_overlay ( 6201 ) 6202 { 6203 auto_mutex M(m); 6204 overlay_rects.clear(); 6205 overlay_lines.clear(); 6206 overlay_circles.clear(); 6207 parent.invalidate_rectangle(rect); 6208 } 6209 6210 // ---------------------------------------------------------------------------------------- 6211 6212 rectangle image_display:: get_rect_on_screen(rectangle orect) const6213 get_rect_on_screen ( 6214 rectangle orect 6215 ) const 6216 { 6217 const point origin(total_rect().tl_corner()); 6218 orect.left() = orect.left()*zoom_in_scale/zoom_out_scale; 6219 orect.top() = orect.top()*zoom_in_scale/zoom_out_scale; 6220 if (zoom_in_scale != 1) 6221 { 6222 // make it so the box surrounds the pixels when we zoom in. 6223 orect.right() = (orect.right()+1)*zoom_in_scale/zoom_out_scale; 6224 orect.bottom() = (orect.bottom()+1)*zoom_in_scale/zoom_out_scale; 6225 } 6226 else 6227 { 6228 orect.right() = orect.right()*zoom_in_scale/zoom_out_scale; 6229 orect.bottom() = orect.bottom()*zoom_in_scale/zoom_out_scale; 6230 } 6231 6232 return translate_rect(orect, origin); 6233 } 6234 6235 // ---------------------------------------------------------------------------------------- 6236 6237 rectangle image_display:: get_rect_on_screen(unsigned long idx) const6238 get_rect_on_screen ( 6239 unsigned long idx 6240 ) const 6241 { 6242 return get_rect_on_screen(overlay_rects[idx].rect); 6243 } 6244 6245 // ---------------------------------------------------------------------------------------- 6246 6247 void image_display:: draw(const canvas & c) const6248 draw ( 6249 const canvas& c 6250 ) const 6251 { 6252 scrollable_region::draw(c); 6253 6254 rectangle area = display_rect().intersect(c); 6255 if (area.is_empty()) 6256 return; 6257 6258 const point origin(total_rect().tl_corner()); 6259 6260 // draw the image on the screen 6261 const double scale = zoom_out_scale/(double)zoom_in_scale; 6262 const rectangle img_area = total_rect().intersect(area); 6263 for (long row = img_area.top(); row <= img_area.bottom(); ++row) 6264 { 6265 const long rc = row-c.top(); 6266 const long rimg = (row-origin.y())*scale; 6267 for (long col = img_area.left(); col <= img_area.right(); ++col) 6268 { 6269 assign_pixel(c[rc][col-c.left()], 6270 img[rimg][(col-origin.x())*scale]); 6271 } 6272 } 6273 6274 // draw the mouse cross-hairs 6275 if (holding_shift_key && total_rect().contains(lastx,lasty) ) 6276 { 6277 draw_line(c, point(lastx,-10000), point(lastx,100000),rgb_pixel(255,255,0), area); 6278 draw_line(c, point(-10000,lasty), point(100000,lasty),rgb_pixel(255,255,0), area); 6279 } 6280 6281 // now draw all the overlay rectangles 6282 for (unsigned long i = 0; i < overlay_rects.size(); ++i) 6283 { 6284 const rectangle orect = get_rect_on_screen(i); 6285 rgb_alpha_pixel color = overlay_rects[i].color; 6286 // draw crossed out boxes slightly faded 6287 if (overlay_rects[i].crossed_out) 6288 color.alpha = 150; 6289 6290 if (rect_is_selected && selected_rect == i) 6291 { 6292 draw_rectangle(c, orect, invert_pixel(color), area); 6293 } 6294 else if (highlighted_rect < overlay_rects.size() && highlighted_rect == i) 6295 { 6296 // Draw the rectangle wider and with a slightly different color that tapers 6297 // out at the edges of the line. 6298 hsi_pixel temp; 6299 assign_pixel(temp, 0); 6300 assign_pixel(temp, overlay_rects[i].color); 6301 temp.s = 255; 6302 temp.h = temp.h + 20; 6303 if (temp.i < 245) 6304 temp.i += 10; 6305 rgb_pixel p; 6306 assign_pixel(p, temp); 6307 rgb_alpha_pixel po, po2; 6308 assign_pixel(po, p); 6309 po.alpha = 160; 6310 po2 = po; 6311 po2.alpha = 90; 6312 draw_rectangle(c, grow_rect(orect,2), po2, area); 6313 draw_rectangle(c, grow_rect(orect,1), po, area); 6314 draw_rectangle(c, orect, p, area); 6315 draw_rectangle(c, shrink_rect(orect,1), po, area); 6316 draw_rectangle(c, shrink_rect(orect,2), po2, area); 6317 } 6318 else 6319 { 6320 draw_rectangle(c, orect, color, area); 6321 } 6322 6323 if (overlay_rects[i].label.size() != 0) 6324 { 6325 // make a rectangle that is at the spot we want to draw our string 6326 rectangle r(orect.br_corner(), c.br_corner()); 6327 mfont->draw_string(c, r, overlay_rects[i].label, color, 0, 6328 std::string::npos, area); 6329 } 6330 6331 6332 // draw circles for each "part" in this overlay rectangle. 6333 std::map<std::string,point>::const_iterator itr; 6334 for (itr = overlay_rects[i].parts.begin(); itr != overlay_rects[i].parts.end(); ++itr) 6335 { 6336 if (itr->second == OBJECT_PART_NOT_PRESENT) 6337 continue; 6338 6339 const long part_size = (long)std::max(1.0,std::round(std::sqrt(orect.area())/part_width)); 6340 rectangle temp = centered_rect(get_rect_on_screen(centered_rect(itr->second,1,1)), part_size, part_size); 6341 6342 if (rect_is_selected && selected_rect == i && 6343 selected_part_name.size() != 0 && selected_part_name == itr->first) 6344 { 6345 draw_circle(c, center(temp), temp.width(), invert_pixel(color), area); 6346 } 6347 else 6348 { 6349 draw_circle(c, center(temp), temp.width(), color, area); 6350 } 6351 6352 // make a rectangle that is at the spot we want to draw our string 6353 rectangle r((temp.br_corner() + temp.bl_corner())/2, 6354 c.br_corner()); 6355 mfont->draw_string(c, r, itr->first, color, 0, 6356 std::string::npos, area); 6357 } 6358 6359 if (overlay_rects[i].crossed_out) 6360 { 6361 if (rect_is_selected && selected_rect == i) 6362 { 6363 draw_line(c, orect.tl_corner(), orect.br_corner(),invert_pixel(color), area); 6364 draw_line(c, orect.bl_corner(), orect.tr_corner(),invert_pixel(color), area); 6365 } 6366 else 6367 { 6368 draw_line(c, orect.tl_corner(), orect.br_corner(),color, area); 6369 draw_line(c, orect.bl_corner(), orect.tr_corner(),color, area); 6370 } 6371 } 6372 } 6373 6374 // now draw all the overlay lines 6375 for (unsigned long i = 0; i < overlay_lines.size(); ++i) 6376 { 6377 draw_line(c, 6378 zoom_in_scale*(overlay_lines[i].p1+dpoint(0.5,0.5))/zoom_out_scale + origin, 6379 zoom_in_scale*(overlay_lines[i].p2+dpoint(0.5,0.5))/zoom_out_scale + origin, 6380 overlay_lines[i].color, area); 6381 } 6382 6383 // now draw all the overlay circles 6384 for (unsigned long i = 0; i < overlay_circles.size(); ++i) 6385 { 6386 const dpoint center = (double)zoom_in_scale*(overlay_circles[i].center+dpoint(0.5,0.5))/zoom_out_scale + origin; 6387 const double radius = zoom_in_scale*overlay_circles[i].radius/zoom_out_scale; 6388 draw_circle(c, 6389 center, 6390 radius, 6391 overlay_circles[i].color, area); 6392 6393 if (overlay_circles[i].label.size() != 0) 6394 { 6395 const point temp = center + dpoint(0,radius); 6396 6397 // make a rectangle that is at the spot we want to draw our string 6398 rectangle r(temp, c.br_corner()); 6399 mfont->draw_string(c, r, overlay_circles[i].label, overlay_circles[i].color, 0, 6400 std::string::npos, area); 6401 } 6402 } 6403 6404 if (drawing_rect) 6405 draw_rectangle(c, rect_to_draw, invert_pixel(default_rect_color), area); 6406 } 6407 6408 // ---------------------------------------------------------------------------------------- 6409 6410 void image_display:: on_keydown(unsigned long key,bool is_printable,unsigned long state)6411 on_keydown ( 6412 unsigned long key, 6413 bool is_printable, 6414 unsigned long state 6415 ) 6416 { 6417 scrollable_region::on_keydown(key,is_printable, state); 6418 6419 if (!is_printable && key==base_window::KEY_SHIFT) 6420 { 6421 if (!holding_shift_key) 6422 { 6423 holding_shift_key = true; 6424 parent.invalidate_rectangle(rect); 6425 } 6426 } 6427 else if (holding_shift_key) 6428 { 6429 holding_shift_key = false; 6430 parent.invalidate_rectangle(rect); 6431 } 6432 6433 if (!is_printable && !hidden && enabled && rect_is_selected && 6434 (key == base_window::KEY_BACKSPACE || key == base_window::KEY_DELETE)) 6435 { 6436 moving_overlay = false; 6437 rect_is_selected = false; 6438 parts_menu.disable(); 6439 if (selected_part_name.size() == 0) 6440 overlay_rects.erase(overlay_rects.begin() + selected_rect); 6441 else 6442 overlay_rects[selected_rect].parts.erase(selected_part_name); 6443 parent.invalidate_rectangle(rect); 6444 6445 if (event_handler.is_set()) 6446 event_handler(); 6447 } 6448 6449 if (!hidden && enabled && rect_is_selected && 6450 ((is_printable && key == 'i') || (!is_printable && key==base_window::KEY_END))) 6451 { 6452 overlay_rects[selected_rect].crossed_out = !overlay_rects[selected_rect].crossed_out; 6453 parent.invalidate_rectangle(rect); 6454 6455 if (event_handler.is_set()) 6456 event_handler(); 6457 } 6458 } 6459 6460 // ---------------------------------------------------------------------------------------- 6461 6462 void image_display:: add_labelable_part_name(const std::string & name)6463 add_labelable_part_name ( 6464 const std::string& name 6465 ) 6466 { 6467 auto_mutex lock(m); 6468 if (part_names.insert(name).second) 6469 { 6470 member_function_pointer<const std::string&> mfp; 6471 mfp.set(*this,&image_display::on_part_add); 6472 parts_menu.menu().add_menu_item(menu_item_text("Add " + name,impl::image_display_functor(name,mfp))); 6473 } 6474 } 6475 6476 // ---------------------------------------------------------------------------------------- 6477 6478 void image_display:: clear_labelable_part_names()6479 clear_labelable_part_names ( 6480 ) 6481 { 6482 auto_mutex lock(m); 6483 part_names.clear(); 6484 parts_menu.menu().clear(); 6485 } 6486 6487 // ---------------------------------------------------------------------------------------- 6488 6489 namespace 6490 { 6491 only_contains_equal_sized_int_strings(const std::map<std::string,point> & parts)6492 bool only_contains_equal_sized_int_strings( 6493 const std::map<std::string,point>& parts 6494 ) 6495 { 6496 if (parts.size() == 0) 6497 return true; 6498 6499 const size_t string_size = parts.begin()->first.size(); 6500 for (const auto& p : parts) 6501 { 6502 if (p.first.size() != string_size) 6503 return false; 6504 for (auto ch : p.first) 6505 if (!std::isdigit(ch)) 6506 return false; 6507 } 6508 return true; 6509 } 6510 zero_pad_part_names(std::map<std::string,point> & parts)6511 void zero_pad_part_names ( 6512 std::map<std::string,point>& parts 6513 ) 6514 { 6515 if (parts.size() < 5) 6516 return; 6517 6518 const size_t num_digits_required = 1+std::floor(std::log10(parts.size()-1)); 6519 // if the first thing in the map has the right number of digits then assume 6520 // everything is fine. 6521 if (parts.begin()->first.size() == num_digits_required) 6522 return; 6523 6524 std::map<std::string,point> new_parts; 6525 6526 for (auto& p : parts) 6527 { 6528 auto key = p.first; 6529 while (key.size() < num_digits_required) 6530 key = '0' + key; 6531 new_parts[key] = p.second; 6532 } 6533 6534 parts.swap(new_parts); 6535 } 6536 6537 } 6538 6539 void image_display:: on_mouse_down(unsigned long btn,unsigned long state,long x,long y,bool is_double_click)6540 on_mouse_down ( 6541 unsigned long btn, 6542 unsigned long state, 6543 long x, 6544 long y, 6545 bool is_double_click 6546 ) 6547 { 6548 scrollable_region::on_mouse_down(btn, state, x, y, is_double_click); 6549 6550 if (state&base_window::SHIFT) 6551 { 6552 holding_shift_key = true; 6553 } 6554 else if (holding_shift_key) 6555 { 6556 holding_shift_key = false; 6557 parent.invalidate_rectangle(rect); 6558 } 6559 6560 // if the user shift left clicks when a rect is selected add a part annotation, but 6561 // only if they haven't explicitly indicated part names. If they have part names 6562 // then make them pick them from the right click menu. But if they haven't set 6563 // part names then we will automatically create integer numbered parts starting from 0. 6564 // We will also only allow auto part numbering if all the parts are already 6565 // numbers. 6566 if (btn == base_window::LEFT && (state&base_window::SHIFT) && rect_is_selected && 6567 part_names.size() == 0 && only_contains_equal_sized_int_strings(overlay_rects[selected_rect].parts)) 6568 { 6569 // Find the next part name. Just find the next unused integer starting from 0. 6570 int part_num = 0; 6571 size_t num_digits_required = 0; 6572 for (const auto& p : overlay_rects[selected_rect].parts) 6573 { 6574 num_digits_required = p.first.size(); 6575 const int num = std::atoi(p.first.c_str()); 6576 if (num != part_num) 6577 break; 6578 ++part_num; 6579 } 6580 std::string part_name = std::to_string(part_num); 6581 // pad part name so it's the same length as the other parts. 6582 while (part_name.size() < num_digits_required) 6583 part_name = '0' + part_name; 6584 6585 6586 const point loc(x,y); 6587 6588 // Transform loc from gui window space into the space used by the overlay 6589 // rectangles (i.e. relative to the raw image) 6590 const point origin(total_rect().tl_corner()); 6591 point c1 = loc - origin; 6592 if (zoom_in_scale != 1) 6593 { 6594 c1 = c1/zoom_in_scale; 6595 } 6596 else if (zoom_out_scale != 1) 6597 { 6598 c1 = c1*zoom_out_scale; 6599 } 6600 6601 overlay_rects[selected_rect].parts[part_name] = c1; 6602 6603 zero_pad_part_names(overlay_rects[selected_rect].parts); 6604 parent.invalidate_rectangle(rect); 6605 6606 6607 if (event_handler.is_set()) 6608 event_handler(); 6609 6610 return; 6611 } 6612 6613 6614 if (rect.contains(x,y) == false || hidden || !enabled) 6615 return; 6616 6617 if (image_clicked_handler.is_set()) 6618 { 6619 const point origin(total_rect().tl_corner()); 6620 point p(x,y); 6621 p -= origin; 6622 if (zoom_in_scale != 1) 6623 p = p/zoom_in_scale; 6624 else if (zoom_out_scale != 1) 6625 p = p*zoom_out_scale; 6626 6627 if (dlib::get_rect(img).contains(p)) 6628 image_clicked_handler(p, is_double_click, btn); 6629 } 6630 6631 if (!overlay_editing_enabled) 6632 return; 6633 6634 if (btn == base_window::RIGHT && (state&base_window::SHIFT)) 6635 { 6636 const bool rect_was_selected = rect_is_selected; 6637 parts_menu.disable(); 6638 6639 long best_dist = std::numeric_limits<long>::max(); 6640 long best_idx = 0; 6641 std::string best_part; 6642 6643 // check if this click landed on any of the overlay rectangles 6644 for (unsigned long i = 0; i < overlay_rects.size(); ++i) 6645 { 6646 const rectangle orect = get_rect_on_screen(i); 6647 6648 const long dist = distance_to_rect_edge(orect, point(x,y)); 6649 6650 if (dist < best_dist) 6651 { 6652 best_dist = dist; 6653 best_idx = i; 6654 best_part.clear(); 6655 } 6656 6657 std::map<std::string,point>::const_iterator itr; 6658 for (itr = overlay_rects[i].parts.begin(); itr != overlay_rects[i].parts.end(); ++itr) 6659 { 6660 if (itr->second == OBJECT_PART_NOT_PRESENT) 6661 continue; 6662 6663 const long part_size = (long)std::max(1.0,std::round(std::sqrt(orect.area())/part_width)); 6664 rectangle temp = centered_rect(get_rect_on_screen(centered_rect(itr->second,1,1)), part_size, part_size); 6665 point c = center(temp); 6666 6667 // distance from edge of part circle 6668 const long dist = static_cast<long>(std::abs(length(c - point(x,y)) + 0.5 - temp.width())); 6669 if (dist < best_dist) 6670 { 6671 best_idx = i; 6672 best_dist = dist; 6673 best_part = itr->first; 6674 } 6675 } 6676 } 6677 6678 6679 if (best_dist < 13) 6680 { 6681 moving_overlay = true; 6682 moving_rect = best_idx; 6683 moving_part_name = best_part; 6684 // If we are moving one of the sides of the rectangle rather than one of 6685 // the parts circles then we need to figure out which side of the rectangle 6686 // we are moving. 6687 if (best_part.size() == 0) 6688 { 6689 // which side is the click closest to? 6690 const rectangle orect = get_rect_on_screen(best_idx); 6691 const point p = nearest_point(orect,point(x,y)); 6692 long dist_left = std::abs(p.x()-orect.left()); 6693 long dist_top = std::abs(p.y()-orect.top()); 6694 long dist_right = std::abs(p.x()-orect.right()); 6695 long dist_bottom = std::abs(p.y()-orect.bottom()); 6696 long min_val = std::min(std::min(dist_left,dist_right),std::min(dist_top,dist_bottom)); 6697 if (dist_left == min_val) 6698 moving_what = MOVING_RECT_LEFT; 6699 else if (dist_top == min_val) 6700 moving_what = MOVING_RECT_TOP; 6701 else if (dist_right == min_val) 6702 moving_what = MOVING_RECT_RIGHT; 6703 else 6704 moving_what = MOVING_RECT_BOTTOM; 6705 } 6706 else 6707 { 6708 moving_what = MOVING_PART; 6709 } 6710 // Do this to make the moving stuff snap to the mouse immediately. 6711 on_mouse_move(state|btn,x,y); 6712 } 6713 6714 if (rect_was_selected) 6715 parent.invalidate_rectangle(rect); 6716 6717 return; 6718 } 6719 6720 if (btn == base_window::RIGHT && rect_is_selected) 6721 { 6722 last_right_click_pos = point(x,y); 6723 parts_menu.set_rect(rect); 6724 return; 6725 } 6726 6727 if (btn == base_window::LEFT && (state&base_window::CONTROL) && !drawing_rect) 6728 { 6729 long best_dist = std::numeric_limits<long>::max(); 6730 long best_idx = 0; 6731 // check if this click landed on any of the overlay rectangles 6732 for (unsigned long i = 0; i < overlay_rects.size(); ++i) 6733 { 6734 const rectangle orect = get_rect_on_screen(i); 6735 const long dist = distance_to_rect_edge(orect, point(x,y)); 6736 6737 if (dist < best_dist) 6738 { 6739 best_dist = dist; 6740 best_idx = i; 6741 } 6742 } 6743 if (best_dist < 13) 6744 { 6745 overlay_rects[best_idx].label = default_rect_label; 6746 overlay_rects[best_idx].color = default_rect_color; 6747 highlighted_rect = best_idx; 6748 highlight_timer.stop(); 6749 highlight_timer.start(); 6750 if (event_handler.is_set()) 6751 event_handler(); 6752 parent.invalidate_rectangle(rect); 6753 } 6754 return; 6755 } 6756 6757 6758 if (!is_double_click && btn == base_window::LEFT && (state&base_window::SHIFT)) 6759 { 6760 drawing_rect = true; 6761 rect_anchor = point(x,y); 6762 6763 if (rect_is_selected) 6764 { 6765 rect_is_selected = false; 6766 parts_menu.disable(); 6767 parent.invalidate_rectangle(rect); 6768 } 6769 } 6770 else if (drawing_rect) 6771 { 6772 if (rect_is_selected) 6773 { 6774 rect_is_selected = false; 6775 parts_menu.disable(); 6776 } 6777 6778 drawing_rect = false; 6779 parent.invalidate_rectangle(rect); 6780 } 6781 else if (is_double_click) 6782 { 6783 const bool rect_was_selected = rect_is_selected; 6784 rect_is_selected = false; 6785 parts_menu.disable(); 6786 6787 long best_dist = std::numeric_limits<long>::max(); 6788 long best_idx = 0; 6789 std::string best_part; 6790 6791 // check if this click landed on any of the overlay rectangles 6792 for (unsigned long i = 0; i < overlay_rects.size(); ++i) 6793 { 6794 const rectangle orect = get_rect_on_screen(i); 6795 6796 const long dist = distance_to_rect_edge(orect, point(x,y)); 6797 6798 if (dist < best_dist) 6799 { 6800 best_dist = dist; 6801 best_idx = i; 6802 best_part.clear(); 6803 } 6804 6805 std::map<std::string,point>::const_iterator itr; 6806 for (itr = overlay_rects[i].parts.begin(); itr != overlay_rects[i].parts.end(); ++itr) 6807 { 6808 if (itr->second == OBJECT_PART_NOT_PRESENT) 6809 continue; 6810 6811 const long part_size = (long)std::max(1.0,std::round(std::sqrt(orect.area())/part_width)); 6812 rectangle temp = centered_rect(get_rect_on_screen(centered_rect(itr->second,1,1)), part_size, part_size); 6813 point c = center(temp); 6814 6815 // distance from edge of part circle 6816 const long dist = static_cast<long>(std::abs(length(c - point(x,y)) + 0.5 - temp.width())); 6817 if (dist < best_dist) 6818 { 6819 best_idx = i; 6820 best_dist = dist; 6821 best_part = itr->first; 6822 } 6823 } 6824 } 6825 6826 6827 if (best_dist < 13) 6828 { 6829 rect_is_selected = true; 6830 if (part_names.size() != 0) 6831 parts_menu.enable(); 6832 selected_rect = best_idx; 6833 selected_part_name = best_part; 6834 if (orect_selected_event_handler.is_set()) 6835 orect_selected_event_handler(overlay_rects[best_idx]); 6836 } 6837 6838 if (rect_is_selected || rect_was_selected) 6839 parent.invalidate_rectangle(rect); 6840 } 6841 else if (rect_is_selected) 6842 { 6843 rect_is_selected = false; 6844 parts_menu.disable(); 6845 parent.invalidate_rectangle(rect); 6846 } 6847 } 6848 6849 // ---------------------------------------------------------------------------------------- 6850 6851 std::vector<image_display::overlay_rect> image_display:: get_overlay_rects() const6852 get_overlay_rects ( 6853 ) const 6854 { 6855 auto_mutex lock(m); 6856 return overlay_rects; 6857 } 6858 6859 // ---------------------------------------------------------------------------------------- 6860 6861 void image_display:: set_default_overlay_rect_label(const std::string & label)6862 set_default_overlay_rect_label ( 6863 const std::string& label 6864 ) 6865 { 6866 auto_mutex lock(m); 6867 default_rect_label = label; 6868 } 6869 6870 // ---------------------------------------------------------------------------------------- 6871 6872 std::string image_display:: get_default_overlay_rect_label() const6873 get_default_overlay_rect_label ( 6874 ) const 6875 { 6876 auto_mutex lock(m); 6877 return default_rect_label; 6878 } 6879 6880 // ---------------------------------------------------------------------------------------- 6881 6882 void image_display:: set_default_overlay_rect_color(const rgb_alpha_pixel & color)6883 set_default_overlay_rect_color ( 6884 const rgb_alpha_pixel& color 6885 ) 6886 { 6887 auto_mutex lock(m); 6888 default_rect_color = color; 6889 } 6890 6891 // ---------------------------------------------------------------------------------------- 6892 6893 rgb_alpha_pixel image_display:: get_default_overlay_rect_color() const6894 get_default_overlay_rect_color ( 6895 ) const 6896 { 6897 auto_mutex lock(m); 6898 return default_rect_color; 6899 } 6900 6901 // ---------------------------------------------------------------------------------------- 6902 6903 void image_display:: on_mouse_up(unsigned long btn,unsigned long state,long x,long y)6904 on_mouse_up ( 6905 unsigned long btn, 6906 unsigned long state, 6907 long x, 6908 long y 6909 ) 6910 { 6911 scrollable_region::on_mouse_up(btn,state,x,y); 6912 6913 if (state&base_window::SHIFT) 6914 { 6915 holding_shift_key = true; 6916 } 6917 else if (holding_shift_key) 6918 { 6919 holding_shift_key = false; 6920 parent.invalidate_rectangle(rect); 6921 } 6922 6923 if (drawing_rect && btn == base_window::LEFT && (state&base_window::SHIFT) && 6924 !hidden && enabled) 6925 { 6926 const point origin(total_rect().tl_corner()); 6927 point c1 = point(x,y) - origin; 6928 point c2 = rect_anchor - origin; 6929 6930 if (zoom_in_scale != 1) 6931 { 6932 c1 = c1/(double)zoom_in_scale; 6933 c2 = c2/(double)zoom_in_scale; 6934 } 6935 else if (zoom_out_scale != 1) 6936 { 6937 c1 = c1*(double)zoom_out_scale; 6938 c2 = c2*(double)zoom_out_scale; 6939 } 6940 6941 rectangle new_rect(c1,c2); 6942 if (zoom_in_scale != 1) 6943 { 6944 // When we are zoomed in we adjust the rectangles a little so they 6945 // are drown surrounding the pixels inside the rect. This adjustment 6946 // is necessary to make this code consistent with this goal. 6947 new_rect.right() -= 1; 6948 new_rect.bottom() -= 1; 6949 } 6950 6951 6952 if (new_rect.width() > 0 && new_rect.height() > 0) 6953 { 6954 add_overlay(overlay_rect(new_rect, default_rect_color, default_rect_label)); 6955 6956 if (event_handler.is_set()) 6957 event_handler(); 6958 } 6959 } 6960 6961 if (drawing_rect) 6962 { 6963 drawing_rect = false; 6964 parent.invalidate_rectangle(rect); 6965 } 6966 if (moving_overlay) 6967 { 6968 moving_overlay = false; 6969 } 6970 } 6971 6972 // ---------------------------------------------------------------------------------------- 6973 6974 void image_display:: on_mouse_move(unsigned long state,long x,long y)6975 on_mouse_move ( 6976 unsigned long state, 6977 long x, 6978 long y 6979 ) 6980 { 6981 scrollable_region::on_mouse_move(state,x,y); 6982 6983 if (enabled && !hidden) 6984 { 6985 if (holding_shift_key) 6986 parent.invalidate_rectangle(rect); 6987 6988 if (state&base_window::SHIFT) 6989 holding_shift_key = true; 6990 else if (holding_shift_key) 6991 holding_shift_key = false; 6992 } 6993 6994 if (drawing_rect) 6995 { 6996 if ((state&base_window::LEFT) && (state&base_window::SHIFT) && !hidden && enabled) 6997 { 6998 rectangle new_rect(point(x,y), rect_anchor); 6999 parent.invalidate_rectangle(new_rect + rect_to_draw); 7000 rect_to_draw = new_rect; 7001 } 7002 else 7003 { 7004 drawing_rect = false; 7005 parent.invalidate_rectangle(rect); 7006 } 7007 moving_overlay = false; 7008 } 7009 else if (moving_overlay) 7010 { 7011 if ((state&base_window::RIGHT) && (state&base_window::SHIFT) && !hidden && enabled) 7012 { 7013 // map point(x,y) into the image coordinate space. 7014 point p = point(x,y) - total_rect().tl_corner(); 7015 if (zoom_in_scale != 1) 7016 { 7017 if (moving_what == MOVING_PART) 7018 p = p/(double)zoom_in_scale-dpoint(0.5,0.5); 7019 else 7020 p = p/(double)zoom_in_scale; 7021 } 7022 else if (zoom_out_scale != 1) 7023 { 7024 p = p*(double)zoom_out_scale; 7025 } 7026 7027 7028 if (moving_what == MOVING_PART) 7029 { 7030 if (overlay_rects[moving_rect].parts[moving_part_name] != p) 7031 { 7032 overlay_rects[moving_rect].parts[moving_part_name] = p; 7033 parent.invalidate_rectangle(rect); 7034 if (event_handler.is_set()) 7035 event_handler(); 7036 } 7037 } 7038 else 7039 { 7040 rectangle original = overlay_rects[moving_rect].rect; 7041 if (moving_what == MOVING_RECT_LEFT) 7042 overlay_rects[moving_rect].rect.left() = std::min(p.x(), overlay_rects[moving_rect].rect.right()); 7043 else if (moving_what == MOVING_RECT_RIGHT) 7044 overlay_rects[moving_rect].rect.right() = std::max(p.x()-1, overlay_rects[moving_rect].rect.left()); 7045 else if (moving_what == MOVING_RECT_TOP) 7046 overlay_rects[moving_rect].rect.top() = std::min(p.y(), overlay_rects[moving_rect].rect.bottom()); 7047 else 7048 overlay_rects[moving_rect].rect.bottom() = std::max(p.y()-1, overlay_rects[moving_rect].rect.top()); 7049 7050 if (original != overlay_rects[moving_rect].rect) 7051 { 7052 parent.invalidate_rectangle(rect); 7053 if (event_handler.is_set()) 7054 event_handler(); 7055 } 7056 } 7057 } 7058 else 7059 { 7060 moving_overlay = false; 7061 } 7062 } 7063 } 7064 7065 // ---------------------------------------------------------------------------------------- 7066 7067 void image_display:: zoom_in()7068 zoom_in ( 7069 ) 7070 { 7071 auto_mutex M(m); 7072 if (zoom_in_scale < 100 && zoom_out_scale == 1) 7073 { 7074 const point mouse_loc(lastx, lasty); 7075 // the pixel in img that the mouse is over 7076 const point pix_loc = (mouse_loc - total_rect().tl_corner())/zoom_in_scale; 7077 7078 zoom_in_scale = zoom_in_scale*10/9 + 1; 7079 7080 set_total_rect_size(img.nc()*zoom_in_scale, img.nr()*zoom_in_scale); 7081 7082 // make is to the pixel under the mouse doesn't move while we zoom 7083 const point delta = total_rect().tl_corner() - (mouse_loc - pix_loc*zoom_in_scale); 7084 scroll_to_rect(translate_rect(display_rect(), delta)); 7085 } 7086 else if (zoom_out_scale != 1) 7087 { 7088 const point mouse_loc(lastx, lasty); 7089 // the pixel in img that the mouse is over 7090 const point pix_loc = (mouse_loc - total_rect().tl_corner())*zoom_out_scale; 7091 7092 zoom_out_scale = zoom_out_scale*9/10; 7093 if (zoom_out_scale == 0) 7094 zoom_out_scale = 1; 7095 7096 set_total_rect_size(img.nc()/zoom_out_scale, img.nr()/zoom_out_scale); 7097 7098 // make is to the pixel under the mouse doesn't move while we zoom 7099 const point delta = total_rect().tl_corner() - (mouse_loc - pix_loc/zoom_out_scale); 7100 scroll_to_rect(translate_rect(display_rect(), delta)); 7101 } 7102 } 7103 7104 // ---------------------------------------------------------------------------------------- 7105 7106 void image_display:: on_wheel_up(unsigned long state)7107 on_wheel_up ( 7108 unsigned long state 7109 ) 7110 { 7111 // disable mouse wheel if the user is drawing a rectangle 7112 if (drawing_rect) 7113 return; 7114 7115 // if CONTROL is not being held down 7116 if ((state & base_window::CONTROL) == 0) 7117 { 7118 scrollable_region::on_wheel_up(state); 7119 return; 7120 } 7121 7122 if (rect.contains(lastx,lasty) == false || hidden || !enabled) 7123 return; 7124 7125 zoom_in(); 7126 } 7127 7128 // ---------------------------------------------------------------------------------------- 7129 7130 void image_display:: zoom_out()7131 zoom_out ( 7132 ) 7133 { 7134 auto_mutex M(m); 7135 if (zoom_in_scale != 1) 7136 { 7137 const point mouse_loc(lastx, lasty); 7138 // the pixel in img that the mouse is over 7139 const point pix_loc = (mouse_loc - total_rect().tl_corner())/zoom_in_scale; 7140 7141 zoom_in_scale = zoom_in_scale*9/10; 7142 if (zoom_in_scale == 0) 7143 zoom_in_scale = 1; 7144 7145 set_total_rect_size(img.nc()*zoom_in_scale, img.nr()*zoom_in_scale); 7146 7147 // make is to the pixel under the mouse doesn't move while we zoom 7148 const point delta = total_rect().tl_corner() - (mouse_loc - pix_loc*zoom_in_scale); 7149 scroll_to_rect(translate_rect(display_rect(), delta)); 7150 } 7151 else if (std::max(img.nr(), img.nc())/zoom_out_scale > 10) 7152 { 7153 const point mouse_loc(lastx, lasty); 7154 // the pixel in img that the mouse is over 7155 const point pix_loc = (mouse_loc - total_rect().tl_corner())*zoom_out_scale; 7156 7157 zoom_out_scale = zoom_out_scale*10/9 + 1; 7158 7159 set_total_rect_size(img.nc()/zoom_out_scale, img.nr()/zoom_out_scale); 7160 7161 // make is to the pixel under the mouse doesn't move while we zoom 7162 const point delta = total_rect().tl_corner() - (mouse_loc - pix_loc/zoom_out_scale); 7163 scroll_to_rect(translate_rect(display_rect(), delta)); 7164 } 7165 } 7166 7167 // ---------------------------------------------------------------------------------------- 7168 7169 void image_display:: on_wheel_down(unsigned long state)7170 on_wheel_down ( 7171 unsigned long state 7172 ) 7173 { 7174 // disable mouse wheel if the user is drawing a rectangle 7175 if (drawing_rect) 7176 return; 7177 7178 // if CONTROL is not being held down 7179 if ((state & base_window::CONTROL) == 0) 7180 { 7181 scrollable_region::on_wheel_down(state); 7182 return; 7183 } 7184 7185 if (rect.contains(lastx,lasty) == false || hidden || !enabled) 7186 return; 7187 7188 zoom_out(); 7189 } 7190 7191 // ---------------------------------------------------------------------------------------- 7192 // ---------------------------------------------------------------------------------------- 7193 // image_window member functions 7194 // ---------------------------------------------------------------------------------------- 7195 // ---------------------------------------------------------------------------------------- 7196 7197 image_window:: image_window()7198 image_window( 7199 ) : 7200 gui_img(*this), 7201 window_has_closed(false), 7202 have_last_click(false), 7203 mouse_btn(0), 7204 clicked_signaler(this->wm), 7205 tie_input_events(false) 7206 { 7207 7208 gui_img.set_image_clicked_handler(*this, &image_window::on_image_clicked); 7209 gui_img.disable_overlay_editing(); 7210 // show this window on the screen 7211 show(); 7212 } 7213 7214 // ---------------------------------------------------------------------------------------- 7215 7216 image_window:: ~image_window()7217 ~image_window( 7218 ) 7219 { 7220 // You should always call close_window() in the destructor of window 7221 // objects to ensure that no events will be sent to this window while 7222 // it is being destructed. 7223 close_window(); 7224 } 7225 7226 // ---------------------------------------------------------------------------------------- 7227 7228 base_window::on_close_return_code image_window:: on_window_close()7229 on_window_close( 7230 ) 7231 { 7232 window_has_closed = true; 7233 clicked_signaler.broadcast(); 7234 return base_window::CLOSE_WINDOW; 7235 } 7236 7237 // ---------------------------------------------------------------------------------------- 7238 7239 bool image_window:: get_next_keypress(unsigned long & key,bool & is_printable,unsigned long & state)7240 get_next_keypress ( 7241 unsigned long& key, 7242 bool& is_printable, 7243 unsigned long& state 7244 ) 7245 { 7246 auto_mutex lock(wm); 7247 while (have_last_keypress == false && !window_has_closed && 7248 (have_last_click == false || !tie_input_events)) 7249 { 7250 clicked_signaler.wait(); 7251 } 7252 7253 if (window_has_closed) 7254 return false; 7255 7256 if (have_last_keypress) 7257 { 7258 // Mark that we are taking the key click so the next call to get_next_keypress() 7259 // will have to wait for another click. 7260 have_last_keypress = false; 7261 key = next_key; 7262 is_printable = next_is_printable; 7263 state = next_state; 7264 return true; 7265 } 7266 else 7267 { 7268 key = 0; 7269 is_printable = true; 7270 return false; 7271 } 7272 } 7273 7274 // ---------------------------------------------------------------------------------------- 7275 7276 void image_window:: on_keydown(unsigned long key,bool is_printable,unsigned long state)7277 on_keydown ( 7278 unsigned long key, 7279 bool is_printable, 7280 unsigned long state 7281 ) 7282 { 7283 dlib::drawable_window::on_keydown(key,is_printable,state); 7284 7285 have_last_keypress = true; 7286 next_key = key; 7287 next_is_printable = is_printable; 7288 next_state = state; 7289 clicked_signaler.signal(); 7290 } 7291 7292 // ---------------------------------------------------------------------------------------- 7293 7294 void image_window:: tie_events()7295 tie_events ( 7296 ) 7297 { 7298 auto_mutex lock(wm); 7299 tie_input_events = true; 7300 } 7301 7302 // ---------------------------------------------------------------------------------------- 7303 7304 void image_window:: untie_events()7305 untie_events ( 7306 ) 7307 { 7308 auto_mutex lock(wm); 7309 tie_input_events = false; 7310 } 7311 7312 // ---------------------------------------------------------------------------------------- 7313 7314 bool image_window:: events_tied() const7315 events_tied ( 7316 ) const 7317 { 7318 auto_mutex lock(wm); 7319 return tie_input_events; 7320 } 7321 7322 // ---------------------------------------------------------------------------------------- 7323 7324 bool image_window:: get_next_double_click(point & p,unsigned long & mouse_button)7325 get_next_double_click ( 7326 point& p, 7327 unsigned long& mouse_button 7328 ) 7329 { 7330 p = point(-1,-1); 7331 7332 auto_mutex lock(wm); 7333 while (have_last_click == false && !window_has_closed && 7334 (have_last_keypress==false || !tie_input_events)) 7335 { 7336 clicked_signaler.wait(); 7337 } 7338 7339 if (window_has_closed) 7340 return false; 7341 7342 if (have_last_click) 7343 { 7344 // Mark that we are taking the point click so the next call to 7345 // get_next_double_click() will have to wait for another click. 7346 have_last_click = false; 7347 mouse_button = mouse_btn; 7348 p = last_clicked_point; 7349 return true; 7350 } 7351 else 7352 { 7353 return false; 7354 } 7355 } 7356 7357 // ---------------------------------------------------------------------------------------- 7358 7359 void image_window:: on_image_clicked(const point & p,bool is_double_click,unsigned long btn)7360 on_image_clicked ( 7361 const point& p, 7362 bool is_double_click, 7363 unsigned long btn 7364 ) 7365 { 7366 if (is_double_click) 7367 { 7368 have_last_click = true; 7369 last_clicked_point = p; 7370 mouse_btn = btn; 7371 clicked_signaler.signal(); 7372 } 7373 } 7374 7375 // ---------------------------------------------------------------------------------------- 7376 7377 void image_window:: add_overlay(const overlay_rect & overlay)7378 add_overlay ( 7379 const overlay_rect& overlay 7380 ) 7381 { 7382 gui_img.add_overlay(overlay); 7383 } 7384 7385 // ---------------------------------------------------------------------------------------- 7386 7387 void image_window:: add_overlay(const overlay_line & overlay)7388 add_overlay ( 7389 const overlay_line& overlay 7390 ) 7391 { 7392 gui_img.add_overlay(overlay); 7393 } 7394 7395 // ---------------------------------------------------------------------------------------- 7396 7397 void image_window:: add_overlay(const overlay_circle & overlay)7398 add_overlay ( 7399 const overlay_circle& overlay 7400 ) 7401 { 7402 gui_img.add_overlay(overlay); 7403 } 7404 7405 // ---------------------------------------------------------------------------------------- 7406 7407 void image_window:: add_overlay(const std::vector<overlay_rect> & overlay)7408 add_overlay ( 7409 const std::vector<overlay_rect>& overlay 7410 ) 7411 { 7412 gui_img.add_overlay(overlay); 7413 } 7414 7415 // ---------------------------------------------------------------------------------------- 7416 7417 void image_window:: add_overlay(const std::vector<overlay_line> & overlay)7418 add_overlay ( 7419 const std::vector<overlay_line>& overlay 7420 ) 7421 { 7422 gui_img.add_overlay(overlay); 7423 } 7424 7425 // ---------------------------------------------------------------------------------------- 7426 7427 void image_window:: add_overlay(const std::vector<overlay_circle> & overlay)7428 add_overlay ( 7429 const std::vector<overlay_circle>& overlay 7430 ) 7431 { 7432 gui_img.add_overlay(overlay); 7433 } 7434 7435 // ---------------------------------------------------------------------------------------- 7436 7437 void image_window:: clear_overlay()7438 clear_overlay ( 7439 ) 7440 { 7441 gui_img.clear_overlay(); 7442 } 7443 7444 // ---------------------------------------------------------------------------------------- 7445 7446 void image_window:: on_window_resized()7447 on_window_resized( 7448 ) 7449 { 7450 drawable_window::on_window_resized(); 7451 unsigned long width, height; 7452 get_size(width,height); 7453 gui_img.set_size(width, height); 7454 7455 } 7456 7457 // ---------------------------------------------------------------------------------------- 7458 7459 } 7460 7461 #endif // DLIB_WIDGETs_CPP_ 7462 7463