2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file console_gui.cpp Handling the GUI of the in-game console. */
11 #include "textbuf_type.h"
12 #include "window_gui.h"
13 #include "console_gui.h"
14 #include "console_internal.h"
15 #include "window_func.h"
16 #include "string_func.h"
17 #include "strings_func.h"
19 #include "settings_type.h"
20 #include "console_func.h"
22 #include "video/video_driver.hpp"
24 #include "widgets/console_widget.h"
26 #include "table/strings.h"
28 #include "safeguards.h"
30 static const uint ICON_HISTORY_SIZE
= 20;
31 static const uint ICON_LINE_SPACING
= 2;
32 static const uint ICON_RIGHT_BORDERWIDTH
= 10;
33 static const uint ICON_BOTTOM_BORDERWIDTH
= 12;
36 * Container for a single line of console output
39 static IConsoleLine
*front
; ///< The front of the console backlog buffer
40 static int size
; ///< The amount of items in the backlog
42 IConsoleLine
*previous
; ///< The previous console message.
43 char *buffer
; ///< The data to store.
44 TextColour colour
; ///< The colour of the line.
45 uint16 time
; ///< The amount of time the line is in the backlog.
48 * Initialize the console line.
49 * @param buffer the data to print.
50 * @param colour the colour of the line.
52 IConsoleLine(char *buffer
, TextColour colour
) :
53 previous(IConsoleLine::front
),
58 IConsoleLine::front
= this;
63 * Clear this console line and any further ones.
74 * Get the index-ed item in the list.
76 static const IConsoleLine
*Get(uint index
)
78 const IConsoleLine
*item
= IConsoleLine::front
;
79 while (index
!= 0 && item
!= nullptr) {
81 item
= item
->previous
;
88 * Truncate the list removing everything older than/more than the amount
89 * as specified in the config file.
90 * As a side effect also increase the time the other lines have been in
92 * @return true if and only if items got removed.
94 static bool Truncate()
96 IConsoleLine
*cur
= IConsoleLine::front
;
97 if (cur
== nullptr) return false;
100 for (IConsoleLine
*item
= cur
->previous
; item
!= nullptr; count
++, cur
= item
, item
= item
->previous
) {
101 if (item
->time
> _settings_client
.gui
.console_backlog_timeout
&&
102 count
> _settings_client
.gui
.console_backlog_length
) {
104 cur
->previous
= nullptr;
108 if (item
->time
!= MAX_UVALUE(uint16
)) item
->time
++;
115 * Reset the complete console line backlog.
119 delete IConsoleLine::front
;
120 IConsoleLine::front
= nullptr;
121 IConsoleLine::size
= 0;
125 /* static */ IConsoleLine
*IConsoleLine::front
= nullptr;
126 /* static */ int IConsoleLine::size
= 0;
129 /* ** main console cmd buffer ** */
130 static Textbuf
_iconsole_cmdline(ICON_CMDLN_SIZE
);
131 static char *_iconsole_history
[ICON_HISTORY_SIZE
];
132 static int _iconsole_historypos
;
133 IConsoleModes _iconsole_mode
;
139 static void IConsoleClearCommand()
141 memset(_iconsole_cmdline
.buf
, 0, ICON_CMDLN_SIZE
);
142 _iconsole_cmdline
.chars
= _iconsole_cmdline
.bytes
= 1; // only terminating zero
143 _iconsole_cmdline
.pixels
= 0;
144 _iconsole_cmdline
.caretpos
= 0;
145 _iconsole_cmdline
.caretxoffs
= 0;
146 SetWindowDirty(WC_CONSOLE
, 0);
149 static inline void IConsoleResetHistoryPos()
151 _iconsole_historypos
= -1;
155 static const char *IConsoleHistoryAdd(const char *cmd
);
156 static void IConsoleHistoryNavigate(int direction
);
158 static const struct NWidgetPart _nested_console_window_widgets
[] = {
159 NWidget(WWT_EMPTY
, INVALID_COLOUR
, WID_C_BACKGROUND
), SetResize(1, 1),
162 static WindowDesc
_console_window_desc(
163 WDP_MANUAL
, nullptr, 0, 0,
166 _nested_console_window_widgets
, lengthof(_nested_console_window_widgets
)
169 struct IConsoleWindow
: Window
172 int line_height
; ///< Height of one line of text in the console.
175 IConsoleWindow() : Window(&_console_window_desc
)
177 _iconsole_mode
= ICONSOLE_OPENED
;
178 this->line_height
= FONT_HEIGHT_NORMAL
+ ICON_LINE_SPACING
;
179 this->line_offset
= GetStringBoundingBox("] ").width
+ 5;
182 ResizeWindow(this, _screen
.width
, _screen
.height
/ 3);
187 _iconsole_mode
= ICONSOLE_CLOSED
;
188 VideoDriver::GetInstance()->EditBoxLostFocus();
192 * Scroll the content of the console.
193 * @param amount Number of lines to scroll back.
195 void Scroll(int amount
)
197 int max_scroll
= max
<int>(0, IConsoleLine::size
+ 1 - this->height
/ this->line_height
);
198 IConsoleWindow::scroll
= Clamp
<int>(IConsoleWindow::scroll
+ amount
, 0, max_scroll
);
202 void OnPaint() override
204 const int right
= this->width
- 5;
206 GfxFillRect(0, 0, this->width
- 1, this->height
- 1, PC_BLACK
);
207 int ypos
= this->height
- this->line_height
;
208 for (const IConsoleLine
*print
= IConsoleLine::Get(IConsoleWindow::scroll
); print
!= nullptr; print
= print
->previous
) {
209 SetDParamStr(0, print
->buffer
);
210 ypos
= DrawStringMultiLine(5, right
, -this->line_height
, ypos
, STR_JUST_RAW_STRING
, print
->colour
, SA_LEFT
| SA_BOTTOM
| SA_FORCE
) - ICON_LINE_SPACING
;
213 /* If the text is longer than the window, don't show the starting ']' */
214 int delta
= this->width
- this->line_offset
- _iconsole_cmdline
.pixels
- ICON_RIGHT_BORDERWIDTH
;
216 DrawString(5, right
, this->height
- this->line_height
, "]", (TextColour
)CC_COMMAND
, SA_LEFT
| SA_FORCE
);
220 /* If we have a marked area, draw a background highlight. */
221 if (_iconsole_cmdline
.marklength
!= 0) GfxFillRect(this->line_offset
+ delta
+ _iconsole_cmdline
.markxoffs
, this->height
- this->line_height
, this->line_offset
+ delta
+ _iconsole_cmdline
.markxoffs
+ _iconsole_cmdline
.marklength
, this->height
- 1, PC_DARK_RED
);
223 DrawString(this->line_offset
+ delta
, right
, this->height
- this->line_height
, _iconsole_cmdline
.buf
, (TextColour
)CC_COMMAND
, SA_LEFT
| SA_FORCE
);
225 if (_focused_window
== this && _iconsole_cmdline
.caret
) {
226 DrawString(this->line_offset
+ delta
+ _iconsole_cmdline
.caretxoffs
, right
, this->height
- this->line_height
, "_", TC_WHITE
, SA_LEFT
| SA_FORCE
);
230 void OnHundredthTick() override
232 if (IConsoleLine::Truncate() &&
233 (IConsoleWindow::scroll
> IConsoleLine::size
)) {
234 IConsoleWindow::scroll
= max(0, IConsoleLine::size
- (this->height
/ this->line_height
) + 1);
239 void OnMouseLoop() override
241 if (_iconsole_cmdline
.HandleCaret()) this->SetDirty();
244 EventState
OnKeyPress(WChar key
, uint16 keycode
) override
246 if (_focused_window
!= this) return ES_NOT_HANDLED
;
248 const int scroll_height
= (this->height
/ this->line_height
) - 1;
251 IConsoleHistoryNavigate(1);
256 IConsoleHistoryNavigate(-1);
260 case WKC_SHIFT
| WKC_PAGEDOWN
:
261 this->Scroll(-scroll_height
);
264 case WKC_SHIFT
| WKC_PAGEUP
:
265 this->Scroll(scroll_height
);
268 case WKC_SHIFT
| WKC_DOWN
:
272 case WKC_SHIFT
| WKC_UP
:
280 case WKC_RETURN
: case WKC_NUM_ENTER
: {
281 /* We always want the ] at the left side; we always force these strings to be left
282 * aligned anyway. So enforce this in all cases by adding a left-to-right marker,
283 * otherwise it will be drawn at the wrong side with right-to-left texts. */
284 IConsolePrintF(CC_COMMAND
, LRM
"] %s", _iconsole_cmdline
.buf
);
285 const char *cmd
= IConsoleHistoryAdd(_iconsole_cmdline
.buf
);
286 IConsoleClearCommand();
288 if (cmd
!= nullptr) IConsoleCmdExec(cmd
);
292 case WKC_CTRL
| WKC_RETURN
:
293 _iconsole_mode
= (_iconsole_mode
== ICONSOLE_FULL
) ? ICONSOLE_OPENED
: ICONSOLE_FULL
;
294 IConsoleResize(this);
295 MarkWholeScreenDirty();
298 case (WKC_CTRL
| 'L'):
299 IConsoleCmdExec("clear");
303 if (_iconsole_cmdline
.HandleKeyPress(key
, keycode
) != HKPR_NOT_HANDLED
) {
304 IConsoleWindow::scroll
= 0;
305 IConsoleResetHistoryPos();
308 return ES_NOT_HANDLED
;
315 void InsertTextString(int wid
, const char *str
, bool marked
, const char *caret
, const char *insert_location
, const char *replacement_end
) override
317 if (_iconsole_cmdline
.InsertString(str
, marked
, caret
, insert_location
, replacement_end
)) {
318 IConsoleWindow::scroll
= 0;
319 IConsoleResetHistoryPos();
324 const char *GetFocusedText() const override
326 return _iconsole_cmdline
.buf
;
329 const char *GetCaret() const override
331 return _iconsole_cmdline
.buf
+ _iconsole_cmdline
.caretpos
;
334 const char *GetMarkedText(size_t *length
) const override
336 if (_iconsole_cmdline
.markend
== 0) return nullptr;
338 *length
= _iconsole_cmdline
.markend
- _iconsole_cmdline
.markpos
;
339 return _iconsole_cmdline
.buf
+ _iconsole_cmdline
.markpos
;
342 Point
GetCaretPosition() const override
344 int delta
= min(this->width
- this->line_offset
- _iconsole_cmdline
.pixels
- ICON_RIGHT_BORDERWIDTH
, 0);
345 Point pt
= {this->line_offset
+ delta
+ _iconsole_cmdline
.caretxoffs
, this->height
- this->line_height
};
350 Rect
GetTextBoundingRect(const char *from
, const char *to
) const override
352 int delta
= min(this->width
- this->line_offset
- _iconsole_cmdline
.pixels
- ICON_RIGHT_BORDERWIDTH
, 0);
354 Point p1
= GetCharPosInString(_iconsole_cmdline
.buf
, from
, FS_NORMAL
);
355 Point p2
= from
!= to
? GetCharPosInString(_iconsole_cmdline
.buf
, from
) : p1
;
357 Rect r
= {this->line_offset
+ delta
+ p1
.x
, this->height
- this->line_height
, this->line_offset
+ delta
+ p2
.x
, this->height
};
361 const char *GetTextCharacterAtPosition(const Point
&pt
) const override
363 int delta
= min(this->width
- this->line_offset
- _iconsole_cmdline
.pixels
- ICON_RIGHT_BORDERWIDTH
, 0);
365 if (!IsInsideMM(pt
.y
, this->height
- this->line_height
, this->height
)) return nullptr;
367 return GetCharAtPosition(_iconsole_cmdline
.buf
, pt
.x
- delta
);
370 void OnMouseWheel(int wheel
) override
372 this->Scroll(-wheel
);
375 void OnFocus() override
377 VideoDriver::GetInstance()->EditBoxGainedFocus();
380 void OnFocusLost() override
382 VideoDriver::GetInstance()->EditBoxLostFocus();
386 int IConsoleWindow::scroll
= 0;
388 void IConsoleGUIInit()
390 IConsoleResetHistoryPos();
391 _iconsole_mode
= ICONSOLE_CLOSED
;
393 IConsoleLine::Reset();
394 memset(_iconsole_history
, 0, sizeof(_iconsole_history
));
396 IConsolePrintF(CC_WARNING
, "OpenTTD Game Console Revision 7 - %s", _openttd_revision
);
397 IConsolePrint(CC_WHITE
, "------------------------------------");
398 IConsolePrint(CC_WHITE
, "use \"help\" for more information");
399 IConsolePrint(CC_WHITE
, "");
400 IConsoleClearCommand();
403 void IConsoleClearBuffer()
405 IConsoleLine::Reset();
408 void IConsoleGUIFree()
410 IConsoleClearBuffer();
413 /** Change the size of the in-game console window after the screen size changed, or the window state changed. */
414 void IConsoleResize(Window
*w
)
416 switch (_iconsole_mode
) {
417 case ICONSOLE_OPENED
:
418 w
->height
= _screen
.height
/ 3;
419 w
->width
= _screen
.width
;
422 w
->height
= _screen
.height
- ICON_BOTTOM_BORDERWIDTH
;
423 w
->width
= _screen
.width
;
428 MarkWholeScreenDirty();
431 /** Toggle in-game console between opened and closed. */
432 void IConsoleSwitch()
434 switch (_iconsole_mode
) {
435 case ICONSOLE_CLOSED
:
436 new IConsoleWindow();
439 case ICONSOLE_OPENED
: case ICONSOLE_FULL
:
440 DeleteWindowById(WC_CONSOLE
, 0);
444 MarkWholeScreenDirty();
447 /** Close the in-game console. */
450 if (_iconsole_mode
== ICONSOLE_OPENED
) IConsoleSwitch();
454 * Add the entered line into the history so you can look it back
455 * scroll, etc. Put it to the beginning as it is the latest text
456 * @param cmd Text to be entered into the 'history'
457 * @return the command to execute
459 static const char *IConsoleHistoryAdd(const char *cmd
)
461 /* Strip all spaces at the begin */
462 while (IsWhitespace(*cmd
)) cmd
++;
464 /* Do not put empty command in history */
465 if (StrEmpty(cmd
)) return nullptr;
467 /* Do not put in history if command is same as previous */
468 if (_iconsole_history
[0] == nullptr || strcmp(_iconsole_history
[0], cmd
) != 0) {
469 free(_iconsole_history
[ICON_HISTORY_SIZE
- 1]);
470 memmove(&_iconsole_history
[1], &_iconsole_history
[0], sizeof(_iconsole_history
[0]) * (ICON_HISTORY_SIZE
- 1));
471 _iconsole_history
[0] = stredup(cmd
);
474 /* Reset the history position */
475 IConsoleResetHistoryPos();
476 return _iconsole_history
[0];
480 * Navigate Up/Down in the history of typed commands
481 * @param direction Go further back in history (+1), go to recently typed commands (-1)
483 static void IConsoleHistoryNavigate(int direction
)
485 if (_iconsole_history
[0] == nullptr) return; // Empty history
486 _iconsole_historypos
= Clamp(_iconsole_historypos
+ direction
, -1, ICON_HISTORY_SIZE
- 1);
488 if (direction
> 0 && _iconsole_history
[_iconsole_historypos
] == nullptr) _iconsole_historypos
--;
490 if (_iconsole_historypos
== -1) {
491 _iconsole_cmdline
.DeleteAll();
493 _iconsole_cmdline
.Assign(_iconsole_history
[_iconsole_historypos
]);
498 * Handle the printing of text entered into the console or redirected there
499 * by any other means. Text can be redirected to other clients in a network game
500 * as well as to a logfile. If the network server is a dedicated server, all activities
501 * are also logged. All lines to print are added to a temporary buffer which can be
502 * used as a history to print them onscreen
503 * @param colour_code the colour of the command. Red in case of errors, etc.
504 * @param str the message entered or output on the console (notice, error, etc.)
506 void IConsoleGUIPrint(TextColour colour_code
, char *str
)
508 new IConsoleLine(str
, colour_code
);
509 SetWindowDirty(WC_CONSOLE
, 0);
514 * Check whether the given TextColour is valid for console usage.
515 * @param c The text colour to compare to.
516 * @return true iff the TextColour is valid for console usage.
518 bool IsValidConsoleColour(TextColour c
)
520 /* A normal text colour is used. */
521 if (!(c
& TC_IS_PALETTE_COLOUR
)) return TC_BEGIN
<= c
&& c
< TC_END
;
523 /* A text colour from the palette is used; must be the company
524 * colour gradient, so it must be one of those. */
525 c
&= ~TC_IS_PALETTE_COLOUR
;
526 for (uint i
= COLOUR_BEGIN
; i
< COLOUR_END
; i
++) {
527 if (_colour_gradient
[i
][4] == c
) return true;