Teh first one
[mldemos:kalians-mldemos.git] / _AlgorithmsPlugins / KernelMethods / dlib / gui_widgets / base_widgets.cpp
1 // Copyright (C) 2005  Davis E. King (davis@dlib.net), Keita Mochizuki\r
2 // License: Boost Software License   See LICENSE.txt for the full license.\r
3 #ifndef DLIB_BASE_WIDGETs_CPP_\r
4 #define DLIB_BASE_WIDGETs_CPP_\r
5 \r
6 #include "base_widgets.h"\r
7 #include "../assert.h"\r
8 #include <iostream>\r
9 \r
10 \r
11 namespace dlib\r
12 {\r
13 \r
14 // ----------------------------------------------------------------------------------------\r
15 // ----------------------------------------------------------------------------------------\r
16     // button object methods  \r
17 // ----------------------------------------------------------------------------------------\r
18 // ----------------------------------------------------------------------------------------\r
19 \r
20     void button::\r
21     set_size (\r
22         unsigned long width,\r
23         unsigned long height\r
24     )\r
25     {\r
26         auto_mutex M(m);\r
27         rectangle min_rect = style->get_min_size(name_,*mfont);\r
28         // only change the size if it isn't going to be too small to fit the name\r
29         if (height >= min_rect.height() &&\r
30             width >= min_rect.width())\r
31         {\r
32             rectangle old(rect);\r
33             rect = resize_rect(rect,width,height);\r
34             parent.invalidate_rectangle(style->get_invalidation_rect(rect+old));\r
35             btn_tooltip.set_size(width,height);\r
36         }\r
37     }\r
38 \r
39 // ----------------------------------------------------------------------------------------\r
40 \r
41     void button::\r
42     show (\r
43     )\r
44     {\r
45         button_action::show();\r
46         btn_tooltip.show();\r
47     }\r
48 \r
49 // ----------------------------------------------------------------------------------------\r
50 \r
51     void button::\r
52     hide (\r
53     )\r
54     {\r
55         button_action::hide();\r
56         btn_tooltip.hide();\r
57     }\r
58 \r
59 // ----------------------------------------------------------------------------------------\r
60 \r
61     void button::\r
62     enable (\r
63     )\r
64     {\r
65         button_action::enable();\r
66         btn_tooltip.enable();\r
67     }\r
68 \r
69 // ----------------------------------------------------------------------------------------\r
70 \r
71     void button::\r
72     disable (\r
73     )\r
74     {\r
75         button_action::disable();\r
76         btn_tooltip.disable();\r
77     }\r
78 \r
79 // ----------------------------------------------------------------------------------------\r
80 \r
81     void button::\r
82     set_tooltip_text (\r
83         const std::string& text\r
84     )\r
85     {\r
86         btn_tooltip.set_text(text);\r
87     }\r
88 \r
89 // ----------------------------------------------------------------------------------------\r
90 \r
91     void button::\r
92     set_tooltip_text (\r
93         const std::wstring& text\r
94     )\r
95     {\r
96         btn_tooltip.set_text(text);\r
97     }\r
98 \r
99 // ----------------------------------------------------------------------------------------\r
100 \r
101     void button::\r
102     set_tooltip_text (\r
103         const ustring& text\r
104     )\r
105     {\r
106         btn_tooltip.set_text(text);\r
107     }\r
108 \r
109 // ----------------------------------------------------------------------------------------\r
110 \r
111     const std::string button::\r
112     tooltip_text (\r
113     ) const\r
114     {\r
115         return btn_tooltip.text();\r
116     }\r
117 \r
118     const std::wstring button::\r
119     tooltip_wtext (\r
120     ) const\r
121     {\r
122         return btn_tooltip.wtext();\r
123     }\r
124 \r
125     const dlib::ustring button::\r
126     tooltip_utext (\r
127     ) const\r
128     {\r
129         return btn_tooltip.utext();\r
130     }\r
131 \r
132 // ----------------------------------------------------------------------------------------\r
133 \r
134     void button::\r
135     set_main_font (\r
136         const shared_ptr_thread_safe<font>& f\r
137     )\r
138     {\r
139         auto_mutex M(m);\r
140         mfont = f;\r
141         set_name(name_);\r
142     }\r
143 \r
144 // ----------------------------------------------------------------------------------------\r
145 \r
146     void button::\r
147     set_pos (\r
148         long x,\r
149         long y\r
150     )\r
151     {\r
152         auto_mutex M(m);\r
153         button_action::set_pos(x,y);\r
154         btn_tooltip.set_pos(x,y);\r
155     }\r
156 \r
157 // ----------------------------------------------------------------------------------------\r
158 \r
159     void button::\r
160     set_name (\r
161         const std::string& name\r
162     )\r
163     {\r
164         set_name(convert_mbstring_to_wstring(name));\r
165     }\r
166 \r
167     void button::\r
168     set_name (\r
169         const std::wstring& name\r
170     )\r
171     {\r
172         set_name(convert_wstring_to_utf32(name));\r
173     }\r
174 \r
175     void button::\r
176     set_name (\r
177         const ustring& name\r
178     )\r
179     {\r
180         auto_mutex M(m);\r
181         name_ = name;\r
182         // do this to get rid of any reference counting that may be present in \r
183         // the std::string implementation.\r
184         name_[0] = name_[0];\r
185 \r
186         rectangle old(rect);\r
187         rect = move_rect(style->get_min_size(name,*mfont),rect.left(),rect.top());\r
188         btn_tooltip.set_size(rect.width(),rect.height());\r
189         \r
190         parent.invalidate_rectangle(style->get_invalidation_rect(rect+old));\r
191     }\r
192 \r
193 // ----------------------------------------------------------------------------------------\r
194 \r
195     const std::string button::\r
196     name (\r
197     ) const\r
198     {\r
199         auto_mutex M(m);\r
200         std::string temp = convert_wstring_to_mbstring(wname());\r
201         // do this to get rid of any reference counting that may be present in \r
202         // the std::string implementation.\r
203         char c = temp[0];\r
204         temp[0] = c;\r
205         return temp;\r
206     }\r
207 \r
208     const std::wstring button::\r
209     wname (\r
210     ) const\r
211     {\r
212         auto_mutex M(m);\r
213         std::wstring temp = convert_utf32_to_wstring(uname());\r
214         // do this to get rid of any reference counting that may be present in \r
215         // the std::wstring implementation.\r
216         wchar_t w = temp[0];\r
217         temp[0] = w;\r
218         return temp;\r
219     }\r
220 \r
221     const dlib::ustring button::\r
222     uname (\r
223     ) const\r
224     {\r
225         auto_mutex M(m);\r
226         dlib::ustring temp = name_;\r
227         // do this to get rid of any reference counting that may be present in \r
228         // the dlib::ustring implementation.\r
229         temp[0] = name_[0];\r
230         return temp;\r
231     }\r
232 \r
233 // ----------------------------------------------------------------------------------------\r
234 \r
235     void button::\r
236     on_button_up (\r
237         bool mouse_over\r
238     )\r
239     {\r
240         if (mouse_over)                \r
241         {\r
242             // this is a valid button click\r
243             if (event_handler.is_set())\r
244                 event_handler();\r
245             if (event_handler_self.is_set())\r
246                 event_handler_self(*this);\r
247         }\r
248         if (button_up_handler.is_set())\r
249             button_up_handler(mouse_over);\r
250         if (button_up_handler_self.is_set())\r
251             button_up_handler_self(mouse_over,*this);\r
252     }\r
253 \r
254 // ----------------------------------------------------------------------------------------\r
255 \r
256     void button::\r
257     on_button_down (\r
258     )\r
259     {\r
260         if (button_down_handler.is_set())\r
261             button_down_handler();\r
262         if (button_down_handler_self.is_set())\r
263             button_down_handler_self(*this);\r
264     }\r
265 \r
266 // ----------------------------------------------------------------------------------------\r
267 // ----------------------------------------------------------------------------------------\r
268     // draggable object methods  \r
269 // ----------------------------------------------------------------------------------------\r
270 // ----------------------------------------------------------------------------------------\r
271 \r
272     draggable::~draggable() {}\r
273 \r
274 // ----------------------------------------------------------------------------------------\r
275 \r
276     void draggable::\r
277     on_mouse_move (\r
278         unsigned long state,\r
279         long x,\r
280         long y\r
281     )\r
282     {\r
283         if (drag && (state & base_window::LEFT) && enabled && !hidden)\r
284         {\r
285             // the user is trying to drag this object.  we should calculate the new\r
286             // x and y positions for the upper left corner of this object's rectangle\r
287 \r
288             long new_x = x - this->x;\r
289             long new_y = y - this->y;\r
290 \r
291             // make sure these points are inside the draggable area.  \r
292             if (new_x < area.left())\r
293                 new_x = area.left();\r
294             if (new_x + static_cast<long>(rect.width()) - 1 > area.right())\r
295                 new_x = area.right() - rect.width() + 1;\r
296 \r
297             if (new_y + static_cast<long>(rect.height()) - 1 > area.bottom())\r
298                 new_y = area.bottom() - rect.height() + 1;\r
299             if (new_y < area.top())\r
300                 new_y = area.top();\r
301 \r
302             // now make the new rectangle for this object\r
303             rectangle new_rect(\r
304                 new_x,\r
305                 new_y,\r
306                 new_x + rect.width() - 1,\r
307                 new_y + rect.height() - 1\r
308             );\r
309 \r
310             // only do anything if this is a new rectangle and it is inside area\r
311             if (new_rect != rect && area.intersect(new_rect) == new_rect)\r
312             {\r
313                 parent.invalidate_rectangle(new_rect + rect);\r
314                 rect = new_rect;\r
315 \r
316                 // call the on_drag() event handler\r
317                 on_drag();\r
318             }\r
319         }\r
320         else\r
321         {\r
322             drag = false;\r
323             on_drag_stop();\r
324         }\r
325     }\r
326 \r
327 // ----------------------------------------------------------------------------------------\r
328 \r
329     void draggable::\r
330     on_mouse_up (\r
331         unsigned long ,\r
332         unsigned long state,\r
333         long ,\r
334         long \r
335     )\r
336     {\r
337         if (drag && (state & base_window::LEFT) == 0)\r
338         {\r
339             drag = false;\r
340             on_drag_stop();\r
341         }\r
342     }\r
343 \r
344 // ----------------------------------------------------------------------------------------\r
345 \r
346     void draggable::\r
347     on_mouse_down (\r
348         unsigned long btn,\r
349         unsigned long ,\r
350         long x,\r
351         long y,\r
352         bool \r
353     )\r
354     {\r
355         if (enabled && !hidden && rect.contains(x,y) && btn == base_window::LEFT)\r
356         {\r
357             drag = true;\r
358             this->x = x - rect.left();\r
359             this->y = y - rect.top();\r
360         }\r
361     }\r
362 \r
363 // ----------------------------------------------------------------------------------------\r
364 // ----------------------------------------------------------------------------------------\r
365     // mouse_over_event object methods  \r
366 // ----------------------------------------------------------------------------------------\r
367 // ----------------------------------------------------------------------------------------\r
368 \r
369     mouse_over_event::~mouse_over_event() {}\r
370 \r
371 // ----------------------------------------------------------------------------------------\r
372 \r
373     void mouse_over_event::\r
374     on_mouse_leave (\r
375     )\r
376     {\r
377         if (is_mouse_over_)\r
378         {\r
379             is_mouse_over_ = false;\r
380             on_mouse_not_over();\r
381         }\r
382     }\r
383 \r
384 // ----------------------------------------------------------------------------------------\r
385 \r
386     void mouse_over_event::\r
387     on_mouse_move (\r
388         unsigned long ,\r
389         long x,\r
390         long y\r
391     )\r
392     {\r
393         if (rect.contains(x,y) == false)\r
394         {\r
395             if (is_mouse_over_)\r
396             {\r
397                 is_mouse_over_ = false;\r
398                 on_mouse_not_over();\r
399             }\r
400         }\r
401         else if (is_mouse_over_ == false)\r
402         {\r
403             is_mouse_over_ = true;\r
404             if (enabled && !hidden)\r
405                 on_mouse_over();\r
406         }\r
407     }\r
408 \r
409 // ----------------------------------------------------------------------------------------\r
410 \r
411     bool mouse_over_event::\r
412     is_mouse_over (\r
413     ) const\r
414     {\r
415         // check if the mouse is still really over this button\r
416         if (is_mouse_over_ && rect.contains(lastx,lasty) == false)\r
417         {\r
418             // trigger a user event to call on_mouse_not_over() and repaint this object.\r
419             // we must do this in another event because someone might call is_mouse_over()\r
420             // from draw() and you don't want this function to end up calling \r
421             // parent.invalidate_rectangle().  It would lead to draw() being called over\r
422             // and over.\r
423             parent.trigger_user_event((void*)this,drawable::next_free_user_event_number());\r
424             return false;\r
425         }\r
426 \r
427         return is_mouse_over_;\r
428     }\r
429 \r
430 // ----------------------------------------------------------------------------------------\r
431 \r
432     void mouse_over_event::\r
433     on_user_event (\r
434         int num \r
435     )\r
436     {\r
437         if (is_mouse_over_ && num == drawable::next_free_user_event_number())\r
438         {\r
439             is_mouse_over_ = false;\r
440             on_mouse_not_over();\r
441         }\r
442     }\r
443 \r
444 // ----------------------------------------------------------------------------------------\r
445 // ----------------------------------------------------------------------------------------\r
446     // button_action object methods  \r
447 // ----------------------------------------------------------------------------------------\r
448 // ----------------------------------------------------------------------------------------\r
449 \r
450     button_action::~button_action() {}\r
451 \r
452 // ----------------------------------------------------------------------------------------\r
453 \r
454     void button_action::\r
455     on_mouse_down (\r
456         unsigned long btn,\r
457         unsigned long ,\r
458         long x,\r
459         long y,\r
460         bool\r
461     )\r
462     {\r
463         if (enabled && !hidden && btn == base_window::LEFT && rect.contains(x,y))\r
464         {\r
465             is_depressed_ = true;\r
466             seen_click = true;\r
467             parent.invalidate_rectangle(rect);\r
468             on_button_down();\r
469         }\r
470     }\r
471 \r
472 // ----------------------------------------------------------------------------------------\r
473 \r
474     void button_action::\r
475     on_mouse_not_over (\r
476     )\r
477     {\r
478         if (is_depressed_)\r
479         {\r
480             is_depressed_ = false;\r
481             parent.invalidate_rectangle(rect);\r
482             on_button_up(false);\r
483         }\r
484     }\r
485 \r
486 // ----------------------------------------------------------------------------------------\r
487 \r
488     void button_action::\r
489     on_mouse_move (\r
490         unsigned long state,\r
491         long x,\r
492         long y\r
493     )\r
494     {\r
495         // forward event to the parent class so it can do it's thing as well as us\r
496         mouse_over_event::on_mouse_move(state,x,y);\r
497 \r
498         if (enabled == false || hidden == true)\r
499             return;\r
500 \r
501 \r
502         if ((state & base_window::LEFT) == 0)\r
503         {\r
504             seen_click = false;\r
505             if (is_depressed_)\r
506             {\r
507                 is_depressed_ = false;\r
508                 parent.invalidate_rectangle(rect);\r
509                 on_button_up(false);\r
510             }\r
511 \r
512             // the left button isn't down so we don't care about anything else\r
513             return;\r
514         }\r
515 \r
516         if (rect.contains(x,y) == false)\r
517         {\r
518             if (is_depressed_)\r
519             {\r
520                 is_depressed_ = false;\r
521                 parent.invalidate_rectangle(rect);\r
522                 on_button_up(false);\r
523             }\r
524         }\r
525         else if (is_depressed_ == false && seen_click)\r
526         {\r
527             is_depressed_ = true;\r
528             parent.invalidate_rectangle(rect);\r
529             on_button_down();\r
530         }\r
531     }\r
532 \r
533 // ----------------------------------------------------------------------------------------\r
534 \r
535     void button_action::\r
536     on_mouse_up (\r
537         unsigned long btn,\r
538         unsigned long,\r
539         long x,\r
540         long y\r
541     )\r
542     {\r
543         if (enabled && !hidden && btn == base_window::LEFT)\r
544         {\r
545             if (is_depressed_)\r
546             {\r
547                 is_depressed_ = false;\r
548                 parent.invalidate_rectangle(rect);\r
549 \r
550                 if (rect.contains(x,y))                \r
551                 {\r
552                     on_button_up(true);\r
553                 }\r
554                 else\r
555                 {\r
556                     on_button_up(false);\r
557                 }\r
558             }\r
559             else if (seen_click && rect.contains(x,y))\r
560             {\r
561                 // this case here covers the unlikly event that you click on a button,\r
562                 // move the mouse off the button and then move it back very quickly and\r
563                 // release the mouse button.   It is possible that this mouse up event\r
564                 // will occurr before any mouse move event so you might not have set\r
565                 // that the button is depressed yet.\r
566                 \r
567                 // So we should say that this triggers an on_button_down() event and\r
568                 // then an on_button_up(true) event.\r
569 \r
570                 parent.invalidate_rectangle(rect);\r
571 \r
572                 on_button_down();\r
573                 on_button_up(true);\r
574             }\r
575 \r
576             seen_click = false;\r
577         }\r
578     }\r
579 \r
580 // ----------------------------------------------------------------------------------------\r
581 \r
582     bool button_action::\r
583     is_depressed (\r
584     ) const\r
585     {\r
586         // check if the mouse is still really over this button\r
587         if (enabled && !hidden && is_depressed_ && rect.contains(lastx,lasty) == false)\r
588         {\r
589             // trigger a user event to call on_button_up() and repaint this object.\r
590             // we must do this in another event because someone might call is_depressed()\r
591             // from draw() and you don't want this function to end up calling \r
592             // parent.invalidate_rectangle().  It would lead to draw() being called over\r
593             // and over.\r
594             parent.trigger_user_event((void*)this,mouse_over_event::next_free_user_event_number());\r
595             return false;\r
596         }\r
597 \r
598         return is_depressed_;\r
599     }\r
600 \r
601 // ----------------------------------------------------------------------------------------\r
602 \r
603     void button_action::\r
604     on_user_event (\r
605         int num\r
606     )\r
607     {\r
608         // forward event to the parent class so it can do it's thing as well as us\r
609         mouse_over_event::on_user_event(num);\r
610 \r
611         if (is_depressed_ && num == mouse_over_event::next_free_user_event_number())\r
612         {\r
613             is_depressed_ = false;\r
614             parent.invalidate_rectangle(rect);\r
615             on_button_up(false);\r
616         }\r
617     }\r
618 \r
619 // ----------------------------------------------------------------------------------------\r
620 // ----------------------------------------------------------------------------------------\r
621     // scroll_bar object methods  \r
622 // ----------------------------------------------------------------------------------------\r
623 // ----------------------------------------------------------------------------------------\r
624 \r
625     scroll_bar::\r
626     scroll_bar(  \r
627         drawable_window& w,\r
628         bar_orientation orientation \r
629     ) :\r
630         drawable(w),\r
631         b1(w),\r
632         b2(w),\r
633         slider(w,*this,&scroll_bar::on_slider_drag),\r
634         ori(orientation),\r
635         top_filler(w,*this,&scroll_bar::top_filler_down,&scroll_bar::top_filler_up),\r
636         bottom_filler(w,*this,&scroll_bar::bottom_filler_down,&scroll_bar::bottom_filler_up),\r
637         pos(0),\r
638         max_pos(0),\r
639         js(10),\r
640         b1_timer(*this,&scroll_bar::b1_down_t),\r
641         b2_timer(*this,&scroll_bar::b2_down_t),\r
642         top_filler_timer(*this,&scroll_bar::top_filler_down_t),\r
643         bottom_filler_timer(*this,&scroll_bar::bottom_filler_down_t)\r
644     {\r
645         set_style(scroll_bar_style_default());\r
646 \r
647         // don't show the slider when there is no place it can move.\r
648         slider.hide();\r
649 \r
650         set_length(100);\r
651 \r
652         b1.set_button_down_handler(*this,&scroll_bar::b1_down);\r
653         b2.set_button_down_handler(*this,&scroll_bar::b2_down);\r
654 \r
655         b1.set_button_up_handler(*this,&scroll_bar::b1_up);\r
656         b2.set_button_up_handler(*this,&scroll_bar::b2_up);\r
657         b1.disable();\r
658         b2.disable();\r
659         enable_events();\r
660     }\r
661 \r
662 // ----------------------------------------------------------------------------------------\r
663 \r
664     scroll_bar::\r
665     ~scroll_bar(\r
666     )\r
667     {\r
668         disable_events();\r
669         parent.invalidate_rectangle(rect); \r
670         // wait for all the timers to be stopped\r
671         b1_timer.stop_and_wait();\r
672         b2_timer.stop_and_wait();\r
673         top_filler_timer.stop_and_wait();\r
674         bottom_filler_timer.stop_and_wait();\r
675     }\r
676 \r
677 // ----------------------------------------------------------------------------------------\r
678 \r
679     scroll_bar::bar_orientation scroll_bar::\r
680     orientation (\r
681     ) const\r
682     {\r
683         auto_mutex M(m);\r
684         return ori;\r
685     }\r
686 \r
687 // ----------------------------------------------------------------------------------------\r
688     \r
689     void scroll_bar::\r
690     set_length (\r
691         unsigned long length\r
692     )\r
693     {\r
694         auto_mutex M(m);\r
695         // make the min length be at least 1\r
696         if (length == 0)\r
697         {\r
698             length = 1;\r
699         }\r
700 \r
701 \r
702         parent.invalidate_rectangle(rect);\r
703 \r
704         if (ori == HORIZONTAL)\r
705         {\r
706             rect.set_right(rect.left() + length - 1);\r
707             rect.set_bottom(rect.top() + style->get_width() - 1);\r
708 \r
709             const long btn_size = style->get_button_length(rect.width(), max_pos);\r
710 \r
711             b1.set_size(btn_size,style->get_width());\r
712             b2.set_size(btn_size,style->get_width());\r
713 \r
714             slider.set_size(get_slider_size(),style->get_width());\r
715         }\r
716         else\r
717         {\r
718             rect.set_right(rect.left() + style->get_width() - 1);\r
719             rect.set_bottom(rect.top() + length - 1);\r
720 \r
721             const long btn_size = style->get_button_length(rect.height(), max_pos);\r
722 \r
723             b1.set_size(style->get_width(),btn_size);\r
724             b2.set_size(style->get_width(),btn_size);\r
725 \r
726             slider.set_size(style->get_width(),get_slider_size());\r
727         }\r
728 \r
729         // call this to put everything is in the right spot.\r
730         set_pos (rect.left(),rect.top());\r
731 \r
732         if ((b2.get_rect().top() - b1.get_rect().bottom() - 1 <= 8 && ori == VERTICAL) || \r
733             (b2.get_rect().left() - b1.get_rect().right() - 1 <= 8 && ori == HORIZONTAL) || \r
734             max_pos == 0)\r
735         {\r
736             hide_slider();\r
737         }\r
738         else if (enabled && !hidden)\r
739         {\r
740             show_slider();\r
741         }\r
742     }\r
743 \r
744 // ----------------------------------------------------------------------------------------\r
745 \r
746     void scroll_bar::\r
747     set_pos (\r
748         long x,\r
749         long y\r
750     )\r
751     {\r
752         auto_mutex M(m);\r
753         drawable::set_pos(x,y);\r
754 \r
755         b1.set_pos(rect.left(),rect.top());\r
756         if (ori == HORIZONTAL)\r
757         {\r
758             // make the b2 button appear at the end of the scroll_bar\r
759             b2.set_pos(rect.right()-b2.get_rect().width() + 1,rect.top());\r
760 \r
761             if (max_pos != 0)\r
762             {\r
763                 double range = b2.get_rect().left() - b1.get_rect().right() - slider.get_rect().width() - 1;\r
764                 double slider_pos = pos;\r
765                 slider_pos /= max_pos;\r
766                 slider_pos *= range;\r
767                 slider.set_pos(\r
768                     static_cast<long>(slider_pos)+rect.left() + b1.get_rect().width(),\r
769                     rect.top()\r
770                     );\r
771 \r
772                 // move the draggable area for the slider to the new location\r
773                 rectangle area = rect;\r
774                 area.set_left(area.left() + style->get_width());\r
775                 area.set_right(area.right() - style->get_width());\r
776                 slider.set_draggable_area(area);\r
777 \r
778             }\r
779 \r
780             \r
781         }\r
782         else\r
783         {\r
784             // make the b2 button appear at the end of the scroll_bar\r
785             b2.set_pos(rect.left(), rect.bottom() - b2.get_rect().height() + 1);\r
786 \r
787             if (max_pos != 0)\r
788             {\r
789                 double range = b2.get_rect().top() - b1.get_rect().bottom() - slider.get_rect().height() - 1;\r
790                 double slider_pos = pos;\r
791                 slider_pos /= max_pos;\r
792                 slider_pos *= range;\r
793                 slider.set_pos(\r
794                     rect.left(), \r
795                     static_cast<long>(slider_pos) + rect.top() + b1.get_rect().height()\r
796                     );\r
797 \r
798                 // move the draggable area for the slider to the new location\r
799                 rectangle area = rect;\r
800                 area.set_top(area.top() + style->get_width());\r
801                 area.set_bottom(area.bottom() - style->get_width());\r
802                 slider.set_draggable_area(area);\r
803             }\r
804         }\r
805 \r
806         adjust_fillers();\r
807     }\r
808 \r
809 // ----------------------------------------------------------------------------------------\r
810 \r
811     unsigned long scroll_bar::\r
812     get_slider_size (\r
813     ) const\r
814     {\r
815         if (ori == HORIZONTAL)\r
816             return style->get_slider_length(rect.width(),max_pos);\r
817         else\r
818             return style->get_slider_length(rect.height(),max_pos);\r
819     }\r
820 \r
821 // ----------------------------------------------------------------------------------------\r
822 \r
823     void scroll_bar::\r
824     adjust_fillers (\r
825     )\r
826     {\r
827         rectangle top(rect), bottom(rect);\r
828 \r
829         if (ori == HORIZONTAL)\r
830         {\r
831             if (slider.is_hidden())\r
832             {\r
833                 top.set_left(b1.get_rect().right()+1);\r
834                 top.set_right(b2.get_rect().left()-1);\r
835                 bottom.set_left(1);\r
836                 bottom.set_right(-1);\r
837             }\r
838             else\r
839             {\r
840                 top.set_left(b1.get_rect().right()+1);\r
841                 top.set_right(slider.get_rect().left()-1);\r
842                 bottom.set_left(slider.get_rect().right()+1);\r
843                 bottom.set_right(b2.get_rect().left()-1);\r
844             }\r
845         }\r
846         else\r
847         {\r
848             if (slider.is_hidden())\r
849             {\r
850                 top.set_top(b1.get_rect().bottom()+1);\r
851                 top.set_bottom(b2.get_rect().top()-1);\r
852                 bottom.set_top(1);\r
853                 bottom.set_bottom(-1);\r
854             }\r
855             else\r
856             {\r
857                 top.set_top(b1.get_rect().bottom()+1);\r
858                 top.set_bottom(slider.get_rect().top()-1);\r
859                 bottom.set_top(slider.get_rect().bottom()+1);\r
860                 bottom.set_bottom(b2.get_rect().top()-1);\r
861             }\r
862         }\r
863 \r
864         top_filler.rect = top;\r
865         bottom_filler.rect = bottom;\r
866     }\r
867 \r
868 // ----------------------------------------------------------------------------------------\r
869 \r
870     void scroll_bar::\r
871     hide_slider (\r
872     )\r
873     {\r
874         rectangle top(rect), bottom(rect);\r
875         slider.hide();\r
876         top_filler.disable();\r
877         bottom_filler.disable();\r
878         bottom_filler.hide();\r
879         if (ori == HORIZONTAL)\r
880         {\r
881             top.set_left(b1.get_rect().right()+1);\r
882             top.set_right(b2.get_rect().left()-1);\r
883         }\r
884         else\r
885         {\r
886             top.set_top(b1.get_rect().bottom()+1);\r
887             top.set_bottom(b2.get_rect().top()-1);\r
888         }\r
889         top_filler.rect = top;\r
890         bottom_filler.rect = bottom;\r
891     }\r
892 \r
893 // ----------------------------------------------------------------------------------------\r
894 \r
895     void scroll_bar::\r
896     show_slider (\r
897     )\r
898     {\r
899         if ((b2.get_rect().top() - b1.get_rect().bottom() - 1 <= 8 && ori == VERTICAL) || \r
900             (b2.get_rect().left() - b1.get_rect().right() - 1 <= 8 && ori == HORIZONTAL) || \r
901             max_pos == 0)\r
902             return;\r
903 \r
904         rectangle top(rect), bottom(rect);\r
905         slider.show();\r
906         top_filler.enable();\r
907         bottom_filler.enable();\r
908         bottom_filler.show();\r
909         if (ori == HORIZONTAL)\r
910         {\r
911             top.set_left(b1.get_rect().right()+1);\r
912             top.set_right(slider.get_rect().left()-1);\r
913             bottom.set_left(slider.get_rect().right()+1);\r
914             bottom.set_right(b2.get_rect().left()-1);\r
915         }\r
916         else\r
917         {\r
918             top.set_top(b1.get_rect().bottom()+1);\r
919             top.set_bottom(slider.get_rect().top()-1);\r
920             bottom.set_top(slider.get_rect().bottom()+1);\r
921             bottom.set_bottom(b2.get_rect().top()-1);\r
922         }\r
923         top_filler.rect = top;\r
924         bottom_filler.rect = bottom;\r
925     }\r
926 \r
927 // ----------------------------------------------------------------------------------------\r
928 \r
929     long scroll_bar::\r
930     max_slider_pos (\r
931     ) const\r
932     {\r
933         auto_mutex M(m);\r
934         return max_pos;\r
935     }\r
936 \r
937 // ----------------------------------------------------------------------------------------\r
938 \r
939     void scroll_bar::\r
940     set_max_slider_pos (\r
941         long mpos\r
942     )\r
943     {\r
944         auto_mutex M(m);\r
945         max_pos = mpos;\r
946         if (pos > mpos)\r
947             pos = mpos;\r
948 \r
949         if (ori == HORIZONTAL)\r
950             set_length(rect.width());\r
951         else\r
952             set_length(rect.height());\r
953 \r
954         if (mpos != 0 && enabled)\r
955         {\r
956             b1.enable();\r
957             b2.enable();\r
958         }\r
959         else\r
960         {\r
961             b1.disable();\r
962             b2.disable();\r
963         }\r
964              \r
965     }\r
966 \r
967 // ----------------------------------------------------------------------------------------\r
968 \r
969     void scroll_bar::\r
970     set_slider_pos (\r
971         long pos\r
972     )\r
973     {\r
974         auto_mutex M(m);\r
975         if (pos < 0)\r
976             pos = 0;\r
977         if (pos > max_pos)\r
978             pos = max_pos;\r
979 \r
980         this->pos = pos;\r
981 \r
982         // move the slider object to its new position\r
983         set_pos(rect.left(),rect.top());\r
984     }\r
985 \r
986 // ----------------------------------------------------------------------------------------\r
987 \r
988     long scroll_bar::\r
989     slider_pos (\r
990     ) const\r
991     {\r
992         auto_mutex M(m);\r
993         return pos;\r
994     }\r
995 \r
996 // ----------------------------------------------------------------------------------------\r
997 \r
998     void scroll_bar::\r
999     on_slider_drag (\r
1000     )\r
1001     {\r
1002         if (ori == HORIZONTAL)\r
1003         {\r
1004             double slider_pos = slider.get_rect().left() - b1.get_rect().right() - 1;\r
1005             double range = b2.get_rect().left() - b1.get_rect().right() - slider.get_rect().width() - 1;\r
1006             slider_pos /= range;\r
1007             slider_pos *= max_pos;\r
1008             pos = static_cast<unsigned long>(slider_pos);\r
1009         }\r
1010         else\r
1011         {\r
1012             double slider_pos = slider.get_rect().top() - b1.get_rect().bottom() - 1;\r
1013             double range = b2.get_rect().top() - b1.get_rect().bottom() - slider.get_rect().height() - 1;\r
1014             slider_pos /= range;\r
1015             slider_pos *= max_pos;\r
1016             pos = static_cast<unsigned long>(slider_pos);\r
1017         }\r
1018 \r
1019         adjust_fillers();\r
1020         \r
1021         if (scroll_handler.is_set())\r
1022             scroll_handler();\r
1023     }\r
1024 \r
1025 // ----------------------------------------------------------------------------------------\r
1026 \r
1027     void scroll_bar::\r
1028     draw (\r
1029         const canvas& \r
1030     ) const\r
1031     {\r
1032     }\r
1033 \r
1034 // ----------------------------------------------------------------------------------------\r
1035 \r
1036     void scroll_bar::\r
1037     b1_down (\r
1038     )\r
1039     {\r
1040         if (pos != 0)\r
1041         {\r
1042             set_slider_pos(pos-1);\r
1043             if (scroll_handler.is_set())\r
1044                 scroll_handler();\r
1045 \r
1046             if (b1_timer.delay_time() == 1000)\r
1047                 b1_timer.set_delay_time(500);\r
1048             else\r
1049                 b1_timer.set_delay_time(50);\r
1050             b1_timer.start();\r
1051         }\r
1052     }\r
1053 \r
1054 // ----------------------------------------------------------------------------------------\r
1055 \r
1056     void scroll_bar::\r
1057     b1_up (\r
1058         bool \r
1059     )\r
1060     {\r
1061         b1_timer.stop();\r
1062         b1_timer.set_delay_time(1000);\r
1063     }\r
1064 \r
1065 // ----------------------------------------------------------------------------------------\r
1066 \r
1067     void scroll_bar::\r
1068     b2_down (\r
1069     )\r
1070     {\r
1071         if (pos != max_pos)\r
1072         {\r
1073             set_slider_pos(pos+1);\r
1074             if (scroll_handler.is_set())\r
1075                 scroll_handler();\r
1076 \r
1077             if (b2_timer.delay_time() == 1000)\r
1078                 b2_timer.set_delay_time(500);\r
1079             else\r
1080                 b2_timer.set_delay_time(50);\r
1081             b2_timer.start();\r
1082         }\r
1083     }\r
1084 \r
1085 // ----------------------------------------------------------------------------------------\r
1086 \r
1087     void scroll_bar::\r
1088     b2_up (\r
1089         bool \r
1090     )\r
1091     {\r
1092         b2_timer.stop();\r
1093         b2_timer.set_delay_time(1000);\r
1094     }\r
1095         \r
1096 // ----------------------------------------------------------------------------------------\r
1097 \r
1098     void scroll_bar::\r
1099     top_filler_down (\r
1100     )\r
1101     {\r
1102         // ignore this if the mouse is now outside this object.  This could happen\r
1103         // since the timers are also calling this function.\r
1104         if (top_filler.rect.contains(lastx,lasty) == false)\r
1105         {\r
1106             top_filler_up(false);\r
1107             return;\r
1108         }\r
1109 \r
1110         if (pos != 0)\r
1111         {\r
1112             if (pos < js)\r
1113             {\r
1114                 // if there is less than jump_size() space left then jump the remaining\r
1115                 // amount.\r
1116                 delayed_set_slider_pos(0);\r
1117             }\r
1118             else\r
1119             {\r
1120                 delayed_set_slider_pos(pos-js);\r
1121             }\r
1122 \r
1123             if (top_filler_timer.delay_time() == 1000)\r
1124                 top_filler_timer.set_delay_time(500);\r
1125             else\r
1126                 top_filler_timer.set_delay_time(50);\r
1127             top_filler_timer.start();\r
1128         }\r
1129     }\r
1130 \r
1131 // ----------------------------------------------------------------------------------------\r
1132 \r
1133     void scroll_bar::\r
1134     top_filler_up (\r
1135         bool \r
1136     )\r
1137     {\r
1138         top_filler_timer.stop();\r
1139         top_filler_timer.set_delay_time(1000);\r
1140     }\r
1141 \r
1142 // ----------------------------------------------------------------------------------------\r
1143 \r
1144     void scroll_bar::\r
1145     bottom_filler_down (\r
1146     )\r
1147     {\r
1148         // ignore this if the mouse is now outside this object.  This could happen\r
1149         // since the timers are also calling this function.\r
1150         if (bottom_filler.rect.contains(lastx,lasty) == false)\r
1151         {\r
1152             bottom_filler_up(false);\r
1153             return;\r
1154         }\r
1155 \r
1156         if (pos != max_pos)\r
1157         {\r
1158             if (max_pos - pos < js)\r
1159             {\r
1160                 // if there is less than jump_size() space left then jump the remaining\r
1161                 // amount.\r
1162                 delayed_set_slider_pos(max_pos);\r
1163             }\r
1164             else\r
1165             {\r
1166                 delayed_set_slider_pos(pos+js);\r
1167             }\r
1168 \r
1169             if (bottom_filler_timer.delay_time() == 1000)\r
1170                 bottom_filler_timer.set_delay_time(500);\r
1171             else\r
1172                 bottom_filler_timer.set_delay_time(50);\r
1173             bottom_filler_timer.start();\r
1174         }\r
1175     }\r
1176 \r
1177 // ----------------------------------------------------------------------------------------\r
1178 \r
1179     void scroll_bar::\r
1180     bottom_filler_up (\r
1181         bool \r
1182     )\r
1183     {\r
1184         bottom_filler_timer.stop();\r
1185         bottom_filler_timer.set_delay_time(1000);\r
1186     }\r
1187 \r
1188 // ----------------------------------------------------------------------------------------\r
1189 \r
1190     void scroll_bar::\r
1191     set_jump_size (\r
1192         long js_\r
1193     )\r
1194     {\r
1195         auto_mutex M(m);\r
1196         if (js_ < 1)\r
1197             js = 1;\r
1198         else\r
1199             js = js_;\r
1200     }\r
1201 \r
1202 // ----------------------------------------------------------------------------------------\r
1203 \r
1204     long scroll_bar::\r
1205     jump_size (\r
1206     ) const\r
1207     {\r
1208         auto_mutex M(m);\r
1209         return js;\r
1210     }\r
1211 \r
1212 // ----------------------------------------------------------------------------------------\r
1213 \r
1214     void scroll_bar::\r
1215     on_user_event (\r
1216         int i\r
1217     )\r
1218     {\r
1219         switch (i)\r
1220         {\r
1221             case 0:\r
1222                 b1_down();\r
1223                 break;\r
1224             case 1:\r
1225                 b2_down();\r
1226                 break;\r
1227             case 2:\r
1228                 top_filler_down();\r
1229                 break;\r
1230             case 3:\r
1231                 bottom_filler_down();\r
1232                 break;\r
1233             case 4:\r
1234                 // if the position we are supposed to switch the slider too isn't \r
1235                 // already set\r
1236                 if (delayed_pos != pos)\r
1237                 {\r
1238                     set_slider_pos(delayed_pos);\r
1239                     if (scroll_handler.is_set())\r
1240                         scroll_handler();\r
1241                 }\r
1242                 break;\r
1243             default:\r
1244                 break;\r
1245         }\r
1246     }\r
1247 \r
1248 // ----------------------------------------------------------------------------------------\r
1249 \r
1250     void scroll_bar::\r
1251     delayed_set_slider_pos (\r
1252         unsigned long dpos\r
1253     ) \r
1254     {\r
1255         delayed_pos = dpos;\r
1256         parent.trigger_user_event(this,4); \r
1257     }\r
1258 \r
1259 // ----------------------------------------------------------------------------------------\r
1260 \r
1261     void scroll_bar::\r
1262     b1_down_t (\r
1263     ) \r
1264     { \r
1265         parent.trigger_user_event(this,0); \r
1266     }\r
1267 \r
1268 // ----------------------------------------------------------------------------------------\r
1269 \r
1270     void scroll_bar::\r
1271     b2_down_t (\r
1272     ) \r
1273     { \r
1274         parent.trigger_user_event(this,1); \r
1275     }\r
1276 \r
1277 // ----------------------------------------------------------------------------------------\r
1278 \r
1279     void scroll_bar::\r
1280     top_filler_down_t (\r
1281     ) \r
1282     { \r
1283         parent.trigger_user_event(this,2); \r
1284     }\r
1285 \r
1286 // ----------------------------------------------------------------------------------------\r
1287 \r
1288     void scroll_bar::\r
1289     bottom_filler_down_t (\r
1290     ) \r
1291     { \r
1292         parent.trigger_user_event(this,3); \r
1293     }\r
1294 \r
1295 // ----------------------------------------------------------------------------------------\r
1296 // ----------------------------------------------------------------------------------------\r
1297 //                  widget_group object methods  \r
1298 // ----------------------------------------------------------------------------------------\r
1299 // ----------------------------------------------------------------------------------------\r
1300 \r
1301     void widget_group::\r
1302     empty (\r
1303     ) \r
1304     {  \r
1305         auto_mutex M(m); \r
1306         widgets.clear(); \r
1307         wg_widgets.clear();\r
1308     }\r
1309 \r
1310 // ----------------------------------------------------------------------------------------\r
1311 \r
1312     void widget_group::\r
1313     add (\r
1314         drawable& widget,\r
1315         unsigned long x,\r
1316         unsigned long y\r
1317     )\r
1318     {\r
1319         auto_mutex M(m); \r
1320         drawable* w = &widget;\r
1321         relpos rp;\r
1322         rp.x = x;\r
1323         rp.y = y;\r
1324         if (widgets.is_in_domain(w))\r
1325         {\r
1326             widgets[w].x = x;\r
1327             widgets[w].y = y;\r
1328         }\r
1329         else\r
1330         {\r
1331             widgets.add(w,rp);\r
1332         }\r
1333         if (is_hidden())\r
1334             widget.hide();\r
1335         else\r
1336             widget.show();\r
1337 \r
1338         if (is_enabled())\r
1339             widget.enable();\r
1340         else\r
1341             widget.disable();\r
1342 \r
1343         widget.set_z_order(z_order());\r
1344         widget.set_pos(x+rect.left(),y+rect.top());\r
1345     }\r
1346 \r
1347 // ----------------------------------------------------------------------------------------\r
1348 \r
1349     void widget_group::\r
1350     add (\r
1351         widget_group& widget,\r
1352         unsigned long x,\r
1353         unsigned long y\r
1354     )\r
1355     {\r
1356         auto_mutex M(m); \r
1357         drawable& w = widget;\r
1358         add(w, x, y);\r
1359 \r
1360         widget_group* wg = &widget;\r
1361         wg_widgets.add(wg);\r
1362     }\r
1363 \r
1364 // ----------------------------------------------------------------------------------------\r
1365 \r
1366     bool widget_group::\r
1367     is_member (\r
1368         const drawable& widget\r
1369     ) const \r
1370     { \r
1371         auto_mutex M(m); \r
1372         drawable* w = const_cast<drawable*>(&widget);\r
1373         return widgets.is_in_domain(w); \r
1374     }\r
1375 \r
1376 // ----------------------------------------------------------------------------------------\r
1377 \r
1378     void widget_group::\r
1379     remove (\r
1380         const drawable& widget\r
1381     )\r
1382     {\r
1383         auto_mutex M(m); \r
1384         drawable* w = const_cast<drawable*>(&widget);\r
1385         if (widgets.is_in_domain(w))\r
1386         {\r
1387             widgets.destroy(w);\r
1388 \r
1389             // check if we also have an entry in the wg_widgets set and if\r
1390             // so then remove that too\r
1391             widget_group* wg = reinterpret_cast<widget_group*>(w);\r
1392             if (wg_widgets.is_member(wg))\r
1393             {\r
1394                 wg_widgets.destroy(wg);\r
1395             }\r
1396         }\r
1397     }\r
1398 \r
1399 // ----------------------------------------------------------------------------------------\r
1400 \r
1401     unsigned long widget_group::\r
1402     size (\r
1403     ) const \r
1404     {  \r
1405         auto_mutex M(m); \r
1406         return widgets.size(); \r
1407     }\r
1408 \r
1409 // ----------------------------------------------------------------------------------------\r
1410 \r
1411     void widget_group::\r
1412     set_pos (\r
1413         long x,\r
1414         long y\r
1415     )\r
1416     {\r
1417         auto_mutex M(m);\r
1418         widgets.reset();\r
1419         while (widgets.move_next())\r
1420         {\r
1421             const unsigned long rx = widgets.element().value().x;\r
1422             const unsigned long ry = widgets.element().value().y;\r
1423             widgets.element().key()->set_pos(x+rx,y+ry);\r
1424         }\r
1425         drawable::set_pos(x,y);\r
1426     }\r
1427 \r
1428 // ----------------------------------------------------------------------------------------\r
1429 \r
1430     void widget_group::\r
1431     set_z_order (\r
1432         long order\r
1433     )\r
1434     {\r
1435         auto_mutex M(m);\r
1436         widgets.reset();\r
1437         while (widgets.move_next())\r
1438             widgets.element().key()->set_z_order(order);\r
1439         drawable::set_z_order(order);\r
1440     }\r
1441 \r
1442 // ----------------------------------------------------------------------------------------\r
1443 \r
1444     void widget_group::\r
1445     show (\r
1446     )\r
1447     {\r
1448         auto_mutex M(m);\r
1449         widgets.reset();\r
1450         while (widgets.move_next())\r
1451             widgets.element().key()->show();\r
1452         drawable::show();\r
1453     }\r
1454 \r
1455 // ----------------------------------------------------------------------------------------\r
1456 \r
1457     void widget_group::\r
1458     hide (\r
1459     )\r
1460     {\r
1461         auto_mutex M(m);\r
1462         widgets.reset();\r
1463         while (widgets.move_next())\r
1464             widgets.element().key()->hide();\r
1465         drawable::hide();\r
1466     }\r
1467 \r
1468 // ----------------------------------------------------------------------------------------\r
1469 \r
1470     void widget_group::\r
1471     enable (\r
1472     )\r
1473     {\r
1474         auto_mutex M(m);\r
1475         widgets.reset();\r
1476         while (widgets.move_next())\r
1477             widgets.element().key()->enable();\r
1478         drawable::enable();\r
1479     }\r
1480 \r
1481 // ----------------------------------------------------------------------------------------\r
1482 \r
1483     void widget_group::\r
1484     disable ()\r
1485     {\r
1486         auto_mutex M(m);\r
1487         widgets.reset();\r
1488         while (widgets.move_next())\r
1489             widgets.element().key()->disable();\r
1490         drawable::disable();\r
1491     }\r
1492 \r
1493 // ----------------------------------------------------------------------------------------\r
1494 \r
1495     void widget_group::\r
1496     fit_to_contents (\r
1497     )\r
1498     {\r
1499         auto_mutex M(m);\r
1500 \r
1501         // call fit_to_contents on all the widget_groups we contain\r
1502         wg_widgets.reset();\r
1503         while (wg_widgets.move_next())\r
1504             wg_widgets.element()->fit_to_contents();\r
1505 \r
1506         // now accumulate a rectangle that contains everything in this widget_group\r
1507         rectangle r;\r
1508         widgets.reset();\r
1509         while (widgets.move_next())\r
1510             r = r + widgets.element().key()->get_rect();\r
1511 \r
1512         if (r.is_empty())\r
1513         {\r
1514             // make sure it is still empty after we set it at the correct position \r
1515             r.set_right(rect.left()-1);\r
1516             r.set_bottom(rect.top()-1);\r
1517         }\r
1518 \r
1519         r.set_left(rect.left());\r
1520         r.set_top(rect.top());\r
1521         rect = r;\r
1522     }\r
1523 \r
1524 // ----------------------------------------------------------------------------------------\r
1525 // ----------------------------------------------------------------------------------------\r
1526 //                class popup_menu\r
1527 // ----------------------------------------------------------------------------------------\r
1528 // ----------------------------------------------------------------------------------------\r
1529 \r
1530     popup_menu::\r
1531     popup_menu (\r
1532     ) :\r
1533         base_window(false,true),\r
1534         pad(2),\r
1535         item_pad(3),\r
1536         cur_rect(pad,pad,pad-1,pad-1),\r
1537         left_width(0),\r
1538         middle_width(0),\r
1539         selected_item(0),\r
1540         submenu_open(false)\r
1541     {\r
1542     }\r
1543 \r
1544 // ----------------------------------------------------------------------------------------\r
1545 \r
1546     void popup_menu::\r
1547     enable_menu_item (\r
1548         unsigned long idx\r
1549     )\r
1550     {\r
1551         DLIB_ASSERT ( idx < size() ,\r
1552                       "\tvoid popup_menu::enable_menu_item()"\r
1553                       << "\n\tidx:    " << idx\r
1554                       << "\n\tsize(): " << size() \r
1555         );\r
1556         auto_mutex M(wm);\r
1557         item_enabled[idx] = true;\r
1558         invalidate_rectangle(cur_rect);\r
1559     }\r
1560 \r
1561 // ----------------------------------------------------------------------------------------\r
1562 \r
1563     void popup_menu::\r
1564     disable_menu_item (\r
1565         unsigned long idx\r
1566     )\r
1567     {\r
1568         DLIB_ASSERT ( idx < size() ,\r
1569                       "\tvoid popup_menu::enable_menu_item()"\r
1570                       << "\n\tidx:    " << idx\r
1571                       << "\n\tsize(): " << size() \r
1572         );\r
1573         auto_mutex M(wm);\r
1574         item_enabled[idx] = false;\r
1575         invalidate_rectangle(cur_rect);\r
1576     }\r
1577 \r
1578 // ----------------------------------------------------------------------------------------\r
1579 \r
1580     unsigned long popup_menu::\r
1581     size (\r
1582     ) const\r
1583     { \r
1584         auto_mutex M(wm);\r
1585         return items.size();\r
1586     }\r
1587 \r
1588 // ----------------------------------------------------------------------------------------\r
1589 \r
1590     void popup_menu::\r
1591     clear (\r
1592     )\r
1593     {\r
1594         auto_mutex M(wm);\r
1595         hide();\r
1596         cur_rect = rectangle(pad,pad,pad-1,pad-1);\r
1597         win_rect = rectangle();\r
1598         left_width = 0;\r
1599         middle_width = 0;\r
1600         items.clear();\r
1601         item_enabled.clear();\r
1602         left_rects.clear();\r
1603         middle_rects.clear();\r
1604         right_rects.clear();\r
1605         line_rects.clear();\r
1606         submenus.clear();\r
1607         selected_item = 0;\r
1608         submenu_open = false;\r
1609     }\r
1610 \r
1611 // ----------------------------------------------------------------------------------------\r
1612 \r
1613     void popup_menu::\r
1614     show (\r
1615     )\r
1616     {\r
1617         auto_mutex M(wm);\r
1618         selected_item = submenus.size();\r
1619         base_window::show();\r
1620     }\r
1621 \r
1622 // ----------------------------------------------------------------------------------------\r
1623 \r
1624     void popup_menu::\r
1625     hide (\r
1626     )\r
1627     {\r
1628         auto_mutex M(wm);\r
1629         // hide ourselves\r
1630         close_submenu();\r
1631         selected_item = submenus.size();\r
1632         base_window::hide();\r
1633     }\r
1634 \r
1635 // ----------------------------------------------------------------------------------------\r
1636 \r
1637     void popup_menu::\r
1638     select_first_item (\r
1639     )\r
1640     {\r
1641         auto_mutex M(wm);\r
1642         close_submenu();\r
1643         selected_item = items.size();\r
1644         for (unsigned long i = 0; i < items.size(); ++i)\r
1645         {\r
1646             if ((items[i]->has_click_event() || submenus[i]) && item_enabled[i])\r
1647             {\r
1648                 selected_item = i;\r
1649                 break;\r
1650             }\r
1651         }\r
1652         invalidate_rectangle(cur_rect);\r
1653     }\r
1654 \r
1655 // ----------------------------------------------------------------------------------------\r
1656 \r
1657     bool popup_menu::\r
1658     forwarded_on_keydown (\r
1659         unsigned long key,\r
1660         bool is_printable,\r
1661         unsigned long state\r
1662     )\r
1663     {\r
1664         auto_mutex M(wm);\r
1665         // do nothing if this popup menu is empty\r
1666         if (items.size() == 0)\r
1667             return false;\r
1668 \r
1669 \r
1670         // check if the selected item is a submenu\r
1671         if (selected_item != submenus.size() && submenus[selected_item] != 0 && submenu_open)\r
1672         {\r
1673             // send the key to the submenu and return if that menu used the key\r
1674             if (submenus[selected_item]->forwarded_on_keydown(key,is_printable,state) == true)\r
1675                 return true;\r
1676         }\r
1677 \r
1678         if (key == KEY_UP)\r
1679         {\r
1680             for (unsigned long i = 0; i < items.size(); ++i)\r
1681             {\r
1682                 selected_item = (selected_item + items.size() - 1)%items.size();\r
1683                 // only stop looking if this one is enabled and has a click event or is a submenu\r
1684                 if (item_enabled[selected_item] && (items[selected_item]->has_click_event() || submenus[selected_item]) )\r
1685                     break;\r
1686             }\r
1687             invalidate_rectangle(cur_rect);\r
1688             return true;\r
1689         }\r
1690         else if (key == KEY_DOWN)\r
1691         {\r
1692             for (unsigned long i = 0; i < items.size(); ++i)\r
1693             {\r
1694                 selected_item = (selected_item + 1)%items.size();\r
1695                 // only stop looking if this one is enabled and has a click event or is a submenu\r
1696                 if (item_enabled[selected_item] && (items[selected_item]->has_click_event() || submenus[selected_item]))\r
1697                     break;\r
1698             }\r
1699             invalidate_rectangle(cur_rect);\r
1700             return true;\r
1701         }\r
1702         else if (key == KEY_RIGHT && submenu_open == false && display_selected_submenu())\r
1703         {\r
1704             submenus[selected_item]->select_first_item();\r
1705             return true;\r
1706         }\r
1707         else if (key == KEY_LEFT && selected_item != submenus.size() && \r
1708                  submenus[selected_item] != 0 && submenu_open)\r
1709         {\r
1710             close_submenu();\r
1711             return true;\r
1712         }\r
1713         else if (key == '\n')\r
1714         {\r
1715             if (selected_item != submenus.size() && (items[selected_item]->has_click_event() || submenus[selected_item]))\r
1716             {\r
1717                 const long idx = selected_item;\r
1718                 // only hide this popup window if this isn't a submenu\r
1719                 if (submenus[idx] == 0)\r
1720                 {\r
1721                     hide();\r
1722                     hide_handlers.reset();\r
1723                     while (hide_handlers.move_next())\r
1724                         hide_handlers.element()();\r
1725                 }\r
1726                 else\r
1727                 {\r
1728                     display_selected_submenu();\r
1729                     submenus[idx]->select_first_item();\r
1730                 }\r
1731                 items[idx]->on_click();\r
1732                 return true;\r
1733             }\r
1734         }\r
1735         else if (is_printable)\r
1736         {\r
1737             // check if there is a hotkey for this key\r
1738             for (unsigned long i = 0; i < items.size(); ++i)\r
1739             {\r
1740                 if (std::tolower(key) == std::tolower(items[i]->get_hot_key()) && \r
1741                     (items[i]->has_click_event() || submenus[i]) && item_enabled[i] )\r
1742                 {\r
1743                     // only hide this popup window if this isn't a submenu\r
1744                     if (submenus[i] == 0)\r
1745                     {\r
1746                         hide();\r
1747                         hide_handlers.reset();\r
1748                         while (hide_handlers.move_next())\r
1749                             hide_handlers.element()();\r
1750                     }\r
1751                     else\r
1752                     {\r
1753                         if (selected_item != items.size())\r
1754                             invalidate_rectangle(line_rects[selected_item]);\r
1755 \r
1756                         selected_item = i;\r
1757                         display_selected_submenu();\r
1758                         invalidate_rectangle(line_rects[i]);\r
1759                         submenus[i]->select_first_item();\r
1760                     }\r
1761                     items[i]->on_click();\r
1762                 }\r
1763             }\r
1764 \r
1765             // always say we use a printable key for hotkeys\r
1766             return true;\r
1767         }\r
1768 \r
1769         return false;\r
1770     }\r
1771 \r
1772 // ----------------------------------------------------------------------------------------\r
1773 \r
1774     void popup_menu::\r
1775     on_submenu_hide (\r
1776     )\r
1777     {\r
1778         hide();\r
1779         hide_handlers.reset();\r
1780         while (hide_handlers.move_next())\r
1781             hide_handlers.element()();\r
1782     }\r
1783 \r
1784 // ----------------------------------------------------------------------------------------\r
1785 \r
1786     void popup_menu::\r
1787     on_window_resized(\r
1788     )\r
1789     {\r
1790         invalidate_rectangle(win_rect);\r
1791     }\r
1792 \r
1793 // ----------------------------------------------------------------------------------------\r
1794 \r
1795     void popup_menu::\r
1796     on_mouse_up (\r
1797         unsigned long btn,\r
1798         unsigned long,\r
1799         long x,\r
1800         long y\r
1801     )\r
1802     {\r
1803         if (cur_rect.contains(x,y) && btn == LEFT)\r
1804         {\r
1805             // figure out which item this was on \r
1806             for (unsigned long i = 0; i < items.size(); ++i)\r
1807             {\r
1808                 if (line_rects[i].contains(x,y) && item_enabled[i] && items[i]->has_click_event())\r
1809                 {\r
1810                     // only hide this popup window if this isn't a submenu\r
1811                     if (submenus[i] == 0)\r
1812                     {\r
1813                         hide();\r
1814                         hide_handlers.reset();\r
1815                         while (hide_handlers.move_next())\r
1816                             hide_handlers.element()();\r
1817                     }\r
1818                     items[i]->on_click();\r
1819                     break;\r
1820                 }\r
1821             }\r
1822         }\r
1823     }\r
1824 \r
1825 // ----------------------------------------------------------------------------------------\r
1826 \r
1827     void popup_menu::\r
1828     on_mouse_move (\r
1829         unsigned long ,\r
1830         long x,\r
1831         long y\r
1832     )\r
1833     {\r
1834         if (cur_rect.contains(x,y))\r
1835         {\r
1836             // check if the mouse is still in the same rect it was in last time\r
1837             rectangle last_rect;\r
1838             if (selected_item != submenus.size())\r
1839             {\r
1840                 last_rect = line_rects[selected_item];\r
1841             }\r
1842 \r
1843             // if the mouse isn't in the same rectangle any more\r
1844             if (last_rect.contains(x,y) == false)\r
1845             {\r
1846                 if (selected_item != submenus.size())\r
1847                 {\r
1848                     invalidate_rectangle(last_rect);\r
1849                     close_submenu();\r
1850                     selected_item = submenus.size();\r
1851                 }\r
1852 \r
1853 \r
1854                 // figure out if we should redraw any menu items \r
1855                 for (unsigned long i = 0; i < items.size(); ++i)\r
1856                 {\r
1857                     if (items[i]->has_click_event() || submenus[i])\r
1858                     {\r
1859                         if (line_rects[i].contains(x,y))\r
1860                         {\r
1861                             selected_item = i;\r
1862                             break;\r
1863                         }\r
1864                     }\r
1865                 }\r
1866 \r
1867                 // if we found a rectangle that contains the mouse then\r
1868                 // tell it to redraw itself\r
1869                 if (selected_item != submenus.size())\r
1870                 {\r
1871                     display_selected_submenu();\r
1872                     invalidate_rectangle(line_rects[selected_item]);\r
1873                 }\r
1874             }\r
1875         }\r
1876     }\r
1877 \r
1878 // ----------------------------------------------------------------------------------------\r
1879 \r
1880     void popup_menu::\r
1881     close_submenu (\r
1882     )\r
1883     {\r
1884         if (selected_item != submenus.size() && submenus[selected_item] && submenu_open)\r
1885         {\r
1886             submenus[selected_item]->hide();\r
1887             submenu_open = false;\r
1888         }\r
1889     }\r
1890 \r
1891 // ----------------------------------------------------------------------------------------\r
1892 \r
1893     bool popup_menu::\r
1894     display_selected_submenu (\r
1895     )\r
1896     {\r
1897         // show the submenu if one exists\r
1898         if (selected_item != submenus.size() && submenus[selected_item])\r
1899         {\r
1900             long wx, wy;\r
1901             get_pos(wx,wy);\r
1902             wx += line_rects[selected_item].right();\r
1903             wy += line_rects[selected_item].top();\r
1904             submenus[selected_item]->set_pos(wx+1,wy-2);\r
1905             submenus[selected_item]->show();\r
1906             submenu_open = true;\r
1907             return true;\r
1908         }\r
1909         return false;\r
1910     }\r
1911 \r
1912 // ----------------------------------------------------------------------------------------\r
1913 \r
1914     void popup_menu::\r
1915     on_mouse_leave (\r
1916     )\r
1917     {\r
1918         if (selected_item != submenus.size())\r
1919         {\r
1920             // only unhighlight a menu item if it isn't a submenu item\r
1921             if (submenus[selected_item] == 0)\r
1922             {\r
1923                 invalidate_rectangle(line_rects[selected_item]);\r
1924                 selected_item = submenus.size();\r
1925             }\r
1926         }\r
1927     }\r
1928 \r
1929 // ----------------------------------------------------------------------------------------\r
1930 \r
1931     void popup_menu::\r
1932     paint (\r
1933         const canvas& c\r
1934     )\r
1935     {\r
1936         c.fill(200,200,200);\r
1937         draw_rectangle(c, win_rect);\r
1938         for (unsigned long i = 0; i < items.size(); ++i)\r
1939         {\r
1940             bool is_selected = false;\r
1941             if (selected_item != submenus.size() && i == selected_item && \r
1942                 item_enabled[i])\r
1943                 is_selected = true;\r
1944 \r
1945             items[i]->draw_background(c,line_rects[i], item_enabled[i], is_selected);\r
1946             items[i]->draw_left(c,left_rects[i], item_enabled[i], is_selected);\r
1947             items[i]->draw_middle(c,middle_rects[i], item_enabled[i], is_selected);\r
1948             items[i]->draw_right(c,right_rects[i], item_enabled[i], is_selected);\r
1949         }\r
1950     }\r
1951 \r
1952 // ----------------------------------------------------------------------------------------\r
1953 // ----------------------------------------------------------------------------------------\r
1954 //              class zoomable_region\r
1955 // ----------------------------------------------------------------------------------------\r
1956 // ----------------------------------------------------------------------------------------\r
1957 \r
1958     zoomable_region::\r
1959     zoomable_region (\r
1960         drawable_window& w,\r
1961         unsigned long events \r
1962     ) :\r
1963         drawable(w,MOUSE_CLICK | MOUSE_WHEEL | MOUSE_MOVE | events),\r
1964         min_scale(0.15),\r
1965         max_scale(1.0),\r
1966         zoom_increment_(0.90),\r
1967         vsb(w, scroll_bar::VERTICAL),\r
1968         hsb(w, scroll_bar::HORIZONTAL)\r
1969     {\r
1970         scale = 1;\r
1971         mouse_drag_screen = false;\r
1972         style.reset(new scrollable_region_style_default());\r
1973 \r
1974         hsb.set_scroll_handler(*this,&zoomable_region::on_h_scroll);\r
1975         vsb.set_scroll_handler(*this,&zoomable_region::on_v_scroll);\r
1976     }\r
1977 \r
1978 // ----------------------------------------------------------------------------------------\r
1979 \r
1980     zoomable_region::\r
1981     ~zoomable_region() \r
1982     {\r
1983     }\r
1984 \r
1985 // ----------------------------------------------------------------------------------------\r
1986 \r
1987     void zoomable_region::\r
1988     set_pos (\r
1989         long x,\r
1990         long y\r
1991     )\r
1992     {\r
1993         auto_mutex M(m);\r
1994         drawable::set_pos(x,y);\r
1995         const long border_size = style->get_border_size();\r
1996         vsb.set_pos(rect.right()-border_size+1-vsb.width(),rect.top()+border_size);\r
1997         hsb.set_pos(rect.left()+border_size,rect.bottom()-border_size+1-hsb.height());\r
1998 \r
1999         display_rect_ = rectangle(rect.left()+border_size,\r
2000                                   rect.top()+border_size,\r
2001                                   rect.right()-border_size-vsb.width(),\r
2002                                   rect.bottom()-border_size-hsb.height());\r
2003 \r
2004     }\r
2005 \r
2006 // ----------------------------------------------------------------------------------------\r
2007 \r
2008     void zoomable_region::\r
2009     set_zoom_increment (\r
2010         double zi\r
2011     )\r
2012     {\r
2013         DLIB_ASSERT(0.0 < zi && zi < 1.0,\r
2014                     "\tvoid zoomable_region::set_zoom_increment(zi)"\r
2015                     << "\n\t the zoom increment must be between 0 and 1"\r
2016                     << "\n\t zi:   " << zi\r
2017                     << "\n\t this: " << this\r
2018         );\r
2019 \r
2020         auto_mutex M(m);\r
2021         zoom_increment_ = zi;\r
2022     }\r
2023 \r
2024 // ----------------------------------------------------------------------------------------\r
2025 \r
2026     double zoomable_region::\r
2027     zoom_increment (\r
2028     ) const\r
2029     {\r
2030         auto_mutex M(m);\r
2031         return zoom_increment_;\r
2032     }\r
2033 \r
2034 // ----------------------------------------------------------------------------------------\r
2035 \r
2036     void zoomable_region::\r
2037     set_max_zoom_scale (\r
2038         double ms \r
2039     )\r
2040     {\r
2041         DLIB_ASSERT(ms > 0,\r
2042                     "\tvoid zoomable_region::set_max_zoom_scale(ms)"\r
2043                     << "\n\t the max zoom scale must be greater than 0"\r
2044                     << "\n\t ms:   " << ms \r
2045                     << "\n\t this: " << this\r
2046         );\r
2047 \r
2048         auto_mutex M(m);\r
2049         max_scale = ms;\r
2050         if (scale > ms)\r
2051         {\r
2052             scale = max_scale;\r
2053             lr_point = gui_to_graph_space(point(display_rect_.right(),display_rect_.bottom()));\r
2054             redraw_graph();\r
2055         }\r
2056     }\r
2057 \r
2058 // ----------------------------------------------------------------------------------------\r
2059 \r
2060     void zoomable_region::\r
2061     set_min_zoom_scale (\r
2062         double ms \r
2063     )\r
2064     {\r
2065         DLIB_ASSERT(ms > 0,\r
2066                     "\tvoid zoomable_region::set_min_zoom_scale(ms)"\r
2067                     << "\n\t the min zoom scale must be greater than 0"\r
2068                     << "\n\t ms:   " << ms \r
2069                     << "\n\t this: " << this\r
2070         );\r
2071 \r
2072         auto_mutex M(m);\r
2073         min_scale = ms;\r
2074 \r
2075         if (scale < ms)\r
2076         {\r
2077             scale = min_scale;\r
2078         }\r
2079 \r
2080         // just call set_size so that everything gets redrawn right\r
2081         set_size(rect.width(), rect.height());\r
2082     }\r
2083 \r
2084 // ----------------------------------------------------------------------------------------\r
2085 \r
2086     double zoomable_region::\r
2087     min_zoom_scale (\r
2088     ) const\r
2089     {\r
2090         auto_mutex M(m);\r
2091         return min_scale;\r
2092     }\r
2093 \r
2094 // ----------------------------------------------------------------------------------------\r
2095 \r
2096     double zoomable_region::\r
2097     max_zoom_scale (\r
2098     ) const\r
2099     {\r
2100         auto_mutex M(m);\r
2101         return max_scale;\r
2102     }\r
2103 \r
2104 // ----------------------------------------------------------------------------------------\r
2105 \r
2106     void zoomable_region::\r
2107     set_size (\r
2108         unsigned long width,\r
2109         unsigned long height\r
2110     )\r
2111     {\r
2112         auto_mutex M(m);\r
2113         rectangle old(rect);\r
2114         const long border_size = style->get_border_size();\r
2115         rect = resize_rect(rect,width,height);\r
2116         vsb.set_pos(rect.right()-border_size+1-vsb.width(),  rect.top()+border_size);\r
2117         hsb.set_pos(rect.left()+border_size,  rect.bottom()-border_size+1-hsb.height());\r
2118 \r
2119         display_rect_ = rectangle(rect.left()+border_size,\r
2120                                   rect.top()+border_size,\r
2121                                   rect.right()-border_size-vsb.width(),\r
2122                                   rect.bottom()-border_size-hsb.height());\r
2123         vsb.set_length(display_rect_.height());\r
2124         hsb.set_length(display_rect_.width());\r
2125         parent.invalidate_rectangle(rect+old);\r
2126 \r
2127         const double old_scale = scale;\r
2128         const vector<double,2> old_gr_orig(gr_orig);\r
2129         scale = min_scale;\r
2130         gr_orig = vector<double,2>(0,0);\r
2131         lr_point = gui_to_graph_space(point(display_rect_.right(),display_rect_.bottom()));\r
2132         scale = old_scale;\r
2133 \r
2134         // call adjust_origin() so that the scroll bars get their max slider positions\r
2135         // setup right\r
2136         const point rect_corner(display_rect_.left(), display_rect_.top());\r
2137         adjust_origin(rect_corner, old_gr_orig);\r
2138     }\r
2139 \r
2140 // ----------------------------------------------------------------------------------------\r
2141 \r
2142     void zoomable_region::\r
2143     show (\r
2144     )\r
2145     {\r
2146         auto_mutex M(m);\r
2147         drawable::show();\r
2148         hsb.show();\r
2149         vsb.show();\r
2150     }\r
2151 \r
2152 // ----------------------------------------------------------------------------------------\r
2153 \r
2154     void zoomable_region::\r
2155     hide (\r
2156     )\r
2157     {\r
2158         auto_mutex M(m);\r
2159         drawable::hide();\r
2160         hsb.hide();\r
2161         vsb.hide();\r
2162     }\r
2163 \r
2164 // ----------------------------------------------------------------------------------------\r
2165 \r
2166     void zoomable_region::\r
2167     enable (\r
2168     )\r
2169     {\r
2170         auto_mutex M(m);\r
2171         drawable::enable();\r
2172         hsb.enable();\r
2173         vsb.enable();\r
2174     }\r
2175 \r
2176 // ----------------------------------------------------------------------------------------\r
2177 \r
2178     void zoomable_region::\r
2179     disable (\r
2180     )\r
2181     {\r
2182         auto_mutex M(m);\r
2183         drawable::disable();\r
2184         hsb.disable();\r
2185         vsb.disable();\r
2186     }\r
2187 \r
2188 // ----------------------------------------------------------------------------------------\r
2189 \r
2190     void zoomable_region::\r
2191     set_z_order (\r
2192         long order\r
2193     )\r
2194     {\r
2195         auto_mutex M(m);\r
2196         drawable::set_z_order(order);\r
2197         hsb.set_z_order(order);\r
2198         vsb.set_z_order(order);\r
2199     }\r
2200 \r
2201 // ----------------------------------------------------------------------------------------\r
2202 \r
2203     point zoomable_region::\r
2204     graph_to_gui_space (\r
2205         const vector<double,2>& p\r
2206     ) const\r
2207     {\r
2208         const point rect_corner(display_rect_.left(), display_rect_.top());\r
2209         return (p - gr_orig)*scale + rect_corner;\r
2210     }\r
2211 \r
2212 // ----------------------------------------------------------------------------------------\r
2213 \r
2214     vector<double,2> zoomable_region::\r
2215     gui_to_graph_space (\r
2216         const point& p\r
2217     ) const\r
2218     {\r
2219         const point rect_corner(display_rect_.left(), display_rect_.top());\r
2220         return (p - rect_corner)/scale + gr_orig;\r
2221     }\r
2222 \r
2223 // ----------------------------------------------------------------------------------------\r
2224 \r
2225     point zoomable_region::\r
2226     max_graph_point (\r
2227     ) const\r
2228     {\r
2229         return lr_point;\r
2230     }\r
2231 \r
2232 // ----------------------------------------------------------------------------------------\r
2233 \r
2234     rectangle zoomable_region::\r
2235     display_rect (\r
2236     ) const \r
2237     {\r
2238         return display_rect_;\r
2239     }\r
2240 \r
2241 // ----------------------------------------------------------------------------------------\r
2242 \r
2243     double zoomable_region::\r
2244     zoom_scale (\r
2245     ) const\r
2246     {\r
2247         return scale;\r
2248     }\r
2249 \r
2250 // ----------------------------------------------------------------------------------------\r
2251 \r
2252     void zoomable_region::\r
2253     set_zoom_scale (\r
2254         double new_scale\r
2255     )\r
2256     {\r
2257         // if new_scale isn't in the right range then put it back in range before we do the \r
2258         // rest of this function\r
2259         if (!(min_scale <= new_scale && new_scale <= max_scale))\r
2260         {\r
2261             if (new_scale > max_scale)\r
2262                 new_scale = max_scale;\r
2263             else\r
2264                 new_scale = min_scale;\r
2265         }\r
2266 \r
2267         // find the point in the center of the graph area\r
2268         point center((display_rect_.left()+display_rect_.right())/2,  (display_rect_.top()+display_rect_.bottom())/2);\r
2269         point graph_p(gui_to_graph_space(center));\r
2270         scale = new_scale;\r
2271         adjust_origin(center, graph_p);\r
2272         redraw_graph();\r
2273     }\r
2274 \r
2275 // ----------------------------------------------------------------------------------------\r
2276 \r
2277     void zoomable_region::\r
2278     center_display_at_graph_point (\r
2279         const vector<double,2>& p\r
2280     )\r
2281     {\r
2282         // find the point in the center of the graph area\r
2283         point center((display_rect_.left()+display_rect_.right())/2,  (display_rect_.top()+display_rect_.bottom())/2);\r
2284         adjust_origin(center, p);\r
2285         redraw_graph();\r
2286     }\r
2287 \r
2288 // ----------------------------------------------------------------------------------------\r
2289 \r
2290     void zoomable_region::\r
2291     on_wheel_down (\r
2292         unsigned long \r
2293     )\r
2294     {\r
2295         // zoom out\r
2296         if (enabled && !hidden && scale > min_scale && display_rect_.contains(lastx,lasty))\r
2297         {\r
2298             point gui_p(lastx,lasty);\r
2299             point graph_p(gui_to_graph_space(gui_p));\r
2300             scale *= zoom_increment_;\r
2301             if (scale < min_scale)\r
2302                 scale = min_scale;\r
2303             redraw_graph(); \r
2304             adjust_origin(gui_p, graph_p);\r
2305         }\r
2306     }\r
2307 \r
2308 // ----------------------------------------------------------------------------------------\r
2309 \r
2310     void zoomable_region::\r
2311     on_wheel_up (\r
2312         unsigned long \r
2313     )\r
2314     {\r
2315         // zoom in \r
2316         if (enabled && !hidden && scale < max_scale  && display_rect_.contains(lastx,lasty))\r
2317         {\r
2318             point gui_p(lastx,lasty);\r
2319             point graph_p(gui_to_graph_space(gui_p));\r
2320             scale /= zoom_increment_;\r
2321             if (scale > max_scale)\r
2322                 scale = max_scale;\r
2323             redraw_graph(); \r
2324             adjust_origin(gui_p, graph_p);\r
2325         }\r
2326     }\r
2327 \r
2328 // ----------------------------------------------------------------------------------------\r
2329 \r
2330     void zoomable_region::\r
2331     on_mouse_move (\r
2332         unsigned long state,\r
2333         long x,\r
2334         long y\r
2335     )\r
2336     {\r
2337         if (enabled && !hidden && mouse_drag_screen)\r
2338         {\r
2339             adjust_origin(point(x,y), drag_screen_point);\r
2340             redraw_graph();\r
2341         }\r
2342 \r
2343         // check if the mouse isn't being dragged anymore\r
2344         if ((state & base_window::LEFT) == 0)\r
2345         {\r
2346             mouse_drag_screen = false;\r
2347         }\r
2348     }\r
2349 \r
2350 // ----------------------------------------------------------------------------------------\r
2351 \r
2352     void zoomable_region::\r
2353     on_mouse_up (\r
2354         unsigned long ,\r
2355         unsigned long ,\r
2356         long ,\r
2357         long \r
2358     )\r
2359     {\r
2360         mouse_drag_screen = false;\r
2361     }\r
2362 \r
2363 // ----------------------------------------------------------------------------------------\r
2364 \r
2365     void zoomable_region::\r
2366     on_mouse_down (\r
2367         unsigned long btn,\r
2368         unsigned long ,\r
2369         long x,\r
2370         long y,\r
2371         bool \r
2372     )\r
2373     {\r
2374         if (enabled && !hidden && display_rect_.contains(x,y) && btn == base_window::LEFT)\r
2375         {\r
2376             mouse_drag_screen = true;\r
2377             drag_screen_point = gui_to_graph_space(point(x,y));\r