1 /* GG is a GUI for OpenGL.
2    Copyright (C) 2003-2008 T. Zachary Laine
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public License
6    as published by the Free Software Foundation; either version 2.1
7    of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with this library; if not, write to the Free
16    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307 USA
18 
19    If you do not wish to comply with the terms of the LGPL please
20    contact the author as other terms are available for a fee.
21 
22    Zach Laine
23    whatwasthataddress@gmail.com */
24 
25 #include <GG/GUI.h>
26 
27 #include <GG/BrowseInfoWnd.h>
28 #include <GG/Config.h>
29 #include <GG/Cursor.h>
30 #include <GG/Edit.h>
31 #include <GG/Layout.h>
32 #include <GG/ListBox.h>
33 #include <GG/StyleFactory.h>
34 #include <GG/Timer.h>
35 #include <GG/utf8/checked.h>
36 #include <GG/ZList.h>
37 
38 #if GG_HAVE_LIBPNG
39 # if GIGI_CONFIG_USE_OLD_IMPLEMENTATION_OF_GIL_PNG_IO
40 #  if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7)
41 #   pragma GCC diagnostic push
42 #   pragma GCC diagnostic ignored "-Wunused-local-typedefs"
43 #  endif
44 #  include "gilext/io/png_io.hpp"
45 #  include "gilext/io/png_io_v2_compat.hpp"
46 #  if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ > 7)
47 #   pragma GCC diagnostic pop
48 #  endif
49 # else
50 #  include <boost/gil/extension/io/png.hpp>
51 # endif
52 #endif
53 
54 #include <boost/algorithm/string/predicate.hpp>
55 #include <boost/format.hpp>
56 #include <boost/xpressive/xpressive.hpp>
57 
58 #include <cassert>
59 #include <functional>
60 #include <fstream>
61 #include <iostream>
62 #include <list>
63 #include <thread>
64 
65 using namespace GG;
66 
67 namespace {
68     const bool INSTRUMENT_GET_WINDOW_UNDER = false;
69 
70     struct AcceleratorEcho
71     {
AcceleratorEcho__anon56d1648e0111::AcceleratorEcho72         AcceleratorEcho(Key key, Flags<ModKey> mod_keys) :
73             m_str("GG SIGNAL : GUI::AcceleratorSignal(key=" +
74                   boost::lexical_cast<std::string>(key) +
75                   " mod_keys=" +
76                   boost::lexical_cast<std::string>(mod_keys) +
77                   ")")
78         {}
operator ()__anon56d1648e0111::AcceleratorEcho79         bool operator()()
80         {
81             std::cerr << m_str << std::endl;
82             return false;
83         }
84         std::string m_str;
85     };
86 
87     // calculates WndEvent::EventType corresponding to a given mouse button
88     // and a given left mouse button event type. For example, given the
89     // left mouse button drag and button 2 (the right mouse button),
90     // this will return right button drag.
ButtonEvent(WndEvent::EventType left_type,unsigned int mouse_button)91     WndEvent::EventType ButtonEvent(WndEvent::EventType left_type, unsigned int mouse_button)
92     { return WndEvent::EventType(left_type + (WndEvent::MButtonDown - WndEvent::LButtonDown) * mouse_button); }
93 
94     typedef utf8::wchar_iterator<std::string::const_iterator> utf8_wchar_iterator;
95     typedef boost::xpressive::basic_regex<utf8_wchar_iterator> word_regex;
96     typedef boost::xpressive::regex_iterator<utf8_wchar_iterator> word_regex_iterator;
97     const wchar_t WIDE_DASH = '-';
98     const word_regex DEFAULT_WORD_REGEX =
99         +boost::xpressive::set[boost::xpressive::_w | WIDE_DASH];
100 
WriteWndToPNG(const Wnd * wnd,const std::string & filename)101     void WriteWndToPNG(const Wnd* wnd, const std::string& filename)
102     {
103 #if GG_HAVE_LIBPNG
104         Pt ul = wnd->UpperLeft();
105         Pt size = wnd->Size();
106 
107         std::vector<GLubyte> bytes(Value(size.x) * Value(size.y) * 4);
108 
109         glFinish();
110 
111         glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT);
112 
113         glPixelStorei(GL_PACK_SWAP_BYTES, false);
114         glPixelStorei(GL_PACK_LSB_FIRST, false);
115         glPixelStorei(GL_PACK_ROW_LENGTH, 0);
116         glPixelStorei(GL_PACK_SKIP_ROWS, 0);
117         glPixelStorei(GL_PACK_SKIP_PIXELS, 0);
118         glPixelStorei(GL_PACK_ALIGNMENT, 1);
119 
120         glReadPixels(Value(ul.x),
121                      Value(GUI::GetGUI()->AppHeight() - wnd->Bottom()),
122                      Value(size.x),
123                      Value(size.y),
124                      GL_RGBA,
125                      GL_UNSIGNED_BYTE,
126                      &bytes[0]);
127 
128         glPopClientAttrib();
129 
130         using namespace boost::gil;
131         write_view(
132             filename,
133             flipped_up_down_view(
134                 interleaved_view(
135                     Value(size.x),
136                     Value(size.y),
137                     static_cast<rgba8_pixel_t*>(static_cast<void*>(&bytes[0])),
138                     Value(size.x) * sizeof(rgba8_pixel_t))),
139             png_tag());
140 #endif
141     }
142 }
143 
144 
145 // implementation data types
146 struct GG::GUIImpl
147 {
148     GUIImpl();
149 
150     void HandleMouseButtonPress(  unsigned int mouse_button, const GG::Pt& pos, int curr_ticks);
151     void HandleMouseDrag(         unsigned int mouse_button, const GG::Pt& pos, int curr_ticks);
152     void HandleMouseButtonRelease(unsigned int mouse_button, const GG::Pt& pos, int curr_ticks);
153     void HandleIdle(             Flags<ModKey> mod_keys, const GG::Pt& pos, int curr_ticks);
154 
155     void HandleKeyPress(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys, int curr_ticks);
156 
157     void HandleKeyRelease(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys, int curr_ticks);
158 
159     void HandleTextInput(        const std::string* text);
160     void HandleMouseMove(        Flags<ModKey> mod_keys, const GG::Pt& pos, const Pt& rel, int curr_ticks);
161     void HandleMouseWheel(       Flags<ModKey> mod_keys, const GG::Pt& pos, const Pt& rel, int curr_ticks);
162     void HandleMouseEnter(       Flags<ModKey> mod_keys, const GG::Pt& pos, const std::shared_ptr<Wnd>& w);
163 
164     void ClearState();
165 
166     std::shared_ptr<Wnd> FocusWnd() const;
167     void SetFocusWnd(const std::shared_ptr<Wnd>& wnd);
168 
169     void GouvernFPS();
170 
171     std::string  m_app_name;            // the user-defined name of the apllication
172 
173     ZList               m_zlist;        // object that keeps the GUI windows in the correct depth ordering
174     std::weak_ptr<Wnd>  m_focus_wnd;    // GUI window that currently has the input focus (this is the base level focus window, used when no modal windows are active)
175 
176     std::list<std::pair<std::shared_ptr<Wnd>, std::weak_ptr<Wnd>>>
177                         m_modal_wnds;                               // modal GUI windows, and the window with focus for that modality (only the one in back is active, simulating a stack but allowing traversal of the list)
178     bool                m_allow_modal_accelerator_signals = false;  // iff true: keyboard accelerator signals will be output while modal window(s) is open
179 
180     bool         m_mouse_button_state[3] = {false, false, false};   // the up/down states of the three buttons on the mouse are kept here
181     Pt           m_mouse_pos;           // absolute position of mouse, based on last MOUSEMOVE event
182     Pt           m_mouse_rel;           // relative position of mouse, based on last MOUSEMOVE event
183     Flags<ModKey>m_mod_keys;            // currently-depressed modifier keys, based on last KEYPRESS event
184 
185     int          m_key_press_repeat_delay = 1;      // see note above GUI class definition
186     int          m_key_press_repeat_interval = 1;
187     int          m_last_key_press_repeat_time = 1;  // last time of a simulated key press message
188 
189     std::pair<Key, std::uint32_t> m_last_pressed_key_code_point;
190 
191     int          m_prev_key_press_time = 0;         // the time of the most recent key press
192 
193     int          m_mouse_button_down_repeat_delay = 0;      // see note above GUI class definition
194     int          m_mouse_button_down_repeat_interval = 0;
195     int          m_last_mouse_button_down_repeat_time= 0;   // last time of a simulated button-down message
196 
197     int          m_double_click_interval = 0;   // the maximum interval allowed between clicks that is still considered a double-click, in ms
198     int          m_min_drag_time = 0;           // the minimum amount of time that a drag must be in progress before it is considered a drag, in ms
199     int          m_min_drag_distance = 0;       // the minimum distance that a drag must cover before it is considered a drag
200 
201     int                 m_prev_mouse_button_press_time = 0; // the time of the most recent mouse button press
202     Pt                  m_prev_mouse_button_press_pos;      // the location of the most recent mouse button press
203     std::weak_ptr<Wnd>  m_prev_wnd_under_cursor;            // GUI window most recently under the input cursor; may be 0
204     int                 m_prev_wnd_under_cursor_time = 0;   // the time at which prev_wnd_under_cursor was initially set to its current value
205     std::weak_ptr<Wnd>  m_curr_wnd_under_cursor;            // GUI window currently under the input cursor; may be 0
206     std::weak_ptr<Wnd>  m_drag_wnds[3];                     // GUI window currently being clicked or dragged by each mouse button
207     Pt                  m_prev_wnd_drag_position;           // the upper-left corner of the dragged window when the last *Drag message was generated
208     Pt                  m_wnd_drag_offset;                  // the offset from the upper left corner of the dragged window to the cursor for the current drag
209     bool                m_curr_drag_wnd_dragged = false;    // true iff the currently-pressed window (m_drag_wnds[N]) has actually been dragged some distance (in which case releasing the mouse button is not a click). note that a dragged wnd is one being continuously repositioned by the dragging, and not a wnd being drag-dropped.
210     std::shared_ptr<Wnd>m_curr_drag_wnd;                    // nonzero iff m_curr_drag_wnd_dragged is true (that is, we have actually started dragging the Wnd, not just pressed the mouse button); will always be one of m_drag_wnds.
211     std::weak_ptr<Wnd>  m_curr_drag_drop_here_wnd;          // the Wnd that most recently received a DragDropEnter or DragDropHere message (0 if DragDropLeave was sent as well, or if none)
212     Pt                  m_wnd_resize_offset;                // offset from the cursor of either the upper-left or lower-right corner of the GUI window currently being resized
213     WndRegion           m_wnd_region;                       // window region currently being dragged or clicked; for non-frame windows, this will always be WR_NONE
214 
215     /** The current browse info window, if any. */
216     std::shared_ptr<BrowseInfoWnd> m_browse_info_wnd;
217 
218     int                 m_browse_info_mode = 0;      // the current browse info mode (only valid if browse_info_wnd is non-null)
219     Wnd*                m_browse_target = nullptr;   // the current browse info target
220 
221     std::weak_ptr<Wnd>  m_drag_drop_originating_wnd; // the window that originally owned the Wnds in drag_drop_wnds
222 
223     /** The Wnds currently being dragged and dropped. They are owned by the GUI and rendered separately.*/
224     std::map<std::shared_ptr<Wnd>, Pt> m_drag_drop_wnds;
225 
226     /** Tracks whether Wnd is acceptable for dropping on the current target Wnd.*/
227     std::map<const Wnd*, bool> m_drag_drop_wnds_acceptable;
228 
229     std::set<std::pair<Key, Flags<ModKey>>> m_accelerators; // the keyboard accelerators
230 
231     /** The signals emitted by the keyboard accelerators. */
232     std::map<std::pair<Key, Flags<ModKey>>, std::shared_ptr<GUI::AcceleratorSignalType>> m_accelerator_sigs;
233 
234     bool  m_mouse_lr_swap = false; // treat left and right mouse events as each other
235 
236     bool  m_rendering_drag_drop_wnds = false;
237 
238     //! The most recent calculation of the frames per second rendering speed (-1.0 if calcs are disabled)
239     double m_FPS = 0.0;         //! true iff FPS calcs are to be done
240     bool m_calc_FPS = false;    //! true iff FPS calcs are to be done
241     double m_max_FPS = 60.0;    //! The maximum allowed frames per second rendering speed
242     //! The last time an FPS calculation was done.
243     std::chrono::high_resolution_clock::time_point m_last_FPS_time;
244     //! The time of the last frame rendered.
245     std::chrono::high_resolution_clock::time_point m_last_frame_time;
246     //! The number of frames rendered since \a m_last_frame_time.
247     std::size_t  m_frames;
248 
249     Wnd*         m_double_click_wnd = nullptr;  // GUI window most recently clicked
250     unsigned int m_double_click_button = 0;     // the index of the mouse button used in the last click
251     int          m_double_click_start_time = 0; // the time from which we started measuring double_click_time, in ms
252     int          m_double_click_time = 0;       // time elapsed since last click, in ms
253 
254     std::shared_ptr<StyleFactory>   m_style_factory;
255     bool                            m_render_cursor = false;
256     std::shared_ptr<Cursor>         m_cursor;
257 
258     std::set<Timer*> m_timers;
259 
260     const Wnd* m_save_as_png_wnd = nullptr;
261     std::string m_save_as_png_filename;
262 
263     std::string m_clipboard_text;
264 };
265 
GUIImpl()266 GUIImpl::GUIImpl() :
267     m_allow_modal_accelerator_signals(false),
268     m_mouse_pos(X(-1000), Y(-1000)),
269     m_mouse_rel(X(0), Y(0)),
270     m_key_press_repeat_delay(250),
271     m_key_press_repeat_interval(66),
272     m_last_key_press_repeat_time(0),
273     m_last_pressed_key_code_point{GGK_NONE, 0u},
274     m_prev_key_press_time(-1),
275     m_mouse_button_down_repeat_delay(250),
276     m_mouse_button_down_repeat_interval(66),
277     m_last_mouse_button_down_repeat_time(0),
278     m_double_click_interval(500),
279     m_min_drag_time(250),
280     m_min_drag_distance(5),
281     m_prev_mouse_button_press_time(-1),
282     m_prev_wnd_under_cursor(),
283     m_prev_wnd_under_cursor_time(-1),
284     m_wnd_region(WR_NONE),
285     m_browse_info_mode(0),
286     m_FPS(-1.0),
287     m_max_FPS(0.0),
288     m_last_FPS_time(std::chrono::high_resolution_clock::now()),
289     m_last_frame_time(std::chrono::high_resolution_clock::now()),
290     m_double_click_button(0),
291     m_double_click_start_time(-1),
292     m_double_click_time(-1),
293     m_style_factory(new StyleFactory())
294 {}
295 
HandleMouseButtonPress(unsigned int mouse_button,const Pt & pos,int curr_ticks)296 void GUIImpl::HandleMouseButtonPress(unsigned int mouse_button, const Pt& pos, int curr_ticks)
297 {
298     auto curr_wnd_under_cursor = GUI::s_gui->CheckedGetWindowUnder(pos, m_mod_keys);
299     m_curr_wnd_under_cursor = curr_wnd_under_cursor;
300     m_last_mouse_button_down_repeat_time = 0;
301     m_prev_mouse_button_press_time = 0;
302     m_browse_info_wnd.reset();
303     m_browse_target = nullptr;
304     m_prev_wnd_under_cursor_time = curr_ticks;
305     m_prev_mouse_button_press_time = curr_ticks;
306     m_prev_mouse_button_press_pos = pos;
307 
308     m_mouse_button_state[mouse_button] = true;
309 
310     // If not already dragging, start tracking the dragged window.
311     if (m_drag_wnds[0].expired() && m_drag_wnds[1].expired() && m_drag_wnds[2].expired()) {
312         m_drag_wnds[mouse_button] = curr_wnd_under_cursor;
313         if (curr_wnd_under_cursor) {
314             m_prev_wnd_drag_position = curr_wnd_under_cursor->UpperLeft();
315             m_wnd_drag_offset = pos - m_prev_wnd_drag_position;
316         }
317     }
318 
319     // if this window is not a disabled Control window, it becomes the focus window
320     auto control = (curr_wnd_under_cursor
321                     ? dynamic_cast<Control*>(curr_wnd_under_cursor.get())
322                     : nullptr);
323     if (control && !control->Disabled())
324         SetFocusWnd(curr_wnd_under_cursor);
325 
326     if (curr_wnd_under_cursor) {
327         m_wnd_region = curr_wnd_under_cursor->WindowRegion(pos); // and determine whether a resize-region of it is being dragged
328         if (m_wnd_region % 3 == 0) // left regions
329             m_wnd_resize_offset.x = curr_wnd_under_cursor->Left() - pos.x;
330         else
331             m_wnd_resize_offset.x = curr_wnd_under_cursor->Right() - pos.x;
332         if (m_wnd_region < 3) // top regions
333             m_wnd_resize_offset.y = curr_wnd_under_cursor->Top() - pos.y;
334         else
335             m_wnd_resize_offset.y = curr_wnd_under_cursor->Bottom() - pos.y;
336         auto&& drag_wnds_root_parent = curr_wnd_under_cursor->RootParent();
337         GUI::s_gui->MoveUp(drag_wnds_root_parent ? drag_wnds_root_parent : curr_wnd_under_cursor);
338         curr_wnd_under_cursor->HandleEvent(WndEvent(ButtonEvent(WndEvent::LButtonDown, mouse_button), pos, m_mod_keys));
339     }
340 
341     m_prev_wnd_under_cursor = m_curr_wnd_under_cursor; // update this for the next time around
342 }
343 
HandleMouseDrag(unsigned int mouse_button,const Pt & pos,int curr_ticks)344 void GUIImpl::HandleMouseDrag(unsigned int mouse_button, const Pt& pos, int curr_ticks)
345 {
346     const auto&& dragged_wnd = LockAndResetIfExpired(m_drag_wnds[mouse_button]);
347     if (!dragged_wnd)
348         return;
349 
350     if (m_wnd_region == WR_MIDDLE || m_wnd_region == WR_NONE) {
351         // send drag message to window or initiate drag-and-drop
352 
353         Pt diff = m_prev_mouse_button_press_pos - pos;
354         int drag_distance = Value(diff.x * diff.x) + Value(diff.y * diff.y);
355 
356         // check that sufficient time has passed and the mouse has moved far
357         // enough since the previous drag motion response to signal the drag to
358         // the UI
359         if (m_min_drag_time < (curr_ticks - m_prev_mouse_button_press_time) &&
360             m_min_drag_distance * m_min_drag_distance < drag_distance &&
361             !m_drag_drop_wnds.count(dragged_wnd))
362         {
363             // several conditions to allow drag-and-drop to occur:
364             if (!dragged_wnd->Dragable() &&             // normal-dragable non-drop wnds can't be drag-dropped
365                 dragged_wnd->DragDropDataType() != "" &&// Wnd must have a defined drag-drop data type to be drag-dropped
366                 mouse_button == 0)                      // left mouse button drag-drop only
367             {
368                 auto&& parent = dragged_wnd->Parent();
369                 Pt offset = m_prev_mouse_button_press_pos - dragged_wnd->UpperLeft();
370                 // start drag
371                 GUI::s_gui->RegisterDragDropWnd(dragged_wnd, offset, parent);
372                 // inform parent
373                 if (parent)
374                     parent->StartingChildDragDrop(dragged_wnd.get(), offset);
375             } else {
376                 // can't drag-and-drop...
377 
378                 // instead signal to dragged-over Wnd that a drag has occurred.
379                 Pt start_pos = dragged_wnd->UpperLeft();
380                 Pt move = (pos - m_wnd_drag_offset) - m_prev_wnd_drag_position;
381                 dragged_wnd->HandleEvent(WndEvent(ButtonEvent(WndEvent::LDrag, mouse_button), pos, move, m_mod_keys));
382 
383                 // update "latest" drag position, so future drags will have
384                 // proper position from which to judge distance dragged since
385                 // the lastdrag position
386                 m_prev_wnd_drag_position = dragged_wnd->UpperLeft();
387 
388                 // if Wnd is draggable and position has changed, initiate or
389                 // update normal dragging (non-drop, such as repositioning a
390                 // Wnd without "dropping" it at the end)
391                 if (dragged_wnd->Dragable() && start_pos != dragged_wnd->UpperLeft()) {
392                     m_curr_drag_wnd_dragged = true;
393                     m_curr_drag_wnd = dragged_wnd;
394                 }
395             }
396         }
397 
398         // notify wnd under cursor of presence of drag-and-drop wnd(s)
399         if ((m_curr_drag_wnd_dragged &&
400              (dragged_wnd->DragDropDataType() != "") &&
401              (mouse_button == 0)) ||
402             !m_drag_drop_wnds.empty())
403         {
404             std::set<Wnd*> ignores;
405             auto curr_wnd_under_cursor = m_zlist.Pick(pos, GUI::s_gui->ModalWindow(), &ignores);
406             m_curr_wnd_under_cursor = curr_wnd_under_cursor;
407             std::map<std::shared_ptr<Wnd>, Pt> drag_drop_wnds;
408             drag_drop_wnds[dragged_wnd] = m_wnd_drag_offset;
409             const auto&& prev_wnd_under_cursor = LockAndResetIfExpired(m_prev_wnd_under_cursor);
410             if (curr_wnd_under_cursor && prev_wnd_under_cursor == curr_wnd_under_cursor) {
411                 // Wnd under cursor has remained the same for the last two updates
412                 const auto&& curr_drag_drop_here_wnd = LockAndResetIfExpired(m_curr_drag_drop_here_wnd);
413                 if (curr_drag_drop_here_wnd == curr_wnd_under_cursor) {
414                     // Wnd being dragged over is still being dragged over...
415                     WndEvent event(WndEvent::DragDropHere, pos, m_drag_drop_wnds, m_mod_keys);
416                     curr_wnd_under_cursor->HandleEvent(event);
417                     m_drag_drop_wnds_acceptable = event.GetAcceptableDropWnds();
418 
419                 } else {
420                     // pass drag-drop event to check if the various dragged Wnds are acceptable to drop
421                     WndEvent event(WndEvent::CheckDrops, pos, m_drag_drop_wnds, m_mod_keys);
422                     curr_wnd_under_cursor->HandleEvent(event);
423                     m_drag_drop_wnds_acceptable = event.GetAcceptableDropWnds();
424 
425                     // Wnd being dragged over is new; give it an Enter message
426                     WndEvent enter_event(WndEvent::DragDropEnter, pos, m_drag_drop_wnds, m_mod_keys);
427                     curr_wnd_under_cursor->HandleEvent(enter_event);
428                     m_curr_drag_drop_here_wnd = curr_wnd_under_cursor;
429                 }
430             }
431         }
432 
433     } else if (dragged_wnd->Resizable()) {
434         // send appropriate resize message to window, depending on the position
435         // of the cursor within / at the edge of the Wnd being dragged over
436         Pt offset_pos = pos + m_wnd_resize_offset;
437         if (auto&& parent = dragged_wnd->Parent())
438             offset_pos -= parent->ClientUpperLeft();
439         GG::Pt rel_lr = dragged_wnd->RelativeLowerRight();
440         GG::Pt rel_ul = dragged_wnd->RelativeUpperLeft();
441 
442         switch (m_wnd_region)
443         {
444         case WR_TOPLEFT:
445             dragged_wnd->SizeMove(offset_pos,                       rel_lr);
446             break;
447         case WR_TOP:
448             dragged_wnd->SizeMove(Pt(rel_ul.x,      offset_pos.y),  rel_lr);
449             break;
450         case WR_TOPRIGHT:
451             dragged_wnd->SizeMove(Pt(rel_ul.x,      offset_pos.y),  Pt(offset_pos.x,    rel_lr.y));
452             break;
453         case WR_MIDLEFT:
454             dragged_wnd->SizeMove(Pt(offset_pos.x,  rel_ul.y),      rel_lr);
455             break;
456         case WR_MIDRIGHT:
457             dragged_wnd->SizeMove(rel_ul,                           Pt(offset_pos.x,    rel_lr.y));
458             break;
459         case WR_BOTTOMLEFT:
460             dragged_wnd->SizeMove(Pt(offset_pos.x,  rel_ul.y),      Pt(rel_lr.x,        offset_pos.y));
461             break;
462         case WR_BOTTOM:
463             dragged_wnd->SizeMove(rel_ul,                           Pt(rel_lr.x,        offset_pos.y));
464             break;
465         case WR_BOTTOMRIGHT:
466             dragged_wnd->SizeMove(rel_ul,                           offset_pos);
467             break;
468         default:
469             break;
470         }
471     }
472 }
473 
HandleMouseButtonRelease(unsigned int mouse_button,const GG::Pt & pos,int curr_ticks)474 void GUIImpl::HandleMouseButtonRelease(unsigned int mouse_button, const GG::Pt& pos, int curr_ticks)
475 {
476     auto curr_wnd_under_cursor = GUI::s_gui->CheckedGetWindowUnder(pos, m_mod_keys);
477     m_curr_wnd_under_cursor = curr_wnd_under_cursor;
478     m_last_mouse_button_down_repeat_time = 0;
479     m_browse_info_wnd.reset();
480     m_browse_target = nullptr;
481     m_prev_wnd_under_cursor_time = curr_ticks;
482 
483     const auto&& click_drag_wnd = LockAndResetIfExpired(m_drag_wnds[mouse_button]);
484     std::set<Wnd*> ignores;
485     if (m_curr_drag_wnd_dragged && click_drag_wnd)
486         ignores.insert(click_drag_wnd.get());
487     curr_wnd_under_cursor = m_zlist.Pick(pos, GUI::s_gui->ModalWindow(), &ignores);
488     m_curr_wnd_under_cursor = curr_wnd_under_cursor;
489 
490     bool in_drag_drop =
491         !m_drag_drop_wnds.empty() ||
492         (m_curr_drag_wnd_dragged && click_drag_wnd && (click_drag_wnd->DragDropDataType() != "") && (mouse_button == 0));
493 
494     m_mouse_button_state[mouse_button] = false;
495     m_drag_wnds[mouse_button].reset(); // if the mouse button is released, stop tracking the drag window
496     m_wnd_region = WR_NONE;        // and clear this, just in case
497 
498     if (!in_drag_drop && click_drag_wnd && curr_wnd_under_cursor == click_drag_wnd) {
499         // the release is over the Wnd where the button-down event occurred
500         // and that Wnd has not been dragged
501 
502         if (m_double_click_time > 0 && m_double_click_wnd == click_drag_wnd.get() &&
503             // this is second click over a window that just received an click
504             // within the time limit, so it's a double-click, not a click
505             m_double_click_button == mouse_button)
506         {
507             m_double_click_wnd = nullptr;
508             m_double_click_start_time = -1;
509             m_double_click_time = -1;
510             click_drag_wnd->HandleEvent(WndEvent(ButtonEvent(WndEvent::LDoubleClick, mouse_button), pos, m_mod_keys));
511 
512         } else {
513             // just a single click
514             if (m_double_click_time > 0) {
515                 m_double_click_wnd = nullptr;
516                 m_double_click_start_time = -1;
517                 m_double_click_time = -1;
518             } else {
519                 m_double_click_start_time = curr_ticks;
520                 m_double_click_time = 0;
521                 m_double_click_wnd = click_drag_wnd.get();
522                 m_double_click_button = mouse_button;
523             }
524             click_drag_wnd->HandleEvent(WndEvent(ButtonEvent(WndEvent::LClick, mouse_button), pos, m_mod_keys));
525         }
526 
527     } else if (click_drag_wnd) {
528         // drag-dropping
529         m_double_click_wnd = nullptr;
530         m_double_click_time = -1;
531         if (click_drag_wnd)
532             click_drag_wnd->HandleEvent(WndEvent(ButtonEvent(WndEvent::LButtonUp, mouse_button), pos, m_mod_keys));
533 
534         if (curr_wnd_under_cursor) {
535             // dropped onto a Wnd, which can react to the drop
536 
537             if (m_drag_drop_wnds.empty()) {
538                 // dropped a dragged Wnd without having dragged it anywhere yet
539                 if (click_drag_wnd && click_drag_wnd->DragDropDataType() != "" && mouse_button == 0) {
540                     // pass drag-drop-here event to check if the single dragged Wnd is acceptable to drop
541                     WndEvent event(WndEvent::CheckDrops, pos, click_drag_wnd.get(), m_mod_keys);
542                     curr_wnd_under_cursor->HandleEvent(event);
543                     m_drag_drop_wnds_acceptable = event.GetAcceptableDropWnds();
544 
545                     // prep / handle end of drag-drop
546                     auto&& drag_drop_originating_wnd = click_drag_wnd->Parent();
547                     m_drag_drop_originating_wnd = drag_drop_originating_wnd;
548                     curr_wnd_under_cursor->HandleEvent(WndEvent(WndEvent::DragDropLeave));
549                     m_curr_drag_drop_here_wnd.reset();
550 
551                     // put dragged Wnd into container depending on whether it is accepted by the drop target
552                     std::vector<std::shared_ptr<Wnd>> accepted_wnds;
553                     std::vector<Wnd*> removed_wnds;
554                     std::vector<const Wnd*> unaccepted_wnds;
555                     if (m_drag_drop_wnds_acceptable[click_drag_wnd.get()]) {
556                         accepted_wnds.push_back(click_drag_wnd);
557                         removed_wnds.push_back(click_drag_wnd.get());
558                     }
559                     else
560                         unaccepted_wnds.push_back(click_drag_wnd.get());
561 
562                     // if dragged Wnd came from somehwere, inform originating
563                     // Wnd its child is or is not being dragged away
564                     if (drag_drop_originating_wnd) {
565                         drag_drop_originating_wnd->CancellingChildDragDrop(unaccepted_wnds);
566                         drag_drop_originating_wnd->ChildrenDraggedAway(removed_wnds, curr_wnd_under_cursor.get());
567                     }
568                     // implement drop onto target if the dragged Wnd was accepted
569                     if (!accepted_wnds.empty())
570                         curr_wnd_under_cursor->HandleEvent(WndEvent(WndEvent::DragDroppedOn, pos, std::move(accepted_wnds), m_mod_keys));
571                 }
572 
573             } else {
574                 // dragged one or more Wnds to another location and then dropped them
575                 // pass checkdrops event to check if the dropped Wnds are acceptable to drop here
576                 WndEvent event(WndEvent::CheckDrops, pos, std::move(m_drag_drop_wnds), m_mod_keys);
577                 curr_wnd_under_cursor->HandleEvent(event);
578                 m_drag_drop_wnds_acceptable = event.GetAcceptableDropWnds();
579 
580                 // prep / handle end of drag-drop
581                 auto&& drag_drop_originating_wnd = click_drag_wnd->Parent();
582                 m_drag_drop_originating_wnd = drag_drop_originating_wnd;
583                 curr_wnd_under_cursor->HandleEvent(WndEvent(WndEvent::DragDropLeave));
584                 m_curr_drag_drop_here_wnd.reset();
585 
586                 // put dragged Wnds into containers depending on whether they were accepted by the drop target
587                 std::vector<std::shared_ptr<Wnd>> accepted_wnds;
588                 std::vector<Wnd*> removed_wnds;
589                 std::vector<const Wnd*> unaccepted_wnds;
590                 for (auto& drop_wnd : m_drag_drop_wnds) {
591                     if (m_drag_drop_wnds_acceptable[drop_wnd.first.get()]) {
592                         accepted_wnds.push_back(std::move(drop_wnd.first));
593                         removed_wnds.push_back(const_cast<Wnd*>(drop_wnd.first.get()));
594                     } else
595                         unaccepted_wnds.push_back(drop_wnd.first.get());
596                 }
597                 // if dragged Wnds came from somehwere, inform originating
598                  // Wnd its children are or are not being dragged away
599                 if (drag_drop_originating_wnd) {
600                     drag_drop_originating_wnd->CancellingChildDragDrop(unaccepted_wnds);
601                     drag_drop_originating_wnd->ChildrenDraggedAway(removed_wnds, curr_wnd_under_cursor.get());
602                 }
603                 // implement drop onto target if any of the dragged Wnds were accepted
604                 if (!accepted_wnds.empty())
605                     curr_wnd_under_cursor->HandleEvent(WndEvent(
606                         WndEvent::DragDroppedOn, pos, std::move(accepted_wnds), m_mod_keys));
607             }
608         }
609     }
610 
611     // Reset drag drop if drop on either originating window or a new window.
612     if (click_drag_wnd) {
613         m_prev_wnd_drag_position = Pt();
614         m_drag_drop_originating_wnd.reset();
615         m_drag_drop_wnds.clear();
616         m_drag_drop_wnds_acceptable.clear();
617         m_curr_drag_wnd_dragged = false;
618         m_curr_drag_wnd.reset();
619     }
620 
621     m_prev_wnd_under_cursor = m_curr_wnd_under_cursor; // update this for the next time around
622 }
623 
HandleIdle(Flags<ModKey> mod_keys,const GG::Pt & pos,int curr_ticks)624 void GUIImpl::HandleIdle(Flags<ModKey> mod_keys, const GG::Pt& pos, int curr_ticks)
625 {
626     const auto&& curr_wnd_under_cursor = LockAndResetIfExpired(m_curr_wnd_under_cursor);
627     if (m_mouse_button_down_repeat_delay != 0 &&
628         curr_wnd_under_cursor &&
629         curr_wnd_under_cursor == GUI::s_gui->CheckedGetWindowUnder(pos, mod_keys) &&
630         curr_wnd_under_cursor->RepeatButtonDown() &&
631         LockAndResetIfExpired(m_drag_wnds[0]) == curr_wnd_under_cursor)
632     {
633         // convert to a key press message after ensuring that timing requirements are met
634         if (curr_ticks - m_prev_mouse_button_press_time > m_mouse_button_down_repeat_delay) {
635             if (!m_last_mouse_button_down_repeat_time ||
636                 curr_ticks - m_last_mouse_button_down_repeat_time > m_mouse_button_down_repeat_interval)
637             {
638                 m_last_mouse_button_down_repeat_time = curr_ticks;
639                 curr_wnd_under_cursor->HandleEvent(WndEvent(
640                     WndEvent::LButtonDown, pos, mod_keys));
641             }
642         }
643 
644         return;
645     }
646 
647     auto&& focus_wnd = FocusWnd();
648     if (m_key_press_repeat_delay != 0 &&
649         m_last_pressed_key_code_point.first != GGK_NONE &&
650         focus_wnd &&
651         focus_wnd->RepeatKeyPress())
652     {
653         // convert to a key press message after ensuring that timing requirements are met
654         if (curr_ticks - m_prev_key_press_time > m_key_press_repeat_delay) {
655             if (!m_last_key_press_repeat_time ||
656                 curr_ticks - m_last_key_press_repeat_time > m_key_press_repeat_interval)
657             {
658                 m_last_key_press_repeat_time = curr_ticks;
659                 focus_wnd->HandleEvent(
660                     WndEvent(WndEvent::KeyPress, m_last_pressed_key_code_point.first,
661                              m_last_pressed_key_code_point.second, mod_keys));
662             }
663         }
664         return;
665     }
666 
667     if (curr_wnd_under_cursor)
668         GUI::s_gui->ProcessBrowseInfo();
669 }
670 
HandleKeyPress(Key key,std::uint32_t key_code_point,Flags<ModKey> mod_keys,int curr_ticks)671 void GUIImpl::HandleKeyPress(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys, int curr_ticks)
672 {
673     m_browse_info_wnd.reset();
674     m_browse_info_mode = -1;
675     m_browse_target = nullptr;
676     m_last_pressed_key_code_point = {key, key_code_point};
677     m_last_key_press_repeat_time = 0;
678     m_prev_key_press_time = curr_ticks;
679 
680     bool processed = false;
681     // only process accelerators when there are no modal windows active;
682     // otherwise, accelerators would be an end-run around modality
683     if (m_modal_wnds.empty() || m_allow_modal_accelerator_signals) {
684         // the focus_wnd may care about the state of the numlock and
685         // capslock, or which side of the keyboard's CTRL, SHIFT, etc.
686         // was pressed, but the accelerators don't
687         Flags<ModKey> massaged_mods = MassagedAccelModKeys(mod_keys);
688         if (m_accelerators.find({key, massaged_mods})
689             != m_accelerators.end())
690         {
691             processed = GUI::s_gui->AcceleratorSignal(key, massaged_mods)();
692         }
693     }
694     auto&& focus_wnd = FocusWnd();
695     if (!processed && focus_wnd)
696         focus_wnd->HandleEvent(WndEvent(
697             WndEvent::KeyPress, key, key_code_point, mod_keys));
698 }
699 
HandleKeyRelease(Key key,std::uint32_t key_code_point,Flags<ModKey> mod_keys,int curr_ticks)700 void GUIImpl::HandleKeyRelease(Key key, std::uint32_t key_code_point, Flags<ModKey> mod_keys, int curr_ticks)
701 {
702     m_last_key_press_repeat_time = 0;
703     m_last_pressed_key_code_point.first = GGK_NONE;
704     m_browse_info_wnd.reset();
705     m_browse_info_mode = -1;
706     m_browse_target = nullptr;
707     auto&& focus_wnd = FocusWnd();
708     if (focus_wnd)
709         focus_wnd->HandleEvent(WndEvent(
710             WndEvent::KeyRelease, key, key_code_point, mod_keys));
711 }
712 
HandleTextInput(const std::string * text)713 void GUIImpl::HandleTextInput(const std::string* text) {
714     m_browse_info_wnd.reset();
715     m_browse_info_mode = -1;
716     m_browse_target = nullptr;
717     auto&& focus_wnd = FocusWnd();
718     if (focus_wnd)
719         focus_wnd->HandleEvent(WndEvent(WndEvent::TextInput, text));
720 }
721 
HandleMouseMove(Flags<ModKey> mod_keys,const GG::Pt & pos,const Pt & rel,int curr_ticks)722 void GUIImpl::HandleMouseMove(Flags<ModKey> mod_keys, const GG::Pt& pos, const Pt& rel, int curr_ticks)
723 {
724     auto curr_wnd_under_cursor = GUI::s_gui->CheckedGetWindowUnder(pos, m_mod_keys);
725     m_curr_wnd_under_cursor = curr_wnd_under_cursor;
726     const auto&& prev_wnd_under_cursor = LockAndResetIfExpired(m_prev_wnd_under_cursor);
727 
728     m_mouse_pos = pos; // record mouse position
729     m_mouse_rel = rel; // record mouse movement
730 
731     const auto&& m_drag_wnds_0 = LockAndResetIfExpired(m_drag_wnds[0]);
732     const auto&& m_drag_wnds_1 = LockAndResetIfExpired(m_drag_wnds[1]);
733     const auto&& m_drag_wnds_2 = LockAndResetIfExpired(m_drag_wnds[2]);
734     if (m_drag_wnds_0 || m_drag_wnds_1 || m_drag_wnds_2) {
735         if (m_drag_wnds_0)
736             HandleMouseDrag(0, pos, curr_ticks);
737         if (m_drag_wnds_1)
738             HandleMouseDrag(1, pos, curr_ticks);
739         if (m_drag_wnds_2)
740             HandleMouseDrag(2, pos, curr_ticks);
741     } else if (curr_wnd_under_cursor &&
742                prev_wnd_under_cursor == curr_wnd_under_cursor)
743     {
744         // if !m_drag_wnds[0] and we're moving over the same
745         // (valid) object we were during the last iteration
746         curr_wnd_under_cursor->HandleEvent(WndEvent(WndEvent::MouseHere, pos, mod_keys));
747         GUI::s_gui->ProcessBrowseInfo();
748     }
749     if (prev_wnd_under_cursor != curr_wnd_under_cursor) {
750         m_browse_info_wnd.reset();
751         m_browse_target = nullptr;
752         m_prev_wnd_under_cursor_time = curr_ticks;
753     }
754     m_prev_wnd_under_cursor = m_curr_wnd_under_cursor; // update this for the next time around
755 }
756 
HandleMouseWheel(Flags<ModKey> mod_keys,const GG::Pt & pos,const Pt & rel,int curr_ticks)757 void GUIImpl::HandleMouseWheel(Flags<ModKey> mod_keys, const GG::Pt& pos, const Pt& rel, int curr_ticks)
758 {
759     auto curr_wnd_under_cursor = GUI::s_gui->CheckedGetWindowUnder(pos, m_mod_keys);
760     m_curr_wnd_under_cursor = curr_wnd_under_cursor;
761     m_browse_info_wnd.reset();
762     m_browse_target = nullptr;
763     m_prev_wnd_under_cursor_time = curr_ticks;
764     // don't send out 0-movement wheel messages
765     if (curr_wnd_under_cursor && rel.y)
766         curr_wnd_under_cursor->HandleEvent(WndEvent(
767             WndEvent::MouseWheel, pos, Value(rel.y), mod_keys));
768     m_prev_wnd_under_cursor = m_curr_wnd_under_cursor; // update this for the next time around
769 }
770 
HandleMouseEnter(Flags<ModKey> mod_keys,const GG::Pt & pos,const std::shared_ptr<Wnd> & w)771 void GUIImpl::HandleMouseEnter(Flags< ModKey > mod_keys, const GG::Pt& pos, const std::shared_ptr<Wnd>& w)
772 {
773     w->HandleEvent(WndEvent(WndEvent::MouseEnter, pos, mod_keys));
774     m_curr_wnd_under_cursor = w;
775 }
776 
FocusWnd() const777 std::shared_ptr<Wnd> GUIImpl::FocusWnd() const
778 { return m_modal_wnds.empty() ? m_focus_wnd.lock() : m_modal_wnds.back().second.lock(); }
779 
SetFocusWnd(const std::shared_ptr<Wnd> & wnd)780 void GUIImpl::SetFocusWnd(const std::shared_ptr<Wnd>& wnd)
781 {
782     auto&& focus_wnd = FocusWnd();
783     if (focus_wnd == wnd)
784         return;
785 
786     // inform old focus wnd that it is losing focus
787     if (focus_wnd)
788         focus_wnd->HandleEvent(WndEvent(WndEvent::LosingFocus));
789 
790     if (m_modal_wnds.empty())
791         m_focus_wnd = wnd;
792     else
793         // m_modal_wnds stores the window that is modal and the child that has
794         // focus separately.
795         m_modal_wnds.back().second = wnd;
796 
797     // inform new focus wnd that it is gaining focus
798     auto&& new_focus_wnd = FocusWnd();
799     if (new_focus_wnd)
800         new_focus_wnd->HandleEvent(WndEvent(WndEvent::GainingFocus));
801 }
802 
ClearState()803 void GUIImpl::ClearState()
804 {
805     m_focus_wnd.reset();
806     m_mouse_pos = GG::Pt(X(-1000), Y(-1000));
807     m_mouse_rel = GG::Pt(X(0), Y(0));
808     m_mod_keys = Flags<ModKey>();
809     m_last_mouse_button_down_repeat_time = 0;
810     m_last_key_press_repeat_time = 0;
811     m_last_pressed_key_code_point = {GGK_NONE, 0u};
812 
813     m_prev_wnd_drag_position = Pt();
814     m_browse_info_wnd.reset();
815     m_browse_target = nullptr;
816 
817     m_prev_mouse_button_press_time = -1;
818     m_prev_wnd_under_cursor.reset();
819     m_prev_wnd_under_cursor_time = -1;
820     m_curr_wnd_under_cursor.reset();
821 
822     m_mouse_button_state[0] = m_mouse_button_state[1] = m_mouse_button_state[2] = false;
823     m_drag_wnds[0].reset();
824     m_drag_wnds[1].reset();
825     m_drag_wnds[2].reset();
826 
827     m_curr_drag_wnd_dragged = false;
828     m_curr_drag_wnd = nullptr;
829     m_curr_drag_drop_here_wnd.reset();
830     m_wnd_region = WR_NONE;
831     m_browse_target = nullptr;
832     m_drag_drop_originating_wnd.reset();
833 
834     m_double_click_wnd = nullptr;
835     m_double_click_start_time = -1;
836     m_double_click_time = -1;
837 }
838 
GouvernFPS()839 void GUIImpl::GouvernFPS()
840 {
841     using namespace std::chrono;
842 
843     high_resolution_clock::time_point time = high_resolution_clock::now();
844 
845     // govern FPS speed if needed
846     if (m_max_FPS) {
847         microseconds min_us_per_frame = duration_cast<microseconds>(duration<double>(1.0 / (m_max_FPS + 1)));
848         microseconds us_elapsed = duration_cast<microseconds>(time - m_last_frame_time);
849         microseconds us_to_wait = (min_us_per_frame - us_elapsed);
850         if (microseconds(0) < us_to_wait) {
851             std::this_thread::sleep_for(us_to_wait);
852             time = high_resolution_clock::now();
853         }
854     }
855 
856     m_last_frame_time = time;
857 
858     // track FPS if needed
859     if (m_calc_FPS) {
860         ++m_frames;
861         if (seconds(1) < time - m_last_FPS_time) { // calculate FPS at most once a second
862             double time_since_last_FPS = duration_cast<microseconds>(
863                 time - m_last_FPS_time).count() / 1000000.0;
864             m_FPS = m_frames / time_since_last_FPS;
865             m_last_FPS_time = time;
866             m_frames = 0;
867         }
868     }
869 }
870 
871 
872 // static member(s)
873 GUI* GUI::s_gui = nullptr;
874 
875 // member functions
GUI(const std::string & app_name)876 GUI::GUI(const std::string& app_name) :
877     m_impl(std::make_unique<GUIImpl>())
878 {
879     assert(!s_gui);
880     s_gui = this;
881     m_impl->m_app_name = app_name;
882 }
883 
~GUI()884 GUI::~GUI()
885 {
886     s_gui = nullptr;
887     Wnd::s_default_browse_info_wnd.reset();
888 }
889 
AppName() const890 const std::string& GUI::AppName() const
891 { return m_impl->m_app_name; }
892 
FocusWnd() const893 std::shared_ptr<Wnd> GUI::FocusWnd() const
894 { return m_impl->FocusWnd(); }
895 
FocusWndAcceptsTypingInput() const896 bool GUI::FocusWndAcceptsTypingInput() const
897 {
898     const auto&& focus_wnd = FocusWnd();
899     if (!focus_wnd)
900         return false;
901     return dynamic_cast<const Edit*>(focus_wnd.get());    // currently only Edit controls accept text input, so far as I'm aware. Could add a ->AcceptsTypingInput() function to Wnd if needed
902 }
903 
PrevFocusInteractiveWnd() const904 std::shared_ptr<Wnd> GUI::PrevFocusInteractiveWnd() const
905 {
906     auto&& focus_wnd = FocusWnd();
907     if (!focus_wnd)
908         return focus_wnd;
909 
910     auto&& parent_of_focus_wnd = focus_wnd->Parent();
911     if (!parent_of_focus_wnd)
912         return focus_wnd;
913 
914     // find previous INTERACTIVE sibling wnd
915     const auto& siblings = parent_of_focus_wnd->Children();
916 
917     // find current focus wnd in siblings...
918     const auto& focus_it = std::find(siblings.rbegin(), siblings.rend(), focus_wnd);
919     if (focus_it == siblings.rend())
920         return focus_wnd;
921 
922     // loop around until finding an interactive enabled control sibling or
923     // returning to the focus wnd
924     auto loop_it = focus_it;
925     ++loop_it;
926     while (loop_it != focus_it) {
927         if (loop_it == siblings.rend()) {
928             loop_it = siblings.rbegin();
929             continue;
930         }
931 
932         auto& sibling = *loop_it;
933         if (sibling->Interactive()) {
934             Control* ctrl = dynamic_cast<Control*>(sibling.get());
935             if (ctrl && !ctrl->Disabled()) {
936                 return sibling;
937                 break;
938             }
939         }
940 
941         ++loop_it;
942     }
943     return focus_wnd;
944 }
945 
NextFocusInteractiveWnd() const946 std::shared_ptr<Wnd> GUI::NextFocusInteractiveWnd() const
947 {
948     auto&& focus_wnd = FocusWnd();
949     if (!focus_wnd)
950         return focus_wnd;
951 
952     auto&& parent_of_focus_wnd = focus_wnd->Parent();
953     if (!parent_of_focus_wnd)
954         return focus_wnd;
955 
956     // find next INTERACTIVE sibling wnd
957     const auto& siblings = parent_of_focus_wnd->Children();
958 
959     // find current focus wnd in siblings...
960     auto focus_it = std::find(siblings.begin(), siblings.end(), focus_wnd);
961     if (focus_it == siblings.end())
962         return focus_wnd;
963 
964     // loop around until finding an interactive enabled control sibling or
965     // returning to the focus wnd
966     auto loop_it = focus_it;
967     ++loop_it;
968     while (loop_it != focus_it) {
969         if (loop_it == siblings.end()) {
970             loop_it = siblings.begin();
971             continue;
972         }
973 
974         auto& sibling = *loop_it;
975         if (sibling->Interactive()) {
976             Control* ctrl = dynamic_cast<Control*>(sibling.get());
977             if (ctrl && !ctrl->Disabled()) {
978                 return sibling;
979                 break;
980             }
981         }
982 
983 
984         ++loop_it;
985     }
986     return focus_wnd;
987 }
988 
GetWindowUnder(const Pt & pt) const989 std::shared_ptr<Wnd> GUI::GetWindowUnder(const Pt& pt) const
990 {
991     auto&& wnd = m_impl->m_zlist.Pick(pt, ModalWindow());
992     if (INSTRUMENT_GET_WINDOW_UNDER && wnd)
993         std::cerr << "GUI::GetWindowUnder() : " << wnd->Name() << " @ " << wnd << std::endl;
994 
995     return wnd;
996 }
997 
RenderingDragDropWnds() const998 bool GUI::RenderingDragDropWnds() const
999 { return m_impl->m_rendering_drag_drop_wnds; }
1000 
FPSEnabled() const1001 bool GUI::FPSEnabled() const
1002 { return m_impl->m_calc_FPS; }
1003 
FPS() const1004 double GUI::FPS() const
1005 { return m_impl->m_FPS; }
1006 
FPSString() const1007 std::string GUI::FPSString() const
1008 { return boost::io::str(boost::format("%.2f frames per second") % m_impl->m_FPS); }
1009 
MaxFPS() const1010 double GUI::MaxFPS() const
1011 { return m_impl->m_max_FPS; }
1012 
KeyPressRepeatDelay() const1013 unsigned int GUI::KeyPressRepeatDelay() const
1014 { return m_impl->m_key_press_repeat_delay; }
1015 
KeyPressRepeatInterval() const1016 unsigned int GUI::KeyPressRepeatInterval() const
1017 { return m_impl->m_key_press_repeat_interval; }
1018 
ButtonDownRepeatDelay() const1019 unsigned int GUI::ButtonDownRepeatDelay() const
1020 { return m_impl->m_mouse_button_down_repeat_delay; }
1021 
ButtonDownRepeatInterval() const1022 unsigned int GUI::ButtonDownRepeatInterval() const
1023 { return m_impl->m_mouse_button_down_repeat_interval; }
1024 
DoubleClickInterval() const1025 unsigned int GUI::DoubleClickInterval() const
1026 { return m_impl->m_double_click_interval; }
1027 
MinDragTime() const1028 unsigned int GUI::MinDragTime() const
1029 { return m_impl->m_min_drag_time; }
1030 
MinDragDistance() const1031 unsigned int GUI::MinDragDistance() const
1032 { return m_impl->m_min_drag_distance; }
1033 
DragWnd(const Wnd * wnd,unsigned int mouse_button) const1034 bool GUI::DragWnd(const Wnd* wnd, unsigned int mouse_button) const
1035 { return wnd && wnd == LockAndResetIfExpired(m_impl->m_drag_wnds[mouse_button < 3 ? mouse_button : 0]).get(); }
1036 
DragDropWnd(const Wnd * wnd) const1037 bool GUI::DragDropWnd(const Wnd* wnd) const
1038 {
1039     if (!wnd)
1040         return false;
1041     try {
1042         auto ptr = std::const_pointer_cast<Wnd>(wnd->shared_from_this());
1043         return m_impl->m_drag_drop_wnds.count(ptr);
1044     } catch (const std::bad_weak_ptr&) {
1045         return false;
1046     }
1047 }
1048 
AcceptedDragDropWnd(const Wnd * wnd) const1049 bool GUI::AcceptedDragDropWnd(const Wnd* wnd) const
1050 {
1051     if (!wnd)
1052         return false;
1053     const auto& it = m_impl->m_drag_drop_wnds_acceptable.find(wnd);
1054     return it != m_impl->m_drag_drop_wnds_acceptable.end() && it->second;
1055 }
1056 
MouseButtonDown(unsigned int bn) const1057 bool GUI::MouseButtonDown(unsigned int bn) const
1058 { return (bn <= 2) ? m_impl->m_mouse_button_state[bn] : false; }
1059 
MousePosition() const1060 Pt GUI::MousePosition() const
1061 { return m_impl->m_mouse_pos; }
1062 
MouseMovement() const1063 Pt GUI::MouseMovement() const
1064 { return m_impl->m_mouse_rel; }
1065 
ModKeys() const1066 Flags<ModKey> GUI::ModKeys() const
1067 { return m_impl->m_mod_keys; }
1068 
MouseLRSwapped() const1069 bool GUI::MouseLRSwapped() const
1070 { return m_impl->m_mouse_lr_swap; }
1071 
FindWords(const std::string & str) const1072 std::set<std::pair<CPSize, CPSize>> GUI::FindWords(const std::string& str) const
1073 {
1074     std::set<std::pair<CPSize, CPSize>> retval;
1075     utf8_wchar_iterator first(str.begin(), str.begin(), str.end());
1076     utf8_wchar_iterator last(str.end(), str.begin(), str.end());
1077     word_regex_iterator it(first, last, DEFAULT_WORD_REGEX);
1078     word_regex_iterator end_it;
1079     for ( ; it != end_it; ++it) {
1080         retval.insert(std::pair<CPSize, CPSize>(
1081                           CPSize(it->position()),
1082                           CPSize(it->position() + it->length())));
1083     }
1084     return retval;
1085 }
1086 
FindWordsStringIndices(const std::string & str) const1087 std::set<std::pair<StrSize, StrSize>> GUI::FindWordsStringIndices(const std::string& str) const
1088 {
1089     std::set<std::pair<StrSize, StrSize>> retval;
1090 
1091     utf8_wchar_iterator first(str.begin(), str.begin(), str.end());
1092     utf8_wchar_iterator last(str.end(), str.begin(), str.end());
1093     word_regex_iterator it(first, last, DEFAULT_WORD_REGEX);
1094     word_regex_iterator end_it;
1095 
1096     typedef word_regex_iterator::value_type match_result_type;
1097 
1098     for ( ; it != end_it; ++it) {
1099         match_result_type match_result = *it;
1100         utf8_wchar_iterator word_pos_it = first;
1101 
1102         std::advance(word_pos_it, match_result.position());
1103         StrSize start_idx(std::distance(str.begin(), word_pos_it.base()));
1104         std::advance(word_pos_it, match_result.length());
1105         StrSize end_idx(std::distance(str.begin(), word_pos_it.base()));
1106 
1107         retval.insert({start_idx, end_idx});
1108     }
1109     return retval;
1110 }
1111 
ContainsWord(const std::string & str,const std::string & word) const1112 bool GUI::ContainsWord(const std::string& str, const std::string& word) const
1113 {
1114     if (word.empty())
1115         return false;
1116 
1117     utf8_wchar_iterator first(str.begin(), str.begin(), str.end());
1118     utf8_wchar_iterator last(str.end(), str.begin(), str.end());
1119     word_regex_iterator it(first, last, DEFAULT_WORD_REGEX);
1120     word_regex_iterator end_it;
1121 
1122     typedef word_regex_iterator::value_type match_result_type;
1123 
1124     for ( ; it != end_it; ++it) {
1125         match_result_type match_result = *it;
1126         utf8_wchar_iterator word_pos_it = first;
1127 
1128         std::advance(word_pos_it, match_result.position());
1129         auto start_it = word_pos_it.base();
1130         std::advance(word_pos_it, match_result.length());
1131         std::string word_in_str(start_it, word_pos_it.base());
1132 
1133         if (boost::iequals(word_in_str, word))
1134             return true;
1135     }
1136 
1137     return false;
1138 }
1139 
GetStyleFactory() const1140 const std::shared_ptr<StyleFactory>& GUI::GetStyleFactory() const
1141 { return m_impl->m_style_factory; }
1142 
RenderCursor() const1143 bool GUI::RenderCursor() const
1144 { return m_impl->m_render_cursor; }
1145 
GetCursor() const1146 const std::shared_ptr<Cursor>& GUI::GetCursor() const
1147 { return m_impl->m_cursor; }
1148 
accel_begin() const1149 GUI::const_accel_iterator GUI::accel_begin() const
1150 { return m_impl->m_accelerators.begin(); }
1151 
accel_end() const1152 GUI::const_accel_iterator GUI::accel_end() const
1153 { return m_impl->m_accelerators.end(); }
1154 
AcceleratorSignal(Key key,Flags<ModKey> mod_keys) const1155 GUI::AcceleratorSignalType& GUI::AcceleratorSignal(Key key, Flags<ModKey> mod_keys/* = MOD_KEY_NONE*/) const
1156 {
1157     std::shared_ptr<AcceleratorSignalType>& sig_ptr = m_impl->m_accelerator_sigs[{key, mod_keys}];
1158     if (!sig_ptr)
1159         sig_ptr.reset(new AcceleratorSignalType());
1160     if (INSTRUMENT_ALL_SIGNALS)
1161         sig_ptr->connect(AcceleratorEcho(key, mod_keys));
1162     return *sig_ptr;
1163 }
1164 
ModalAcceleratorSignalsEnabled() const1165 bool GUI::ModalAcceleratorSignalsEnabled() const
1166 { return m_impl->m_allow_modal_accelerator_signals; }
1167 
ModalWndsOpen() const1168 bool GUI::ModalWndsOpen() const
1169 { return !m_impl->m_modal_wnds.empty(); }
1170 
SaveWndAsPNG(const Wnd * wnd,const std::string & filename) const1171 void GUI::SaveWndAsPNG(const Wnd* wnd, const std::string& filename) const
1172 {
1173     m_impl->m_save_as_png_wnd = wnd;
1174     m_impl->m_save_as_png_filename = filename;
1175 }
1176 
HandleGGEvent(EventType event,Key key,std::uint32_t key_code_point,Flags<ModKey> mod_keys,const Pt & pos,const Pt & rel,const std::string * text)1177 void GUI::HandleGGEvent(EventType event, Key key, std::uint32_t key_code_point,
1178                         Flags<ModKey> mod_keys, const Pt& pos, const Pt& rel, const std::string* text)
1179 {
1180     m_impl->m_mod_keys = mod_keys;
1181 
1182     int curr_ticks = Ticks();
1183 
1184     // track double-click time and time-out any pending double-click that has
1185     // outlived its interval
1186     if (m_impl->m_double_click_time >= 0) {
1187         m_impl->m_double_click_time = curr_ticks - m_impl->m_double_click_start_time;
1188         if (m_impl->m_double_click_time >= m_impl->m_double_click_interval) {
1189             m_impl->m_double_click_start_time = -1;
1190             m_impl->m_double_click_time = -1;
1191             m_impl->m_double_click_wnd = nullptr;
1192         }
1193     }
1194 
1195     switch (event) {
1196 
1197     case IDLE:
1198         m_impl->HandleIdle(mod_keys, pos, curr_ticks);
1199         break;
1200 
1201     case KEYPRESS:
1202         m_impl->HandleKeyPress(key, key_code_point, mod_keys, curr_ticks);
1203         break;
1204 
1205     case KEYRELEASE:
1206         m_impl->HandleKeyRelease(key, key_code_point, mod_keys, curr_ticks);
1207         break;
1208 
1209     case TEXTINPUT:
1210         m_impl->HandleTextInput(text);
1211         break;
1212 
1213     case MOUSEMOVE:
1214         m_impl->HandleMouseMove(mod_keys, pos, rel, curr_ticks);
1215         break;
1216 
1217     case LPRESS:
1218         m_impl->HandleMouseButtonPress((m_impl->m_mouse_lr_swap ? RPRESS : LPRESS) - LPRESS, pos, curr_ticks);
1219         break;
1220 
1221     case MPRESS:
1222         m_impl->HandleMouseButtonPress(MPRESS - LPRESS, pos, curr_ticks);
1223         break;
1224 
1225     case RPRESS:
1226         m_impl->HandleMouseButtonPress((m_impl->m_mouse_lr_swap ? LPRESS : RPRESS) - LPRESS, pos, curr_ticks);
1227         break;
1228 
1229     case LRELEASE:
1230         m_impl->HandleMouseButtonRelease((m_impl->m_mouse_lr_swap ? RRELEASE : LRELEASE) - LRELEASE, pos, curr_ticks);
1231         break;
1232 
1233     case MRELEASE:
1234         m_impl->HandleMouseButtonRelease(MRELEASE - LRELEASE, pos, curr_ticks);
1235         break;
1236 
1237     case RRELEASE:
1238         m_impl->HandleMouseButtonRelease((m_impl->m_mouse_lr_swap ? LRELEASE : RRELEASE) - LRELEASE, pos, curr_ticks);
1239         break;
1240 
1241     case MOUSEWHEEL:
1242         m_impl->HandleMouseWheel(mod_keys, pos, rel, curr_ticks);
1243         break;
1244 
1245     default:
1246         break;
1247     }
1248 }
1249 
ClearEventState()1250 void GUI::ClearEventState()
1251 { m_impl->ClearState(); }
1252 
SetFocusWnd(const std::shared_ptr<Wnd> & wnd)1253 void GUI::SetFocusWnd(const std::shared_ptr<Wnd>& wnd)
1254 { m_impl->SetFocusWnd(wnd); }
1255 
SetPrevFocusWndInCycle()1256 bool GUI::SetPrevFocusWndInCycle()
1257 {
1258     auto&& prev_wnd = PrevFocusInteractiveWnd();
1259     if (prev_wnd)
1260         SetFocusWnd(prev_wnd);
1261     return true;
1262 }
1263 
SetNextFocusWndInCycle()1264 bool GUI::SetNextFocusWndInCycle()
1265 {
1266     auto&& next_wnd = NextFocusInteractiveWnd();
1267     if (next_wnd)
1268         SetFocusWnd(next_wnd);
1269     return true;
1270 }
1271 
Wait(unsigned int ms)1272 void GUI::Wait(unsigned int ms)
1273 { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); }
1274 
Wait(std::chrono::microseconds us)1275 void GUI::Wait(std::chrono::microseconds us)
1276 { std::this_thread::sleep_for(us); }
1277 
Register(std::shared_ptr<Wnd> wnd)1278 void GUI::Register(std::shared_ptr<Wnd> wnd)
1279 {
1280     if (!wnd)
1281         return;
1282 
1283     // Make top level by removing from parent
1284     if (auto&& parent = wnd->Parent())
1285         parent->DetachChild(wnd);
1286 
1287     m_impl->m_zlist.Add(std::forward<std::shared_ptr<Wnd>>(wnd));
1288 }
1289 
RegisterModal(std::shared_ptr<Wnd> wnd)1290 void GUI::RegisterModal(std::shared_ptr<Wnd> wnd)
1291 {
1292     if (wnd && wnd->Modal()) {
1293         m_impl->m_zlist.Remove(wnd.get());
1294         m_impl->m_modal_wnds.push_back({wnd, wnd});
1295         wnd->HandleEvent(WndEvent(WndEvent::GainingFocus));
1296     }
1297 }
1298 
RunModal(std::shared_ptr<Wnd> wnd,bool & done)1299 void GUI::RunModal(std::shared_ptr<Wnd> wnd, bool& done)
1300 {
1301     while (!done) {
1302         HandleSystemEvents();
1303         // send an idle message, so that the gui has timely updates for triggering browse info windows, etc.
1304         HandleGGEvent(GUI::IDLE, GGK_NONE, 0, m_impl->m_mod_keys, m_impl->m_mouse_pos, Pt());
1305         PreRender();
1306         RenderBegin();
1307         Render();
1308         RenderEnd();
1309         m_impl->GouvernFPS();
1310     }
1311 }
1312 
Remove(const std::shared_ptr<Wnd> & wnd)1313 void GUI::Remove(const std::shared_ptr<Wnd>& wnd)
1314 {
1315     if (!wnd)
1316         return;
1317 
1318     if (!m_impl->m_modal_wnds.empty() && m_impl->m_modal_wnds.back().first == wnd) // if it's the current modal window, remove it from the modal list
1319         m_impl->m_modal_wnds.pop_back();
1320     else // if it's not a modal window, remove it from the z-order
1321         m_impl->m_zlist.Remove(wnd);
1322 }
1323 
EnableFPS(bool b)1324 void GUI::EnableFPS(bool b/* = true*/)
1325 {
1326     m_impl->m_calc_FPS = b;
1327     if (!b)
1328         m_impl->m_FPS = -1.0f;
1329 }
1330 
SetMaxFPS(double max)1331 void GUI::SetMaxFPS(double max)
1332 {
1333     if (max && max < 0.1)
1334         max = 0.1;
1335     m_impl->m_max_FPS = max;
1336 }
1337 
MoveUp(const std::shared_ptr<Wnd> & wnd)1338 void GUI::MoveUp(const std::shared_ptr<Wnd>& wnd)
1339 { if (wnd) m_impl->m_zlist.MoveUp(wnd); }
1340 
MoveDown(const std::shared_ptr<Wnd> & wnd)1341 void GUI::MoveDown(const std::shared_ptr<Wnd>& wnd)
1342 { if (wnd) m_impl->m_zlist.MoveDown(wnd); }
1343 
RegisterDragDropWnd(std::shared_ptr<Wnd> wnd,const Pt & offset,std::shared_ptr<Wnd> originating_wnd)1344 void GUI::RegisterDragDropWnd(std::shared_ptr<Wnd> wnd, const Pt& offset, std::shared_ptr<Wnd> originating_wnd)
1345 {
1346     assert(wnd);
1347 
1348     // Throw if all dragged wnds are not from the same original wnd.
1349     const auto&& drag_drop_originating_wnd = LockAndResetIfExpired(m_impl->m_drag_drop_originating_wnd);
1350     if (!m_impl->m_drag_drop_wnds.empty() && originating_wnd != drag_drop_originating_wnd) {
1351         std::string m_impl_orig_wnd_name("NULL");
1352         std::string orig_wnd_name("NULL");
1353         if (drag_drop_originating_wnd)
1354             m_impl_orig_wnd_name = drag_drop_originating_wnd->Name();
1355         if (originating_wnd)
1356             orig_wnd_name = originating_wnd->Name();
1357         throw std::runtime_error("GUI::RegisterDragDropWnd() : Attempted to register a drag drop item"
1358                                 "dragged from  one window(" + orig_wnd_name +
1359                                 "), when another window (" + m_impl_orig_wnd_name +
1360                                 ") already has items being dragged from it.");
1361     }
1362 
1363     m_impl->m_drag_drop_wnds[wnd] = offset;
1364     m_impl->m_drag_drop_wnds_acceptable[wnd.get()] = false;
1365     m_impl->m_drag_drop_originating_wnd = originating_wnd;
1366 }
1367 
CancelDragDrop()1368 void GUI::CancelDragDrop()
1369 {
1370     m_impl->m_drag_drop_wnds.clear();
1371     m_impl->m_drag_drop_wnds_acceptable.clear();
1372 }
1373 
RegisterTimer(Timer & timer)1374 void GUI::RegisterTimer(Timer& timer)
1375 { m_impl->m_timers.insert(&timer); }
1376 
RemoveTimer(Timer & timer)1377 void GUI::RemoveTimer(Timer& timer)
1378 { m_impl->m_timers.erase(&timer); }
1379 
EnableKeyPressRepeat(unsigned int delay,unsigned int interval)1380 void GUI::EnableKeyPressRepeat(unsigned int delay, unsigned int interval)
1381 {
1382     if (!delay) { // setting delay = 0 completely disables key press repeat
1383         m_impl->m_key_press_repeat_delay = 0;
1384         m_impl->m_key_press_repeat_interval = 0;
1385     } else {
1386         m_impl->m_key_press_repeat_delay = delay;
1387         m_impl->m_key_press_repeat_interval = interval;
1388     }
1389 }
1390 
EnableMouseButtonDownRepeat(unsigned int delay,unsigned int interval)1391 void GUI::EnableMouseButtonDownRepeat(unsigned int delay, unsigned int interval)
1392 {
1393     if (!delay) { // setting delay = 0 completely disables mouse button down repeat
1394         m_impl->m_mouse_button_down_repeat_delay = 0;
1395         m_impl->m_mouse_button_down_repeat_interval = 0;
1396     } else {
1397         m_impl->m_mouse_button_down_repeat_delay = delay;
1398         m_impl->m_mouse_button_down_repeat_interval = interval;
1399     }
1400 }
1401 
SetDoubleClickInterval(unsigned int interval)1402 void GUI::SetDoubleClickInterval(unsigned int interval)
1403 { m_impl->m_double_click_interval = interval; }
1404 
SetMinDragTime(unsigned int time)1405 void GUI::SetMinDragTime(unsigned int time)
1406 { m_impl->m_min_drag_time = time; }
1407 
SetMinDragDistance(unsigned int distance)1408 void GUI::SetMinDragDistance(unsigned int distance)
1409 { m_impl->m_min_drag_distance = distance; }
1410 
accel_begin()1411 GUI::accel_iterator GUI::accel_begin()
1412 { return m_impl->m_accelerators.begin(); }
1413 
accel_end()1414 GUI::accel_iterator GUI::accel_end()
1415 { return m_impl->m_accelerators.end(); }
1416 
SetAccelerator(Key key,Flags<ModKey> mod_keys)1417 void GUI::SetAccelerator(Key key, Flags<ModKey> mod_keys/* = MOD_KEY_NONE*/)
1418 {
1419     mod_keys = MassagedAccelModKeys(mod_keys);
1420     m_impl->m_accelerators.insert({key, mod_keys});
1421 }
1422 
RemoveAccelerator(Key key,Flags<ModKey> mod_keys)1423 void GUI::RemoveAccelerator(Key key, Flags<ModKey> mod_keys/* = MOD_KEY_NONE*/)
1424 {
1425     mod_keys = MassagedAccelModKeys(mod_keys);
1426     m_impl->m_accelerators.erase({key, mod_keys});
1427 }
1428 
RemoveAccelerator(accel_iterator it)1429 void GUI::RemoveAccelerator(accel_iterator it)
1430 { m_impl->m_accelerators.erase(it); }
1431 
EnableModalAcceleratorSignals(bool allow)1432 void GUI::EnableModalAcceleratorSignals(bool allow)
1433 { m_impl->m_allow_modal_accelerator_signals = allow; }
1434 
SetMouseLRSwapped(bool swapped)1435 void GUI::SetMouseLRSwapped(bool swapped/* = true*/)
1436 { m_impl->m_mouse_lr_swap = swapped; }
1437 
GetFont(const std::string & font_filename,unsigned int pts)1438 std::shared_ptr<Font> GUI::GetFont(const std::string& font_filename, unsigned int pts)
1439 { return GetFontManager().GetFont(font_filename, pts); }
1440 
GetFont(const std::string & font_filename,unsigned int pts,const std::vector<unsigned char> & file_contents)1441 std::shared_ptr<Font> GUI::GetFont(const std::string& font_filename, unsigned int pts,
1442                                    const std::vector<unsigned char>& file_contents)
1443 { return GetFontManager().GetFont(font_filename, pts, file_contents); }
1444 
GetFont(const std::shared_ptr<Font> & font,unsigned int pts)1445 std::shared_ptr<Font> GUI::GetFont(const std::shared_ptr<Font>& font, unsigned int pts)
1446 {
1447     std::shared_ptr<Font> retval;
1448     if (font->FontName() == StyleFactory::DefaultFontName()) {
1449         retval = GetStyleFactory()->DefaultFont(pts);
1450     } else {
1451         retval = GetFont(font->FontName(), font->PointSize(),
1452                          font->UnicodeCharsets().begin(),
1453                          font->UnicodeCharsets().end());
1454     }
1455     return retval;
1456 }
1457 
FreeFont(const std::string & font_filename,unsigned int pts)1458 void GUI::FreeFont(const std::string& font_filename, unsigned int pts)
1459 { GetFontManager().FreeFont(font_filename, pts); }
1460 
StoreTexture(Texture * texture,const std::string & texture_name)1461 std::shared_ptr<Texture> GUI::StoreTexture(Texture* texture, const std::string& texture_name)
1462 { return GetTextureManager().StoreTexture(texture, texture_name); }
1463 
StoreTexture(const std::shared_ptr<Texture> & texture,const std::string & texture_name)1464 std::shared_ptr<Texture> GUI::StoreTexture(const std::shared_ptr<Texture>& texture, const std::string& texture_name)
1465 { return GetTextureManager().StoreTexture(texture, texture_name); }
1466 
GetTexture(const boost::filesystem::path & path,bool mipmap)1467 std::shared_ptr<Texture> GUI::GetTexture(const boost::filesystem::path& path, bool mipmap/* = false*/)
1468 { return GetTextureManager().GetTexture(path, mipmap); }
1469 
FreeTexture(const boost::filesystem::path & path)1470 void GUI::FreeTexture(const boost::filesystem::path& path)
1471 { GetTextureManager().FreeTexture(path); }
1472 
SetStyleFactory(const std::shared_ptr<StyleFactory> & factory)1473 void GUI::SetStyleFactory(const std::shared_ptr<StyleFactory>& factory)
1474 {
1475     m_impl->m_style_factory = factory;
1476     if (!m_impl->m_style_factory)
1477         m_impl->m_style_factory.reset(new StyleFactory());
1478 }
1479 
RenderCursor(bool render)1480 void GUI::RenderCursor(bool render)
1481 { m_impl->m_render_cursor = render; }
1482 
SetCursor(const std::shared_ptr<Cursor> & cursor)1483 void GUI::SetCursor(const std::shared_ptr<Cursor>& cursor)
1484 { m_impl->m_cursor = cursor; }
1485 
ClipboardText() const1486 std::string GUI::ClipboardText() const
1487 { return m_impl->m_clipboard_text; }
1488 
SetClipboardText(const std::string & text)1489 bool GUI::SetClipboardText(const std::string& text)
1490 {
1491     m_impl->m_clipboard_text = text;
1492     return true;
1493 }
1494 
CopyFocusWndText()1495 bool GUI::CopyFocusWndText()
1496 {
1497     const auto&& focus_wnd = FocusWnd();
1498     if (!focus_wnd)
1499         return false;
1500     return CopyWndText(focus_wnd.get());
1501 }
1502 
CopyWndText(const Wnd * wnd)1503 bool GUI::CopyWndText(const Wnd* wnd)
1504 {
1505     // presently only TextControl copying is supported.
1506 
1507     if (const TextControl* text_control = dynamic_cast<const TextControl*>(wnd)) {
1508         if (const Edit* edit_control = dynamic_cast<const Edit*>(text_control)) {
1509             // if TextControl is an Edit, it may have a subset of its text
1510             // selected. in that case, only copy the selected text. if nothing
1511             // is selected, revert to copying the full text of the TextControl.
1512             std::string selected_text = edit_control->SelectedText();
1513             if (!selected_text.empty()) {
1514                 SetClipboardText(GG::Font::StripTags(selected_text));
1515                 return true;
1516             }
1517         }
1518         SetClipboardText(text_control->Text());
1519         return true;
1520     }
1521     return false;
1522 }
1523 
PasteFocusWndText(const std::string & text)1524 bool GUI::PasteFocusWndText(const std::string& text)
1525 {
1526     auto&& focus_wnd = FocusWnd();
1527     if (!focus_wnd)
1528         return false;
1529     return PasteWndText(focus_wnd.get(), text);
1530 }
1531 
PasteWndText(Wnd * wnd,const std::string & text)1532 bool GUI::PasteWndText(Wnd* wnd, const std::string& text)
1533 {
1534     // presently only pasting into Edit wnds is supported
1535     if (Edit* edit_control = dynamic_cast<Edit*>(wnd)) {
1536         edit_control->AcceptPastedText(text);
1537         return true;
1538     }
1539     return false;
1540 }
1541 
PasteFocusWndClipboardText()1542 bool GUI::PasteFocusWndClipboardText()
1543 {
1544     return PasteFocusWndText(ClipboardText());
1545 }
1546 
CutFocusWndText()1547 bool GUI::CutFocusWndText()
1548 {
1549     auto&& focus_wnd = FocusWnd();
1550     if (!focus_wnd)
1551         return false;
1552     return CutWndText(focus_wnd.get());
1553 }
1554 
CutWndText(Wnd * wnd)1555 bool GUI::CutWndText(Wnd* wnd)
1556 {
1557     return CopyWndText(wnd) && PasteWndText(wnd, "");
1558 }
1559 
WndSelectAll(Wnd * wnd)1560 bool GUI::WndSelectAll(Wnd* wnd)
1561 {
1562     if (!wnd)
1563         return false;
1564     if (Edit* edit_control = dynamic_cast<Edit*>(wnd)) {
1565         edit_control->SelectAll();
1566         return true;
1567     } else if (ListBox* list_control = dynamic_cast<ListBox*>(wnd)) {
1568         list_control->SelectAll(true);
1569         return true;
1570     }
1571     return false;
1572 }
1573 
WndDeselect(Wnd * wnd)1574 bool GUI::WndDeselect(Wnd* wnd)
1575 {
1576     if (!wnd)
1577         return false;
1578     if (Edit* edit_control = dynamic_cast<Edit*>(wnd)) {
1579         edit_control->DeselectAll();
1580         return true;
1581     } else if (ListBox* list_control = dynamic_cast<ListBox*>(wnd)) {
1582         list_control->DeselectAll(true);
1583         return true;
1584     }
1585     return false;
1586 }
1587 
FocusWndSelectAll()1588 bool GUI::FocusWndSelectAll()
1589 {
1590     auto&& focus_wnd = FocusWnd();
1591     if (!focus_wnd)
1592         return false;
1593     return WndSelectAll(focus_wnd.get());
1594 }
1595 
FocusWndDeselect()1596 bool GUI::FocusWndDeselect()
1597 {
1598     auto&& focus_wnd = FocusWnd();
1599     if (!focus_wnd)
1600         return false;
1601     return WndDeselect(focus_wnd.get());
1602 }
1603 
GetGUI()1604 GUI* GUI::GetGUI()
1605 { return s_gui; }
1606 
PreRenderWindow(const std::shared_ptr<Wnd> & wnd)1607 void GUI::PreRenderWindow(const std::shared_ptr<Wnd>& wnd)
1608 { PreRenderWindow(wnd.get()); }
1609 
PreRenderWindow(Wnd * wnd)1610 void GUI::PreRenderWindow(Wnd* wnd)
1611 {
1612     if (!wnd || !wnd->Visible())
1613         return;
1614 
1615     for (auto& child_wnd : wnd->m_children) {
1616         PreRenderWindow(child_wnd.get());
1617     }
1618 
1619     if (wnd->PreRenderRequired())
1620         wnd->PreRender();
1621 }
1622 
RenderWindow(const std::shared_ptr<Wnd> & wnd)1623 void GUI::RenderWindow(const std::shared_ptr<Wnd>& wnd)
1624 { RenderWindow(wnd.get()); }
1625 
RenderWindow(Wnd * wnd)1626 void GUI::RenderWindow(Wnd* wnd)
1627 {
1628     if (!wnd || !wnd->Visible())
1629         return;
1630 
1631     wnd->Render();
1632 
1633     Wnd::ChildClippingMode clip_mode = wnd->GetChildClippingMode();
1634 
1635     if (clip_mode != Wnd::ClipToClientAndWindowSeparately) {
1636         bool clip = clip_mode != Wnd::DontClip;
1637         if (clip)
1638             wnd->BeginClipping();
1639         for (auto& child_wnd : wnd->m_children) {
1640             if (child_wnd && child_wnd->Visible())
1641                 RenderWindow(child_wnd.get());
1642         }
1643         if (clip)
1644             wnd->EndClipping();
1645     } else {
1646 #if BOOST_VERSION >= 106000
1647         using boost::placeholders::_1;
1648 #endif
1649 
1650         std::vector<std::shared_ptr<Wnd>> children_copy{wnd->m_children.begin(), wnd->m_children.end()};
1651         const auto& client_child_begin =
1652             std::partition(children_copy.begin(), children_copy.end(), boost::bind(
1653                 static_cast<bool (Wnd::*)() const>(&Wnd::NonClientChild), _1));
1654 
1655         if (children_copy.begin() != client_child_begin) {
1656             wnd->BeginNonclientClipping();
1657             for (auto it = children_copy.begin(); it != client_child_begin; ++it) {
1658                 if ((*it) && (*it)->Visible())
1659                     RenderWindow(it->get());
1660             }
1661             wnd->EndNonclientClipping();
1662         }
1663 
1664         if (client_child_begin != children_copy.end()) {
1665             wnd->BeginClipping();
1666             for (auto it = client_child_begin; it != children_copy.end(); ++it) {
1667                 if ((*it) && (*it)->Visible())
1668                     RenderWindow(it->get());
1669             }
1670             wnd->EndClipping();
1671         }
1672     }
1673 
1674     if (wnd == GetGUI()->m_impl->m_save_as_png_wnd) {
1675         WriteWndToPNG(GetGUI()->m_impl->m_save_as_png_wnd, GetGUI()->m_impl->m_save_as_png_filename);
1676         GetGUI()->m_impl->m_save_as_png_wnd = nullptr;
1677         GetGUI()->m_impl->m_save_as_png_filename.clear();
1678     }
1679 }
1680 
RenderDragDropWnds()1681 void GUI::RenderDragDropWnds()
1682 {
1683     // render drag-and-drop windows in arbitrary order (sorted by pointer value)
1684     m_impl->m_rendering_drag_drop_wnds = true;
1685     for (const auto drop_wnd : m_impl->m_drag_drop_wnds) {
1686         bool old_visible = drop_wnd.first->Visible();
1687         if (!old_visible)
1688             drop_wnd.first->Show();
1689         auto&& drop_wnd_parent = drop_wnd.first->Parent();
1690         Pt parent_offset = drop_wnd_parent ? drop_wnd_parent->ClientUpperLeft() : Pt();
1691         Pt old_pos = drop_wnd.first->UpperLeft() - parent_offset;
1692         drop_wnd.first->MoveTo(m_impl->m_mouse_pos - parent_offset - drop_wnd.second);
1693         RenderWindow(drop_wnd.first.get());
1694         drop_wnd.first->MoveTo(old_pos);
1695         if (!old_visible)
1696             drop_wnd.first->Hide();
1697     }
1698     m_impl->m_rendering_drag_drop_wnds = false;
1699 }
1700 
ProcessBrowseInfo()1701 void GUI::ProcessBrowseInfo()
1702 {
1703     auto&& wnd = LockAndResetIfExpired(m_impl->m_curr_wnd_under_cursor);
1704     assert(wnd);
1705 
1706     if (!m_impl->m_mouse_button_state[0] && !m_impl->m_mouse_button_state[1] && !m_impl->m_mouse_button_state[2] &&
1707         (m_impl->m_modal_wnds.empty() || wnd->RootParent() == m_impl->m_modal_wnds.back().first))
1708     {
1709         auto&& parent = wnd->Parent();
1710         while (!ProcessBrowseInfoImpl(wnd.get())
1711                && parent
1712                && (dynamic_cast<Control*>(wnd.get()) || dynamic_cast<Layout*>(wnd.get())))
1713         {
1714             wnd = std::move(parent);
1715             parent = wnd->Parent();
1716         }
1717     }
1718 }
1719 
PreRender()1720 void GUI::PreRender()
1721 {
1722     // pre-render normal windows back-to-front
1723     for (auto wnd : m_impl->m_zlist.RenderOrder()) {
1724         PreRenderWindow(wnd.get());
1725     }
1726 
1727     // pre-render modal windows back-to-front (on top of non-modal Wnds rendered above)
1728     for (const auto modal_wnd : m_impl->m_modal_wnds) {
1729         PreRenderWindow(modal_wnd.first.get());
1730     }
1731 
1732     // pre-render the active browse info window, if any
1733     const auto&& curr_wnd_under_cursor = LockAndResetIfExpired(m_impl->m_curr_wnd_under_cursor);
1734     if (m_impl->m_browse_info_wnd && curr_wnd_under_cursor) {
1735         assert(m_impl->m_browse_target);
1736         PreRenderWindow(m_impl->m_browse_info_wnd.get());
1737     }
1738 
1739     for (const auto& drag_drop_wnd : m_impl->m_drag_drop_wnds) {
1740         PreRenderWindow(drag_drop_wnd.first.get());
1741     }
1742 }
1743 
Render()1744 void GUI::Render()
1745 {
1746     // update timers
1747     int ticks = Ticks();
1748     for (auto& timer : m_impl->m_timers) {
1749         timer->Update(ticks);
1750     }
1751 
1752     Enter2DMode();
1753     // render normal windows back-to-front
1754     for (auto wnd : m_impl->m_zlist.RenderOrder()) {
1755         if (wnd)
1756             RenderWindow(wnd.get());
1757     }
1758 
1759     // render modal windows back-to-front (on top of non-modal Wnds rendered above)
1760     for (const auto modal_wnd : m_impl->m_modal_wnds) {
1761         if (modal_wnd.first)
1762             RenderWindow(modal_wnd.first.get());
1763     }
1764 
1765     // render the active browse info window, if any
1766     if (m_impl->m_browse_info_wnd) {
1767         const auto&& curr_wnd_under_cursor = LockAndResetIfExpired(m_impl->m_curr_wnd_under_cursor);
1768         if (!curr_wnd_under_cursor) {
1769             m_impl->m_browse_info_wnd.reset();
1770             m_impl->m_browse_info_mode = -1;
1771             m_impl->m_browse_target = nullptr;
1772             m_impl->m_prev_wnd_under_cursor_time = Ticks();
1773         } else {
1774             assert(m_impl->m_browse_target);
1775             m_impl->m_browse_info_wnd->Update(m_impl->m_browse_info_mode, m_impl->m_browse_target);
1776             RenderWindow(m_impl->m_browse_info_wnd.get());
1777         }
1778     }
1779 
1780     RenderDragDropWnds();
1781 
1782     glDisable(GL_DEPTH_TEST);
1783     glDisable(GL_LIGHTING);
1784     glDisable(GL_CULL_FACE);
1785     glEnable(GL_TEXTURE_2D);
1786     glEnable(GL_BLEND);
1787     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1788 
1789     if (m_impl->m_render_cursor && m_impl->m_cursor && AppHasMouseFocus())
1790         m_impl->m_cursor->Render(m_impl->m_mouse_pos);
1791     Exit2DMode();
1792 }
1793 
ProcessBrowseInfoImpl(Wnd * wnd)1794 bool GUI::ProcessBrowseInfoImpl(Wnd* wnd)
1795 {
1796     bool retval = false;
1797     const std::vector<Wnd::BrowseInfoMode>& browse_modes = wnd->BrowseModes();
1798     if (!browse_modes.empty()) {
1799         unsigned int delta_t = Ticks() - m_impl->m_prev_wnd_under_cursor_time;
1800         std::size_t i = 0;
1801         for (auto it = browse_modes.rbegin();
1802              it != browse_modes.rend();
1803              ++it, ++i)
1804         {
1805             if (it->time < delta_t) {
1806                 if (it->wnd && it->wnd->WndHasBrowseInfo(wnd, i)) {
1807                     if (m_impl->m_browse_target != wnd || m_impl->m_browse_info_wnd != it->wnd || m_impl->m_browse_info_mode != static_cast<int>(i)) {
1808                         m_impl->m_browse_target = wnd;
1809                         m_impl->m_browse_info_wnd = it->wnd;
1810                         m_impl->m_browse_info_mode = i;
1811                         m_impl->m_browse_info_wnd->SetCursorPosition(m_impl->m_mouse_pos);
1812                     }
1813                     retval = true;
1814                 }
1815                 break;
1816             }
1817         }
1818     }
1819     return retval;
1820 }
1821 
ModalWindow() const1822 std::shared_ptr<Wnd> GUI::ModalWindow() const
1823 {
1824     if (!m_impl->m_modal_wnds.empty())
1825         return m_impl->m_modal_wnds.back().first;
1826     return nullptr;
1827 }
1828 
CheckedGetWindowUnder(const Pt & pt,Flags<ModKey> mod_keys)1829 std::shared_ptr<Wnd> GUI::CheckedGetWindowUnder(const Pt& pt, Flags<ModKey> mod_keys)
1830 {
1831     const auto&& wnd_under_pt = GetWindowUnder(pt);
1832     const auto& dragged_wnd = m_impl->m_curr_drag_wnd; // wnd being continuously repositioned / dragged around, not a drag-drop
1833 
1834     //std::cout << "GUI::CheckedGetWindowUnder w: " << w << "  dragged_wnd: " << dragged_wnd << std::endl;
1835 
1836     bool unregistered_drag_drop = dragged_wnd && !dragged_wnd->DragDropDataType().empty();
1837     bool registered_drag_drop = !m_impl->m_drag_drop_wnds.empty();
1838 
1839     const auto&& curr_drag_drop_here_wnd = LockAndResetIfExpired(m_impl->m_curr_drag_drop_here_wnd);
1840     if (curr_drag_drop_here_wnd && !unregistered_drag_drop && !registered_drag_drop) {
1841         curr_drag_drop_here_wnd->HandleEvent(WndEvent(WndEvent::DragDropLeave));
1842         m_impl->m_curr_drag_drop_here_wnd.reset();
1843     }
1844 
1845     const auto&& curr_wnd_under_cursor = LockAndResetIfExpired(m_impl->m_curr_wnd_under_cursor);
1846     if (wnd_under_pt == curr_wnd_under_cursor)
1847         return wnd_under_pt;   // same Wnd is under cursor as before; nothing to do
1848 
1849     if (curr_wnd_under_cursor) {
1850         // inform previous Wnd under the cursor that the cursor has been dragged away
1851         if (unregistered_drag_drop) {
1852             curr_wnd_under_cursor->HandleEvent(WndEvent(WndEvent::DragDropLeave));
1853             m_impl->m_drag_drop_wnds_acceptable[dragged_wnd.get()] = false;
1854             m_impl->m_curr_drag_drop_here_wnd.reset();
1855 
1856         } else if (registered_drag_drop) {
1857             curr_wnd_under_cursor->HandleEvent(WndEvent(WndEvent::DragDropLeave));
1858             for (auto& acceptable_wnd : m_impl->m_drag_drop_wnds_acceptable)
1859             { acceptable_wnd.second = false; }
1860             m_impl->m_curr_drag_drop_here_wnd.reset();
1861 
1862         } else {
1863             curr_wnd_under_cursor->HandleEvent(WndEvent(WndEvent::MouseLeave));
1864         }
1865     }
1866 
1867     if (!wnd_under_pt) {
1868         //std::cout << "CheckedGetWindowUnder returning " << w << std::endl;
1869         return wnd_under_pt;
1870     }
1871 
1872 
1873     // inform new Wnd under cursor that something was dragged over it
1874     if (unregistered_drag_drop) {
1875         // pass drag-drop event to check if the single dragged Wnd is acceptable to drop
1876         WndEvent event(WndEvent::CheckDrops, pt, dragged_wnd.get(), mod_keys);
1877         wnd_under_pt->HandleEvent(event);
1878         m_impl->m_drag_drop_wnds_acceptable = event.GetAcceptableDropWnds();
1879 
1880         // Wnd being dragged over is new; give it an Enter message
1881         WndEvent enter_event(WndEvent::DragDropEnter, pt, dragged_wnd.get(), mod_keys);
1882         wnd_under_pt->HandleEvent(enter_event);
1883         m_impl->m_curr_drag_drop_here_wnd = wnd_under_pt;
1884 
1885     } else if (registered_drag_drop) {
1886         // pass drag-drop event to check if the various dragged Wnds are acceptable to drop
1887         WndEvent event(WndEvent::CheckDrops, pt, m_impl->m_drag_drop_wnds, mod_keys);
1888         wnd_under_pt->HandleEvent(event);
1889         m_impl->m_drag_drop_wnds_acceptable = event.GetAcceptableDropWnds();
1890 
1891         // Wnd being dragged over is new; give it an Enter message
1892         WndEvent enter_event(WndEvent::DragDropEnter, pt, m_impl->m_drag_drop_wnds, mod_keys);
1893         wnd_under_pt->HandleEvent(enter_event);
1894         m_impl->m_curr_drag_drop_here_wnd = wnd_under_pt;
1895 
1896     } else {
1897         m_impl->HandleMouseEnter(mod_keys, pt, wnd_under_pt);
1898     }
1899 
1900     //std::cout << "CheckedGetWindowUnder returning " << w << std::endl;
1901     return wnd_under_pt;
1902 }
1903 
MatchesOrContains(const Wnd * lwnd,const Wnd * rwnd)1904 bool GG::MatchesOrContains(const Wnd* lwnd, const Wnd* rwnd)
1905 {
1906     // Note: this does not touch lwnd because it is used in WndDying and it may already be destroyed
1907     if (rwnd) {
1908         for (auto w = rwnd; w; w = w->Parent().get()) {
1909             if (w == lwnd)
1910                 return true;
1911         }
1912     } else if (rwnd == lwnd) {
1913         return true;
1914     }
1915     return false;
1916 }
1917 
MatchesOrContains(const std::shared_ptr<Wnd> & lwnd,const Wnd * rwnd)1918 bool GG::MatchesOrContains(const std::shared_ptr<Wnd>& lwnd, const Wnd* rwnd)
1919 { return MatchesOrContains(lwnd.get(), rwnd);}
1920 
MatchesOrContains(const Wnd * lwnd,const std::shared_ptr<Wnd> & rwnd)1921 bool GG::MatchesOrContains(const Wnd* lwnd, const std::shared_ptr<Wnd>& rwnd)
1922 { return MatchesOrContains(lwnd, rwnd.get());}
1923 
MatchesOrContains(const std::shared_ptr<Wnd> & lwnd,const std::shared_ptr<Wnd> & rwnd)1924 bool GG::MatchesOrContains(const std::shared_ptr<Wnd>& lwnd, const std::shared_ptr<Wnd>& rwnd)
1925 { return MatchesOrContains(lwnd.get(), rwnd.get());}
1926 
MassagedAccelModKeys(Flags<ModKey> mod_keys)1927 Flags<ModKey> GG::MassagedAccelModKeys(Flags<ModKey> mod_keys)
1928 {
1929     mod_keys &= ~(MOD_KEY_NUM | MOD_KEY_CAPS);
1930     if (mod_keys & MOD_KEY_CTRL)
1931         mod_keys |= MOD_KEY_CTRL;
1932     if (mod_keys & MOD_KEY_SHIFT)
1933         mod_keys |= MOD_KEY_SHIFT;
1934     if (mod_keys & MOD_KEY_ALT)
1935         mod_keys |= MOD_KEY_ALT;
1936     if (mod_keys & MOD_KEY_META)
1937         mod_keys |= MOD_KEY_META;
1938     return mod_keys;
1939 }
1940