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