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