OpenTTD
console_gui.cpp
Go to the documentation of this file.
1 /* $Id: console_gui.cpp 26538 2014-04-28 21:06:51Z rubidium $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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  */
9 
12 #include "stdafx.h"
13 #include "textbuf_type.h"
14 #include "window_gui.h"
15 #include "console_gui.h"
16 #include "console_internal.h"
17 #include "window_func.h"
18 #include "string_func.h"
19 #include "strings_func.h"
20 #include "gfx_func.h"
21 #include "settings_type.h"
22 #include "console_func.h"
23 #include "rev.h"
24 #include "video/video_driver.hpp"
25 
26 #include "widgets/console_widget.h"
27 
28 #include "table/strings.h"
29 
30 #include "safeguards.h"
31 
32 static const uint ICON_HISTORY_SIZE = 20;
33 static const uint ICON_LINE_SPACING = 2;
34 static const uint ICON_RIGHT_BORDERWIDTH = 10;
35 static const uint ICON_BOTTOM_BORDERWIDTH = 12;
36 
40 struct IConsoleLine {
41  static IConsoleLine *front;
42  static int size;
43 
45  char *buffer;
47  uint16 time;
48 
56  buffer(buffer),
57  colour(colour),
58  time(0)
59  {
60  IConsoleLine::front = this;
62  }
63 
68  {
70  free(buffer);
71 
72  delete previous;
73  }
74 
78  static const IConsoleLine *Get(uint index)
79  {
80  const IConsoleLine *item = IConsoleLine::front;
81  while (index != 0 && item != NULL) {
82  index--;
83  item = item->previous;
84  }
85 
86  return item;
87  }
88 
96  static bool Truncate()
97  {
99  if (cur == NULL) return false;
100 
101  int count = 1;
102  for (IConsoleLine *item = cur->previous; item != NULL; count++, cur = item, item = item->previous) {
103  if (item->time > _settings_client.gui.console_backlog_timeout &&
105  delete item;
106  cur->previous = NULL;
107  return true;
108  }
109 
110  if (item->time != MAX_UVALUE(uint16)) item->time++;
111  }
112 
113  return false;
114  }
115 
119  static void Reset()
120  {
121  delete IConsoleLine::front;
122  IConsoleLine::front = NULL;
123  IConsoleLine::size = 0;
124  }
125 };
126 
127 /* static */ IConsoleLine *IConsoleLine::front = NULL;
128 /* static */ int IConsoleLine::size = 0;
129 
130 
131 /* ** main console cmd buffer ** */
132 static Textbuf _iconsole_cmdline(ICON_CMDLN_SIZE);
133 static char *_iconsole_history[ICON_HISTORY_SIZE];
134 static int _iconsole_historypos;
135 IConsoleModes _iconsole_mode;
136 
137 /* *************** *
138  * end of header *
139  * *************** */
140 
141 static void IConsoleClearCommand()
142 {
143  memset(_iconsole_cmdline.buf, 0, ICON_CMDLN_SIZE);
144  _iconsole_cmdline.chars = _iconsole_cmdline.bytes = 1; // only terminating zero
145  _iconsole_cmdline.pixels = 0;
146  _iconsole_cmdline.caretpos = 0;
147  _iconsole_cmdline.caretxoffs = 0;
149 }
150 
151 static inline void IConsoleResetHistoryPos()
152 {
153  _iconsole_historypos = -1;
154 }
155 
156 
157 static const char *IConsoleHistoryAdd(const char *cmd);
158 static void IConsoleHistoryNavigate(int direction);
159 
160 static const struct NWidgetPart _nested_console_window_widgets[] = {
161  NWidget(WWT_EMPTY, INVALID_COLOUR, WID_C_BACKGROUND), SetResize(1, 1),
162 };
163 
164 static WindowDesc _console_window_desc(
165  WDP_MANUAL, NULL, 0, 0,
167  0,
168  _nested_console_window_widgets, lengthof(_nested_console_window_widgets)
169 );
170 
172 {
173  static int scroll;
175  int line_offset;
176 
177  IConsoleWindow() : Window(&_console_window_desc)
178  {
179  _iconsole_mode = ICONSOLE_OPENED;
180  this->line_height = FONT_HEIGHT_NORMAL + ICON_LINE_SPACING;
181  this->line_offset = GetStringBoundingBox("] ").width + 5;
182 
183  this->InitNested(0);
184  ResizeWindow(this, _screen.width, _screen.height / 3);
185  }
186 
187  ~IConsoleWindow()
188  {
189  _iconsole_mode = ICONSOLE_CLOSED;
191  }
192 
197  void Scroll(int amount)
198  {
199  int max_scroll = max<int>(0, IConsoleLine::size + 1 - this->height / this->line_height);
200  IConsoleWindow::scroll = Clamp<int>(IConsoleWindow::scroll + amount, 0, max_scroll);
201  this->SetDirty();
202  }
203 
204  virtual void OnPaint()
205  {
206  const int right = this->width - 5;
207 
208  GfxFillRect(0, 0, this->width - 1, this->height - 1, PC_BLACK);
209  int ypos = this->height - this->line_height;
210  for (const IConsoleLine *print = IConsoleLine::Get(IConsoleWindow::scroll); print != NULL; print = print->previous) {
211  SetDParamStr(0, print->buffer);
212  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 (ypos < 0) break;
214  }
215  /* If the text is longer than the window, don't show the starting ']' */
216  int delta = this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH;
217  if (delta > 0) {
218  DrawString(5, right, this->height - this->line_height, "]", (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
219  delta = 0;
220  }
221 
222  /* If we have a marked area, draw a background highlight. */
223  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);
224 
225  DrawString(this->line_offset + delta, right, this->height - this->line_height, _iconsole_cmdline.buf, (TextColour)CC_COMMAND, SA_LEFT | SA_FORCE);
226 
227  if (_focused_window == this && _iconsole_cmdline.caret) {
228  DrawString(this->line_offset + delta + _iconsole_cmdline.caretxoffs, right, this->height - this->line_height, "_", TC_WHITE, SA_LEFT | SA_FORCE);
229  }
230  }
231 
232  virtual void OnHundredthTick()
233  {
234  if (IConsoleLine::Truncate() &&
235  (IConsoleWindow::scroll > IConsoleLine::size)) {
236  IConsoleWindow::scroll = max(0, IConsoleLine::size - (this->height / this->line_height) + 1);
237  this->SetDirty();
238  }
239  }
240 
241  virtual void OnMouseLoop()
242  {
243  if (_iconsole_cmdline.HandleCaret()) this->SetDirty();
244  }
245 
246  virtual EventState OnKeyPress(WChar key, uint16 keycode)
247  {
248  if (_focused_window != this) return ES_NOT_HANDLED;
249 
250  const int scroll_height = (this->height / this->line_height) - 1;
251  switch (keycode) {
252  case WKC_UP:
254  this->SetDirty();
255  break;
256 
257  case WKC_DOWN:
259  this->SetDirty();
260  break;
261 
262  case WKC_SHIFT | WKC_PAGEDOWN:
263  this->Scroll(-scroll_height);
264  break;
265 
266  case WKC_SHIFT | WKC_PAGEUP:
267  this->Scroll(scroll_height);
268  break;
269 
270  case WKC_SHIFT | WKC_DOWN:
271  this->Scroll(-1);
272  break;
273 
274  case WKC_SHIFT | WKC_UP:
275  this->Scroll(1);
276  break;
277 
278  case WKC_BACKQUOTE:
279  IConsoleSwitch();
280  break;
281 
282  case WKC_RETURN: case WKC_NUM_ENTER: {
283  /* We always want the ] at the left side; we always force these strings to be left
284  * aligned anyway. So enforce this in all cases by addding a left-to-right marker,
285  * otherwise it will be drawn at the wrong side with right-to-left texts. */
286  IConsolePrintF(CC_COMMAND, LRM "] %s", _iconsole_cmdline.buf);
287  const char *cmd = IConsoleHistoryAdd(_iconsole_cmdline.buf);
288  IConsoleClearCommand();
289 
290  if (cmd != NULL) IConsoleCmdExec(cmd);
291  break;
292  }
293 
294  case WKC_CTRL | WKC_RETURN:
295  _iconsole_mode = (_iconsole_mode == ICONSOLE_FULL) ? ICONSOLE_OPENED : ICONSOLE_FULL;
296  IConsoleResize(this);
298  break;
299 
300  case (WKC_CTRL | 'L'):
301  IConsoleCmdExec("clear");
302  break;
303 
304  default:
305  if (_iconsole_cmdline.HandleKeyPress(key, keycode) != HKPR_NOT_HANDLED) {
306  IConsoleWindow::scroll = 0;
307  IConsoleResetHistoryPos();
308  this->SetDirty();
309  } else {
310  return ES_NOT_HANDLED;
311  }
312  break;
313  }
314  return ES_HANDLED;
315  }
316 
317  virtual void InsertTextString(int wid, const char *str, bool marked, const char *caret, const char *insert_location, const char *replacement_end)
318  {
319  if (_iconsole_cmdline.InsertString(str, marked, caret, insert_location, replacement_end)) {
320  IConsoleWindow::scroll = 0;
321  IConsoleResetHistoryPos();
322  this->SetDirty();
323  }
324  }
325 
326  virtual const char *GetFocusedText() const
327  {
328  return _iconsole_cmdline.buf;
329  }
330 
331  virtual const char *GetCaret() const
332  {
333  return _iconsole_cmdline.buf + _iconsole_cmdline.caretpos;
334  }
335 
336  virtual const char *GetMarkedText(size_t *length) const
337  {
338  if (_iconsole_cmdline.markend == 0) return NULL;
339 
340  *length = _iconsole_cmdline.markend - _iconsole_cmdline.markpos;
341  return _iconsole_cmdline.buf + _iconsole_cmdline.markpos;
342  }
343 
344  virtual Point GetCaretPosition() const
345  {
346  int delta = min(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
347  Point pt = {this->line_offset + delta + _iconsole_cmdline.caretxoffs, this->height - this->line_height};
348 
349  return pt;
350  }
351 
352  virtual Rect GetTextBoundingRect(const char *from, const char *to) const
353  {
354  int delta = min(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
355 
356  Point p1 = GetCharPosInString(_iconsole_cmdline.buf, from, FS_NORMAL);
357  Point p2 = from != to ? GetCharPosInString(_iconsole_cmdline.buf, from) : p1;
358 
359  Rect r = {this->line_offset + delta + p1.x, this->height - this->line_height, this->line_offset + delta + p2.x, this->height};
360  return r;
361  }
362 
363  virtual const char *GetTextCharacterAtPosition(const Point &pt) const
364  {
365  int delta = min(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0);
366 
367  if (!IsInsideMM(pt.y, this->height - this->line_height, this->height)) return NULL;
368 
369  return GetCharAtPosition(_iconsole_cmdline.buf, pt.x - delta);
370  }
371 
372  virtual void OnMouseWheel(int wheel)
373  {
374  this->Scroll(-wheel);
375  }
376 
377  virtual void OnFocusLost()
378  {
380  }
381 };
382 
383 int IConsoleWindow::scroll = 0;
384 
385 void IConsoleGUIInit()
386 {
387  IConsoleResetHistoryPos();
388  _iconsole_mode = ICONSOLE_CLOSED;
389 
391  memset(_iconsole_history, 0, sizeof(_iconsole_history));
392 
393  IConsolePrintF(CC_WARNING, "OpenTTD Game Console Revision 7 - %s", _openttd_revision);
394  IConsolePrint(CC_WHITE, "------------------------------------");
395  IConsolePrint(CC_WHITE, "use \"help\" for more information");
396  IConsolePrint(CC_WHITE, "");
397  IConsoleClearCommand();
398 }
399 
400 void IConsoleClearBuffer()
401 {
403 }
404 
405 void IConsoleGUIFree()
406 {
407  IConsoleClearBuffer();
408 }
409 
412 {
413  switch (_iconsole_mode) {
414  case ICONSOLE_OPENED:
415  w->height = _screen.height / 3;
416  w->width = _screen.width;
417  break;
418  case ICONSOLE_FULL:
419  w->height = _screen.height - ICON_BOTTOM_BORDERWIDTH;
420  w->width = _screen.width;
421  break;
422  default: return;
423  }
424 
426 }
427 
430 {
431  switch (_iconsole_mode) {
432  case ICONSOLE_CLOSED:
433  new IConsoleWindow();
434  break;
435 
436  case ICONSOLE_OPENED: case ICONSOLE_FULL:
438  break;
439  }
440 
442 }
443 
446 {
447  if (_iconsole_mode == ICONSOLE_OPENED) IConsoleSwitch();
448 }
449 
456 static const char *IConsoleHistoryAdd(const char *cmd)
457 {
458  /* Strip all spaces at the begin */
459  while (IsWhitespace(*cmd)) cmd++;
460 
461  /* Do not put empty command in history */
462  if (StrEmpty(cmd)) return NULL;
463 
464  /* Do not put in history if command is same as previous */
465  if (_iconsole_history[0] == NULL || strcmp(_iconsole_history[0], cmd) != 0) {
466  free(_iconsole_history[ICON_HISTORY_SIZE - 1]);
467  memmove(&_iconsole_history[1], &_iconsole_history[0], sizeof(_iconsole_history[0]) * (ICON_HISTORY_SIZE - 1));
468  _iconsole_history[0] = stredup(cmd);
469  }
470 
471  /* Reset the history position */
472  IConsoleResetHistoryPos();
473  return _iconsole_history[0];
474 }
475 
480 static void IConsoleHistoryNavigate(int direction)
481 {
482  if (_iconsole_history[0] == NULL) return; // Empty history
483  _iconsole_historypos = Clamp(_iconsole_historypos + direction, -1, ICON_HISTORY_SIZE - 1);
484 
485  if (direction > 0 && _iconsole_history[_iconsole_historypos] == NULL) _iconsole_historypos--;
486 
487  if (_iconsole_historypos == -1) {
488  _iconsole_cmdline.DeleteAll();
489  } else {
490  _iconsole_cmdline.Assign(_iconsole_history[_iconsole_historypos]);
491  }
492 }
493 
503 void IConsoleGUIPrint(TextColour colour_code, char *str)
504 {
505  new IConsoleLine(str, colour_code);
507 }
508 
509 
516 {
517  /* A normal text colour is used. */
518  if (!(c & TC_IS_PALETTE_COLOUR)) return TC_BEGIN <= c && c < TC_END;
519 
520  /* A text colour from the palette is used; must be the company
521  * colour gradient, so it must be one of those. */
522  c &= ~TC_IS_PALETTE_COLOUR;
523  for (uint i = COLOUR_BEGIN; i < COLOUR_END; i++) {
524  if (_colour_gradient[i][4] == c) return true;
525  }
526 
527  return false;
528 }