source: trunk/FACT++/src/Shell.cc@ 17048

Last change on this file since 17048 was 10312, checked in by tbretz, 14 years ago
Some updates to the output of help messages.
File size: 13.8 KB
Line 
1// **************************************************************************
2/** @class Shell
3
4@brief Implementation of a console based user shell with an input and output window
5
6Shell is based on the ReadlineWindow class. It creates two windows
7(panels) using ncurses which are used for input and output. The output
8window panel is displayed on top of the input panel, but can be hidden
9for example by pressing F1.
10
11The idea is that continous messages like logging messages do not interfere
12with the input area, although one still has both diplayed in the same
13console window.
14
15To get a list of the command and functions supported by Shell
16type 'h' or 'help' at a command line prompt.
17
18The usage is quite simple. Instantiate an object of Shell with the
19programname as an argument. For its meaning see the base class
20documentation Readline::Readline(). The created input- and output-
21stream can be accessed through GetStreamIn() and GetStreamOut()
22whihc are both ostreams, one redirected to the input window and the
23other one redirected to the output window. Especially, GetStreamIn()
24should not be used while the Readline prompt is in progress, but can for
25example be used to display errors about what was entered.
26
27The recommanded way of usage is:
28
29\code
30
31 static Shell shell("myprog"); // readline will read the myprog.his history file
32
33 while (1)
34 {
35 string txt = shell.Prompt("prompt> ");
36 if (txt=="quit)
37 break;
38
39 // ... do something ...
40
41 shell.AddHistory(txt);
42 }
43
44 // On destruction redline will write the current history to the file
45 // By declaring the Shell static the correct terminal is restored even
46 // the program got killed (not if killed with SIGABRT)
47
48\endcode
49
50If for some reason the terminal is not correctly restored type (maybe blindly)
51<I>reset</I> in your console. This should restor everything back to normal.
52
53@section References
54
55 - <A HREF="http://cnswww.cns.cwru.edu/php/chet/readline/rltop.html">GNU Readline</A>
56 - <A HREF="http://www.gnu.org/software/ncurses">GNU Ncurses</A>
57
58
59@todo
60 - Introduce the possibility to scroll in both windows
61 - Add redisplay functionality for both panels if the console was resized
62
63*/
64// **************************************************************************
65#include "Shell.h"
66
67#include <fstream>
68#include <iostream>
69
70#include <signal.h> // SIGWINCH
71#include <sys/wait.h> // waitpid
72#include <sys/ioctl.h> // ioctl
73
74#include <panel.h>
75
76#define FORK_ALLOWED
77
78using namespace std;
79
80Shell *Shell::This = 0;
81
82// --------------------------------------------------------------------------
83//
84//! This initializes the Ncurses environment. Since the ncurses environment
85//! is global only one instance of this class is allowed.
86//!
87//! The first 8 color pairs (COLOR_PAIR) are set to the first 8 color
88//! with default background.
89//!
90//! The shells windows and panels are created. And their pointers are
91//! propagated to the two associated WindowLog streams.
92//!
93//! Also some key bindings are initialized.
94//
95Shell::Shell(const char *prgname) : ReadlineWindow(prgname),
96 fPanelHeight(13), fIsVisible(1), fLine(0)
97{
98 if (stdscr!=0)
99 {
100 endwin();
101 cout << "ERROR - Only one instance of class Shell is allowed." << endl;
102 exit(-1);
103 }
104
105 This = this;
106
107 // ---------------------- Setup ncurses -------------------------
108
109 initscr(); // Start curses mode
110
111 cbreak(); // Line buffering disabled, Pass on
112 noecho(); // Switch off echo mode
113 nonl(); // Associate return with CR
114
115 intrflush(stdscr, FALSE);
116 keypad(stdscr, FALSE); // Switch off keymapping for function keys
117
118 start_color(); // Initialize ncurses colors
119 use_default_colors(); // Assign terminal default colors to -1
120 //assume_default_colors(-1, -1); // standard terminal colors assigned to pair 0
121
122 // Setup colors
123 for (int i=1; i<8; i++)
124 init_pair(i, i, -1); // -1: def background
125
126 signal(SIGWINCH, HandleResizeImp); // Attach HandleResize to SIGWINCH signal
127
128 // ---------------------- Setup pansl --------------------------
129
130 // Create the necessary windows
131 WINDOW *wins[4];
132 CreateWindows(wins);
133
134 // Initialize the panels
135 fPanelIn = new_panel(wins[0]);
136 fPanelFrame = new_panel(wins[1]);
137 fPanelOut = new_panel(wins[2]);
138
139 win.SetWindow(wins[0]);
140 wout.SetWindow(wins[2]);
141
142 // Get the panels into the right order for startup
143 ShowHide(1);
144
145 // Setup Readline
146 SetWindow(wins[0]);
147 SetColorPrompt(COLOR_PAIR(COLOR_BLUE));
148
149 // ------------------- Setup key bindings -----------------------
150 BindKeySequence("\033OP", rl_proc_F1);
151 BindKeySequence("\033[1;5B", rl_scroll_top);
152 BindKeySequence("\033[1;5A", rl_scroll_top);
153 BindKeySequence("\033[1;3A", rl_scroll_bot);
154 BindKeySequence("\033[1;3B", rl_scroll_bot);
155 BindKeySequence("\033[5;3~", rl_top_inc);
156 BindKeySequence("\033[6;3~", rl_top_dec);
157 BindKeySequence("\033+", rl_top_resize);
158 BindKeySequence("\033-", rl_top_resize);
159
160 /*
161 rl_bind_keyseq("\033\t", rl_complete); // Meta-Tab
162 rl_bind_keyseq("\033[1~", home); // Home (console)
163 rl_bind_keyseq("\033[H", home); // Home (x)
164 rl_bind_keyseq("\033[4~", end); // End (console)
165 rl_bind_keyseq("\033[F", end); // End (x)
166 rl_bind_keyseq("\033[A", up); // Up
167 rl_bind_keyseq("\033[B", down); // Down
168 rl_bind_keyseq("\033[[B", accept); // F2 (console)
169 rl_bind_keyseq("\033OQ", accept); // F2 (x)
170 rl_bind_keyseq("\033[21~", cancel); // F10
171 */
172
173 // Ctrl+dn: \033[1;5B
174 // Ctrl+up: \033[1;5A
175 // Alt+up: \033[1;3A
176 // Alt+dn: \033[1;3B
177 // Alt+pg up: \033[5;3~
178 // Alt+pg dn: \033[6;3~
179}
180
181// --------------------------------------------------------------------------
182//
183//! Ends the ncurses environment by calling endwin().
184//
185Shell::~Shell()
186{
187 // Maybe not needed because the window is more or less valid until the
188 // object is destructed anyway.
189 //win.SetWindow(0);
190 //wout.SetWindow(0);
191 //SetWindow(0);
192
193 endwin();
194 cout << "The end." << endl;
195}
196
197// --------------------------------------------------------------------------
198//
199//! This function gets the windows into the expected order which is:
200//!
201//! @param v
202//! - \b 0: Do not show the output panel
203//! - \b 1: Show the output panel
204//! - \b -1: Toggle the visibility of the output panel
205//! - \b -2: Just update the panels, do not change their visibility
206//
207void Shell::ShowHide(int v)
208{
209 if (v>-2)
210 fIsVisible = v==-1 ? !fIsVisible : v;
211
212 if (fIsVisible)
213 {
214 show_panel(fPanelIn);
215 show_panel(fPanelFrame);
216 show_panel(fPanelOut);
217 }
218 else
219 {
220 show_panel(fPanelIn);
221 hide_panel(fPanelFrame);
222 hide_panel(fPanelOut);
223 }
224
225 update_panels();
226 doupdate();
227}
228
229
230// --------------------------------------------------------------------------
231//
232//! Creates the windows to be used as panels and draws a frame around one
233//!
234//! @param w
235//! pointers to the three windows which have been created are returned
236//!
237//! @param all
238//! If all is false do not (re-)create the bottom or input-window
239//
240void Shell::CreateWindows(WINDOW *w[3], int all)
241{
242 int maxx, maxy;
243 getmaxyx(stdscr, maxy, maxx);
244
245 int separator = maxy-fPanelHeight;
246
247 WINDOW *new_in = all ? newwin(maxy, maxx, 0, 0) : 0;
248 WINDOW *new_frame = newwin(separator-1, maxx, 0, 0);
249 WINDOW *new_out = newwin(separator-1-2, maxx-2, 1, 1);
250
251 box(new_frame, 0,0);
252 wmove(new_frame, 0, 1);
253 waddch(new_frame, ACS_RTEE);
254 wprintw(new_frame, " F1 ");
255 waddch(new_frame, ACS_LTEE);
256
257 scrollok(new_out, true);
258 leaveok (new_out, true);
259
260 if (new_in)
261 {
262 scrollok(new_in, true); // Allow scrolling
263 leaveok (new_in, false); // Move the cursor with the output
264
265 wmove(new_in, maxy-1, 0);
266 }
267
268 w[0] = new_in;
269 w[1] = new_frame;
270 w[2] = new_out;
271}
272
273// --------------------------------------------------------------------------
274//
275//! Key binding for F1. Toggles upper panel by calling ShowHide(-1)
276//
277int Shell::rl_proc_F1(int /*cnt*/, int /*key*/)
278{
279 This->ShowHide(-1); // toggle
280 return 0;
281}
282
283int Shell::rl_scroll_top(int, int key)
284{
285 This->win << "Scroll " << key << endl;
286 return 0;
287}
288
289int Shell::rl_scroll_bot(int, int key)
290{
291 This->win << "Scroll " << key << endl;
292 return 0;
293}
294
295int Shell::rl_top_inc(int, int key)
296{
297 This->win << "Increase " << key << endl;
298 return 0;
299}
300
301int Shell::rl_top_dec(int, int key)
302{
303 This->win << "Increase " << key << endl;
304 return 0;
305}
306
307int Shell::rl_top_resize(int, int key)
308{
309 This->Resize(key=='+' ? This->fPanelHeight-1 : This->fPanelHeight+1);
310 return 0;
311}
312
313
314// --------------------------------------------------------------------------
315//
316//! Signal handler for SIGWINCH, calls HandleResize
317//
318void Shell::HandleResizeImp(int)
319{
320 This->HandleResize();
321}
322
323// --------------------------------------------------------------------------
324//
325//! Signal handler for SIGWINCH. It resizes the terminal and all panels
326//! according to the new terminal size and redisplay the backlog buffer
327//! in both windows
328//!
329//! @todo
330//! Maybe there are more efficient ways than to display the whole buffers
331//
332void Shell::HandleResize()
333{
334 // Get the new terminal size
335 struct winsize w;
336 ioctl(0, TIOCGWINSZ, &w);
337
338 // propagate it to the terminal
339 resize_term(w.ws_row, w.ws_col);
340
341 // Store the pointer to the old windows
342 WINDOW *w_in = panel_window(fPanelIn);
343 WINDOW *w_frame = panel_window(fPanelFrame);
344 WINDOW *w_out = panel_window(fPanelOut);
345
346 // Create new windows
347 WINDOW *wins[3];
348 CreateWindows(wins);
349
350 // Redirect the streams and the readline output to the new windows
351 win.SetWindow(wins[0]);
352 wout.SetWindow(wins[2]);
353
354 SetWindow(wins[0]);
355
356 // Replace windows in the panels
357 replace_panel(fPanelIn, wins[0]);
358 replace_panel(fPanelFrame, wins[1]);
359 replace_panel(fPanelOut, wins[2]);
360
361 // delete the old obsolete windows
362 delwin(w_in);
363 delwin(w_out);
364 delwin(w_frame);
365
366 // FIXME: NEEDED also in Redisplay panel
367 //Redisplay();
368
369 // Redisplay their contents
370 win.Display();
371 wout.Display();
372}
373
374// --------------------------------------------------------------------------
375//
376//! This resized the top panel or output panel as requested by the argument.
377//! The argument is the number of lines which are kept free for the input
378//! panel below the top panel
379//!
380//! @returns
381//! always true
382//
383bool Shell::Resize(int h)
384{
385 // Get curretn terminal size
386 int lines, cols;
387 getmaxyx(stdscr, lines, cols);
388
389 // Check if we are in a valid range
390 if (h<1 || h>lines-5)
391 return false;
392
393 // Set new height for panel to be kept free
394 fPanelHeight = h;
395
396 // Store the pointers of the old windows associated with the panels
397 // which should be resized
398 WINDOW *w_frame = panel_window(fPanelFrame);
399 WINDOW *w_out = panel_window(fPanelOut);
400
401 // Create new windows
402 WINDOW *wins[3];
403 CreateWindows(wins, false);
404
405 // Redirect the output stream to the new window
406 wout.SetWindow(wins[2]);
407
408 // Replace the windows associated with the panels
409 replace_panel(fPanelFrame, wins[1]);
410 replace_panel(fPanelOut, wins[2]);
411
412 // delete the ols windows
413 delwin(w_out);
414 delwin(w_frame);
415
416 // FIXME: NEEDED also in Redisplay panel
417 //Redisplay();
418
419 // Redisplay the contents
420 wout.Display();
421
422 return true;
423}
424
425bool Shell::PrintKeyBindings()
426{
427 ReadlineColor::PrintKeyBindings(win);
428 win << " " << kUnderline << "Special key bindings:" << endl << endl;;
429 win << kBold << " F1 " << kReset << "Toggle visibility of upper panel" << endl;
430 win << endl;
431 return true;
432}
433
434bool Shell::PrintGeneralHelp()
435{
436 ReadlineColor::PrintGeneralHelp(win, GetName());
437 win << kBold << " hide " << kReset << "Hide upper panel." << endl;
438 win << kBold << " show " << kReset << "Show upper panel." << endl;
439 win << kBold << " height <h> " << kReset << "Set height of upper panel to h." << endl;
440 win << endl;
441 return true;
442}
443
444// --------------------------------------------------------------------------
445//
446//! Processes the command provided by the Shell-class.
447//!
448//! @returns
449//! whether a command was successfully processed or could not be found
450//
451bool Shell::Process(const string &str)
452{
453 // Implement readline commands:
454 // rl set (rl_variable_bind(..))
455 // rl_read_init_file(filename)
456 // int rl_add_defun (const char *name, rl_command_func_t *function, int key)
457
458 if (ReadlineColor::Process(win, str))
459 return true;
460
461 if (Readline::Process(str))
462 return true;
463
464 // ----------- ReadlineNcurses -----------
465
466 if (string(str)=="hide")
467 {
468 ShowHide(0);
469 return true;
470 }
471 if (string(str)=="show")
472 {
473 ShowHide(1);
474 return true;
475 }
476
477 if (str.substr(0, 7)=="height ")
478 {
479 int h;
480 sscanf(str.c_str()+7, "%d", &h);
481 return Resize(h);
482 }
483
484 if (str=="d")
485 {
486 wout.Display();
487 return true;
488 }
489
490 return false;
491}
492
493// --------------------------------------------------------------------------
494//
495//! Overwrites Shutdown. It's main purpose is to re-output
496//! the prompt and the buffer using the WindowLog stream so that it is
497//! buffered in its backlog.
498//!
499//! @param buf
500//! A pointer to the buffer returned by readline
501//!
502void Shell::Shutdown(const char *buf)
503{
504 ReadlineWindow::Shutdown(buf);
505
506 // Now move the cursor to the start of the prompt
507 RewindCursor();
508
509 // Output the text ourself to get it into the backlog
510 // buffer of win. We cannot use GetBuffer() because rl_end
511 // is not updated finally.
512 win << kBlue << GetPrompt() << kReset << buf << endl;
513}
Note: See TracBrowser for help on using the repository browser.