fix menu width, didn't committ the tileset fix, miscelaneous small fixes
[supertux.git] / src / console.cpp
index 78acb1e..2867c9e 100644 (file)
 //  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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-
 #include <config.h>
+
 #include <iostream>
 #include "console.hpp"
 #include "video/drawing_context.hpp"
 #include "video/surface.hpp"
+#include "scripting/squirrel_error.hpp"
+#include "scripting/wrapper_util.hpp"
 #include "player_status.hpp"
+#include "script_manager.hpp"
 #include "main.hpp"
 #include "resources.hpp"
 
-namespace {
-  int ticks; // TODO: use a clock?
-}
+/// speed (pixels/s) the console closes
+static const float FADE_SPEED = 1;
 
-Console::Console(DrawingContext* context) : context(context)
+Console::Console()
+  : backgroundOffset(0), height(0), alpha(1.0), offset(0), focused(false),
+    stayOpen(0)
 {
-  background = new Surface("images/engine/console.jpg");
+  font.reset(new Font("images/engine/fonts/white-small.png",
+                      "images/engine/fonts/shadow-small.png", 8, 9, 1));
+  background.reset(new Surface("images/engine/console.png"));
+  background2.reset(new Surface("images/engine/console2.png"));
 }
 
 Console::~Console() 
 {
-  delete background;
 }
 
 void 
@@ -62,27 +68,173 @@ Console::flush(ConsoleStreamBuffer* buffer)
   }
 }
 
