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