view gui-main.cpp @ 5:54edd85237ab

use signal to send input to qt interpreter object
author John W. Eaton <jwe@octave.org>
date Wed, 22 May 2019 18:07:37 -0400
parents 0e154787183d
children 04867eba6428
line wrap: on
line source

#include <iostream>
#include <sstream>
#include <string>

#include <cstdlib>
#include <cstring>

#include <QApplication>
#include <QKeyEvent>
#include <QTextDocument>
#include <QTextEdit>
#include <QTimer>

#include "gui-main.h"
#include "main.h"

#include "gui-main.h"
#include "interpreter.h"
#include "parser.h"

#include <readline/readline.h>
#include <readline/history.h>

namespace gui
{
  static int available_char = 0;

  static command_window *calc_interaction_window = 0;

  static inline int ctrl (int c)
  {
    return c & 0x1f;
  }

  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 ? 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 ()),
      m_interpreter (new calc::qt_interpreter ()),
      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)));

    connect (this, SIGNAL (input_char_available (int)),
             this, SLOT (handle_input_char (int)));

    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)
  {
    if (calc::debug_mode)
      std::cerr << "accept: " << line << std::endl;

    insert_at_end ("\n");

    if (! line.empty ())
      {
        add_history (line.c_str ());
        using_history ();

        emit accept_line_signal (QString::fromStdString (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 && 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::insert_at_end (const std::string& text)
  {
    scroll_to_bottom ();

    insert_at_cursor (text);
  }

  void command_window::emit_error (const std::string& msg)
  {
    insert_at_end ("parse error: " + msg);

    rl_abort (0, 0);
  }

  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)
          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);
  }

  int main (int argc, char *argv[])
  {
    QApplication app (argc, argv);

    calc_interaction_window = new command_window ();

    calc_interaction_window->show ();

    readline_init ();

    int status = app.exec ();

    readline_fini ();

    return status;
  }

  void emit_error (const std::string& msg)
  {
    calc_interaction_window->emit_error (msg);
  }

  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.