Mercurial > jwe > qt-gui-with-push-parser
view command-window.cpp @ 15:79783f3e2017
use rl_display_prompt instead of rl_prompt
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Fri, 24 May 2019 09:22:29 -0400 |
parents | 1e5a1e15fa56 |
children | 2ddf3fe6fa33 |
line wrap: on
line source
#include <iostream> #include <sstream> #include <string> #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 { // vvvvv readline vvvvv // We could eliminate this global variable and the global // command_window::the_command_window variable if readline callbacks // could be defined as C++ lambda functions. Then the lambdas could // have direct access to the command_window object and it could // contain the next available character. static int available_char = 0; 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) { // Anything to do here? } static void deprep_term (void) { // Anything to do here? } static void accept_line (char *line) { if (command_window::the_command_window) { if (! line) command_window::the_command_window->eof (); else command_window::the_command_window->accept_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 (); } } static void readline_init (void) { // What we really want here is a readline object that we could // create in the command_window constructor. // We might also want the option of shared history? 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 (); rl_getc_function = nullptr; rl_redisplay_function = nullptr; rl_prep_term_function = nullptr; rl_deprep_term_function = nullptr; rl_completion_display_matches_hook = nullptr; // Is there a function that undoes readline initialization? } // ^^^^^ 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&))); connect (this, SIGNAL (eof_signal (void)), this, SLOT (close (void))); 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 (); readline_init (); // 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))); } command_window::~command_window (void) { readline_fini (); } // 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::eof (void) { emit eof_signal (); } 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 (); interpreter& interp = m_interpreter->get_interpreter (); parser& parser = interp.get_parser (); std::string line = rl_line_buffer ? rl_line_buffer : ""; std::string prompt = parser.beg_of_stmt () ? rl_display_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_display_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); } }