rubidium@9339: /* $Id$ */ rubidium@9339: rubidium@9339: /** @file console.cpp Handling of the in-game console. */ rubidium@9339: rubidium@9339: #include "stdafx.h" rubidium@9339: #include "openttd.h" rubidium@9339: #include "textbuf_gui.h" rubidium@9339: #include "window_gui.h" rubidium@9339: #include "console_gui.h" rubidium@9339: #include rubidium@9339: #include rubidium@9339: #include "console_internal.h" rubidium@9339: #include "window_func.h" rubidium@9339: #include "string_func.h" rubidium@9339: #include "gfx_func.h" rubidium@9339: #include "core/math_func.hpp" rubidium@9906: #include "settings_type.h" rubidium@9339: #include "rev.h" rubidium@9339: rubidium@9339: #include "table/strings.h" rubidium@9339: rubidium@9906: enum { rubidium@9906: ICON_HISTORY_SIZE = 20, rubidium@9906: ICON_LINE_HEIGHT = 12, rubidium@9906: ICON_RIGHT_BORDERWIDTH = 10, rubidium@9906: ICON_BOTTOM_BORDERWIDTH = 12, rubidium@9906: }; rubidium@9339: rubidium@9906: /** rubidium@9906: * Container for a single line of console output rubidium@9906: */ rubidium@9906: struct IConsoleLine { rubidium@9906: static IConsoleLine *front; ///< The front of the console backlog buffer rubidium@9906: static int size; ///< The amount of items in the backlog rubidium@9339: rubidium@9906: IConsoleLine *previous; ///< The previous console message. rubidium@9906: char *buffer; ///< The data to store. rubidium@9906: uint16 colour; ///< The colour of the line. rubidium@9906: uint16 time; ///< The amount of time the line is in the backlog. rubidium@9906: rubidium@9906: /** rubidium@9906: * Initialize the console line. rubidium@9906: * @param buffer the data to print. rubidium@9906: * @param colour the colour of the line. rubidium@9906: */ rubidium@9906: IConsoleLine(char *buffer, uint16 colour) : rubidium@9906: previous(IConsoleLine::front), rubidium@9906: buffer(buffer), rubidium@9906: colour(colour), rubidium@9906: time(0) rubidium@9906: { rubidium@9906: IConsoleLine::front = this; rubidium@9906: IConsoleLine::size++; rubidium@9906: } rubidium@9906: rubidium@9906: /** rubidium@9906: * Clear this console line and any further ones. rubidium@9906: */ rubidium@9906: ~IConsoleLine() rubidium@9906: { rubidium@9906: IConsoleLine::size--; rubidium@9906: free(buffer); rubidium@9906: rubidium@9906: delete previous; rubidium@9906: } rubidium@9906: rubidium@9906: /** rubidium@9906: * Get the index-ed item in the list. rubidium@9906: */ rubidium@9906: static const IConsoleLine *Get(uint index) rubidium@9906: { rubidium@9906: const IConsoleLine *item = IConsoleLine::front; rubidium@9906: while (index != 0 && item != NULL) { rubidium@9906: index--; rubidium@9906: item = item->previous; rubidium@9906: } rubidium@9906: rubidium@9906: return item; rubidium@9906: } rubidium@9906: rubidium@9906: /** rubidium@9906: * Truncate the list removing everything older than/more than the amount rubidium@9906: * as specified in the config file. rubidium@9906: * As a side effect also increase the time the other lines have been in rubidium@9906: * the list. rubidium@9906: * @return true if and only if items got removed. rubidium@9906: */ rubidium@9906: static bool Truncate() rubidium@9906: { rubidium@9906: IConsoleLine *cur = IConsoleLine::front; rubidium@9906: if (cur == NULL) return false; rubidium@9906: rubidium@9906: int count = 1; rubidium@9906: for (IConsoleLine *item = cur->previous; item != NULL; count++, cur = item, item = item->previous) { rubidium@9906: if (item->time > _settings_client.gui.console_backlog_timeout && rubidium@9906: count > _settings_client.gui.console_backlog_length) { rubidium@9906: delete item; rubidium@9906: cur->previous = NULL; rubidium@9906: return true; rubidium@9906: } rubidium@9906: rubidium@9915: if (item->time != MAX_UVALUE(uint16)) item->time++; rubidium@9906: } rubidium@9906: rubidium@9906: return false; rubidium@9906: } rubidium@9906: rubidium@9906: /** rubidium@9906: * Reset the complete console line backlog. rubidium@9906: */ rubidium@9906: static void Reset() rubidium@9906: { rubidium@9906: delete IConsoleLine::front; rubidium@9906: IConsoleLine::front = NULL; rubidium@9906: IConsoleLine::size = 0; rubidium@9906: } rubidium@9906: }; rubidium@9906: rubidium@9906: /* static */ IConsoleLine *IConsoleLine::front = NULL; rubidium@9906: /* static */ int IConsoleLine::size = 0; rubidium@9906: rubidium@9339: rubidium@9339: /* ** main console cmd buffer ** */ rubidium@9906: static Textbuf _iconsole_cmdline; rubidium@9339: static char *_iconsole_history[ICON_HISTORY_SIZE]; rubidium@9339: static byte _iconsole_historypos; rubidium@9906: IConsoleModes _iconsole_mode; rubidium@9339: rubidium@9339: /* *************** * rubidium@9339: * end of header * rubidium@9339: * *************** */ rubidium@9339: rubidium@9339: static void IConsoleClearCommand() rubidium@9339: { rubidium@9339: memset(_iconsole_cmdline.buf, 0, ICON_CMDLN_SIZE); rubidium@9339: _iconsole_cmdline.length = 0; rubidium@9339: _iconsole_cmdline.width = 0; rubidium@9339: _iconsole_cmdline.caretpos = 0; rubidium@9339: _iconsole_cmdline.caretxoffs = 0; rubidium@9339: SetWindowDirty(FindWindowById(WC_CONSOLE, 0)); rubidium@9339: } rubidium@9339: rubidium@9339: static inline void IConsoleResetHistoryPos() {_iconsole_historypos = ICON_HISTORY_SIZE - 1;} rubidium@9339: rubidium@9339: rubidium@9339: static void IConsoleHistoryAdd(const char *cmd); rubidium@9339: static void IConsoleHistoryNavigate(int direction); rubidium@9339: rubidium@9339: struct IConsoleWindow : Window rubidium@9339: { rubidium@9906: static int scroll; rubidium@9339: rubidium@9339: IConsoleWindow(const WindowDesc *desc) : Window(desc) rubidium@9339: { rubidium@9339: _iconsole_mode = ICONSOLE_OPENED; rubidium@9339: SetBit(_no_scroll, SCROLL_CON); // override cursor arrows; the gamefield will not scroll rubidium@9339: rubidium@9339: this->height = _screen.height / 3; rubidium@9339: this->width = _screen.width; rubidium@9339: } rubidium@9339: rubidium@9339: ~IConsoleWindow() rubidium@9339: { rubidium@9339: _iconsole_mode = ICONSOLE_CLOSED; rubidium@9339: ClrBit(_no_scroll, SCROLL_CON); rubidium@9339: } rubidium@9339: rubidium@9339: virtual void OnPaint() rubidium@9339: { rubidium@9339: int max = (this->height / ICON_LINE_HEIGHT) - 1; rubidium@9906: const IConsoleLine *print = IConsoleLine::Get(IConsoleWindow::scroll); rubidium@9339: GfxFillRect(this->left, this->top, this->width, this->height - 1, 0); rubidium@9906: for (int i = 0; i < max && print != NULL; i++, print = print->previous) { rubidium@9906: DoDrawString(print->buffer, 5, rubidium@9906: this->height - (2 + i) * ICON_LINE_HEIGHT, print->colour); rubidium@9339: } rubidium@9339: /* If the text is longer than the window, don't show the starting ']' */ rubidium@9906: int delta = this->width - 10 - _iconsole_cmdline.width - ICON_RIGHT_BORDERWIDTH; rubidium@9339: if (delta > 0) { rubidium@9339: DoDrawString("]", 5, this->height - ICON_LINE_HEIGHT, CC_COMMAND); rubidium@9339: delta = 0; rubidium@9339: } rubidium@9339: rubidium@9339: DoDrawString(_iconsole_cmdline.buf, 10 + delta, this->height - ICON_LINE_HEIGHT, CC_COMMAND); rubidium@9339: rubidium@9339: if (_iconsole_cmdline.caret) { rubidium@9339: DoDrawString("_", 10 + delta + _iconsole_cmdline.caretxoffs, this->height - ICON_LINE_HEIGHT, TC_WHITE); rubidium@9339: } rubidium@9339: } rubidium@9339: rubidium@9906: virtual void OnHundredthTick() rubidium@9906: { rubidium@9906: if (IConsoleLine::Truncate() && rubidium@9906: (IConsoleWindow::scroll > IConsoleLine::size)) { rubidium@9906: IConsoleWindow::scroll = max(0, IConsoleLine::size - (this->height / ICON_LINE_HEIGHT) + 1); rubidium@9906: this->SetDirty(); rubidium@9906: } rubidium@9906: } rubidium@9906: rubidium@9339: virtual void OnMouseLoop() rubidium@9339: { rubidium@9339: if (HandleCaret(&_iconsole_cmdline)) this->SetDirty(); rubidium@9339: } rubidium@9339: rubidium@9339: virtual EventState OnKeyPress(uint16 key, uint16 keycode) rubidium@9339: { rubidium@9906: const int scroll_height = (this->height / ICON_LINE_HEIGHT) - 1; rubidium@9339: switch (keycode) { rubidium@9339: case WKC_UP: rubidium@9339: IConsoleHistoryNavigate(+1); rubidium@9339: this->SetDirty(); rubidium@9339: break; rubidium@9339: rubidium@9339: case WKC_DOWN: rubidium@9339: IConsoleHistoryNavigate(-1); rubidium@9339: this->SetDirty(); rubidium@9339: break; rubidium@9339: rubidium@9906: case WKC_SHIFT | WKC_PAGEDOWN: rubidium@9906: if (IConsoleWindow::scroll - scroll_height < 0) { rubidium@9339: IConsoleWindow::scroll = 0; rubidium@9339: } else { rubidium@9906: IConsoleWindow::scroll -= scroll_height; rubidium@9339: } rubidium@9339: this->SetDirty(); rubidium@9339: break; rubidium@9339: rubidium@9906: case WKC_SHIFT | WKC_PAGEUP: rubidium@9906: if (IConsoleWindow::scroll + scroll_height > IConsoleLine::size - scroll_height) { rubidium@9906: IConsoleWindow::scroll = IConsoleLine::size - scroll_height; rubidium@9339: } else { rubidium@9906: IConsoleWindow::scroll += scroll_height; rubidium@9339: } rubidium@9339: this->SetDirty(); rubidium@9339: break; rubidium@9339: rubidium@9906: case WKC_SHIFT | WKC_DOWN: rubidium@9339: if (IConsoleWindow::scroll <= 0) { rubidium@9339: IConsoleWindow::scroll = 0; rubidium@9339: } else { rubidium@9339: --IConsoleWindow::scroll; rubidium@9339: } rubidium@9339: this->SetDirty(); rubidium@9339: break; rubidium@9339: rubidium@9906: case WKC_SHIFT | WKC_UP: rubidium@9906: if (IConsoleWindow::scroll >= IConsoleLine::size) { rubidium@9906: IConsoleWindow::scroll = IConsoleLine::size; rubidium@9339: } else { rubidium@9339: ++IConsoleWindow::scroll; rubidium@9339: } rubidium@9339: this->SetDirty(); rubidium@9339: break; rubidium@9339: rubidium@9339: case WKC_BACKQUOTE: rubidium@9339: IConsoleSwitch(); rubidium@9339: break; rubidium@9339: rubidium@9339: case WKC_RETURN: case WKC_NUM_ENTER: rubidium@9339: IConsolePrintF(CC_COMMAND, "] %s", _iconsole_cmdline.buf); rubidium@9339: IConsoleHistoryAdd(_iconsole_cmdline.buf); rubidium@9339: rubidium@9339: IConsoleCmdExec(_iconsole_cmdline.buf); rubidium@9339: IConsoleClearCommand(); rubidium@9339: break; rubidium@9339: rubidium@9339: case WKC_CTRL | WKC_RETURN: rubidium@9339: _iconsole_mode = (_iconsole_mode == ICONSOLE_FULL) ? ICONSOLE_OPENED : ICONSOLE_FULL; rubidium@9339: IConsoleResize(this); rubidium@9339: MarkWholeScreenDirty(); rubidium@9339: break; rubidium@9339: rubidium@9339: case (WKC_CTRL | 'V'): rubidium@9339: if (InsertTextBufferClipboard(&_iconsole_cmdline)) { rubidium@9339: IConsoleResetHistoryPos(); rubidium@9339: this->SetDirty(); rubidium@9339: } rubidium@9339: break; rubidium@9339: rubidium@9339: case (WKC_CTRL | 'L'): rubidium@9339: IConsoleCmdExec("clear"); rubidium@9339: break; rubidium@9339: rubidium@9339: case (WKC_CTRL | 'U'): rubidium@9339: DeleteTextBufferAll(&_iconsole_cmdline); rubidium@9339: this->SetDirty(); rubidium@9339: break; rubidium@9339: rubidium@9339: case WKC_BACKSPACE: case WKC_DELETE: rubidium@9339: if (DeleteTextBufferChar(&_iconsole_cmdline, keycode)) { rubidium@9339: IConsoleResetHistoryPos(); rubidium@9339: this->SetDirty(); rubidium@9339: } rubidium@9339: break; rubidium@9339: rubidium@9339: case WKC_LEFT: case WKC_RIGHT: case WKC_END: case WKC_HOME: rubidium@9339: if (MoveTextBufferPos(&_iconsole_cmdline, keycode)) { rubidium@9339: IConsoleResetHistoryPos(); rubidium@9339: this->SetDirty(); rubidium@9339: } rubidium@9339: break; rubidium@9339: rubidium@9339: default: rubidium@9339: if (IsValidChar(key, CS_ALPHANUMERAL)) { rubidium@9906: IConsoleWindow::scroll = 0; rubidium@9339: InsertTextBufferChar(&_iconsole_cmdline, key); rubidium@9339: IConsoleResetHistoryPos(); rubidium@9339: this->SetDirty(); rubidium@9339: } else { rubidium@9339: return ES_NOT_HANDLED; rubidium@9339: } rubidium@9339: } rubidium@9339: return ES_HANDLED; rubidium@9339: } rubidium@9339: }; rubidium@9339: rubidium@9906: int IConsoleWindow::scroll = 0; rubidium@9339: rubidium@9339: static const Widget _iconsole_window_widgets[] = { rubidium@9339: {WIDGETS_END} rubidium@9339: }; rubidium@9339: rubidium@9339: static const WindowDesc _iconsole_window_desc = { rubidium@9339: 0, 0, 2, 2, 2, 2, rubidium@9339: WC_CONSOLE, WC_NONE, rubidium@9339: WDF_STD_TOOLTIPS | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS, rubidium@9339: _iconsole_window_widgets, rubidium@9339: }; rubidium@9339: rubidium@9339: void IConsoleGUIInit() rubidium@9339: { rubidium@9339: _iconsole_historypos = ICON_HISTORY_SIZE - 1; rubidium@9339: _iconsole_mode = ICONSOLE_CLOSED; rubidium@9339: rubidium@9906: IConsoleLine::Reset(); rubidium@9339: memset(_iconsole_history, 0, sizeof(_iconsole_history)); rubidium@9906: rubidium@9339: _iconsole_cmdline.buf = CallocT(ICON_CMDLN_SIZE); // create buffer and zero it rubidium@9339: _iconsole_cmdline.maxlength = ICON_CMDLN_SIZE; rubidium@9339: rubidium@9339: IConsolePrintF(CC_WARNING, "OpenTTD Game Console Revision 7 - %s", _openttd_revision); rubidium@9339: IConsolePrint(CC_WHITE, "------------------------------------"); rubidium@9339: IConsolePrint(CC_WHITE, "use \"help\" for more information"); rubidium@9339: IConsolePrint(CC_WHITE, ""); rubidium@9339: IConsoleClearCommand(); rubidium@9339: IConsoleHistoryAdd(""); rubidium@9339: } rubidium@9339: rubidium@9339: void IConsoleClearBuffer() rubidium@9339: { rubidium@9906: IConsoleLine::Reset(); rubidium@9339: } rubidium@9339: rubidium@9339: void IConsoleGUIFree() rubidium@9339: { rubidium@9339: free(_iconsole_cmdline.buf); rubidium@9339: IConsoleClearBuffer(); rubidium@9339: } rubidium@9339: rubidium@9339: void IConsoleResize(Window *w) rubidium@9339: { rubidium@9339: switch (_iconsole_mode) { rubidium@9339: case ICONSOLE_OPENED: rubidium@9339: w->height = _screen.height / 3; rubidium@9339: w->width = _screen.width; rubidium@9339: break; rubidium@9339: case ICONSOLE_FULL: rubidium@9339: w->height = _screen.height - ICON_BOTTOM_BORDERWIDTH; rubidium@9339: w->width = _screen.width; rubidium@9339: break; rubidium@9339: default: return; rubidium@9339: } rubidium@9339: rubidium@9339: MarkWholeScreenDirty(); rubidium@9339: } rubidium@9339: rubidium@9339: void IConsoleSwitch() rubidium@9339: { rubidium@9339: switch (_iconsole_mode) { rubidium@9339: case ICONSOLE_CLOSED: rubidium@9339: new IConsoleWindow(&_iconsole_window_desc); rubidium@9339: break; rubidium@9339: rubidium@9339: case ICONSOLE_OPENED: case ICONSOLE_FULL: rubidium@9339: DeleteWindowById(WC_CONSOLE, 0); rubidium@9339: break; rubidium@9339: } rubidium@9339: rubidium@9339: MarkWholeScreenDirty(); rubidium@9339: } rubidium@9339: rubidium@9339: void IConsoleClose() {if (_iconsole_mode == ICONSOLE_OPENED) IConsoleSwitch();} rubidium@9339: void IConsoleOpen() {if (_iconsole_mode == ICONSOLE_CLOSED) IConsoleSwitch();} rubidium@9339: rubidium@9339: /** rubidium@9339: * Add the entered line into the history so you can look it back rubidium@9339: * scroll, etc. Put it to the beginning as it is the latest text rubidium@9339: * @param cmd Text to be entered into the 'history' rubidium@9339: */ rubidium@9339: static void IConsoleHistoryAdd(const char *cmd) rubidium@9339: { rubidium@9339: free(_iconsole_history[ICON_HISTORY_SIZE - 1]); rubidium@9339: rubidium@9339: memmove(&_iconsole_history[1], &_iconsole_history[0], sizeof(_iconsole_history[0]) * (ICON_HISTORY_SIZE - 1)); rubidium@9339: _iconsole_history[0] = strdup(cmd); rubidium@9339: IConsoleResetHistoryPos(); rubidium@9339: } rubidium@9339: rubidium@9339: /** rubidium@9339: * Navigate Up/Down in the history of typed commands rubidium@9339: * @param direction Go further back in history (+1), go to recently typed commands (-1) rubidium@9339: */ rubidium@9339: static void IConsoleHistoryNavigate(int direction) rubidium@9339: { rubidium@9339: int i = _iconsole_historypos + direction; rubidium@9339: rubidium@9339: /* watch out for overflows, just wrap around */ rubidium@9339: if (i < 0) i = ICON_HISTORY_SIZE - 1; rubidium@9339: if (i >= ICON_HISTORY_SIZE) i = 0; rubidium@9339: rubidium@9339: if (direction > 0) { rubidium@9339: if (_iconsole_history[i] == NULL) i = 0; rubidium@9339: } rubidium@9339: rubidium@9339: if (direction < 0) { rubidium@9339: while (i > 0 && _iconsole_history[i] == NULL) i--; rubidium@9339: } rubidium@9339: rubidium@9339: _iconsole_historypos = i; rubidium@9339: IConsoleClearCommand(); rubidium@9339: /* copy history to 'command prompt / bash' */ rubidium@9339: assert(_iconsole_history[i] != NULL && IsInsideMM(i, 0, ICON_HISTORY_SIZE)); rubidium@9339: ttd_strlcpy(_iconsole_cmdline.buf, _iconsole_history[i], _iconsole_cmdline.maxlength); rubidium@9339: UpdateTextBufferSize(&_iconsole_cmdline); rubidium@9339: } rubidium@9339: rubidium@9339: /** rubidium@9339: * Handle the printing of text entered into the console or redirected there rubidium@9339: * by any other means. Text can be redirected to other players in a network game rubidium@9339: * as well as to a logfile. If the network server is a dedicated server, all activities rubidium@9339: * are also logged. All lines to print are added to a temporary buffer which can be rubidium@9339: * used as a history to print them onscreen rubidium@9339: * @param color_code the colour of the command. Red in case of errors, etc. rubidium@9339: * @param string the message entered or output on the console (notice, error, etc.) rubidium@9339: */ rubidium@9339: void IConsoleGUIPrint(ConsoleColour color_code, char *str) rubidium@9339: { rubidium@9906: new IConsoleLine(str, color_code); rubidium@9339: SetWindowDirty(FindWindowById(WC_CONSOLE, 0)); rubidium@9339: }