aboutsummaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
authorMax Horn2006-09-16 16:58:27 +0000
committerMax Horn2006-09-16 16:58:27 +0000
commit919092e5fcd0389b4cbd862e9e8cceab202e6741 (patch)
tree7d9c8f643191812bb1eb24f2e7cc2e67586149d5 /gui
parent1add07becae0179e026a90924b419b74ffb1078f (diff)
downloadscummvm-rg350-919092e5fcd0389b4cbd862e9e8cceab202e6741.tar.gz
scummvm-rg350-919092e5fcd0389b4cbd862e9e8cceab202e6741.tar.bz2
scummvm-rg350-919092e5fcd0389b4cbd862e9e8cceab202e6741.zip
Overhaul of the debugger code
* Moved Common::Debuggger to GUI::Debugger (mainly to satisfy linker restrictions) * Change the base Debugger class to *not* be a template class anymore; instead, a thin (template based) wrapper class is used to hook up debugger commands * Removed duplicate Cmd_Exit and Cmd_Help methods in favor of a single version of each in GUI::Debugger * New Cmd_Help doesn't word wrap after 39/78 chars, but rather queries the console to determine when to wrap * Debugger::preEnter and postEnter aren't pure virtual anymore svn-id: r23890
Diffstat (limited to 'gui')
-rw-r--r--gui/console.h2
-rw-r--r--gui/debugger.cpp466
-rw-r--r--gui/debugger.h158
-rw-r--r--gui/module.mk1
4 files changed, 626 insertions, 1 deletions
diff --git a/gui/console.h b/gui/console.h
index d6ac4e3350..8079e2620b 100644
--- a/gui/console.h
+++ b/gui/console.h
@@ -36,7 +36,6 @@ public:
typedef bool (*InputCallbackProc)(ConsoleDialog *console, const char *input, void *refCon);
typedef bool (*CompletionCallbackProc)(ConsoleDialog* console, const char *input, char*& completion, void *refCon);
-protected:
enum {
kBufferSize = 32768,
kCharsPerLine = 128,
@@ -45,6 +44,7 @@ protected:
kHistorySize = 20
};
+protected:
const Graphics::Font *_font;
char _buffer[kBufferSize];
diff --git a/gui/debugger.cpp b/gui/debugger.cpp
new file mode 100644
index 0000000000..cde8b22a03
--- /dev/null
+++ b/gui/debugger.cpp
@@ -0,0 +1,466 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2001-2006 The ScummVM project
+ *
+ * This program 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; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/stdafx.h"
+
+#include "common/system.h"
+
+#include "gui/debugger.h"
+#if USE_CONSOLE
+ #include "gui/console.h"
+#endif
+
+namespace GUI {
+
+Debugger::Debugger() {
+ _frame_countdown = 0;
+ _dvar_count = 0;
+ _dcmd_count = 0;
+ _detach_now = false;
+ _isAttached = false;
+ _errStr = NULL;
+ _firstTime = true;
+ _debuggerDialog = new GUI::ConsoleDialog(1.0, 0.67F);
+ _debuggerDialog->setInputCallback(debuggerInputCallback, this);
+ _debuggerDialog->setCompletionCallback(debuggerCompletionCallback, this);
+
+ //DCmd_Register("continue", WRAP_METHOD(Debugger, Cmd_Exit));
+ DCmd_Register("exit", WRAP_METHOD(Debugger, Cmd_Exit));
+ DCmd_Register("quit", WRAP_METHOD(Debugger, Cmd_Exit));
+
+ DCmd_Register("help", WRAP_METHOD(Debugger, Cmd_Help));
+
+ DCmd_Register("debugflag_list", WRAP_METHOD(Debugger, Cmd_DebugFlagsList));
+ DCmd_Register("debugflag_enable", WRAP_METHOD(Debugger, Cmd_DebugFlagEnable));
+ DCmd_Register("debugflag_disable", WRAP_METHOD(Debugger, Cmd_DebugFlagDisable));
+}
+
+Debugger::~Debugger() {
+ for (int i = 0; i < _dcmd_count; i++) {
+ delete _dcmds[i].debuglet;
+ _dcmds[i].debuglet = 0;
+ }
+ delete _debuggerDialog;
+}
+
+
+// Initialisation Functions
+int Debugger::DebugPrintf(const char *format, ...) {
+ va_list argptr;
+
+ va_start(argptr, format);
+ int count;
+#if USE_CONSOLE
+ count = _debuggerDialog->vprintf(format, argptr);
+#else
+ count = ::vprintf(format, argptr);
+#endif
+ va_end (argptr);
+ return count;
+}
+
+#ifndef __SYMBIAN32__ // gcc/UIQ doesn't like the debugger code for some reason? Actually get a cc1plus core dump here :)
+void Debugger::attach(const char *entry) {
+
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true);
+
+ if (entry) {
+ _errStr = strdup(entry);
+ }
+
+ _frame_countdown = 1;
+ _detach_now = false;
+ _isAttached = true;
+}
+
+void Debugger::detach() {
+ g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
+
+ _detach_now = false;
+ _isAttached = false;
+}
+
+// Temporary execution handler
+void Debugger::onFrame() {
+ if (_frame_countdown == 0)
+ return;
+ --_frame_countdown;
+
+ if (!_frame_countdown) {
+
+ preEnter();
+ enter();
+ postEnter();
+
+ // Detach if we're finished with the debugger
+ if (_detach_now)
+ detach();
+ }
+}
+#endif // of ifndef __SYMBIAN32__ // gcc/UIQ doesn't like the debugger code for some reason? Actually get a cc1plus core dump here :)
+
+// Main Debugger Loop
+void Debugger::enter() {
+#if USE_CONSOLE
+ if (_firstTime) {
+ DebugPrintf("Debugger started, type 'exit' to return to the game.\n");
+ DebugPrintf("Type 'help' to see a little list of commands and variables.\n");
+ _firstTime = false;
+ }
+
+ if (_errStr) {
+ DebugPrintf("ERROR: %s\n\n", _errStr);
+ free(_errStr);
+ _errStr = NULL;
+ }
+
+ _debuggerDialog->runModal();
+#else
+ // TODO: compared to the console input, this here is very bare bone.
+ // For example, no support for tab completion and no history. At least
+ // we should re-add (optional) support for the readline library.
+ // Or maybe instead of choosing between a console dialog and stdio,
+ // we should move that choice into the ConsoleDialog class - that is,
+ // the console dialog code could be #ifdef'ed to not print to the dialog
+ // but rather to stdio. This way, we could also reuse the command history
+ // and tab completion of the console. It would still require a lot of
+ // work, but at least no dependency on a 3rd party library...
+
+ printf("Debugger entered, please switch to this console for input.\n");
+
+ int i;
+ char buf[256];
+
+ do {
+ printf("debug> ");
+ if (!fgets(buf, sizeof(buf), stdin))
+ return;
+
+ i = strlen(buf);
+ while (i > 0 && buf[i - 1] == '\n')
+ buf[--i] = 0;
+
+ if (i == 0)
+ continue;
+ } while (parseCommand(buf));
+
+#endif
+}
+
+bool Debugger::handleCommand(int argc, const char **argv, bool &result) {
+ for (int i = 0; i < _dcmd_count; ++i) {
+ if (!strcmp(_dcmds[i].name, argv[0])) {
+ Debuglet *debuglet = _dcmds[i].debuglet;
+ assert(debuglet);
+ result = (*debuglet)(argc, argv);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// Command execution loop
+bool Debugger::parseCommand(const char *inputOrig) {
+ int i = 0, num_params = 0;
+ const char *param[256];
+ char *input = strdup(inputOrig); // One of the rare occasions using strdup is OK (although avoiding strtok might be more elegant here).
+
+ // Parse out any params
+ char *tok = strtok(input, " ");
+ if (tok) {
+ do {
+ param[num_params++] = tok;
+ } while ((tok = strtok(NULL, " ")) != NULL);
+ } else {
+ param[num_params++] = input;
+ }
+
+ // Handle commands first
+ bool result;
+ if (handleCommand(num_params, param, result)) {
+ free(input);
+ return result;
+ }
+
+ // It's not a command, so things get a little tricky for variables. Do fuzzy matching to ignore things like subscripts.
+ for (i = 0; i < _dvar_count; i++) {
+ if (!strncmp(_dvars[i].name, param[0], strlen(_dvars[i].name))) {
+ if (num_params > 1) {
+ // Alright, we need to check the TYPE of the variable to deref and stuff... the array stuff is a bit ugly :)
+ switch (_dvars[i].type) {
+ // Integer
+ case DVAR_BYTE:
+ *(byte *)_dvars[i].variable = atoi(param[1]);
+ DebugPrintf("byte%s = %d\n", param[0], *(byte *)_dvars[i].variable);
+ break;
+ case DVAR_INT:
+ *(int32 *)_dvars[i].variable = atoi(param[1]);
+ DebugPrintf("(int)%s = %d\n", param[0], *(int32 *)_dvars[i].variable);
+ break;
+ // Integer Array
+ case DVAR_INTARRAY: {
+ char *chr = (char *)strchr(param[0], '[');
+ if (!chr) {
+ DebugPrintf("You must access this array as %s[element]\n", param[0]);
+ } else {
+ int element = atoi(chr+1);
+ int32 *var = *(int32 **)_dvars[i].variable;
+ if (element >= _dvars[i].optional) {
+ DebugPrintf("%s is out of range (array is %d elements big)\n", param[0], _dvars[i].optional);
+ } else {
+ var[element] = atoi(param[1]);
+ DebugPrintf("(int)%s = %d\n", param[0], var[element]);
+ }
+ }
+ }
+ break;
+ default:
+ DebugPrintf("Failed to set variable %s to %s - unknown type\n", _dvars[i].name, param[1]);
+ break;
+ }
+ } else {
+ // And again, type-dependent prints/defrefs. The array one is still ugly.
+ switch (_dvars[i].type) {
+ // Integer
+ case DVAR_BYTE:
+ DebugPrintf("(byte)%s = %d\n", param[0], *(const byte *)_dvars[i].variable);
+ break;
+ case DVAR_INT:
+ DebugPrintf("(int)%s = %d\n", param[0], *(const int32 *)_dvars[i].variable);
+ break;
+ // Integer array
+ case DVAR_INTARRAY: {
+ const char *chr = strchr(param[0], '[');
+ if (!chr) {
+ DebugPrintf("You must access this array as %s[element]\n", param[0]);
+ } else {
+ int element = atoi(chr+1);
+ const int32 *var = *(const int32 **)_dvars[i].variable;
+ if (element >= _dvars[i].optional) {
+ DebugPrintf("%s is out of range (array is %d elements big)\n", param[0], _dvars[i].optional);
+ } else {
+ DebugPrintf("(int)%s = %d\n", param[0], var[element]);
+ }
+ }
+ }
+ break;
+ // String
+ case DVAR_STRING:
+ DebugPrintf("(string)%s = %s\n", param[0], ((Common::String *)_dvars[i].variable)->c_str());
+ break;
+ default:
+ DebugPrintf("%s = (unknown type)\n", param[0]);
+ break;
+ }
+ }
+
+ free(input);
+ return true;
+ }
+ }
+
+ DebugPrintf("Unknown command or variable\n");
+ free(input);
+ return true;
+}
+
+// returns true if something has been completed
+// completion has to be delete[]-ed then
+bool Debugger::tabComplete(const char *input, char*& completion) {
+ // very basic tab completion
+ // for now it just supports command completions
+
+ // adding completions of command parameters would be nice (but hard) :-)
+ // maybe also give a list of possible command completions?
+ // (but this will require changes to console)
+
+ if (strchr(input, ' '))
+ return false; // already finished the first word
+
+ unsigned int inputlen = strlen(input);
+
+ unsigned int matchlen = 0;
+ char match[30]; // the max. command name is 30 chars
+
+ for (int i = 0; i < _dcmd_count; i++) {
+ if (!strncmp(_dcmds[i].name, input, inputlen)) {
+ unsigned int commandlen = strlen(_dcmds[i].name);
+ if (commandlen == inputlen) { // perfect match
+ return false;
+ }
+ if (commandlen > inputlen) { // possible match
+ // no previous match
+ if (matchlen == 0) {
+ strcpy(match, _dcmds[i].name + inputlen);
+ matchlen = commandlen - inputlen;
+ } else {
+ // take common prefix of previous match and this command
+ unsigned int j;
+ for (j = 0; j < matchlen; j++) {
+ if (match[j] != _dcmds[i].name[inputlen + j]) break;
+ }
+ matchlen = j;
+ }
+ if (matchlen == 0)
+ return false;
+ }
+ }
+ }
+ if (matchlen == 0)
+ return false;
+
+ completion = new char[matchlen + 1];
+ memcpy(completion, match, matchlen);
+ completion[matchlen] = 0;
+ return true;
+}
+
+// Variable registration function
+void Debugger::DVar_Register(const char *varname, void *pointer, int type, int optional) {
+ assert(_dvar_count < ARRAYSIZE(_dvars));
+ strcpy(_dvars[_dvar_count].name, varname);
+ _dvars[_dvar_count].type = type;
+ _dvars[_dvar_count].variable = pointer;
+ _dvars[_dvar_count].optional = optional;
+
+ _dvar_count++;
+}
+
+// Command registration function
+void Debugger::DCmd_Register(const char *cmdname, Debuglet *debuglet) {
+ assert(_dcmd_count < ARRAYSIZE(_dcmds));
+ strcpy(_dcmds[_dcmd_count].name, cmdname);
+ _dcmds[_dcmd_count].debuglet = debuglet;
+
+ _dcmd_count++;
+}
+
+
+// Detach ("exit") the debugger
+bool Debugger::Cmd_Exit(int argc, const char **argv) {
+ _detach_now = true;
+ return false;
+}
+
+// Print a list of all registered commands (and variables, if any),
+// nicely word-wrapped.
+bool Debugger::Cmd_Help(int argc, const char **argv) {
+
+ int width, size, i;
+
+ DebugPrintf("Commands are:\n");
+ width = 0;
+ for (i = 0; i < _dcmd_count; i++) {
+ size = strlen(_dcmds[i].name) + 1;
+
+ if ((width + size) >= GUI::ConsoleDialog::kCharsPerLine) {
+ DebugPrintf("\n");
+ width = size;
+ } else
+ width += size;
+
+ DebugPrintf("%s ", _dcmds[i].name);
+ }
+ DebugPrintf("\n");
+
+ if (_dvar_count > 0) {
+ DebugPrintf("\n");
+ DebugPrintf("Variables are:\n");
+ width = 0;
+ for (i = 0; i < _dvar_count; i++) {
+ size = strlen(_dvars[i].name) + 1;
+
+ if ((width + size) >= GUI::ConsoleDialog::kCharsPerLine) {
+ DebugPrintf("\n");
+ width = size;
+ } else
+ width += size;
+
+ DebugPrintf("%s ", _dvars[i].name);
+ }
+ DebugPrintf("\n");
+ }
+
+ return true;
+}
+
+bool Debugger::Cmd_DebugFlagsList(int argc, const char **argv) {
+ const Common::Array<Common::EngineDebugLevel> &debugLevels = Common::listSpecialDebugLevels();
+
+ DebugPrintf("Engine debug levels:\n");
+ DebugPrintf("--------------------\n");
+ if (!debugLevels.size()) {
+ DebugPrintf("No engine debug levels\n");
+ return true;
+ }
+ for (uint i = 0; i < debugLevels.size(); ++i) {
+ DebugPrintf("'%s' - Description: %s\n", debugLevels[i].option.c_str(), debugLevels[i].description.c_str());
+ }
+ DebugPrintf("\n");
+ return true;
+}
+
+bool Debugger::Cmd_DebugFlagEnable(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("debugflag_enable <flag>\n");
+ } else {
+ if (Common::enableSpecialDebugLevel(argv[1])) {
+ DebugPrintf("Enabled debug flag '%s'\n", argv[1]);
+ } else {
+ DebugPrintf("Failed to enable debug flag '%s'\n", argv[1]);
+ }
+ }
+ return true;
+}
+
+bool Debugger::Cmd_DebugFlagDisable(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("debugflag_disable <flag>\n");
+ } else {
+ if (Common::disableSpecialDebugLevel(argv[1])) {
+ DebugPrintf("Disabled debug flag '%s'\n", argv[1]);
+ } else {
+ DebugPrintf("Failed to disable debug flag '%s'\n", argv[1]);
+ }
+ }
+ return true;
+}
+
+// Console handler
+#if USE_CONSOLE
+bool Debugger::debuggerInputCallback(GUI::ConsoleDialog *console, const char *input, void *refCon) {
+ Debugger *debugger = (Debugger *)refCon;
+
+ return debugger->parseCommand(input);
+}
+
+
+bool Debugger::debuggerCompletionCallback(GUI::ConsoleDialog *console, const char *input, char*& completion, void *refCon) {
+ Debugger *debugger = (Debugger *)refCon;
+
+ return debugger->tabComplete(input, completion);
+}
+
+#endif
+
+} // End of namespace GUI
diff --git a/gui/debugger.h b/gui/debugger.h
new file mode 100644
index 0000000000..7b07b1bca3
--- /dev/null
+++ b/gui/debugger.h
@@ -0,0 +1,158 @@
+/* ScummVM - Scumm Interpreter
+ * Copyright (C) 2002-2006 The ScummVM project
+ *
+ * This program 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; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program 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. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#ifndef GUI_DEBUGGER_H
+#define GUI_DEBUGGER_H
+
+namespace GUI {
+
+// Choose between text console or ScummConsole
+#define USE_CONSOLE 1
+
+#ifdef USE_CONSOLE
+class ConsoleDialog;
+#endif
+
+class Debugger {
+public:
+ Debugger();
+ virtual ~Debugger();
+
+ int DebugPrintf(const char *format, ...);
+
+#ifndef __SYMBIAN32__ // gcc/UIQ doesn't like the debugger code for some reason? Actually get a cc1plus core dump here :)
+ // FIXME: Fingolfin asks: This code *used* to be a problem when GUI::Debugger
+ // was a template class. But is it really still causing problems, or can
+ // this hack go away now?
+ virtual void onFrame();
+
+ virtual void attach(const char *entry = 0);
+#else
+ void onFrame() {}
+ void attach(const char *entry = 0) {}
+#endif
+ bool isAttached() const { return _isAttached; }
+
+protected:
+ class Debuglet {
+ public:
+ virtual ~Debuglet() {}
+ virtual bool operator()(int argc, const char **argv) = 0;
+ };
+
+ template <class T>
+ class DelegateDebuglet : public Debuglet {
+ typedef bool (T::*Method)(int argc, const char **argv);
+
+ T *_delegate;
+ const Method _method;
+ public:
+ DelegateDebuglet(T *delegate, Method method)
+ : _delegate(delegate), _method(method) {
+ assert(delegate != 0);
+ }
+ virtual bool operator()(int argc, const char **argv) {
+ return (_delegate->*_method)(argc, argv);
+ };
+ };
+
+ // Convenicence macro for registering a method of a debugger class
+ // as the current command.
+ #define WRAP_METHOD(cls, method) \
+ new DelegateDebuglet<cls>(this, &cls::method)
+
+ enum {
+ DVAR_BYTE,
+ DVAR_INT,
+ DVAR_BOOL,
+ DVAR_INTARRAY,
+ DVAR_STRING
+ };
+
+ struct DVar {
+ char name[30];
+ void *variable;
+ int type, optional;
+ };
+
+ struct DCmd {
+ char name[30];
+ Debuglet *debuglet;
+ };
+
+ int _frame_countdown;
+ bool _detach_now;
+
+ // TODO: Consider replacing the following two arrays by a Hashmap
+
+ int _dvar_count;
+ DVar _dvars[256];
+
+ int _dcmd_count;
+ DCmd _dcmds[256];
+
+private:
+ bool _isAttached;
+ char *_errStr;
+ bool _firstTime;
+ GUI::ConsoleDialog *_debuggerDialog;
+
+protected:
+ // Hook for subclasses: Called just before enter() is run
+ virtual void preEnter() {}
+
+ // Hook for subclasses: Called just after enter() was run
+ virtual void postEnter() {}
+
+ // Hook for subclasses: Process the given command line.
+ // Should return true if and only if argv[0] is a known command and was
+ // handled, false otherwise.
+ virtual bool handleCommand(int argc, const char **argv, bool &keepRunning);
+
+
+private:
+//protected:
+ void detach();
+ void enter();
+
+ bool parseCommand(const char *input);
+ bool tabComplete(const char *input, char*& completion);
+
+protected:
+ void DVar_Register(const char *varname, void *pointer, int type, int optional);
+ void DCmd_Register(const char *cmdname, Debuglet *debuglet);
+
+ bool Cmd_Exit(int argc, const char **argv);
+ bool Cmd_Help(int argc, const char **argv);
+ bool Cmd_DebugFlagsList(int argc, const char **argv);
+ bool Cmd_DebugFlagEnable(int argc, const char **argv);
+ bool Cmd_DebugFlagDisable(int argc, const char **argv);
+
+#if USE_CONSOLE
+private:
+ static bool debuggerInputCallback(GUI::ConsoleDialog *console, const char *input, void *refCon);
+ static bool debuggerCompletionCallback(GUI::ConsoleDialog *console, const char *input, char*& completion, void *refCon);
+#endif
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/module.mk b/gui/module.mk
index 4ec0140be8..bab4b31c94 100644
--- a/gui/module.mk
+++ b/gui/module.mk
@@ -5,6 +5,7 @@ MODULE_OBJS := \
browser.o \
chooser.o \
console.o \
+ debugger.o \
dialog.o \
editable.o \
EditTextWidget.o \