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

Last change on this file since 10365 was 10312, checked in by tbretz, 10 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.