+void
+Console::execute_script(const std::string& command)
+{
+  using namespace Scripting;
+
+  HSQUIRRELVM vm = ScriptManager::instance->get_vm();
+
+  if(command == "")
+    return;
+  
+  int oldtop = sq_gettop(vm); 
+  try {
+    if(SQ_FAILED(sq_compilebuffer(vm, command.c_str(), command.length(),
+                 "", SQTrue)))
+      throw SquirrelError(vm, "Couldn't compile command");
+
+    sq_pushroottable(vm);
+    if(SQ_FAILED(sq_call(vm, 1, SQTrue)))
+      throw SquirrelError(vm, "Problem while executing command");
+
+    if(sq_gettype(vm, -1) != OT_NULL)
+      addLine(squirrel2string(vm, -1));
+  } catch(std::exception& e) {
+    addLine(e.what());
+  }
+  int newtop = sq_gettop(vm);
+  if(newtop < oldtop) {
+    log_fatal << "Script destroyed squirrel stack..." << std::endl;
+  } else {
+    sq_settop(vm, oldtop);
+  }
+}
+
+void
+Console::backspace()
+{
+  std::string s = inputBuffer.str();
+  if (s.length() > 0) {
+    s.erase(s.length()-1);
+    inputBuffer.str(s);
+    inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
+  }
+}
+
+void
+Console::scroll(int numLines)
+{
+  offset += numLines;
+  if (offset > 0) offset = 0;
+}
+
+void
+Console::autocomplete()
+{
+  std::string cmdPart = inputBuffer.str();
+  addLine("> "+cmdPart);
+
+  std::string cmdList = "";
+  int cmdListLen = 0;
+  for (std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.begin(); i != commands.end(); i++) {
+    std::string cmdKnown = i->first;
+    if (cmdKnown.substr(0, cmdPart.length()) == cmdPart) {
+      if (cmdListLen > 0) cmdList = cmdList + ", ";
+      cmdList = cmdList + cmdKnown;
+      cmdListLen++;
+    }
+  }
+  if (cmdListLen == 0) addLine("No known command starts with \""+cmdPart+"\"");
+  if (cmdListLen == 1) {
+    inputBuffer.str(cmdList);
+    inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
+  }
+  if (cmdListLen > 1) addLine(cmdList);
+}
+
 void 
 Console::addLine(std::string s) 
 {
+  std::cerr << s << std::endl;
+  while (s.length() > 99) {
+    lines.push_front(s.substr(0, 99-3)+"...");
+    s = "..."+s.substr(99-3);
+  }
   lines.push_front(s);
-  if (lines.size() >= 256) lines.pop_back();
+  
+  while (lines.size() >= 1000)
+    lines.pop_back();
+  
   if (height < 64) {
-    if (height < 4+9) height=4+9;
-    height+=9;
+    if(height < 4)
+      height = 4;
+    height += font->get_height();
   }
-  ticks=120;
-  std::cerr << s << std::endl;
+
+  alpha = 1.0;
+  if(stayOpen < 5)
+    stayOpen += 1;
 }
 
 void
 Console::parse(std::string s) 
 {
-  for (std::list<ConsoleCommandReceiver*>::iterator i = commandReceivers.begin(); i != commandReceivers.end(); i++) {
-    ConsoleCommandReceiver* ccr = *i;
-    if (ccr->consoleCommand(s) == true) return;
+  // make sure we actually have something to parse
+  if (s.length() == 0) return;
+       
+  // split line into list of args
+  std::vector<std::string> args;
+  size_t start = 0;
+  size_t end = 0;
+  while (1) {
+    start = s.find_first_not_of(" ,", end);
+    end = s.find_first_of(" ,", start);
+    if (start == s.npos) break;
+    args.push_back(s.substr(start, end-start));
+  }
+
+  // command is args[0]
+  if (args.size() == 0) return;
+  std::string command = args.front();
+  args.erase(args.begin());
+
+  // ignore if it's an internal command
+  if (consoleCommand(command,args)) return;
+
+  // look up registered ccr
+  std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(command);
+  if ((i == commands.end()) || (i->second.size() == 0)) {
+    try {
+      execute_script(s);
+    } catch(std::exception& e) {
+      addLine(e.what());
+    }
+    return;
+  }
+
+  // send command to the most recently registered ccr
+  ConsoleCommandReceiver* ccr = i->second.front();
+  if (ccr->consoleCommand(command, args) != true) log_warning << "Sent command to registered ccr, but command was unhandled" << std::endl;
+}
+
+bool
+Console::consoleCommand(std::string command, std::vector<std::string> arguments) 
+{
+  if (command == "ccrs") {
+    if (arguments.size() != 1) {
+      log_info << "Usage: ccrs <command>" << std::endl;
+      return true;
+    }
+    std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(arguments[0]);
+    if ((i == commands.end()) || (i->second.size() == 0)) {
+      log_info << "unknown command: \"" << arguments[0] << "\"" << std::endl;
+      return true;
+    }
+
+    std::ostringstream ccr_list;
+    std::list<ConsoleCommandReceiver*> &ccrs = i->second;
+    std::list<ConsoleCommandReceiver*>::iterator j;
+    for (j = ccrs.begin(); j != ccrs.end(); j++) {
+      if (j != ccrs.begin()) ccr_list << ", ";
+      ccr_list << "[" << *j << "]";
+    }
+
+    log_info << "registered ccrs for \"" << arguments[0] << "\": " << ccr_list.str() << std::endl;
+    return true;
   }
-  addLine("unknown command: \"" + s + "\"");
+
+  return false;
 }
 
 bool
@@ -95,7 +247,8 @@ void
 Console::show()
 {
   focused = true;
-  height = 128;
+  height = 256;
+  alpha = 1.0;
 }
 
 void 
@@ -103,56 +256,110 @@ Console::hide()
 {
   focused = false;
   height = 0;
+  stayOpen = 0;
+
+  // clear input buffer
+  inputBuffer.str(std::string());
 }
 
 void 
-Console::draw() 
-{
-  if (height == 0) return;
-  if (!focused) {
-    if (ticks-- < 0) {
-      height-=1;
-      ticks=0;
-      if (height < 0) height=0;
+Console::toggle()
+{
+  if (Console::hasFocus()) {
+    Console::hide(); 
+  } 
+  else { 
+    Console::show();
+  }
+}
+
+void
+Console::update(float elapsed_time)
+{
+  if(stayOpen > 0) {
+    stayOpen -= elapsed_time;
+    if(stayOpen < 0)
+      stayOpen = 0;
+  } else if(!focused && height > 0) {
+    alpha -= elapsed_time * FADE_SPEED;
+    if(alpha < 0) {
+      alpha = 0;
+      height = 0;
     }
-    if (height == 0) return;
   }
+}
+
+void 
+Console::draw(DrawingContext& context)
+{
+  if (height == 0)
+    return;
+
+  int layer = LAYER_GUI + 1;
 
-  context->draw_surface(background, Vector(SCREEN_WIDTH/2 - background->get_width()/2, height - background->get_height()), LAYER_FOREGROUND1+1);
+  context.push_transform();
+  context.set_alpha(alpha);
+  context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 - background->get_width() + backgroundOffset, height - background->get_height()), layer);
+  context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 + backgroundOffset, height - background->get_height()), layer);
+  context.draw_surface(background.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2, height - background->get_height()), layer);
+  backgroundOffset+=10;
+  if (backgroundOffset > (int)background->get_width()) backgroundOffset -= (int)background->get_width();
 
   int lineNo = 0;
 
   if (focused) {
     lineNo++;
     float py = height-4-1*9;
-    context->draw_text(white_small_text, "> "+inputBuffer.str(), Vector(4, py), LEFT_ALLIGN, LAYER_FOREGROUND1+1);
+    context.draw_text(font.get(), "> "+inputBuffer.str()+"_", Vector(4, py), LEFT_ALLIGN, layer);
   }
 
