FS2_Open
Open source remastering of the Freespace 2 engine
console.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) Volition, Inc. 1999. All rights reserved.
3  *
4  * All source code herein is the property of Volition, Inc. You may not sell
5  * or otherwise commercially exploit the source or things you created based on the
6  * source.
7  *
8 */
9 
10 
11 #include "debugconsole/console.h"
12 #include "globalincs/alphacolors.h"
13 #include "globalincs/version.h"
14 #include "graphics/2d.h"
15 #include "graphics/font.h"
16 #include "io/key.h"
17 #include "io/timer.h"
18 #include "osapi/osapi.h"
19 
20 #include <algorithm>
21 #include <cmath>
22 
23 // ========================= GLOBALS =========================
25 
26 // Commands and History
28 
30 // Misc
32 uint lastline = 0; // Number of lines written to the console since the last command was processed
33 
34 
35 // ========================= LOCALS ==========================
36 // Text Buffer
37 uint DBROWS = 80; // # of buffer rows
38 uint DBCOLS = 80; // # of buffer columns
39 uint lastwhite = 0; // Last whitespace character encountered, used by putc for 'true' word wrapping
40 ubyte DTABS = 4;
41 
45 static
46 const char *token_str[DCT_MAX_ITEMS] =
47 {
48  "nothing",
49  "string",
50  "float",
51  "integer",
52  "unsigned integer",
53  "byte",
54  "unsigned byte",
55  "boolean"
56 };
57 
59 
60 // Display Window
61 uint DROWS = 25;
62 uint DCOLS = 80;
63 const uint DROWS_MIN = 25;
64 const uint DCOLS_MIN = 80;
65 uint dc_scroll_x; // X scroll position (Leftmost character)
66 uint dc_scroll_y; // Y scroll position (Topmost character)
67 int row_height; // Row/Line height, in pixels
68 int col_width; // Col/Character width, in pixels
69 int dc_font = FONT1;
70 
72 
73 #define SCROLL_X_MAX (DBCOLS - DCOLS)
74 #define SCROLL_Y_MAX (DBROWS - DROWS)
75 
76 // Commands and History
77 uint DCMDS = 40; // Max number of commands to remember
78 
80 SCP_deque<SCP_string>::iterator last_oldcommand; // Iterator to the last old command. Is reset to the start every new command push.
81 
82 const char dc_prompt[]= "> "; // The prompt c_str
83 SCP_string dc_command_buf; // The command line as shown in the console. Essentially an input buffer for dc_command_str
84 
85 // Local functions
89 void dc_init(void);
90 
94 void dc_do_command(SCP_string *cmd_str);
95 
99 void dc_draw(bool show_prompt);
100 
107 void dc_draw_cursor( SCP_string &cmd_string, int x, int y );
108 
112 void dc_draw_window(bool show_prompt);
113 
118 void dc_putc(char c);
119 
124 
125 // ============================== IMPLEMENTATIONS =============================
126 void dc_do_command(SCP_string *cmd_str)
127 {
138  int i;
139  SCP_string command;
140  extern debug_command* dc_commands[]; // z64: I don't like this extern here, at all. Nope nope nope.
141 
142  if (cmd_str->empty()) {
143  return;
144  }
145 
146  dc_parse_init(*cmd_str);
147 
148  dc_stuff_string_white(command); // Grab the first token, presumably this is a command
149 
150  for (i = 0; i < dc_commands_size; ++i) {
151 
152  if (stricmp(dc_commands[i]->name, command.c_str()) == 0) {
153  break;
154  } // Else, continue
155  }
156 
157  if (i == dc_commands_size) {
158  dc_printf("Command not found: '%s'\n", command.c_str());
159  return;
160  } // Else, we found our command
161 
162  try {
163  dc_commands[i]->func(); // Run the command!
164 
165  } catch (errParseString err) {
166  dc_printf("Require string(s) not found: \n");
167  for (uint j = 0; j < err.expected_tokens.size(); ++j) {
168  dc_printf("%i: %s\n", j, err.expected_tokens[j].c_str());
169  }
170 
171  dc_printf("Found '%s' instead\n", err.found_token.c_str());
172 
173  } catch (errParse err) {
174  dc_printf("Invalid argument. Expected %s, found '%s'\n", token_str[err.expected_type], err.found_token.c_str());
175 
176  }
177 
178  // dc_maybe_stuff_string is vulnerable to overflow. Once the errParseOverflow throw class (or w/e) gets
179  // implemented, this last command should be put into its own try{} catch{} block.
180  if (dc_maybe_stuff_string(command)) {
181  dc_printf( "Ignoring the unused command line tail '%s'\n", command.c_str() );
182  }
183 }
184 
185 void dc_draw(bool show_prompt = FALSE)
186 {
187  gr_clear();
190  gr_string( 0x8000, gr_screen.center_offset_y + 3, dc_title.c_str(), GR_RESIZE_NONE );
191 
193 
194  dc_draw_window(show_prompt);
195 
196  gr_flip();
197 }
198 
199 void dc_draw_cursor( SCP_string &cmd_string, int x, int y )
200 {
201  int t;
202  int w, h; // gr_string width and height
203 
204  t = timer_get_fixed_seconds() / (F1_0/3);
205  if ( t & 1 ) {
206  gr_get_string_size( &w, &h, cmd_string.c_str() );
207 
208  w %= (DCOLS * Current_font->w);
209  //gr_string( w, debug_y*16, "_" );
210  gr_rect(gr_screen.center_offset_x + (x + (w + 1)), gr_screen.center_offset_y + (y + (h + 1)), 2, Current_font->h, GR_RESIZE_NONE);
211  }
212 }
213 
214 void dc_draw_window(bool show_prompt)
215 {
216  uint cmd_lines; // Number of lines for the command string
217  uint buffer_lines; // Number of lines from the buffer to draw
218  uint i; // The current row we're drawing
219  uint j; // The current row of the command string we're drawing
220  SCP_string out_str; // The command string + prompt character
221  SCP_string::iterator str_it; // Iterator to out_str
222 
223  out_str = dc_prompt + dc_command_buf;
224  cmd_lines = (out_str.size() / DCOLS) + 1;
225  if (show_prompt) {
226  buffer_lines = DROWS - cmd_lines;
227  } else {
228  buffer_lines = DROWS;
229  }
230 
231  // Ensure the window is not bigger than the buffer
234 
235  // Ensure we don't scroll too far
237  if (dc_buffer.size() >= buffer_lines) {
238  dc_scroll_y = MIN(dc_scroll_y, (dc_buffer.size() - buffer_lines));
239  } else {
240  dc_scroll_y = 0; // Disallow vscroll until the buffer is larger than the window
241  }
242 
243  // Draw the buffer strings
244  for (i = 0; i < buffer_lines; ++i) {
245  if ((i + dc_scroll_y) < dc_buffer.size()) {
246  gr_string(gr_screen.center_offset_x, gr_screen.center_offset_y + ((i * row_height) + row_height), dc_buffer[i + dc_scroll_y].substr(dc_scroll_x).c_str(), GR_RESIZE_NONE);
247  }
248  }
249 
250  // Draw the command string w/ padding only if the prompt is active.
251  if (show_prompt) {
252  i += 1; // 1 line between the output and the input text
253  j = 0;
255  for (str_it = out_str.begin(); str_it < out_str.end(); ++str_it) {
256  if (j == (DCOLS - 1)) {
257  // Insert a newline char at every place the string needs to return the 'carriage'
258  str_it = out_str.insert(str_it, '\n');
259  j = 0;
260  } else {
261  ++j;
262  }
263  }
264  gr_string(gr_screen.center_offset_x, gr_screen.center_offset_y + ((i * row_height) + row_height), out_str.c_str(), GR_RESIZE_NONE);
265 
266  dc_draw_cursor(out_str, 0, ((i * row_height)));
268  }
269 }
270 
271 void dc_init(void)
272 {
273  if (debug_inited) {
274  return;
275  }
276 
277  debug_inited = TRUE;
278 
279  // Init window settings
280  dc_font = FONT1;
281  row_height = ((Current_font->h) * 3) / 2; // Row/Line height, in pixels
282  col_width = Current_font->w; // Col/Character width, in pixels
283  dc_scroll_x = 0;
284  dc_scroll_y = 0;
285  DCOLS = (gr_screen.center_w / col_width) - 1; // Subtract as needed. Windowed mode has some quirks with the resolution
287  DBCOLS = DCOLS;
288  DBROWS = 2 * DROWS;
289 
290  // Init History
291  dc_history.clear();
292  dc_history.push_back("");
293  last_oldcommand = dc_history.begin();
294 
295  // Init buffers
296  dc_buffer.clear();
297  dc_buffer.push_back("");
298 
299  dc_command_buf.reserve(MAX_CLI_LEN);
300  dc_command_buf.clear();
301 
302  sprintf(dc_title, "FreeSpace Open v%i.%i.%i", FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
303  dc_printf("Debug console started.\n" );
304 }
305 
306 bool dc_pause_output(void)
307 {
308  dc_printf("More to follow. Press any key to continue. ESC halts output...");
309 
310  int key;
311  bool loop;
312  do {
313  loop = true;
314 
315  os_poll();
316 
317  dc_draw(FALSE);
318 
319  key = key_inkey();
320  switch (key) {
321  case KEY_ESC:
322  return true;
323  break;
324 
325  case KEY_PAGEUP:
326  if (dc_scroll_y > 1) {
327  dc_scroll_y--;
328  }
329  break;
330 
331  case KEY_PAGEDOWN:
332  if (dc_scroll_y < DBROWS) {
333  dc_scroll_y++;
334  } else {
336  }
337  break;
338 
339  case KEY_LEFT:
340  // TODO: Scroll Left
341  break;
342  case KEY_RIGHT:
343  // TODO: Scroll Right
344  break;
345  case 0:
346  // No key pressed
347  break;
348  default:
349  // Non-control key pressed, break.
350  loop = false;
351  }
352  } while (loop);
353 
354  dc_printf("\n");
355  return false;
356 };
357 
358 void dc_printf(const char *format, ...)
359 {
360  SCP_string tmp;
361  va_list args;
362  SCP_string::iterator tmp_it;
363 
364  va_start(args, format);
365  vsprintf(tmp, format, args);
366  va_end(args);
367 
368  for (tmp_it = tmp.begin(); tmp_it != tmp.end(); ++tmp_it) {
369  dc_putc(*tmp_it);
370  }
371 }
372 
373 void dc_putc(char c)
374 {
375  SCP_string* line_str = &(dc_buffer.back());
376  SCP_string temp_str;
377  int i;
378  int w;
379 
380  if (c == ' ') {
388  temp_str = *line_str;
389  temp_str.push_back(c);
390  gr_get_string_size(&w, NULL, temp_str.c_str());
391 
392  if ((temp_str.size() >= DBCOLS) || (w > gr_screen.center_w)) {
393  c = '\n';
394 
395  } else {
396  lastwhite = temp_str.size();
397  *line_str = temp_str;
398  return;
399  }
400  }
401 
402  if (c == '\t') {
411  i = DTABS - (line_str->size() % DTABS);
412  temp_str = *line_str;
413  temp_str.append(i, ' ');
414  gr_get_string_size(&w, NULL, temp_str.c_str());
415 
416  if ((temp_str.size() >= DBCOLS) || (w > gr_screen.center_w)) {
417  c = '\n';
418 
419  } else {
420  lastwhite = temp_str.size();
421  *line_str = temp_str;
422  return;
423  }
424  }
425 
426  if (c == '\n') {
436  if (line_str->size() > DBCOLS) {
437  line_str->resize(DBCOLS);
438  }
439  dc_buffer.push_back("");
440 
441  if ((dc_buffer.size() > DROWS) && (dc_scroll_y < SCROLL_Y_MAX)) {
442  dc_scroll_y++;
443  }
444 
445  while (dc_buffer.size() > DBROWS) {
446  dc_buffer.pop_front();
447  }
448 
449  lastwhite = 0;
450  lastline++;
451  return;
452  }
453 
454  // By this point, c is probably a writable character
455  temp_str = *line_str;
456  temp_str.push_back(c);
457  gr_get_string_size(&w, NULL, temp_str.c_str());
458 
459  if ((temp_str.size() >= DBCOLS) || (w > gr_screen.center_w)) {
467  temp_str = line_str->substr(lastwhite);
468  line_str->resize(lastwhite);
469  dc_buffer.push_back(temp_str);
470  line_str = &dc_buffer.back();
471 
472  if ((dc_buffer.size() > DROWS) && (dc_scroll_y < SCROLL_Y_MAX)) {
473  dc_scroll_y++;
474  }
475 
476  while (dc_buffer.size() > DBROWS) {
477  dc_buffer.pop_front();
478  }
479 
480  lastwhite = 0;
481  lastline++;
482  line_str->push_back(c);
483  return;
484  }
485 
486  // Else, just push the char onto the line
487  line_str->push_back(c);
488 }
489 
491 {
492  return (strcmp(first->name, second->name) < 0);
493 }
494 
495 void debug_console(void (*_func)(void))
496 {
497  int done = 0;
498 
499  while( key_inkey() ) {
500  os_poll();
501  }
502 
503  if ( !debug_inited ) {
504  dc_init();
505  }
506 
507  dc_draw(TRUE);
508 
509  while (!done) {
510  // poll the os
511  os_poll();
512 
513  int k = key_inkey();
514  switch( k ) {
515 
516  case KEY_SHIFTED+KEY_ENTER:
517  case KEY_ESC:
518  done = TRUE;
519  break;
520 
521  case KEY_BACKSP:
522  if (!dc_command_buf.empty()) {
523  dc_command_buf.erase(dc_command_buf.size() - 1);
524  }
525  break;
526 
527  case KEY_F3:
528  case KEY_UP:
529  if (last_oldcommand < (dc_history.end() - 1)) {
530  ++last_oldcommand;
531  }
532 
534  break;
535 
536  case KEY_DOWN:
537  if (last_oldcommand > dc_history.begin()) {
538  --last_oldcommand;
539  }
540 
542  break;
543 
544  case KEY_PAGEUP:
545  if (dc_scroll_y > 1) {
546  dc_scroll_y--;
547  }
548  break;
549 
550  case KEY_PAGEDOWN:
551  if (dc_scroll_y < (DBROWS - DROWS)) {
552  dc_scroll_y++;
553  } else {
554  dc_scroll_y = (DBROWS - DROWS);
555  }
556  break;
557 
558  case KEY_ENTER:
559  dc_scroll_y = (DBROWS - DROWS); // Set the scroll to look at the bottom
560  last_oldcommand = dc_history.begin(); // Reset the last oldcommand
561  lastline = 0; // Reset the line counter
562 
563  // Clear the command line on the window, but don't print the prompt until the command has processed
564  // Stuff a copy of the command line onto the history
565  // Search for the command
566  // If not found:
567  // abort,
568  // dc_printf("Error: Invalid or Missing command %s", cmd.c_str()), and
569  // dc_printf(dc_prompt) when ready for input
570  // Call the function for that command, and strip the cmd token from the command line string
571  if (dc_command_buf.empty()) {
572  dc_printf("No command given.\n");
573  break;
574  } // Else, continue to process the cmd_line
575 
576  // z64: Thread Note: Maybe lock a mutex here to allow a previous DCF to finish/abort before starting a new one
577  // z64: We'll just assume we won't be here unless a command has finished...
578  dc_history.push_front(dc_command_buf); // Push the command onto the history queue
579  last_oldcommand = dc_history.begin(); // Reset oldcommand
580 
581  while (dc_history.size() > DCMDS) {
582  dc_history.pop_back(); // Keep the commands less than or equal to DCMDS
583  }
584 
585  dc_command_str = dc_command_buf; // Xfer to the command string for processing
586  dc_command_buf.resize(0); // Nullify the buffer
587  dc_printf("%s%s\n", dc_prompt, dc_command_str.c_str()); // Print the command w/ prompt.
588  dc_draw(FALSE); // Redraw the console without the command line.
589  dc_do_command(&dc_command_str); // Try to do the command
590  break;
591 
592  default:
593  // Not any of the control key codes, so it's probably a letter or number.
594  ubyte c = (ubyte)key_to_ascii(k);
595  if ((c != 255) && (dc_command_buf.size() < MAX_CLI_LEN)) {
596  dc_command_buf.push_back(c);
597  }
598  }
599 
600  // Do the passed function
601  if ( _func ) {
602  _func();
603  }
604 
605  // All done, and ready for new entry
606  dc_draw(TRUE);
607  }
608 
609  while( key_inkey() ) {
610  os_poll();
611  }
612 }
void gr_rect(int x, int y, int w, int h, int resize_mode)
Definition: 2d.cpp:2068
GLenum GLsizei GLenum format
Definition: Gl.h:1509
int i
Definition: multi_pxo.cpp:466
#define gr_clear
Definition: 2d.h:749
#define MIN(a, b)
Definition: pstypes.h:296
int key_inkey()
Definition: key.cpp:424
#define KEY_DOWN
Definition: key.h:180
void dc_draw_cursor(SCP_string &cmd_string, int x, int y)
Definition: console.cpp:199
#define F1_0
Definition: fix.h:15
GLfloat GLfloat GLfloat GLfloat h
Definition: Glext.h:7280
#define KEY_LEFT
Definition: key.h:181
void gr_flip()
Definition: 2d.cpp:2113
#define KEY_PAGEDOWN
Definition: key.h:178
#define MAX_CLI_LEN
Definition: consoleparse.h:22
#define KEY_RIGHT
Definition: key.h:182
void dc_putc(char c)
Stuffs the given character into the output buffer.
Definition: console.cpp:373
SCP_deque< SCP_string >::iterator last_oldcommand
Definition: console.cpp:80
__inline void gr_string(int x, int y, const char *string, int resize_mode=GR_RESIZE_FULL)
Definition: 2d.h:769
uint DBCOLS
Definition: console.cpp:38
const uint DROWS_MIN
Definition: console.cpp:63
int center_h
Definition: 2d.h:363
#define FS_VERSION_MAJOR
Definition: version.h:37
#define TRUE
Definition: pstypes.h:399
int center_offset_y
Definition: 2d.h:364
bool dcmd_less(debug_command *first, debug_command *second)
Predicate function used to sort debug_commands.
Definition: console.cpp:490
int center_w
Definition: 2d.h:363
void gr_set_color_fast(color *dst)
Definition: 2d.cpp:1197
#define KEY_PAGEUP
Definition: key.h:175
Class thrown when a required token is not found.
Definition: consoleparse.h:49
std::basic_string< char, std::char_traits< char >, std::allocator< char > > SCP_string
Definition: vmallocator.h:21
int key
int key_to_ascii(int keycode)
Definition: key.cpp:336
#define GR_RESIZE_NONE
Definition: 2d.h:681
void debug_console(void(*_func)(void))
Opens and processes the debug console. (Blocking call)
Definition: console.cpp:495
#define CLAMP(x, min, max)
Definition: pstypes.h:488
void dc_init(void)
Initializes the debug console.
Definition: console.cpp:271
uint DROWS
Definition: console.cpp:61
SCP_string found_token
Definition: consoleparse.h:51
int col_width
Definition: console.cpp:68
uint lastwhite
Definition: console.cpp:39
color Color_bright
Definition: alphacolors.cpp:28
unsigned int uint
Definition: pstypes.h:64
void vsprintf(SCP_string &dest, const char *format, va_list ap)
Definition: parselo.cpp:3800
Maximum number of dc_token elements. Primarily used as an end value in loops.
Definition: consoleparse.h:35
const char dc_prompt[]
Definition: console.cpp:82
void dc_do_command(SCP_string *cmd_str)
Process the entered command string.
Definition: console.cpp:126
font * Current_font
Definition: font.cpp:36
#define KEY_SHIFTED
Definition: key.h:62
#define w(p)
Definition: modelsinc.h:68
sprintf(buf,"(%f,%f,%f)", v3->xyz.x, v3->xyz.y, v3->xyz.z)
#define KEY_ENTER
Definition: key.h:125
#define SCROLL_Y_MAX
Definition: console.cpp:74
int dc_commands_size
Definition: consolecmds.cpp:28
#define FS_VERSION_MINOR
Definition: version.h:38
int w
Definition: font.h:42
Class to aggregate a debug command with its name (as shown in the console) and short help...
Definition: console.h:221
bool dc_maybe_stuff_string(char *out_str, size_t maxlen)
Tries to stuff a string to out_str from the command line, stopping at the end of the command line...
#define KEY_BACKSP
Definition: key.h:126
GLdouble GLdouble t
Definition: Glext.h:5329
uint lastline
Definition: console.cpp:32
GLint GLint GLint GLint GLint x
Definition: Glext.h:5182
unsigned char ubyte
Definition: pstypes.h:62
int h
Definition: font.h:43
void os_poll()
Definition: osapi.cpp:748
void(* func)()
Pointer to the function that to run when this command is evoked.
Definition: console.h:225
SCP_vector< SCP_string > expected_tokens
Definition: consoleparse.h:73
uint dc_scroll_y
Definition: console.cpp:66
Class thrown when an expected string was not found. Can/should contain all of the expected strings...
Definition: consoleparse.h:70
int row_height
Definition: console.cpp:67
SCP_string dc_command_buf
Definition: console.cpp:83
uint DCOLS
Definition: console.cpp:62
const uint DCOLS_MIN
Definition: console.cpp:64
GLuint const GLchar * name
Definition: Glext.h:5608
void dc_stuff_string_white(char *out_str, size_t maxlen)
Stuffs a whitespace delimited string to out_str from the command line, stopping at the end of the com...
dc_token expected_type
Definition: consoleparse.h:52
#define FONT1
Definition: font.h:65
ubyte DTABS
Tab size in spaces.
Definition: console.cpp:40
#define KEY_ESC
Definition: key.h:124
fix timer_get_fixed_seconds()
Definition: timer.cpp:116
screen gr_screen
Definition: 2d.cpp:46
void gr_get_string_size(int *w, int *h, const char *text, int len=9999)
Definition: font.cpp:196
debug_command * dc_commands[DC_MAX_COMMANDS]
Definition: consolecmds.cpp:27
GLint first
Definition: Gl.h:1491
int center_offset_x
Definition: 2d.h:364
bool debug_inited
Definition: console.cpp:31
void dc_parse_init(SCP_string &str)
Initializes the DC command line parser.
An overhauled/updated debug console to allow monitoring, testing, and general debugging of new featur...
uint dc_scroll_x
Definition: console.cpp:65
SCP_string dc_command_str
Is progressively culled from the left as commands, arguments are parsed in DCF's. ...
Definition: console.cpp:27
SCP_string dc_title
Definition: console.cpp:71
color Color_normal
Definition: alphacolors.cpp:28
bool Dc_debug_on
Flag used to print console and command debugging strings.
Definition: console.cpp:24
#define KEY_UP
Definition: key.h:179
#define FS_VERSION_BUILD
Definition: version.h:39
void dc_printf(const char *format,...)
Prints the given char string to the debug console.
Definition: console.cpp:358
void dc_draw(bool show_prompt)
Draws the in-game console.
Definition: console.cpp:185
SCP_deque< SCP_string > dc_history
Definition: console.cpp:79
int dc_font
Definition: console.cpp:69
bool dc_pause_output(void)
Pauses the output of a command and allows user to scroll through the output history.
Definition: console.cpp:306
void gr_set_font(int fontnum)
Definition: font.cpp:566
uint DCMDS
Definition: console.cpp:77
SCP_deque< SCP_string > dc_buffer
Definition: console.cpp:58
void dc_draw_window(bool show_prompt)
Definition: console.cpp:214
uint DBROWS
Definition: console.cpp:37
#define FALSE
Definition: pstypes.h:400
#define stricmp(s1, s2)
Definition: config.h:271
const char * name
The name of the command, as shown in the debug console.
Definition: console.h:223
const GLubyte * c
Definition: Glext.h:8376
#define KEY_F3
Definition: key.h:145
GLint y
Definition: Gl.h:1505