Mercurial > jwe > qt-gui-with-push-parser
diff command-window.cpp @ 9:822a2fe5bb51
move command window to separate file and other refactoring
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Thu, 23 May 2019 12:36:26 -0400 |
parents | |
children | b652a5528fb1 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/command-window.cpp Thu May 23 12:36:26 2019 -0400 @@ -0,0 +1,379 @@ +#include <iostream> +#include <sstream> +#include <string> + +#include <cstdlib> +#include <cstring> + +#include <QApplication> +#include <QKeyEvent> +#include <QTextDocument> +#include <QTextEdit> +#include <QTimer> + +#include "command-window.h" +#include "interpreter.h" +#include "parser.h" + +#include <readline/readline.h> +#include <readline/history.h> + +namespace calc +{ + static int available_char = 0; + + // vvvvv readline vvvvv + + static int getc (FILE *) + { + int tmp = available_char; + available_char = 0; + return tmp; + } + + static void redisplay (void) + { + if (command_window::the_command_window) + emit command_window::the_command_window->redisplay_signal (); + } + + static void prep_term (int) + { + } + + static void deprep_term (void) + { + } + + static void accept_line (char *line) + { + if (command_window::the_command_window) + command_window::the_command_window->accept_line (line ? line : ""); + } + + static void display_completion_matches (char **matches, int num_matches, + int /* max_length */) + { + if (command_window::the_command_window) + { + std::ostringstream buf; + + if (num_matches > 1) + buf << "\n"; + + for (int i = 1; i < num_matches; i++) + buf << matches[i] << "\n"; + + command_window::the_command_window->insert_at_end (buf.str ()); + + emit command_window::the_command_window->redisplay_signal (); + } + } + + 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); + } + + void readline_fini (void) + { + rl_callback_handler_remove (); + } + + // ^^^^^ readline ^^^^^ + + command_window *command_window::the_command_window = nullptr; + + command_window::command_window (QWidget *p) + : QTextEdit (p), + m_buffer (new QTextDocument ()), + m_interpreter (new calc::qt_interpreter ()), + beg_mark (), prompt_mark () + { + if (the_command_window) + { + std::cerr << "multiple command_windows are not possible!" << std::endl; + exit (1); + } + + the_command_window = this; + + setWindowTitle ("Qt::TextEdit example"); + + setMinimumSize (QSize (600, 400)); + + setDocument (m_buffer); + + connect (m_interpreter, SIGNAL (result_ready (double)), + this, SLOT (handle_result (double))); + + connect (m_interpreter, SIGNAL (error_signal (const QString&)), + this, SLOT (handle_error (const QString&))); + + connect (this, SIGNAL (input_char_available (int)), + this, SLOT (handle_input_char (int))); + + connect (this, SIGNAL (redisplay_signal (void)), + this, SLOT (redisplay (void))); + + connect (this, SIGNAL (accept_line_signal (const QString&)), + m_interpreter, SLOT (accept_input_line (const QString&))); + + 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 (); + + // Defer initializing and executing the interpreter until after the main + // window and QApplication are running to prevent race conditions + QTimer::singleShot (0, m_interpreter, SLOT (execute (void))); + } + + // Accept an input line, parse and possibly execute it. + + void command_window::accept_line (const std::string& line) + { + insert_at_end ("\n"); + + if (! line.empty ()) + { + add_history (line.c_str ()); + using_history (); + + emit accept_line_signal (QString::fromStdString (line)); + } + } + + void command_window::insert_at_end (const std::string& text) + { + scroll_to_bottom (); + + insert_at_cursor (text); + } + + void command_window::handle_error (const QString& msg) + { + insert_at_end ("parse error: " + msg.toStdString () + "\n"); + + rl_abort (0, 0); + } + + // FIXME: do we really need this extra function? + void command_window::handle_result (double value) + { + insert_result (value); + } + + // 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 && parser::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::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) + emit input_char_available (key); + } + } + + void command_window::handle_input_char (int key) + { + available_char = key; + rl_callback_read_char (); + } + + 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); + } +}