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.