Mercurial > jwe > qt-gui-with-push-parser
diff gui-main.cpp @ 0:dff751fb985c
initial revision
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Mon, 13 May 2019 09:48:06 -0500 |
parents | |
children | 08df60a01bc1 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gui-main.cpp Mon May 13 09:48:06 2019 -0500 @@ -0,0 +1,445 @@ +#include <iostream> +#include <sstream> +#include <string> + +#include <cstdlib> +#include <cstring> + +#include <QApplication> +#include <QKeyEvent> +#include <QTextDocument> +#include <QTextEdit> + +#include "gui-main.h" + +#include "gui-main.h" +#include "parse.h" + +#include <readline/readline.h> +#include <readline/history.h> + +namespace gui +{ + static int available_char = 0; + + static inline int ctrl (int c) + { + return c & 0x1f; + } + + static command_window *calc_interaction_window = 0; + + static int getc (FILE *) + { + int tmp = available_char; + available_char = 0; + return tmp; + } + + static void redisplay (void) + { + if (calc_interaction_window) + calc_interaction_window->redisplay (); + } + + static void prep_term (int) + { + } + + static void deprep_term (void) + { + } + + static void accept_line (char *line) + { + if (calc_interaction_window) + calc_interaction_window->accept (line ? line : ""); + } + + static void display_completion_matches (char **matches, int num_matches, + int /* max_length */) + { + if (calc_interaction_window) + { + std::ostringstream buf; + + if (num_matches > 1) + buf << "\n"; + + for (int i = 1; i < num_matches; i++) + buf << matches[i] << "\n"; + + calc_interaction_window->insert_at_end (buf.str ()); + + calc_interaction_window->redisplay (); + } + } + + static void readline_init (void) + { + rl_initialize (); + + rl_getc_function = getc; + rl_redisplay_function = redisplay; + rl_prep_term_function = prep_term; + rl_deprep_term_function = deprep_term; + rl_completion_display_matches_hook = display_completion_matches; + + rl_callback_handler_install (">> ", accept_line); + } + + static void readline_fini (void) + { + rl_callback_handler_remove (); + } + + command_window::command_window (QWidget *p) + : QTextEdit (p), + m_buffer (new QTextDocument ()), + beg_mark (), prompt_mark () + { + setWindowTitle ("Qt::TextEdit example"); + + setMinimumSize (QSize (600, 400)); + + setDocument (m_buffer); + + connect (this, SIGNAL (result_available (double)), + this, SLOT (handle_result (double))); + + insert_at_end + ("Qt Example Calculator.\n" + "Available operations: + - * / ^ ()\n" + "Semicolon terminates statement.\n" + "Up Arrow key moves to previous line in the command history.\n" + "Down Arrow key moves to next line in the comand history.\n\n"); + + beg_mark = set_mark (); + } + + // Accept an input line, parse and possibly execute it. + + void command_window::accept (const std::string& line) + { + insert_at_end ("\n"); + + if (! line.empty ()) + { + add_history (line.c_str ()); + using_history (); + + // FIXME: what is the right thing to do with status returned + // by parser. + interpreter::parse_and_execute (line); + } + } + + // Redisplay current command line. + + void command_window::redisplay (void) + { + erase_line (); + + std::string line = rl_line_buffer ? rl_line_buffer : ""; + std::string prompt = (rl_prompt && interpreter::beg_of_stmt) ? rl_prompt : ""; + + insert_line (prompt, line); + + scroll_to_bottom (); + + QTextCursor cursor = textCursor (); + + cursor.setPosition (prompt_mark + rl_point, QTextCursor::MoveAnchor); + + setTextCursor (cursor); + } + + void command_window::insert_at_end (const std::string& text) + { + scroll_to_bottom (); + + insert_at_cursor (text); + } + + void command_window::emit_result (double value) + { + emit result_available (value); + } + + // FIXME: do we really need this extra function? + void command_window::handle_result (double value) + { + insert_result (value); + } + + void command_window::keyPressEvent (QKeyEvent *event) + { + if (! event) + return; + + if (event->type () == QEvent::KeyPress) + { + int key = event->key (); + + switch (key) + { + case Qt::Key_Return: + key = 0x0A; + break; + + case Qt::Key_Backspace: + key = 0x08; + break; + + case Qt::Key_Tab: + key = 0x09; + break; + + case Qt::Key_Escape: + key = 0x1b; + break; + + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Right: + case Qt::Key_Left: + key = do_arrow_key (key); + break; + + default: + { + switch (event->modifiers ()) + { + case Qt::ControlModifier: + if (key > 0x3f && key < 0x7b) + key &= 0x1f; + else + key = -1; + break; + + default: + { + // Don't shoot me, this is just a demo... + QString text = event->text (); + QByteArray latin_text = text.toLatin1 (); + key = latin_text[0]; + } + break; + } + } + break; + } + + if (key >= 0) + { + // FIXME: should this emit a signal instead? + available_char = key; + rl_callback_read_char (); + return; + } + } + } + + int command_window::do_arrow_key (int arrow_key) + { + int retval = 0; + + available_char = 0x1b; + rl_callback_read_char (); + + available_char = '['; + rl_callback_read_char (); + + switch (arrow_key) + { + case Qt::Key_Up: + retval = 'A'; + break; + + case Qt::Key_Down: + retval = 'B'; + break; + + case Qt::Key_Right: + retval = 'C'; + break; + + case Qt::Key_Left: + retval = 'D'; + break; + } + + return retval; + } + + // Retrieve a command from the history list and insert it on the + // current command. + + void command_window::history (bool up) + { + HIST_ENTRY *entry = up ? previous_history () : next_history (); + + if (entry) + { + erase_line (); + + std::string prompt = rl_prompt ? rl_prompt : ""; + + insert_line (prompt, entry->line); + } + else if (! up) + erase_line (); + + scroll_to_bottom (); + } + + void command_window::erase_line (void) + { + QTextCursor cursor = textCursor (); + + cursor.movePosition (QTextCursor::End); + cursor.select (QTextCursor::LineUnderCursor); + cursor.removeSelectedText (); + + setTextCursor (cursor); + } + + void command_window::insert_at_cursor (const std::string& text) + { + QTextCursor cursor = textCursor (); + + cursor.insertText (QString::fromStdString (text)); + + setTextCursor (cursor); + } + + void command_window::insert_line (const std::string& prompt, + const std::string& line) + { + beg_mark = set_mark (); + + insert_at_cursor (prompt); + + prompt_mark = set_mark (); + + insert_at_cursor (line); + } + + int command_window::set_mark (void) + { + return textCursor ().position (); + } + + void command_window::insert_result (double value) + { + std::ostringstream buf; + buf << "ans = " << value << "\n"; + + insert_at_cursor (buf.str ()); + + beg_mark = set_mark (); + + scroll_to_bottom (); + } + + void command_window::scroll_to_bottom (void) + { + QTextCursor cursor = textCursor (); + + cursor.movePosition (QTextCursor::End); + + setTextCursor (cursor); + } + + int main (int argc, char *argv[]) + { + interpreter::parser_init (); + + QApplication app (argc, argv); + + calc_interaction_window = new command_window (); + + calc_interaction_window->show (); + + readline_init (); + + int status = app.exec (); + + readline_fini (); + + interpreter::parser_fini (); + + return status; + } + + void emit_result (double value) + { + calc_interaction_window->emit_result (value); + } +} + +// -- Variable: rl_getc_func_t * rl_getc_function +// If non-zero, Readline will call indirectly through this pointer to +// get a character from the input stream. By default, it is set to +// `rl_getc', the default Readline character input function (*note +// Character Input::). + +// -- Variable: rl_voidfunc_t * rl_redisplay_function +// If non-zero, Readline will call indirectly through this pointer to +// update the display with the current contents of the editing buffer. +// By default, it is set to `rl_redisplay', the default Readline +// redisplay function (*note Redisplay::). + +// -- Variable: rl_vintfunc_t * rl_prep_term_function +// If non-zero, Readline will call indirectly through this pointer to +// initialize the terminal. The function takes a single argument, an +// `int' flag that says whether or not to use eight-bit characters. +// By default, this is set to `rl_prep_terminal' (*note Terminal +// Management::). + +// -- Variable: rl_voidfunc_t * rl_deprep_term_function +// If non-zero, Readline will call indirectly through this pointer to +// reset the terminal. This function should undo the effects of +// `rl_prep_term_function'. By default, this is set to +// `rl_deprep_terminal' (*note Terminal Management::). + +// -- Function: void rl_callback_handler_install (const char *prompt, +// rl_vcpfunc_t *lhandler) +// Set up the terminal for readline I/O and display the initial +// expanded value of PROMPT. Save the value of LHANDLER to use as a +// function to call when a complete line of input has been entered. +// The function takes the text of the line as an argument. + +// -- Function: void rl_callback_read_char (void) +// Whenever an application determines that keyboard input is +// available, it should call `rl_callback_read_char()', which will +// read the next character from the current input source. If that +// character completes the line, `rl_callback_read_char' will invoke +// the LHANDLER function saved by `rl_callback_handler_install' to +// process the line. Before calling the LHANDLER function, the +// terminal settings are reset to the values they had before calling +// `rl_callback_handler_install'. If the LHANDLER function returns, +// the terminal settings are modified for Readline's use again. +// `EOF' is indicated by calling LHANDLER with a `NULL' line. + +// -- Function: void rl_callback_handler_remove (void) +// Restore the terminal to its initial state and remove the line +// handler. This may be called from within a callback as well as +// independently. If the LHANDLER installed by +// `rl_callback_handler_install' does not exit the program, either +// this function or the function referred to by the value of +// `rl_deprep_term_function' should be called before the program +// exits to reset the terminal settings. + +// -- Variable: rl_compdisp_func_t * rl_completion_display_matches_hook +// If non-zero, then this is the address of a function to call when +// completing a word would normally display the list of possible +// matches. This function is called in lieu of Readline displaying +// the list. It takes three arguments: (`char **'MATCHES, `int' +// NUM_MATCHES, `int' MAX_LENGTH) where MATCHES is the array of +// matching strings, NUM_MATCHES is the number of strings in that +// array, and MAX_LENGTH is the length of the longest string in that +// array. Readline provides a convenience function, +// `rl_display_match_list', that takes care of doing the display to +// Readline's output stream. That function may be called from this +// hook.