1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name interface.cpp - The interface. */
12 //
13 //      (c) Copyright 1998-2011 by Lutz Sammer, Jimmy Salmon and Pali Rohár
14 //
15 //      This program is free software; you can redistribute it and/or modify
16 //      it under the terms of the GNU General Public License as published by
17 //      the Free Software Foundation; only version 2 of the License.
18 //
19 //      This program is distributed in the hope that it will be useful,
20 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
21 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 //      GNU General Public License for more details.
23 //
24 //      You should have received a copy of the GNU General Public License
25 //      along with this program; if not, write to the Free Software
26 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
27 //      02111-1307, USA.
28 //
29 
30 //@{
31 
32 /*----------------------------------------------------------------------------
33 --  Includes
34 ----------------------------------------------------------------------------*/
35 
36 #include "stratagus.h"
37 
38 #include "interface.h"
39 
40 #include "ai.h"
41 #include "commands.h"
42 #include "cursor.h"
43 #include "font.h"
44 #include "iolib.h"
45 #include "network.h"
46 #include "player.h"
47 #include "replay.h"
48 #include "sound.h"
49 #include "sound_server.h"
50 #include "translate.h"
51 #include "ui.h"
52 #include "unit.h"
53 #include "unit_find.h"
54 #include "unittype.h"
55 #include "video.h"
56 #include "widgets.h"
57 
58 /*----------------------------------------------------------------------------
59 --  Defines
60 ----------------------------------------------------------------------------*/
61 
62 /// Scrolling area (<= 15 y)
63 #define SCROLL_UP     15
64 /// Scrolling area (>= VideoHeight - 16 y)
65 #define SCROLL_DOWN   (Video.Height - 16)
66 /// Scrolling area (<= 15 y)
67 #define SCROLL_LEFT   15
68 /// Scrolling area (>= VideoWidth - 16 x)
69 #define SCROLL_RIGHT  (Video.Width - 16)
70 
71 /*----------------------------------------------------------------------------
72 --  Variables
73 ----------------------------------------------------------------------------*/
74 
75 const char Cursor[] = "~!_";         /// Input cursor
76 static Vec2i SavedMapPosition[3];    /// Saved map position
77 static char Input[80];               /// line input for messages/long commands
78 static int InputIndex;               /// current index into input
79 static char InputStatusLine[99];     /// Last input status line
80 const int MaxInputHistorySize = 16;  /// Max history of inputs
81 static char InputHistory[MaxInputHistorySize * sizeof(Input)] = { '\0' }; /// History of inputs
82 static int InputHistoryIdx = 0;      /// Next history idx
83 static int InputHistoryPos = 0;      /// Current position in history
84 static int InputHistorySize = 0;     /// History fill size
85 const char DefaultGroupKeys[] = "0123456789`";/// Default group keys
86 std::string UiGroupKeys = DefaultGroupKeys;/// Up to 11 keys, last unselect. Default for qwerty
87 bool GameRunning;                    /// Current running state
88 bool GamePaused;                     /// Current pause state
89 bool GameObserve;                    /// Observe mode
90 bool GameEstablishing;               /// Game establishing mode
91 char SkipGameCycle;                  /// Skip the next game cycle
92 char BigMapMode;                     /// Show only the map
93 enum _iface_state_ InterfaceState;   /// Current interface state
94 bool GodMode;                        /// Invincibility cheat
95 enum _key_state_ KeyState;           /// current key state
96 CUnit *LastIdleWorker;               /// Last called idle worker
97 
98 /*----------------------------------------------------------------------------
99 --  Functions
100 ----------------------------------------------------------------------------*/
101 
moveInputContent(int targetPos,int srcPos)102 static void moveInputContent(int targetPos, int srcPos) {
103 	memmove(Input + targetPos, Input + srcPos, sizeof(Input) - std::max(targetPos, srcPos));
104 }
105 
removeCursorFromInput()106 static void removeCursorFromInput() {
107 	// remove cursor, which is at InputIndex. there might be other chars behind it, if we're in the middle
108 	moveInputContent(InputIndex, InputIndex + strlen(Cursor));
109 }
110 
addCursorToInput()111 static void addCursorToInput() {
112 	// insert cursor at pos
113 	moveInputContent(InputIndex + strlen(Cursor), InputIndex);
114 	strncpy(Input + InputIndex, Cursor, strlen(Cursor));
115 }
116 
117 /**
118 **  Show input.
119 */
ShowInput()120 static void ShowInput()
121 {
122 	snprintf(InputStatusLine, sizeof(InputStatusLine), _("MESSAGE:%s"), Input);
123 	char *input = InputStatusLine;
124 	// FIXME: This is slow!
125 	while (UI.StatusLine.Font->Width(input) > UI.StatusLine.Width) {
126 		++input;
127 	}
128 	KeyState = KeyStateCommand;
129 	UI.StatusLine.Clear();
130 	UI.StatusLine.Set(input);
131 	KeyState = KeyStateInput;
132 }
133 
134 /**
135 **  Begin input.
136 */
UiBeginInput()137 static void UiBeginInput()
138 {
139 	KeyState = KeyStateInput;
140 	memset(Input, 0, sizeof(Input));
141 	InputIndex = 0;
142 	addCursorToInput();
143 	UI.StatusLine.ClearCosts();
144 	ShowInput();
145 }
146 
147 //-----------------------------------------------------------------------------
148 //  User interface group commands
149 //-----------------------------------------------------------------------------
150 
151 /**
152 **  Unselect all currently selected units.
153 */
UiUnselectAll()154 static void UiUnselectAll()
155 {
156 	UnSelectAll();
157 	NetworkSendSelection((CUnit **)NULL, 0);
158 	SelectionChanged();
159 }
160 
161 /**
162 **  Center on group.
163 **
164 **  @param group  Group number to center on.
165 **
166 **  @todo Improve this function, try to show all selected units
167 **        or the most possible units.
168 */
UiCenterOnGroup(unsigned group,GroupSelectionMode mode=SELECTABLE_BY_RECTANGLE_ONLY)169 static void UiCenterOnGroup(unsigned group, GroupSelectionMode mode = SELECTABLE_BY_RECTANGLE_ONLY)
170 {
171 	const std::vector<CUnit *> &units = GetUnitsOfGroup(group);
172 	PixelPos pos(-1, -1);
173 
174 	// FIXME: what should we do with the removed units? ignore?
175 	for (size_t i = 0; i != units.size(); ++i) {
176 		if (units[i]->Type && units[i]->Type->CanSelect(mode)) {
177 			if (pos.x != -1) {
178 				pos += (units[i]->GetMapPixelPosCenter() - pos) / 2;
179 			} else {
180 				pos = units[i]->GetMapPixelPosCenter();
181 			}
182 		}
183 	}
184 	if (pos.x != -1) {
185 		UI.SelectedViewport->Center(pos);
186 	}
187 }
188 
189 /**
190 **  Select group.
191 **
192 **  @param group  Group number to select.
193 */
UiSelectGroup(unsigned group,GroupSelectionMode mode=SELECT_ALL)194 static void UiSelectGroup(unsigned group, GroupSelectionMode mode = SELECT_ALL)
195 {
196 	SelectGroup(group, mode);
197 	SelectionChanged();
198 }
199 
200 /**
201 **  Add group to current selection.
202 **
203 **  @param group  Group number to add.
204 */
UiAddGroupToSelection(unsigned group)205 static void UiAddGroupToSelection(unsigned group)
206 {
207 	const std::vector<CUnit *> &units = GetUnitsOfGroup(group);
208 
209 	if (units.empty()) {
210 		return;
211 	}
212 
213 	//  Don't allow to mix units and buildings
214 	if (!Selected.empty() && Selected[0]->Type->Building) {
215 		return;
216 	}
217 
218 	for (size_t i = 0; i != units.size(); ++i) {
219 		if (!(units[i]->Removed || units[i]->Type->Building)) {
220 			SelectUnit(*units[i]);
221 		}
222 	}
223 	SelectionChanged();
224 }
225 
226 /**
227 **  Define a group. The current selected units become a new group.
228 **
229 **  @param group  Group number to create.
230 */
UiDefineGroup(unsigned group)231 static void UiDefineGroup(unsigned group)
232 {
233 	for (size_t i = 0; i != Selected.size(); ++i) {
234 		if (Selected[i]->Player == ThisPlayer && Selected[i]->GroupId) {
235 			RemoveUnitFromGroups(*Selected[i]);
236 		}
237 	}
238 	SetGroup(&Selected[0], Selected.size(), group);
239 }
240 
241 /**
242 **  Add to group. The current selected units are added to the group.
243 **
244 **  @param group  Group number to be expanded.
245 */
UiAddToGroup(unsigned group)246 static void UiAddToGroup(unsigned group)
247 {
248 	AddToGroup(&Selected[0], Selected.size(), group);
249 }
250 
251 /**
252 **  Toggle sound on / off.
253 */
UiToggleSound()254 static void UiToggleSound()
255 {
256 	if (SoundEnabled()) {
257 		if (IsEffectsEnabled()) {
258 			SetEffectsEnabled(false);
259 			SetMusicEnabled(false);
260 		} else {
261 			SetEffectsEnabled(true);
262 			SetMusicEnabled(true);
263 			CheckMusicFinished(true);
264 		}
265 	}
266 
267 	if (SoundEnabled()) {
268 		if (IsEffectsEnabled()) {
269 			UI.StatusLine.Set(_("Sound is on."));
270 		} else {
271 			UI.StatusLine.Set(_("Sound is off."));
272 		}
273 	}
274 }
275 
276 /**
277 **  Toggle music on / off.
278 */
UiToggleMusic()279 static void UiToggleMusic()
280 {
281 	static int vol;
282 	if (SoundEnabled()) {
283 		if (GetMusicVolume()) {
284 			vol = GetMusicVolume();
285 			SetMusicVolume(0);
286 			UI.StatusLine.Set(_("Music is off."));
287 		} else {
288 			SetMusicVolume(vol);
289 			UI.StatusLine.Set(_("Music is on."));
290 		}
291 	}
292 }
293 
294 /**
295 **  Toggle pause on / off.
296 */
UiTogglePause()297 void UiTogglePause()
298 {
299 	if (!IsNetworkGame()) {
300 		GamePaused = !GamePaused;
301 		if (GamePaused) {
302 			UI.StatusLine.Set(_("Game Paused"));
303 		} else {
304 			UI.StatusLine.Set(_("Game Resumed"));
305 		}
306 	}
307 }
308 
309 /**
310 **  Toggle big map mode.
311 **
312 **  @todo FIXME: We should try to keep the same view, if possible
313 */
UiToggleBigMap()314 void UiToggleBigMap()
315 {
316 	static int mapx;
317 	static int mapy;
318 	static int mapex;
319 	static int mapey;
320 
321 	BigMapMode ^= 1;
322 	if (BigMapMode) {
323 		mapx = UI.MapArea.X;
324 		mapy = UI.MapArea.Y;
325 		mapex = UI.MapArea.EndX;
326 		mapey = UI.MapArea.EndY;
327 
328 		UI.MapArea.X = 0;
329 		UI.MapArea.Y = 0;
330 		UI.MapArea.EndX = Video.Width - 1;
331 		UI.MapArea.EndY = Video.Height - 1;
332 
333 		SetViewportMode(UI.ViewportMode);
334 
335 		UI.StatusLine.Set(_("Big map enabled"));
336 	} else {
337 		UI.MapArea.X = mapx;
338 		UI.MapArea.Y = mapy;
339 		UI.MapArea.EndX = mapex;
340 		UI.MapArea.EndY = mapey;
341 
342 		SetViewportMode(UI.ViewportMode);
343 
344 		UI.StatusLine.Set(_("Returning to old map"));
345 	}
346 }
347 
348 /**
349 **  Increase game speed.
350 */
UiIncreaseGameSpeed()351 static void UiIncreaseGameSpeed()
352 {
353 	if (FastForwardCycle >= GameCycle) {
354 		return;
355 	}
356 	VideoSyncSpeed += 10;
357 	SetVideoSync();
358 	UI.StatusLine.Set(_("Faster"));
359 }
360 
361 /**
362 **  Decrease game speed.
363 */
UiDecreaseGameSpeed()364 static void UiDecreaseGameSpeed()
365 {
366 	if (FastForwardCycle >= GameCycle) {
367 		return;
368 	}
369 	if (VideoSyncSpeed <= 10) {
370 		if (VideoSyncSpeed > 1) {
371 			--VideoSyncSpeed;
372 		}
373 	} else {
374 		VideoSyncSpeed -= 10;
375 	}
376 	SetVideoSync();
377 	UI.StatusLine.Set(_("Slower"));
378 }
379 
380 /**
381 **  Set default game speed.
382 */
UiSetDefaultGameSpeed()383 static void UiSetDefaultGameSpeed()
384 {
385 	if (FastForwardCycle >= GameCycle) {
386 		return;
387 	}
388 	VideoSyncSpeed = 100;
389 	SetVideoSync();
390 	UI.StatusLine.Set(_("Set default game speed"));
391 }
392 
393 /**
394 **  Center on the selected units.
395 **
396 **  @todo Improve this function, try to show all selected units
397 **        or the most possible units.
398 */
UiCenterOnSelected()399 static void UiCenterOnSelected()
400 {
401 	if (Selected.empty()) {
402 		return;
403 	}
404 
405 	PixelPos pos;
406 
407 	for (size_t i = 0; i != Selected.size(); ++i) {
408 		pos += Selected[i]->GetMapPixelPosCenter();
409 	}
410 	pos /= Selected.size();
411 	UI.SelectedViewport->Center(pos);
412 }
413 
414 /**
415 **  Save current map position.
416 **
417 **  @param position  Map position slot.
418 */
UiSaveMapPosition(unsigned position)419 static void UiSaveMapPosition(unsigned position)
420 {
421 	SavedMapPosition[position] = UI.SelectedViewport->MapPos;
422 }
423 
424 /**
425 **  Recall map position.
426 **
427 **  @param position  Map position slot.
428 */
UiRecallMapPosition(unsigned position)429 static void UiRecallMapPosition(unsigned position)
430 {
431 	UI.SelectedViewport->Set(SavedMapPosition[position], PixelTileSize / 2);
432 }
433 
434 /**
435 **  Toggle terrain display on/off.
436 */
UiToggleTerrain()437 void UiToggleTerrain()
438 {
439 	UI.Minimap.WithTerrain ^= 1;
440 	if (UI.Minimap.WithTerrain) {
441 		UI.StatusLine.Set(_("Terrain displayed."));
442 	} else {
443 		UI.StatusLine.Set(_("Terrain hidden."));
444 	}
445 }
446 
447 /**
448 **  Find the next idle worker, select it, and center on it
449 */
UiFindIdleWorker()450 void UiFindIdleWorker()
451 {
452 	if (ThisPlayer->FreeWorkers.empty()) {
453 		return;
454 	}
455 	CUnit *unit = ThisPlayer->FreeWorkers[0];
456 	if (LastIdleWorker) {
457 		const std::vector<CUnit *> &freeWorkers = ThisPlayer->FreeWorkers;
458 		std::vector<CUnit *>::const_iterator it = std::find(freeWorkers.begin(),
459 															freeWorkers.end(),
460 															LastIdleWorker);
461 		if (it != ThisPlayer->FreeWorkers.end()) {
462 			if (*it != ThisPlayer->FreeWorkers.back()) {
463 				unit = *(++it);
464 			}
465 		}
466 	}
467 
468 	if (unit != NULL) {
469 		LastIdleWorker = unit;
470 		SelectSingleUnit(*unit);
471 		UI.StatusLine.Clear();
472 		UI.StatusLine.ClearCosts();
473 		CurrentButtonLevel = 0;
474 		PlayUnitSound(*Selected[0], VoiceSelected);
475 		SelectionChanged();
476 		UI.SelectedViewport->Center(unit->GetMapPixelPosCenter());
477 	}
478 }
479 
480 /**
481 **  Toggle grab mouse on/off.
482 */
UiToggleGrabMouse()483 static void UiToggleGrabMouse()
484 {
485 	DebugPrint("%x\n" _C_ KeyModifiers);
486 	ToggleGrabMouse(0);
487 	UI.StatusLine.Set(_("Grab mouse toggled."));
488 }
489 
490 /**
491 **  Track unit, the viewport follows the unit.
492 */
UiTrackUnit()493 void UiTrackUnit()
494 {
495 	//Check if player has selected at least 1 unit
496 	if (Selected.empty()) {
497 		UI.SelectedViewport->Unit = NULL;
498 		return;
499 	}
500 	if (UI.SelectedViewport->Unit == Selected[0]) {
501 		UI.SelectedViewport->Unit = NULL;
502 	} else {
503 		UI.SelectedViewport->Unit = Selected[0];
504 	}
505 }
506 
507 /**
508 **  Call the lua function HandleCommandKey
509 */
HandleCommandKey(int key)510 bool HandleCommandKey(int key)
511 {
512 	int base = lua_gettop(Lua);
513 
514 	lua_getglobal(Lua, "HandleCommandKey");
515 	if (!lua_isfunction(Lua, -1)) {
516 		DebugPrint("No HandleCommandKey function in lua.\n");
517 		return false;
518 	}
519 	lua_pushstring(Lua, SdlKey2Str(key));
520 	lua_pushboolean(Lua, (KeyModifiers & ModifierControl));
521 	lua_pushboolean(Lua, (KeyModifiers & ModifierAlt));
522 	lua_pushboolean(Lua, (KeyModifiers & ModifierShift));
523 	LuaCall(4, 0);
524 	if (lua_gettop(Lua) - base == 1) {
525 		bool ret = LuaToBoolean(Lua, base + 1);
526 		lua_pop(Lua, 1);
527 		return ret;
528 	} else {
529 		LuaError(Lua, "HandleCommandKey must return a boolean");
530 		return false;
531 	}
532 }
533 
534 #ifdef DEBUG
535 extern void ToggleShowBuilListMessages();
536 #endif
537 
CommandKey_Group(int group)538 static void CommandKey_Group(int group)
539 {
540 	if (KeyModifiers & ModifierShift) {
541 		if (KeyModifiers & (ModifierAlt | ModifierDoublePress)) {
542 			if (KeyModifiers & ModifierDoublePress) {
543 				UiCenterOnGroup(group, SELECT_ALL);
544 			} else {
545 				UiSelectGroup(group, SELECT_ALL);
546 			}
547 		} else if (KeyModifiers & ModifierControl) {
548 			UiAddToGroup(group);
549 		} else {
550 			UiAddGroupToSelection(group);
551 		}
552 	} else {
553 		if (KeyModifiers & (ModifierAlt | ModifierDoublePress)) {
554 			if (KeyModifiers & ModifierAlt) {
555 				if (KeyModifiers & ModifierDoublePress) {
556 					UiCenterOnGroup(group, NON_SELECTABLE_BY_RECTANGLE_ONLY);
557 				} else {
558 					UiSelectGroup(group, NON_SELECTABLE_BY_RECTANGLE_ONLY);
559 				}
560 			} else {
561 				UiCenterOnGroup(group);
562 			}
563 		} else if (KeyModifiers & ModifierControl) {
564 			UiDefineGroup(group);
565 		} else {
566 			UiSelectGroup(group);
567 		}
568 	}
569 }
570 
CommandKey_MapPosition(int index)571 static void CommandKey_MapPosition(int index)
572 {
573 	if (KeyModifiers & ModifierShift) {
574 		UiSaveMapPosition(index);
575 	} else {
576 		UiRecallMapPosition(index);
577 	}
578 }
579 
580 /**
581 **  Handle keys in command mode.
582 **
583 **  @param key  Key scancode.
584 **
585 **  @return     True, if key is handled; otherwise false.
586 */
CommandKey(int key)587 static bool CommandKey(int key)
588 {
589 	const char *ptr = strchr(UiGroupKeys.c_str(), key);
590 
591 	// FIXME: don't handle unicode well. Should work on all latin keyboard.
592 	if (ptr) {
593 		key = ((int)'0') + ptr - UiGroupKeys.c_str();
594 		if (key > '9') {
595 			key = SDLK_BACKQUOTE;
596 		}
597 	}
598 
599 	switch (key) {
600 		// Return enters chat/input mode.
601 		case SDLK_RETURN:
602 		case SDLK_KP_ENTER: // RETURN
603 			UiBeginInput();
604 			return true;
605 
606 		// Unselect everything
607 		case SDLK_CARET:
608 		case SDLK_BACKQUOTE:
609 			// UiUnselectAll();
610 			break;
611 
612 		// Group selection
613 		case '0': case '1': case '2':
614 		case '3': case '4': case '5':
615 		case '6': case '7': case '8':
616 		case '9':
617 			CommandKey_Group(key - '0');
618 			break;
619 
620 		case SDLK_F2:
621 		case SDLK_F3:
622 		case SDLK_F4: // Set/Goto place
623 			CommandKey_MapPosition(key - SDLK_F2);
624 			break;
625 
626 		case SDLK_SPACE: // center on last action
627 			CenterOnMessage();
628 			break;
629 
630 		case SDLK_EQUALS: // plus is shift-equals.
631 		case SDLK_KP_PLUS:
632 			UiIncreaseGameSpeed();
633 			break;
634 
635 		case SDLK_MINUS: // - Slower
636 		case SDLK_KP_MINUS:
637 			UiDecreaseGameSpeed();
638 			break;
639 		case SDLK_KP_MULTIPLY:
640 			UiSetDefaultGameSpeed();
641 			break;
642 
643 		case 'b': // ALT+B, CTRL+B Toggle big map
644 			if (!(KeyModifiers & (ModifierAlt | ModifierControl))) {
645 				break;
646 			}
647 			UiToggleBigMap();
648 			break;
649 
650 		case 'c': // ALT+C, CTRL+C C center on units
651 			UiCenterOnSelected();
652 			break;
653 
654 		case 'f': // ALT+F, CTRL+F toggle fullscreen
655 			if (!(KeyModifiers & (ModifierAlt | ModifierControl))) {
656 				break;
657 			}
658 			ToggleFullScreen();
659 			SavePreferences();
660 			break;
661 
662 		case 'g': // ALT+G, CTRL+G grab mouse pointer
663 			if (!(KeyModifiers & (ModifierAlt | ModifierControl))) {
664 				break;
665 			}
666 			UiToggleGrabMouse();
667 			break;
668 
669 		case 'i':
670 			if (!(KeyModifiers & (ModifierAlt | ModifierControl))) {
671 				break;
672 			}
673 		// FALL THROUGH
674 		case SDLK_PERIOD: // ., ALT+I, CTRL+I: Find idle worker
675 			UiFindIdleWorker();
676 			break;
677 
678 		case 'm': // CTRL+M Turn music on / off
679 			if (KeyModifiers & ModifierControl) {
680 				UiToggleMusic();
681 				SavePreferences();
682 				break;
683 			}
684 			break;
685 
686 		case 'p': // CTRL+P, ALT+P Toggle pause
687 			if (!(KeyModifiers & (ModifierAlt | ModifierControl))) {
688 				break;
689 			}
690 		// FALL THROUGH (CTRL+P, ALT+P)
691 		case SDLK_PAUSE:
692 			UiTogglePause();
693 			break;
694 
695 		case 's': // CTRL+S - Turn sound on / off
696 			if (KeyModifiers & ModifierControl) {
697 				UiToggleSound();
698 				SavePreferences();
699 				break;
700 			}
701 			break;
702 
703 		case 't': // ALT+T, CTRL+T Track unit
704 			if (!(KeyModifiers & (ModifierAlt | ModifierControl))) {
705 				break;
706 			}
707 			UiTrackUnit();
708 			break;
709 
710 		case 'v': // ALT+V, CTRL+V: Viewport
711 			if (KeyModifiers & ModifierControl) {
712 				CycleViewportMode(-1);
713 			} else if (KeyModifiers & ModifierAlt) {
714 				CycleViewportMode(1);
715 			}
716 			break;
717 
718 		case 'e': // CTRL+E Turn messages on / off
719 			if (KeyModifiers & ModifierControl) {
720 				ToggleShowMessages();
721 			}
722 #ifdef DEBUG
723 			else if (KeyModifiers & ModifierAlt) {
724 				ToggleShowBuilListMessages();
725 			}
726 #endif
727 			break;
728 
729 		case SDLK_TAB: // TAB toggles minimap.
730 			// FIXME: more...
731 			// FIXME: shift+TAB
732 			if (KeyModifiers & ModifierAlt) {
733 				break;
734 			}
735 			UiToggleTerrain();
736 			break;
737 
738 		case SDLK_UP:
739 		case SDLK_KP_8:
740 			KeyScrollState |= ScrollUp;
741 			break;
742 		case SDLK_DOWN:
743 		case SDLK_KP_2:
744 			KeyScrollState |= ScrollDown;
745 			break;
746 		case SDLK_LEFT:
747 		case SDLK_KP_4:
748 			KeyScrollState |= ScrollLeft;
749 			break;
750 		case SDLK_RIGHT:
751 		case SDLK_KP_6:
752 			KeyScrollState |= ScrollRight;
753 			break;
754 		default:
755 			if (HandleCommandKey(key)) {
756 				break;
757 			}
758 			return false;
759 	}
760 	return true;
761 }
762 
763 /**
764 **  Handle cheats
765 **
766 **  @return  1 if a cheat was handled, 0 otherwise
767 */
HandleCheats(const std::string & input)768 int HandleCheats(const std::string &input)
769 {
770 #if defined(DEBUG) || defined(PROF)
771 	if (input == "ai me") {
772 		if (ThisPlayer->AiEnabled) {
773 			// FIXME: UnitGoesUnderFog and UnitGoesOutOfFog change unit refs
774 			// for human players.  We can't switch back to a human player or
775 			// we'll be using the wrong ref counts.
776 #if 0
777 			ThisPlayer->AiEnabled = false;
778 			ThisPlayer->Type = PlayerPerson;
779 			SetMessage("AI is off, Normal Player");
780 #else
781 			SetMessage("Cannot disable 'ai me' cheat");
782 #endif
783 		} else {
784 			ThisPlayer->AiEnabled = true;
785 			ThisPlayer->Type = PlayerComputer;
786 			if (!ThisPlayer->Ai) {
787 				AiInit(*ThisPlayer);
788 			}
789 			SetMessage("I'm the BORG, resistance is futile!");
790 		}
791 		return 1;
792 	}
793 #endif
794 	int base = lua_gettop(Lua);
795 	lua_getglobal(Lua, "HandleCheats");
796 	if (!lua_isfunction(Lua, -1)) {
797 		DebugPrint("No HandleCheats function in lua.\n");
798 		return 0;
799 	}
800 	lua_pushstring(Lua, input.c_str());
801 	LuaCall(1, 0, false);
802 	if (lua_gettop(Lua) - base == 1) {
803 		int ret = LuaToBoolean(Lua, -1);
804 		lua_pop(Lua, 1);
805 		return ret;
806 	} else {
807 		DebugPrint("HandleCheats must return a boolean");
808 		return 0;
809 	}
810 }
811 
812 // Replace ~~ with ~
Replace2TildeByTilde(char * s)813 static void Replace2TildeByTilde(char *s)
814 {
815 	for (char *p = s; *p; ++p) {
816 		if (*p == '~') {
817 			++p;
818 		}
819 		*s++ = *p;
820 	}
821 	*s = '\0';
822 }
823 
824 // Replace ~ with ~~
ReplaceTildeBy2Tilde(char * s)825 static void ReplaceTildeBy2Tilde(char *s)
826 {
827 	for (char *p = s; *p; ++p) {
828 		if (*p != '~') {
829 			continue;
830 		}
831 		char *q = p + strlen(p);
832 		q[1] = '\0';
833 		while (q > p) {
834 			*q = *(q - 1);
835 			--q;
836 		}
837 		++p;
838 	}
839 }
840 
841 /**
842 **  Handle keys in input mode.
843 **
844 **  @param key  Key scancode.
845 */
InputKey(int key)846 static void InputKey(int key)
847 {
848 	switch (key) {
849 		case SDLK_RETURN:
850 		case SDLK_KP_ENTER: { // RETURN
851 			removeCursorFromInput();
852 			// save to history
853 			strncpy(InputHistory + (InputHistoryIdx * sizeof(Input)), Input, sizeof(Input));
854 			if (InputHistorySize < MaxInputHistorySize) {
855 				InputHistorySize++;
856 				InputHistoryIdx = InputHistorySize;
857 			} else {
858 				InputHistoryIdx = ((InputHistoryIdx + 1) % InputHistorySize + InputHistorySize) % InputHistorySize;
859 			}
860 			InputHistoryPos = InputHistoryIdx;
861 
862 			// Replace ~~ with ~
863 			Replace2TildeByTilde(Input);
864 #ifdef DEBUG
865 			if (Input[0] == '-') {
866 				if (!GameObserve && !GamePaused && !GameEstablishing) {
867 					CommandLog("input", NoUnitP, FlushCommands, -1, -1, NoUnitP, Input, -1);
868 					CclCommand(Input + 1, false);
869 				}
870 			} else
871 #endif
872 				if (!IsNetworkGame()) {
873 					if (!GameObserve && !GamePaused && !GameEstablishing) {
874 						if (HandleCheats(Input)) {
875 							CommandLog("input", NoUnitP, FlushCommands, -1, -1, NoUnitP, Input, -1);
876 						}
877 					}
878 				}
879 
880 			// Check for Replay and ffw x
881 #ifdef DEBUG
882 			if (strncmp(Input, "ffw ", 4) == 0) {
883 #else
884 			if (strncmp(Input, "ffw ", 4) == 0 && ReplayGameType != ReplayNone) {
885 #endif
886 				FastForwardCycle = atoi(&Input[4]);
887 			}
888 
889 			if (Input[0]) {
890 				// Replace ~ with ~~
891 				ReplaceTildeBy2Tilde(Input);
892 				char chatMessage[sizeof(Input) + 40];
893 				snprintf(chatMessage, sizeof(chatMessage), "~%s~<%s>~> %s",
894 						 PlayerColorNames[ThisPlayer->Index].c_str(),
895 						 ThisPlayer->Name.c_str(), Input);
896 				// FIXME: only to selected players ...
897 				NetworkSendChatMessage(chatMessage);
898 			}
899 		}
900 	// FALL THROUGH
901 		case SDLK_ESCAPE:
902 			KeyState = KeyStateCommand;
903 			UI.StatusLine.Clear();
904 			break;
905 
906 #ifdef USE_MAC
907 		case SDLK_DELETE:
908 #endif
909 		case SDLK_BACKSPACE: {
910 			if (InputIndex) {
911 				InputHistoryPos = InputHistoryIdx;
912 				removeCursorFromInput();
913 				if (Input[InputIndex - 1] == '~') {
914 					moveInputContent(InputIndex - 1, InputIndex);
915 					InputIndex--;
916 				}
917 				int prevIndex = UTF8GetPrev(Input, InputIndex);
918 				if (prevIndex >= 0) {
919 					moveInputContent(prevIndex, InputIndex);
920 					InputIndex = prevIndex;
921 				}
922 				addCursorToInput();
923 				ShowInput();
924 			}
925 			break;
926 		}
927 		case SDLK_UP:
928 			removeCursorFromInput();
929 			strncpy(InputHistory + (InputHistoryPos * sizeof(Input)), Input, sizeof(Input));
930 			InputHistoryPos = ((InputHistoryPos - 1) % InputHistorySize + InputHistorySize) % InputHistorySize;
931 			strncpy(Input, InputHistory + (InputHistoryPos * sizeof(Input)), sizeof(Input));
932 			InputIndex = strlen(Input);
933 			addCursorToInput();
934 			ShowInput();
935 			break;
936 
937 		case SDLK_DOWN:
938 			removeCursorFromInput();
939 			strncpy(InputHistory + (InputHistoryPos * sizeof(Input)), Input, sizeof(Input));
940 			InputHistoryPos = ((InputHistoryPos + 1) % InputHistorySize + InputHistorySize) % InputHistorySize;
941 			strncpy(Input, InputHistory + (InputHistoryPos * sizeof(Input)), sizeof(Input));
942 			InputIndex = strlen(Input);
943 			addCursorToInput();
944 			ShowInput();
945 			break;
946 
947 		case SDLK_LEFT:
948 			if (InputIndex) {
949 				removeCursorFromInput();
950 				InputIndex = UTF8GetPrev(Input, InputIndex);
951 				addCursorToInput();
952 				ShowInput();
953 			}
954 			break;
955 
956 		case SDLK_RIGHT:
957 			removeCursorFromInput();
958 			InputIndex = UTF8GetNext(Input, InputIndex);
959 			addCursorToInput();
960 			ShowInput();
961 			break;
962 
963 		case SDLK_TAB: {
964 			InputHistoryPos = InputHistoryIdx;
965 			removeCursorFromInput();
966 			char *namestart = strrchr(Input, ' ');
967 			if (namestart) {
968 				++namestart;
969 			} else {
970 				namestart = Input;
971 			}
972 			if (strlen(namestart)) {
973 				for (int i = 0; i < PlayerMax; ++i) {
974 					if (Players[i].Type != PlayerPerson) {
975 						continue;
976 					}
977 					if (!strncasecmp(namestart, Players[i].Name.c_str(), strlen(namestart))) {
978 						InputIndex += strlen(Players[i].Name.c_str()) - strlen(namestart);
979 						strcpy_s(namestart, sizeof(Input) - (namestart - Input), Players[i].Name.c_str());
980 						if (namestart == Input) {
981 							InputIndex += 2;
982 							strcat_s(namestart, sizeof(Input) - (namestart - Input), ": ");
983 						}
984 					}
985 				}
986 			}
987 			addCursorToInput();
988 			ShowInput();
989 			break;
990 		}
991 		default:
992 			if (key >= ' ') {
993 				InputHistoryPos = InputHistoryIdx;
994 				removeCursorFromInput();
995 				gcn::Key k(key);
996 				std::string kstr = k.toString();
997 				if (key == '~') {
998 					if (InputIndex < (int)sizeof(Input) - 2) {
999 						moveInputContent(InputIndex + 2, InputIndex);
1000 						Input[InputIndex++] = key;
1001 						Input[InputIndex++] = key;
1002 					}
1003 				} else if (InputIndex < (int)(sizeof(Input) - kstr.size())) {
1004 					moveInputContent(InputIndex + kstr.size(), InputIndex);
1005 					for (size_t i = 0; i < kstr.size(); ++i) {
1006 						Input[InputIndex++] = kstr[i];
1007 					}
1008 				}
1009 				addCursorToInput();
1010 				ShowInput();
1011 			}
1012 			break;
1013 	}
1014 }
1015 
1016 /**
1017 **  Save a screenshot.
1018 */
1019 static void Screenshot()
1020 {
1021 	CFile fd;
1022 	char filename[30];
1023 
1024 	for (int i = 1; i <= 99; ++i) {
1025 		// FIXME: what if we can't write to this directory?
1026 		snprintf(filename, sizeof(filename), "screen%02d.png", i);
1027 		if (fd.open(filename, CL_OPEN_READ) == -1) {
1028 			break;
1029 		}
1030 		fd.close();
1031 	}
1032 	SaveScreenshotPNG(filename);
1033 }
1034 
1035 /**
1036 **  Update KeyModifiers if a key is pressed.
1037 **
1038 **  @param key      Key scancode.
1039 **  @param keychar  Character code.
1040 **
1041 **  @return         1 if modifier found, 0 otherwise
1042 */
1043 int HandleKeyModifiersDown(unsigned key, unsigned)
1044 {
1045 	switch (key) {
1046 		case SDLK_LSHIFT:
1047 		case SDLK_RSHIFT:
1048 			KeyModifiers |= ModifierShift;
1049 			return 1;
1050 		case SDLK_LCTRL:
1051 		case SDLK_RCTRL:
1052 			KeyModifiers |= ModifierControl;
1053 			return 1;
1054 		case SDLK_LALT:
1055 		case SDLK_RALT:
1056 			KeyModifiers |= ModifierAlt;
1057 			// maxy: disabled
1058 			if (InterfaceState == IfaceStateNormal) {
1059 				SelectedUnitChanged(); // VLADI: to allow alt-buttons
1060 			}
1061 			return 1;
1062 		case SDLK_LGUI:
1063 		case SDLK_RGUI:
1064 			KeyModifiers |= ModifierSuper;
1065 			return 1;
1066 		case SDLK_SYSREQ:
1067 		case SDLK_PRINTSCREEN:
1068 			Screenshot();
1069 			if (GameRunning) {
1070 				SetMessage("%s", _("Screenshot made."));
1071 			}
1072 			return 1;
1073 		default:
1074 			break;
1075 	}
1076 	return 0;
1077 }
1078 
1079 /**
1080 **  Update KeyModifiers if a key is released.
1081 **
1082 **  @param key      Key scancode.
1083 **  @param keychar  Character code.
1084 **
1085 **  @return         1 if modifier found, 0 otherwise
1086 */
1087 int HandleKeyModifiersUp(unsigned key, unsigned)
1088 {
1089 	switch (key) {
1090 		case SDLK_LSHIFT:
1091 		case SDLK_RSHIFT:
1092 			KeyModifiers &= ~ModifierShift;
1093 			return 1;
1094 		case SDLK_LCTRL:
1095 		case SDLK_RCTRL:
1096 			KeyModifiers &= ~ModifierControl;
1097 			return 1;
1098 		case SDLK_LALT:
1099 		case SDLK_RALT:
1100 			KeyModifiers &= ~ModifierAlt;
1101 			// maxy: disabled
1102 			if (InterfaceState == IfaceStateNormal) {
1103 				SelectedUnitChanged(); // VLADI: to allow alt-buttons
1104 			}
1105 			return 1;
1106 		case SDLK_LGUI:
1107 		case SDLK_RGUI:
1108 			KeyModifiers &= ~ModifierSuper;
1109 			return 1;
1110 	}
1111 	return 0;
1112 }
1113 
1114 /**
1115 **  Check if a key is from the keypad and convert to ascii
1116 */
1117 static bool IsKeyPad(unsigned key, unsigned *kp)
1118 {
1119 	if (key >= SDLK_KP_0 && key <= SDLK_KP_9) {
1120 		*kp = SDLK_0 + (key - SDLK_KP_0);
1121 	} else if (key == SDLK_KP_PERIOD) {
1122 		*kp = SDLK_PERIOD;
1123 	} else if (key == SDLK_KP_DIVIDE) {
1124 		*kp = SDLK_SLASH;
1125 	} else if (key == SDLK_KP_MULTIPLY) {
1126 		*kp = SDLK_ASTERISK;
1127 	} else if (key == SDLK_KP_MINUS) {
1128 		*kp = SDLK_MINUS;
1129 	} else if (key == SDLK_KP_PLUS) {
1130 		*kp = SDLK_PLUS;
1131 	} else if (key == SDLK_KP_ENTER) {
1132 		*kp = SDLK_RETURN;
1133 	} else if (key == SDLK_KP_EQUALS) {
1134 		*kp = SDLK_EQUALS;
1135 	} else if (key == SDLK_UP || key == SDLK_DOWN || key == SDLK_LEFT || key == SDLK_RIGHT) {
1136 		*kp = key;
1137 	} else  {
1138 		*kp = SDLK_UNKNOWN;
1139 		return false;
1140 	}
1141 	return true;
1142 }
1143 
1144 /**
1145 **  Handle key down.
1146 **
1147 **  @param key      Key scancode.
1148 **  @param keychar  Character code.
1149 */
1150 void HandleKeyDown(unsigned key, unsigned keychar)
1151 {
1152 	if (IsDemoMode()) {
1153 		// If we are in "demo mode", exit no matter which key we hit.
1154 		void ActionDraw();
1155 		ActionDraw();
1156 		return;
1157 	}
1158 
1159 	if (HandleKeyModifiersDown(key, keychar)) {
1160 		return;
1161 	}
1162 
1163 	// Handle All other keys
1164 
1165 	// Command line input: for message or cheat
1166 	unsigned kp = 0;
1167 	if (KeyState == KeyStateInput && (keychar || IsKeyPad(key, &kp))) {
1168 		InputKey(kp ? kp : keychar);
1169 	} else {
1170 		// If no modifier look if button bound
1171 		if (!(KeyModifiers & (ModifierControl | ModifierAlt | ModifierSuper))) {
1172 			if (!GameObserve && !GamePaused && !GameEstablishing) {
1173 				if (UI.ButtonPanel.DoKey(key)) {
1174 					return;
1175 				}
1176 			}
1177 		}
1178 		CommandKey(key);
1179 	}
1180 }
1181 
1182 /**
1183 **  Handle key up.
1184 **
1185 **  @param key      Key scancode.
1186 **  @param keychar  Character code.
1187 */
1188 void HandleKeyUp(unsigned key, unsigned keychar)
1189 {
1190 	if (HandleKeyModifiersUp(key, keychar)) {
1191 		return;
1192 	}
1193 
1194 	switch (key) {
1195 		case SDLK_UP:
1196 		case SDLK_KP_8:
1197 			KeyScrollState &= ~ScrollUp;
1198 			break;
1199 		case SDLK_DOWN:
1200 		case SDLK_KP_2:
1201 			KeyScrollState &= ~ScrollDown;
1202 			break;
1203 		case SDLK_LEFT:
1204 		case SDLK_KP_4:
1205 			KeyScrollState &= ~ScrollLeft;
1206 			break;
1207 		case SDLK_RIGHT:
1208 		case SDLK_KP_6:
1209 			KeyScrollState &= ~ScrollRight;
1210 			break;
1211 		default:
1212 			break;
1213 	}
1214 }
1215 
1216 /**
1217 **  Handle key up.
1218 **
1219 **  @param key      Key scancode.
1220 **  @param keychar  Character code.
1221 */
1222 void HandleKeyRepeat(unsigned, unsigned keychar)
1223 {
1224 	if (KeyState == KeyStateInput && keychar) {
1225 		InputKey(keychar);
1226 	}
1227 }
1228 
1229 /**
1230 **  Handle the mouse in scroll area
1231 **
1232 **  @param mousePos  Screen position.
1233 **
1234 **  @return   true if the mouse is in the scroll area, false otherwise
1235 */
1236 bool HandleMouseScrollArea(const PixelPos &mousePos)
1237 {
1238 	if (mousePos.x < SCROLL_LEFT) {
1239 		if (mousePos.y < SCROLL_UP) {
1240 			CursorOn = CursorOnScrollLeftUp;
1241 			MouseScrollState = ScrollLeftUp;
1242 			GameCursor = UI.ArrowNW.Cursor;
1243 		} else if (mousePos.y > SCROLL_DOWN) {
1244 			CursorOn = CursorOnScrollLeftDown;
1245 			MouseScrollState = ScrollLeftDown;
1246 			GameCursor = UI.ArrowSW.Cursor;
1247 		} else {
1248 			CursorOn = CursorOnScrollLeft;
1249 			MouseScrollState = ScrollLeft;
1250 			GameCursor = UI.ArrowW.Cursor;
1251 		}
1252 	} else if (mousePos.x > SCROLL_RIGHT) {
1253 		if (mousePos.y < SCROLL_UP) {
1254 			CursorOn = CursorOnScrollRightUp;
1255 			MouseScrollState = ScrollRightUp;
1256 			GameCursor = UI.ArrowNE.Cursor;
1257 		} else if (mousePos.y > SCROLL_DOWN) {
1258 			CursorOn = CursorOnScrollRightDown;
1259 			MouseScrollState = ScrollRightDown;
1260 			GameCursor = UI.ArrowSE.Cursor;
1261 		} else {
1262 			CursorOn = CursorOnScrollRight;
1263 			MouseScrollState = ScrollRight;
1264 			GameCursor = UI.ArrowE.Cursor;
1265 		}
1266 	} else {
1267 		if (mousePos.y < SCROLL_UP) {
1268 			CursorOn = CursorOnScrollUp;
1269 			MouseScrollState = ScrollUp;
1270 			GameCursor = UI.ArrowN.Cursor;
1271 		} else if (mousePos.y > SCROLL_DOWN) {
1272 			CursorOn = CursorOnScrollDown;
1273 			MouseScrollState = ScrollDown;
1274 			GameCursor = UI.ArrowS.Cursor;
1275 		} else {
1276 			return false;
1277 		}
1278 	}
1279 	return true;
1280 }
1281 
1282 /**
1283 **  Keep coordinates in window and update cursor position
1284 **
1285 **  @param x  screen pixel X position.
1286 **  @param y  screen pixel Y position.
1287 */
1288 void HandleCursorMove(int *x, int *y)
1289 {
1290 	//  Reduce coordinates to window-size.
1291 	clamp(x, 0, Video.Width - 1);
1292 	clamp(y, 0, Video.Height - 1);
1293 	CursorScreenPos.x = *x;
1294 	CursorScreenPos.y = *y;
1295 }
1296 
1297 /**
1298 **  Handle movement of the cursor.
1299 **
1300 **  @param screePos  screen pixel position.
1301 */
1302 void HandleMouseMove(const PixelPos &screenPos)
1303 {
1304 	PixelPos pos(screenPos);
1305 	HandleCursorMove(&pos.x, &pos.y);
1306 	UIHandleMouseMove(pos);
1307 }
1308 
1309 /**
1310 **  Called if mouse button pressed down.
1311 **
1312 **  @param button  Mouse button number (0 left, 1 middle, 2 right)
1313 */
1314 void HandleButtonDown(unsigned button)
1315 {
1316 	UIHandleButtonDown(button);
1317 }
1318 
1319 /**
1320 **  Called if mouse button released.
1321 **
1322 **  @todo FIXME: the mouse handling should be complete rewritten
1323 **  @todo FIXME: this is needed for double click, long click,...
1324 **
1325 **  @param button  Mouse button number (0 left, 1 middle, 2 right)
1326 */
1327 void HandleButtonUp(unsigned button)
1328 {
1329 	UIHandleButtonUp(button);
1330 }
1331 
1332 /*----------------------------------------------------------------------------
1333 --  Lowlevel input functions
1334 ----------------------------------------------------------------------------*/
1335 
1336 int DoubleClickDelay = 300;       /// Time to detect double clicks.
1337 int HoldClickDelay = 1000;        /// Time to detect hold clicks.
1338 
1339 static enum {
1340 	InitialMouseState,            /// start state
1341 	ClickedMouseState             /// button is clicked
1342 } MouseState;                     /// Current state of mouse
1343 
1344 static PixelPos LastMousePos;     /// Last mouse position
1345 static unsigned LastMouseButton;  /// last mouse button handled
1346 static unsigned StartMouseTicks;  /// Ticks of first click
1347 static unsigned LastMouseTicks;   /// Ticks of last mouse event
1348 
1349 /**
1350 **  Called if any mouse button is pressed down
1351 **
1352 **  Handles event conversion to double click, dragging, hold.
1353 **
1354 **  FIXME: dragging is not supported.
1355 **
1356 **  @param callbacks  Callback structure for events.
1357 **  @param ticks      Denotes time-stamp of video-system
1358 **  @param button     Mouse button pressed.
1359 */
1360 void InputMouseButtonPress(const EventCallback &callbacks,
1361 						   unsigned ticks, unsigned button)
1362 {
1363 	//  Button new pressed.
1364 	if (!(MouseButtons & (1 << button))) {
1365 		MouseButtons |= (1 << button);
1366 		//  Detect double click
1367 		if (MouseState == ClickedMouseState && button == LastMouseButton
1368 			&& ticks < StartMouseTicks + DoubleClickDelay) {
1369 			MouseButtons |= (1 << button) << MouseDoubleShift;
1370 			button |= button << MouseDoubleShift;
1371 		} else {
1372 			MouseState = InitialMouseState;
1373 			StartMouseTicks = ticks;
1374 			LastMouseButton = button;
1375 		}
1376 	}
1377 	LastMouseTicks = ticks;
1378 
1379 	callbacks.ButtonPressed(button);
1380 }
1381 
1382 /**
1383 **  Called if any mouse button is released up
1384 **
1385 **  @param callbacks  Callback structure for events.
1386 **  @param ticks      Denotes time-stamp of video-system
1387 **  @param button     Mouse button released.
1388 */
1389 void InputMouseButtonRelease(const EventCallback &callbacks,
1390 							 unsigned ticks, unsigned button)
1391 {
1392 	//  Same button before pressed.
1393 	if (button == LastMouseButton && MouseState == InitialMouseState) {
1394 		MouseState = ClickedMouseState;
1395 	} else {
1396 		LastMouseButton = 0;
1397 		MouseState = InitialMouseState;
1398 	}
1399 	LastMouseTicks = ticks;
1400 
1401 	unsigned mask = 0;
1402 	if (MouseButtons & ((1 << button) << MouseDoubleShift)) {
1403 		mask |= button << MouseDoubleShift;
1404 	}
1405 	if (MouseButtons & ((1 << button) << MouseDragShift)) {
1406 		mask |= button << MouseDragShift;
1407 	}
1408 	if (MouseButtons & ((1 << button) << MouseHoldShift)) {
1409 		mask |= button << MouseHoldShift;
1410 	}
1411 	MouseButtons &= ~(0x01010101 << button);
1412 
1413 	callbacks.ButtonReleased(button | mask);
1414 }
1415 
1416 /**
1417 **  Called if the mouse is moved
1418 **
1419 **  @param callbacks  Callback structure for events.
1420 **  @param ticks      Denotes time-stamp of video-system
1421 **  @param x          X movement
1422 **  @param y          Y movement
1423 */
1424 void InputMouseMove(const EventCallback &callbacks,
1425 					unsigned ticks, int x, int y)
1426 {
1427 	PixelPos mousePos(x, y);
1428 	// Don't reset the mouse state unless we really moved
1429 	if (LastMousePos != mousePos) {
1430 		MouseState = InitialMouseState;
1431 		LastMouseTicks = ticks;
1432 		LastMousePos = mousePos;
1433 	}
1434 	callbacks.MouseMoved(mousePos);
1435 }
1436 
1437 /**
1438 **  Called if the mouse exits the game window (when supported by videomode)
1439 **
1440 **  @param callbacks  Callback structure for events.
1441 **  @param ticks      Denotes time-stamp of video-system
1442 */
1443 void InputMouseExit(const EventCallback &callbacks, unsigned /* ticks */)
1444 {
1445 	// FIXME: should we do anything here with ticks? don't know, but conform others
1446 	// JOHNS: called by callback HandleMouseExit();
1447 	callbacks.MouseExit();
1448 }
1449 
1450 /**
1451 **  Called each frame to handle mouse timeouts.
1452 **
1453 **  @param callbacks  Callback structure for events.
1454 **  @param ticks      Denotes time-stamp of video-system
1455 */
1456 void InputMouseTimeout(const EventCallback &callbacks, unsigned ticks)
1457 {
1458 	if (MouseButtons & (1 << LastMouseButton)) {
1459 		if (ticks > StartMouseTicks + DoubleClickDelay) {
1460 			MouseState = InitialMouseState;
1461 		}
1462 		if (ticks > LastMouseTicks + HoldClickDelay) {
1463 			LastMouseTicks = ticks;
1464 			MouseButtons |= (1 << LastMouseButton) << MouseHoldShift;
1465 			callbacks.ButtonPressed(LastMouseButton | (LastMouseButton << MouseHoldShift));
1466 		}
1467 	}
1468 }
1469 
1470 
1471 static const int HoldKeyDelay = 250;          /// Time to detect hold key
1472 static const int HoldKeyAdditionalDelay = 50; /// Time to detect additional hold key
1473 
1474 static unsigned LastIKey;                    /// last key handled
1475 static unsigned LastIKeyChar;                /// last keychar handled
1476 static unsigned LastKeyTicks;                /// Ticks of last key
1477 static unsigned DoubleKey;                   /// last key pressed
1478 
1479 /**
1480 **  Handle keyboard key press.
1481 **
1482 **  @param callbacks  Callback structure for events.
1483 **  @param ticks      Denotes time-stamp of video-system
1484 **  @param ikey       Key scancode.
1485 **  @param ikeychar   Character code.
1486 */
1487 void InputKeyButtonPress(const EventCallback &callbacks,
1488 						 unsigned ticks, unsigned ikey, unsigned ikeychar)
1489 {
1490 	if (!LastIKey && DoubleKey == ikey && ticks < LastKeyTicks + DoubleClickDelay) {
1491 		KeyModifiers |= ModifierDoublePress;
1492 	}
1493 	DoubleKey = ikey;
1494 	LastIKey = ikey;
1495 	LastIKeyChar = ikeychar;
1496 	LastKeyTicks = ticks;
1497 	callbacks.KeyPressed(ikey, ikeychar);
1498 	KeyModifiers &= ~ModifierDoublePress;
1499 }
1500 
1501 /**
1502 **  Handle keyboard key release.
1503 **
1504 **  @param callbacks  Callback structure for events.
1505 **  @param ticks      Denotes time-stamp of video-system
1506 **  @param ikey       Key scancode.
1507 **  @param ikeychar   Character code.
1508 */
1509 void InputKeyButtonRelease(const EventCallback &callbacks,
1510 						   unsigned ticks, unsigned ikey, unsigned ikeychar)
1511 {
1512 	if (ikey == LastIKey) {
1513 		LastIKey = 0;
1514 	}
1515 	LastKeyTicks = ticks;
1516 	callbacks.KeyReleased(ikey, ikeychar);
1517 }
1518 
1519 /**
1520 **  Called each frame to handle keyboard timeouts.
1521 **
1522 **  @param callbacks  Callback structure for events.
1523 **  @param ticks      Denotes time-stamp of video-system
1524 */
1525 void InputKeyTimeout(const EventCallback &callbacks, unsigned ticks)
1526 {
1527 	if (LastIKey && ticks > LastKeyTicks + HoldKeyDelay) {
1528 		LastKeyTicks = ticks - (HoldKeyDelay - HoldKeyAdditionalDelay);
1529 		callbacks.KeyRepeated(LastIKey, LastIKeyChar);
1530 	}
1531 }
1532 
1533 /**
1534 **  Get double click delay
1535 */
1536 int GetDoubleClickDelay()
1537 {
1538 	return DoubleClickDelay;
1539 }
1540 
1541 /**
1542 **  Set double click delay
1543 **
1544 **  @param delay  Double click delay
1545 */
1546 void SetDoubleClickDelay(int delay)
1547 {
1548 	DoubleClickDelay = delay;
1549 }
1550 
1551 /**
1552 **  Get hold click delay
1553 */
1554 int GetHoldClickDelay()
1555 {
1556 	return HoldClickDelay;
1557 }
1558 
1559 /**
1560 **  Set hold click delay
1561 **
1562 **  @param delay  Hold click delay
1563 */
1564 void SetHoldClickDelay(int delay)
1565 {
1566 	HoldClickDelay = delay;
1567 }
1568 
1569 //@}
1570