+  int skipLines = -offset;
   for (std::list<std::string>::iterator i = lines.begin(); i != lines.end(); i++) {
+    if (skipLines-- > 0) continue;
     lineNo++;
     float py = height-4-lineNo*9;
     if (py < -9) break;
-    context->draw_text(white_small_text, *i, Vector(4, py), LEFT_ALLIGN, LAYER_FOREGROUND1+1);
+    context.draw_text(font.get(), *i, Vector(4, py), LEFT_ALLIGN, layer);
   }
+
+  context.pop_transform();
 }
 
 void 
-Console::registerCommandReceiver(ConsoleCommandReceiver* ccr)
+Console::registerCommand(std::string command, ConsoleCommandReceiver* ccr)
 {
-  commandReceivers.push_front(ccr);
+  commands[command].push_front(ccr);
 }
 
 void 
-Console::unregisterCommandReceiver(ConsoleCommandReceiver* ccr)
+Console::unregisterCommand(std::string command, ConsoleCommandReceiver* ccr)
 {
-  std::list<ConsoleCommandReceiver*>::iterator i = find(commandReceivers.begin(), commandReceivers.end(), ccr);
-  if (i != commandReceivers.end()) commandReceivers.erase(i);
+  std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(command);
+  if ((i == commands.end()) || (i->second.size() == 0)) {
+    log_warning << "Command \"" << command << "\" not associated with a command receiver. Not dissociated." << std::endl;
+    return;
+  }
+  std::list<ConsoleCommandReceiver*>::iterator j = find(i->second.begin(), i->second.end(), ccr);
+  if (j == i->second.end()) {
+    log_warning << "Command \"" << command << "\" not associated with given command receiver. Not dissociated." << std::endl;
+    return;
+  }
+  i->second.erase(j);
+}
+
+void 
+Console::unregisterCommands(ConsoleCommandReceiver* ccr)
+{
+  for (std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.begin(); i != commands.end(); i++) {
+    std::list<ConsoleCommandReceiver*> &ccrs = i->second;
+    std::list<ConsoleCommandReceiver*>::iterator j;
+    while ((j = find(ccrs.begin(), ccrs.end(), ccr)) != ccrs.end()) {
+      ccrs.erase(j);
+    }
+  }
 }
 
-int Console::height = 0;
-bool Console::focused = false;
-std::list<std::string> Console::lines;
-std::list<ConsoleCommandReceiver*> Console::commandReceivers;
+Console* Console::instance = NULL;
 ConsoleStreamBuffer Console::inputBuffer;
 ConsoleStreamBuffer Console::outputBuffer;
 std::ostream Console::input(&Console::inputBuffer);