Fix 03cc0d6: Mark level crossings dirty when removing road from them, not from bridge...
[openttd-github.git] / src / console_gui.cpp
blobeaef1e4efa28bab28e48d41be354d9f8e93cbddf
1 /*
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/>.
6 */
8 /** @file console_gui.cpp Handling the GUI of the in-game console. */
10 #include "stdafx.h"
11 #include "textbuf_type.h"
12 #include "window_gui.h"
13 #include "console_gui.h"
14 #include "console_internal.h"
15 #include "guitimer_func.h"
16 #include "window_func.h"
17 #include "string_func.h"
18 #include "strings_func.h"
19 #include "gfx_func.h"
20 #include "settings_type.h"
21 #include "console_func.h"
22 #include "rev.h"
23 #include "video/video_driver.hpp"
24 #include <deque>
25 #include <string>
27 #include "widgets/console_widget.h"
29 #include "table/strings.h"
31 #include "safeguards.h"
33 static const uint ICON_HISTORY_SIZE = 20;
34 static const uint ICON_LINE_SPACING = 2;
35 static const uint ICON_RIGHT_BORDERWIDTH = 10;
36 static const uint ICON_BOTTOM_BORDERWIDTH = 12;
38 /**
39 * Container for a single line of console output
41 struct IConsoleLine {
42 std::string buffer; ///< The data to store.
43 TextColour colour; ///< The colour of the line.
44 uint16 time; ///< The amount of time the line is in the backlog.
46 IConsoleLine() : buffer(), colour(TC_BEGIN), time(0)
51 /**
52 * Initialize the console line.
53 * @param buffer the data to print.
54 * @param colour the colour of the line.
56 IConsoleLine(std::string buffer, TextColour colour) :
57 buffer(std::move(buffer)),
58 colour(colour),
59 time(0)
63 ~IConsoleLine()
68 /** The console backlog buffer. Item index 0 is the newest line. */
69 static std::deque<IConsoleLine> _iconsole_buffer;
71 static bool TruncateBuffer();
74 /* ** main console cmd buffer ** */
75 static Textbuf _iconsole_cmdline(ICON_CMDLN_SIZE);
76 static char *_iconsole_history[ICON_HISTORY_SIZE];
77 static int _iconsole_historypos;
78 IConsoleModes _iconsole_mode;
80 /* *************** *
81 * end of header *
82 * *************** */
84 static void IConsoleClearCommand()
86 memset(_iconsole_cmdline.buf, 0, ICON_CMDLN_SIZE);
87 _iconsole_cmdline.chars = _iconsole_cmdline.bytes = 1; // only terminating zero
88 _iconsole_cmdline.pixels = 0;
89 _iconsole_cmdline.caretpos = 0;
90 _iconsole_cmdline.caretxoffs = 0;
91 SetWindowDirty(WC_CONSOLE, 0);
94 static inline void IConsoleResetHistoryPos()
96 _iconsole_historypos = -1;
100 static const char *IConsoleHistoryAdd(const char *cmd);
101 static void IConsoleHistoryNavigate(int direction);
103 static const struct NWidgetPart _nested_console_window_widgets[] = {
104 NWidget(WWT_EMPTY, INVALID_COLOUR, WID_C_BACKGROUND), SetResize(1, 1),
107 static WindowDesc _console_window_desc(
108 WDP_MANUAL, nullptr, 0, 0,
109 WC_CONSOLE, WC_NONE,
111 _nested_console_window_widgets, lengthof(_nested_console_window_widgets)
114 struct IConsoleWindow : Window
116 static size_t scroll;
117 int line_height; ///< Height of one line of text in the console.
118 int line_offset;
119 GUITimer truncate_timer;
121 IConsoleWindow() : Window(&_console_window_desc)
123 _iconsole_mode = ICONSOLE_OPENED;
124 this->line_height = FONT_HEIGHT_NORMAL + ICON_LINE_SPACING;
125 this->line_offset = GetStringBoundingBox("] ").width + 5;
127 this->InitNested(0);
128 this->truncate_timer.SetInterval(3000);
129 ResizeWindow(this, _screen.width, _screen.height / 3);
132 void Close() override
134 _iconsole_mode = ICONSOLE_CLOSED;
135 VideoDriver::GetInstance()->EditBoxLostFocus();
136 this->Window::Close();
140 * Scroll the content of the console.
141 * @param amount Number of lines to scroll back.
143 void Scroll(int amount)
145 if (amount < 0) {
146 size_t namount = (size_t) -amount;
147 IConsoleWindow::scroll = (namount > IConsoleWindow::scroll) ? 0 : IConsoleWindow::scroll - namount;
148 } else {
149 assert(this->height >= 0 && this->line_height > 0);
150 size_t visible_lines = (size_t)(this->height / this->line_height);
151 size_t max_scroll = (visible_lines > _iconsole_buffer.size()) ? 0 : _iconsole_buffer.size() + 1 - visible_lines;
152 IConsoleWindow::scroll = std::min<size_t>(IConsoleWindow::scroll + amount, max_scroll);
154 this->SetDirty();
157 void OnPaint() override
159 const int right = this->width - 5;
161 GfxFillRect(0, 0, this->width - 1, this->height - 1, PC_BLACK);
162 int ypos = this->height - this->line_height;
163 for (size_t line_index = IConsoleWindow::scroll; line_index < _iconsole_buffer.size(); line_index++) {
164 const IConsoleLine &print = _iconsole_buffer[line_index];
165 SetDParamStr(0, print.buffer);
166 ypos = DrawStringMultiLine(5, right, -this->line_height, ypos, STR_JUST_RAW_STRING, print.colour, SA_LEFT | SA_BOTTOM | SA_FORCE) - ICON_LINE_SPACING;
167 if (ypos < 0) break;
169 /* If the text is longer than the window, don't show the starting ']' */
170 int delta = this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH;
171 if (delta > 0) {
172 DrawString(5, right, this->height - this->line_height, "]", (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
173 delta = 0;
176 /* If we have a marked area, draw a background highlight. */
177 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);
179 DrawString(this->line_offset + delta, right, this->height - this->line_height, _iconsole_cmdline.buf, (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
181 if (_focused_window == this && _iconsole_cmdline.caret) {
182 DrawString(this->line_offset + delta + _iconsole_cmdline.caretxoffs, right, this->height - this->line_height, "_", TC_WHITE, SA_LEFT | SA_FORCE);
186 void OnRealtimeTick(uint delta_ms) override
188 if (this->truncate_timer.CountElapsed(delta_ms) == 0) return;
190 assert(this->height >= 0 && this->line_height > 0);
191 size_t visible_lines = (size_t)(this->height / this->line_height);
193 if (TruncateBuffer() && IConsoleWindow::scroll + visible_lines > _iconsole_buffer.size()) {
194 size_t max_scroll = (visible_lines > _iconsole_buffer.size()) ? 0 : _iconsole_buffer.size() + 1 - visible_lines;
195 IConsoleWindow::scroll = std::min<size_t>(IConsoleWindow::scroll, max_scroll);
196 this->SetDirty();
200 void OnMouseLoop() override
202 if (_iconsole_cmdline.HandleCaret()) this->SetDirty();
205 EventState OnKeyPress(WChar key, uint16 keycode) override
207 if (_focused_window != this) return ES_NOT_HANDLED;
209 const int scroll_height = (this->height / this->line_height) - 1;
210 switch (keycode) {
211 case WKC_UP:
212 IConsoleHistoryNavigate(1);
213 this->SetDirty();
214 break;
216 case WKC_DOWN:
217 IConsoleHistoryNavigate(-1);
218 this->SetDirty();
219 break;
221 case WKC_SHIFT | WKC_PAGEDOWN:
222 this->Scroll(-scroll_height);
223 break;
225 case WKC_SHIFT | WKC_PAGEUP:
226 this->Scroll(scroll_height);
227 break;
229 case WKC_SHIFT | WKC_DOWN:
230 this->Scroll(-1);
231 break;
233 case WKC_SHIFT | WKC_UP:
234 this->Scroll(1);
235 break;
237 case WKC_BACKQUOTE:
238 IConsoleSwitch();
239 break;
241 case WKC_RETURN: case WKC_NUM_ENTER: {
242 /* We always want the ] at the left side; we always force these strings to be left
243 * aligned anyway. So enforce this in all cases by adding a left-to-right marker,
244 * otherwise it will be drawn at the wrong side with right-to-left texts. */
245 IConsolePrint(CC_COMMAND, LRM "] {}", _iconsole_cmdline.buf);
246 const char *cmd = IConsoleHistoryAdd(_iconsole_cmdline.buf);
247 IConsoleClearCommand();
249 if (cmd != nullptr) IConsoleCmdExec(cmd);
250 break;
253 case WKC_CTRL | WKC_RETURN:
254 _iconsole_mode = (_iconsole_mode == ICONSOLE_FULL) ? ICONSOLE_OPENED : ICONSOLE_FULL;
255 IConsoleResize(this);
256 MarkWholeScreenDirty();
257 break;
259 case (WKC_CTRL | 'L'):
260 IConsoleCmdExec("clear");
261 break;
263 default:
264 if (_iconsole_cmdline.HandleKeyPress(key, keycode) != HKPR_NOT_HANDLED) {
265 IConsoleWindow::scroll = 0;
266 IConsoleResetHistoryPos();
267 this->SetDirty();
268 } else {
269 return ES_NOT_HANDLED;
271 break;
273 return ES_HANDLED;
276 void InsertTextString(int wid, const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end) override
278 if (_iconsole_cmdline.InsertString(str, marked, caret, insert_location, replacement_end)) {
279 IConsoleWindow::scroll = 0;
280 IConsoleResetHistoryPos();
281 this->SetDirty();
285 const char *GetFocusedText() const override
287 return _iconsole_cmdline.buf;
290 const char *GetCaret() const override
292 return _iconsole_cmdline.buf + _iconsole_cmdline.caretpos;
295 const char *GetMarkedText(size_t *length) const override
297 if (_iconsole_cmdline.markend == 0) return nullptr;
299 *length = _iconsole_cmdline.markend - _iconsole_cmdline.markpos;
300 return _iconsole_cmdline.buf + _iconsole_cmdline.markpos;
303 Point GetCaretPosition() const override
305 int delta = std::min<int>(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
306 Point pt = {this->line_offset + delta + _iconsole_cmdline.caretxoffs, this->height - this->line_height};
308 return pt;
311 Rect GetTextBoundingRect(const char *from, const char *to) const override
313 int delta = std::min<int>(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
315 Point p1 = GetCharPosInString(_iconsole_cmdline.buf, from, FS_NORMAL);
316 Point p2 = from != to ? GetCharPosInString(_iconsole_cmdline.buf, from) : p1;
318 Rect r = {this->line_offset + delta + p1.x, this->height - this->line_height, this->line_offset + delta + p2.x, this->height};
319 return r;
322 const char *GetTextCharacterAtPosition(const Point &pt) const override
324 int delta = std::min<int>(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
326 if (!IsInsideMM(pt.y, this->height - this->line_height, this->height)) return nullptr;
328 return GetCharAtPosition(_iconsole_cmdline.buf, pt.x - delta);
331 void OnMouseWheel(int wheel) override
333 this->Scroll(-wheel);
336 void OnFocus() override
338 VideoDriver::GetInstance()->EditBoxGainedFocus();
341 void OnFocusLost() override
343 VideoDriver::GetInstance()->EditBoxLostFocus();
347 size_t IConsoleWindow::scroll = 0;
349 void IConsoleGUIInit()
351 IConsoleResetHistoryPos();
352 _iconsole_mode = ICONSOLE_CLOSED;
354 IConsoleClearBuffer();
355 memset(_iconsole_history, 0, sizeof(_iconsole_history));
357 IConsolePrint(TC_LIGHT_BLUE, "OpenTTD Game Console Revision 7 - {}", _openttd_revision);
358 IConsolePrint(CC_WHITE, "------------------------------------");
359 IConsolePrint(CC_WHITE, "use \"help\" for more information.");
360 IConsolePrint(CC_WHITE, "");
361 IConsoleClearCommand();
364 void IConsoleClearBuffer()
366 _iconsole_buffer.clear();
369 void IConsoleGUIFree()
371 IConsoleClearBuffer();
374 /** Change the size of the in-game console window after the screen size changed, or the window state changed. */
375 void IConsoleResize(Window *w)
377 switch (_iconsole_mode) {
378 case ICONSOLE_OPENED:
379 w->height = _screen.height / 3;
380 w->width = _screen.width;
381 break;
382 case ICONSOLE_FULL:
383 w->height = _screen.height - ICON_BOTTOM_BORDERWIDTH;
384 w->width = _screen.width;
385 break;
386 default: return;
389 MarkWholeScreenDirty();
392 /** Toggle in-game console between opened and closed. */
393 void IConsoleSwitch()
395 switch (_iconsole_mode) {
396 case ICONSOLE_CLOSED:
397 new IConsoleWindow();
398 break;
400 case ICONSOLE_OPENED: case ICONSOLE_FULL:
401 CloseWindowById(WC_CONSOLE, 0);
402 break;
405 MarkWholeScreenDirty();
408 /** Close the in-game console. */
409 void IConsoleClose()
411 if (_iconsole_mode == ICONSOLE_OPENED) IConsoleSwitch();
415 * Add the entered line into the history so you can look it back
416 * scroll, etc. Put it to the beginning as it is the latest text
417 * @param cmd Text to be entered into the 'history'
418 * @return the command to execute
420 static const char *IConsoleHistoryAdd(const char *cmd)
422 /* Strip all spaces at the begin */
423 while (IsWhitespace(*cmd)) cmd++;
425 /* Do not put empty command in history */
426 if (StrEmpty(cmd)) return nullptr;
428 /* Do not put in history if command is same as previous */
429 if (_iconsole_history[0] == nullptr || strcmp(_iconsole_history[0], cmd) != 0) {
430 free(_iconsole_history[ICON_HISTORY_SIZE - 1]);
431 memmove(&_iconsole_history[1], &_iconsole_history[0], sizeof(_iconsole_history[0]) * (ICON_HISTORY_SIZE - 1));
432 _iconsole_history[0] = stredup(cmd);
435 /* Reset the history position */
436 IConsoleResetHistoryPos();
437 return _iconsole_history[0];
441 * Navigate Up/Down in the history of typed commands
442 * @param direction Go further back in history (+1), go to recently typed commands (-1)
444 static void IConsoleHistoryNavigate(int direction)
446 if (_iconsole_history[0] == nullptr) return; // Empty history
447 _iconsole_historypos = Clamp(_iconsole_historypos + direction, -1, ICON_HISTORY_SIZE - 1);
449 if (direction > 0 && _iconsole_history[_iconsole_historypos] == nullptr) _iconsole_historypos--;
451 if (_iconsole_historypos == -1) {
452 _iconsole_cmdline.DeleteAll();
453 } else {
454 _iconsole_cmdline.Assign(_iconsole_history[_iconsole_historypos]);
459 * Handle the printing of text entered into the console or redirected there
460 * by any other means. Text can be redirected to other clients in a network game
461 * as well as to a logfile. If the network server is a dedicated server, all activities
462 * are also logged. All lines to print are added to a temporary buffer which can be
463 * used as a history to print them onscreen
464 * @param colour_code the colour of the command. Red in case of errors, etc.
465 * @param str the message entered or output on the console (notice, error, etc.)
467 void IConsoleGUIPrint(TextColour colour_code, char *str)
469 _iconsole_buffer.push_front(IConsoleLine(str, colour_code));
470 SetWindowDirty(WC_CONSOLE, 0);
474 * Remove old lines from the backlog buffer.
475 * The buffer is limited by a maximum size and a minimum age. Every time truncation runs,
476 * all lines in the buffer are aged by one. When a line exceeds both the maximum position
477 * and also the maximum age, it gets removed.
478 * @return true if any lines were removed
480 static bool TruncateBuffer()
482 bool need_truncation = false;
483 size_t count = 0;
484 for (IConsoleLine &line : _iconsole_buffer) {
485 count++;
486 line.time++;
487 if (line.time > _settings_client.gui.console_backlog_timeout && count > _settings_client.gui.console_backlog_length) {
488 /* Any messages after this are older and need to be truncated */
489 need_truncation = true;
490 break;
494 if (need_truncation) {
495 _iconsole_buffer.resize(count - 1);
498 return need_truncation;
503 * Check whether the given TextColour is valid for console usage.
504 * @param c The text colour to compare to.
505 * @return true iff the TextColour is valid for console usage.
507 bool IsValidConsoleColour(TextColour c)
509 /* A normal text colour is used. */
510 if (!(c & TC_IS_PALETTE_COLOUR)) return TC_BEGIN <= c && c < TC_END;
512 /* A text colour from the palette is used; must be the company
513 * colour gradient, so it must be one of those. */
514 c &= ~TC_IS_PALETTE_COLOUR;
515 for (uint i = COLOUR_BEGIN; i < COLOUR_END; i++) {
516 if (_colour_gradient[i][4] == c) return true;
519 return false;