Massive copyright update. I'm sorry if I'm crediting Matze for something he didn...
[supertux.git] / src / console.cpp
1 //  $Id$
2 //
3 //  SuperTux - Console
4 //  Copyright (C) 2006 Christoph Sommer <christoph.sommer@2006.expires.deltadevelopment.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 #include <config.h>
20
21 #include <iostream>
22 #include "console.hpp"
23 #include "video/drawing_context.hpp"
24 #include "video/surface.hpp"
25 #include "scripting/squirrel_error.hpp"
26 #include "scripting/wrapper_util.hpp"
27 #include "player_status.hpp"
28 #include "script_manager.hpp"
29 #include "main.hpp"
30 #include "resources.hpp"
31
32 /// speed (pixels/s) the console closes
33 static const float FADE_SPEED = 1;
34
35 Console::Console()
36   : backgroundOffset(0), height(0), alpha(1.0), offset(0), focused(false),
37     stayOpen(0)
38 {
39   font.reset(new Font("images/engine/fonts/white-small.png",
40                       "images/engine/fonts/shadow-small.png", 8, 9, 1));
41   background.reset(new Surface("images/engine/console.png"));
42   background2.reset(new Surface("images/engine/console2.png"));
43 }
44
45 Console::~Console() 
46 {
47 }
48
49 void 
50 Console::flush(ConsoleStreamBuffer* buffer) 
51 {
52   if (buffer == &outputBuffer) {
53     std::string s = outputBuffer.str();
54     if ((s.length() > 0) && ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r'))) {
55       while ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r')) s.erase(s.length()-1);
56       addLine(s);
57       outputBuffer.str(std::string());
58     }
59   }
60   if (buffer == &inputBuffer) {
61     std::string s = inputBuffer.str();
62     if ((s.length() > 0) && ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r'))) {
63       while ((s[s.length()-1] == '\n') || (s[s.length()-1] == '\r')) s.erase(s.length()-1);
64       addLine("> "+s);
65       parse(s);
66       inputBuffer.str(std::string());
67     }
68   }
69 }
70
71 void
72 Console::execute_script(const std::string& command)
73 {
74   using namespace Scripting;
75
76   HSQUIRRELVM vm = ScriptManager::instance->get_vm();
77
78   if(command == "")
79     return;
80   
81   int oldtop = sq_gettop(vm); 
82   try {
83     if(SQ_FAILED(sq_compilebuffer(vm, command.c_str(), command.length(),
84                  "", SQTrue)))
85       throw SquirrelError(vm, "Couldn't compile command");
86
87     sq_pushroottable(vm);
88     if(SQ_FAILED(sq_call(vm, 1, SQTrue)))
89       throw SquirrelError(vm, "Problem while executing command");
90
91     if(sq_gettype(vm, -1) != OT_NULL)
92       addLine(squirrel2string(vm, -1));
93   } catch(std::exception& e) {
94     addLine(e.what());
95   }
96   int newtop = sq_gettop(vm);
97   if(newtop < oldtop) {
98     log_fatal << "Script destroyed squirrel stack..." << std::endl;
99   } else {
100     sq_settop(vm, oldtop);
101   }
102 }
103
104 void
105 Console::backspace()
106 {
107   std::string s = inputBuffer.str();
108   if (s.length() > 0) {
109     s.erase(s.length()-1);
110     inputBuffer.str(s);
111     inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
112   }
113 }
114
115 void
116 Console::scroll(int numLines)
117 {
118   offset += numLines;
119   if (offset > 0) offset = 0;
120 }
121
122 void
123 Console::autocomplete()
124 {
125   std::string cmdPart = inputBuffer.str();
126   addLine("> "+cmdPart);
127
128   std::string cmdList = "";
129   int cmdListLen = 0;
130   for (std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.begin(); i != commands.end(); i++) {
131     std::string cmdKnown = i->first;
132     if (cmdKnown.substr(0, cmdPart.length()) == cmdPart) {
133       if (cmdListLen > 0) cmdList = cmdList + ", ";
134       cmdList = cmdList + cmdKnown;
135       cmdListLen++;
136     }
137   }
138   if (cmdListLen == 0) addLine("No known command starts with \""+cmdPart+"\"");
139   if (cmdListLen == 1) {
140     inputBuffer.str(cmdList);
141     inputBuffer.pubseekoff(0, std::ios_base::end, std::ios_base::out);
142   }
143   if (cmdListLen > 1) addLine(cmdList);
144 }
145
146 void 
147 Console::addLine(std::string s) 
148 {
149   std::cerr << s << std::endl;
150   while (s.length() > 99) {
151     lines.push_front(s.substr(0, 99-3)+"...");
152     s = "..."+s.substr(99-3);
153   }
154   lines.push_front(s);
155   
156   while (lines.size() >= 1000)
157     lines.pop_back();
158   
159   if (height < 64) {
160     if(height < 4)
161       height = 4;
162     height += font->get_height();
163   }
164
165   alpha = 1.0;
166   if(stayOpen < 5)
167     stayOpen += 1;
168 }
169
170 void
171 Console::parse(std::string s) 
172 {
173   // make sure we actually have something to parse
174   if (s.length() == 0) return;
175         
176   // split line into list of args
177   std::vector<std::string> args;
178   size_t start = 0;
179   size_t end = 0;
180   while (1) {
181     start = s.find_first_not_of(" ,", end);
182     end = s.find_first_of(" ,", start);
183     if (start == s.npos) break;
184     args.push_back(s.substr(start, end-start));
185   }
186
187   // command is args[0]
188   if (args.size() == 0) return;
189   std::string command = args.front();
190   args.erase(args.begin());
191
192   // ignore if it's an internal command
193   if (consoleCommand(command,args)) return;
194
195   // look up registered ccr
196   std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(command);
197   if ((i == commands.end()) || (i->second.size() == 0)) {
198     try {
199       execute_script(s);
200     } catch(std::exception& e) {
201       addLine(e.what());
202     }
203     return;
204   }
205
206   // send command to the most recently registered ccr
207   ConsoleCommandReceiver* ccr = i->second.front();
208   if (ccr->consoleCommand(command, args) != true) log_warning << "Sent command to registered ccr, but command was unhandled" << std::endl;
209 }
210
211 bool
212 Console::consoleCommand(std::string command, std::vector<std::string> arguments) 
213 {
214   if (command == "ccrs") {
215     if (arguments.size() != 1) {
216       log_info << "Usage: ccrs <command>" << std::endl;
217       return true;
218     }
219     std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(arguments[0]);
220     if ((i == commands.end()) || (i->second.size() == 0)) {
221       log_info << "unknown command: \"" << arguments[0] << "\"" << std::endl;
222       return true;
223     }
224
225     std::ostringstream ccr_list;
226     std::list<ConsoleCommandReceiver*> &ccrs = i->second;
227     std::list<ConsoleCommandReceiver*>::iterator j;
228     for (j = ccrs.begin(); j != ccrs.end(); j++) {
229       if (j != ccrs.begin()) ccr_list << ", ";
230       ccr_list << "[" << *j << "]";
231     }
232
233     log_info << "registered ccrs for \"" << arguments[0] << "\": " << ccr_list.str() << std::endl;
234     return true;
235   }
236
237   return false;
238 }
239
240 bool
241 Console::hasFocus() 
242 {
243   return focused;
244 }
245
246 void
247 Console::show()
248 {
249   focused = true;
250   height = 256;
251   alpha = 1.0;
252 }
253
254 void 
255 Console::hide()
256 {
257   focused = false;
258   height = 0;
259   stayOpen = 0;
260
261   // clear input buffer
262   inputBuffer.str(std::string());
263 }
264
265 void 
266 Console::toggle()
267 {
268   if (Console::hasFocus()) {
269     Console::hide(); 
270   } 
271   else { 
272     Console::show();
273   }
274 }
275
276 void
277 Console::update(float elapsed_time)
278 {
279   if(stayOpen > 0) {
280     stayOpen -= elapsed_time;
281     if(stayOpen < 0)
282       stayOpen = 0;
283   } else if(!focused && height > 0) {
284     alpha -= elapsed_time * FADE_SPEED;
285     if(alpha < 0) {
286       alpha = 0;
287       height = 0;
288     }
289   }
290 }
291
292 void 
293 Console::draw(DrawingContext& context)
294 {
295   if (height == 0)
296     return;
297
298   int layer = LAYER_GUI + 1;
299
300   context.push_transform();
301   context.set_alpha(alpha);
302   context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 - background->get_width() + backgroundOffset, height - background->get_height()), layer);
303   context.draw_surface(background2.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2 + backgroundOffset, height - background->get_height()), layer);
304   context.draw_surface(background.get(), Vector(SCREEN_WIDTH/2 - background->get_width()/2, height - background->get_height()), layer);
305   backgroundOffset+=10;
306   if (backgroundOffset > (int)background->get_width()) backgroundOffset -= (int)background->get_width();
307
308   int lineNo = 0;
309
310   if (focused) {
311     lineNo++;
312     float py = height-4-1*9;
313     context.draw_text(font.get(), "> "+inputBuffer.str()+"_", Vector(4, py), LEFT_ALLIGN, layer);
314   }
315
316   int skipLines = -offset;
317   for (std::list<std::string>::iterator i = lines.begin(); i != lines.end(); i++) {
318     if (skipLines-- > 0) continue;
319     lineNo++;
320     float py = height-4-lineNo*9;
321     if (py < -9) break;
322     context.draw_text(font.get(), *i, Vector(4, py), LEFT_ALLIGN, layer);
323   }
324
325   context.pop_transform();
326 }
327
328 void 
329 Console::registerCommand(std::string command, ConsoleCommandReceiver* ccr)
330 {
331   commands[command].push_front(ccr);
332 }
333
334 void 
335 Console::unregisterCommand(std::string command, ConsoleCommandReceiver* ccr)
336 {
337   std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.find(command);
338   if ((i == commands.end()) || (i->second.size() == 0)) {
339     log_warning << "Command \"" << command << "\" not associated with a command receiver. Not dissociated." << std::endl;
340     return;
341   }
342   std::list<ConsoleCommandReceiver*>::iterator j = find(i->second.begin(), i->second.end(), ccr);
343   if (j == i->second.end()) {
344     log_warning << "Command \"" << command << "\" not associated with given command receiver. Not dissociated." << std::endl;
345     return;
346   }
347   i->second.erase(j);
348 }
349
350 void 
351 Console::unregisterCommands(ConsoleCommandReceiver* ccr)
352 {
353   for (std::map<std::string, std::list<ConsoleCommandReceiver*> >::iterator i = commands.begin(); i != commands.end(); i++) {
354     std::list<ConsoleCommandReceiver*> &ccrs = i->second;
355     std::list<ConsoleCommandReceiver*>::iterator j;
356     while ((j = find(ccrs.begin(), ccrs.end(), ccr)) != ccrs.end()) {
357       ccrs.erase(j);
358     }
359   }
360 }
361
362 Console* Console::instance = NULL;
363 ConsoleStreamBuffer Console::inputBuffer;
364 ConsoleStreamBuffer Console::outputBuffer;
365 std::ostream Console::input(&Console::inputBuffer);
366 std::ostream Console::output(&Console::outputBuffer);
367