comparison gui-main.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 04867eba6428
children b652a5528fb1
comparison
equal deleted inserted replaced
8:7c4b04a6346d 9:822a2fe5bb51
4 4
5 #include <cstdlib> 5 #include <cstdlib>
6 #include <cstring> 6 #include <cstring>
7 7
8 #include <QApplication> 8 #include <QApplication>
9 #include <QKeyEvent>
10 #include <QTextDocument>
11 #include <QTextEdit>
12 #include <QTimer>
13 9
10 #include "command-window.h"
14 #include "gui-main.h" 11 #include "gui-main.h"
15 #include "main.h"
16 12
17 #include "gui-main.h" 13 namespace calc
18 #include "interpreter.h"
19 #include "parser.h"
20
21 #include <readline/readline.h>
22 #include <readline/history.h>
23
24 namespace gui
25 { 14 {
26 static int available_char = 0; 15 int gui_main (int argc, char *argv[])
27
28 static command_window *calc_interaction_window = 0;
29
30 static inline int ctrl (int c)
31 {
32 return c & 0x1f;
33 }
34
35 static int getc (FILE *)
36 {
37 int tmp = available_char;
38 available_char = 0;
39 return tmp;
40 }
41
42 static void redisplay (void)
43 {
44 if (calc_interaction_window)
45 emit calc_interaction_window->redisplay_signal ();
46 }
47
48 static void prep_term (int)
49 {
50 }
51
52 static void deprep_term (void)
53 {
54 }
55
56 static void accept_line (char *line)
57 {
58 if (calc_interaction_window)
59 calc_interaction_window->accept_line (line ? line : "");
60 }
61
62 static void display_completion_matches (char **matches, int num_matches,
63 int /* max_length */)
64 {
65 if (calc_interaction_window)
66 {
67 std::ostringstream buf;
68
69 if (num_matches > 1)
70 buf << "\n";
71
72 for (int i = 1; i < num_matches; i++)
73 buf << matches[i] << "\n";
74
75 calc_interaction_window->insert_at_end (buf.str ());
76
77 emit calc_interaction_window->redisplay_signal ();
78 }
79 }
80
81 static void readline_init (void)
82 {
83 rl_initialize ();
84
85 rl_getc_function = getc;
86 rl_redisplay_function = redisplay;
87 rl_prep_term_function = prep_term;
88 rl_deprep_term_function = deprep_term;
89 rl_completion_display_matches_hook = display_completion_matches;
90
91 rl_callback_handler_install (">> ", accept_line);
92 }
93
94 static void readline_fini (void)
95 {
96 rl_callback_handler_remove ();
97 }
98
99 command_window::command_window (QWidget *p)
100 : QTextEdit (p),
101 m_buffer (new QTextDocument ()),
102 m_interpreter (new calc::qt_interpreter ()),
103 beg_mark (), prompt_mark ()
104 {
105 setWindowTitle ("Qt::TextEdit example");
106
107 setMinimumSize (QSize (600, 400));
108
109 setDocument (m_buffer);
110
111 connect (m_interpreter, SIGNAL (result_ready (double)),
112 this, SLOT (handle_result (double)));
113
114 connect (m_interpreter, SIGNAL (error_signal (const QString&)),
115 this, SLOT (handle_error (const QString&)));
116
117 connect (this, SIGNAL (input_char_available (int)),
118 this, SLOT (handle_input_char (int)));
119
120 connect (this, SIGNAL (redisplay_signal (void)),
121 this, SLOT (redisplay (void)));
122
123 connect (this, SIGNAL (accept_line_signal (const QString&)),
124 m_interpreter, SLOT (accept_input_line (const QString&)));
125
126 insert_at_end
127 ("Qt Example Calculator.\n"
128 "Available operations: + - * / ^ ()\n"
129 "Semicolon terminates statement.\n"
130 "Up Arrow key moves to previous line in the command history.\n"
131 "Down Arrow key moves to next line in the comand history.\n\n");
132
133 beg_mark = set_mark ();
134
135 // Defer initializing and executing the interpreter until after the main
136 // window and QApplication are running to prevent race conditions
137 QTimer::singleShot (0, m_interpreter, SLOT (execute (void)));
138 }
139
140 // Accept an input line, parse and possibly execute it.
141
142 void command_window::accept_line (const std::string& line)
143 {
144 if (calc::debug_mode)
145 std::cerr << "accept: " << line << std::endl;
146
147 insert_at_end ("\n");
148
149 if (! line.empty ())
150 {
151 add_history (line.c_str ());
152 using_history ();
153
154 emit accept_line_signal (QString::fromStdString (line));
155 }
156 }
157
158 void command_window::insert_at_end (const std::string& text)
159 {
160 scroll_to_bottom ();
161
162 insert_at_cursor (text);
163 }
164
165 void command_window::handle_error (const QString& msg)
166 {
167 insert_at_end ("parse error: " + msg.toStdString () + "\n");
168
169 rl_abort (0, 0);
170 }
171
172 // FIXME: do we really need this extra function?
173 void command_window::handle_result (double value)
174 {
175 insert_result (value);
176 }
177
178 // Redisplay current command line.
179
180 void command_window::redisplay (void)
181 {
182 erase_line ();
183
184 std::string line = rl_line_buffer ? rl_line_buffer : "";
185 std::string prompt = (rl_prompt && parser::beg_of_stmt) ? rl_prompt : "";
186
187 insert_line (prompt, line);
188
189 scroll_to_bottom ();
190
191 QTextCursor cursor = textCursor ();
192
193 cursor.setPosition (prompt_mark + rl_point, QTextCursor::MoveAnchor);
194
195 setTextCursor (cursor);
196 }
197
198 void command_window::keyPressEvent (QKeyEvent *event)
199 {
200 if (! event)
201 return;
202
203 if (event->type () == QEvent::KeyPress)
204 {
205 int key = event->key ();
206
207 switch (key)
208 {
209 case Qt::Key_Return:
210 key = 0x0A;
211 break;
212
213 case Qt::Key_Backspace:
214 key = 0x08;
215 break;
216
217 case Qt::Key_Tab:
218 key = 0x09;
219 break;
220
221 case Qt::Key_Escape:
222 key = 0x1b;
223 break;
224
225 case Qt::Key_Up:
226 case Qt::Key_Down:
227 case Qt::Key_Right:
228 case Qt::Key_Left:
229 key = do_arrow_key (key);
230 break;
231
232 default:
233 {
234 switch (event->modifiers ())
235 {
236 case Qt::ControlModifier:
237 if (key > 0x3f && key < 0x7b)
238 key &= 0x1f;
239 else
240 key = -1;
241 break;
242
243 default:
244 {
245 // Don't shoot me, this is just a demo...
246 QString text = event->text ();
247 QByteArray latin_text = text.toLatin1 ();
248 key = latin_text[0];
249 }
250 break;
251 }
252 }
253 break;
254 }
255
256 if (key >= 0)
257 emit input_char_available (key);
258 }
259 }
260
261 void command_window::handle_input_char (int key)
262 {
263 available_char = key;
264 rl_callback_read_char ();
265 }
266
267 int command_window::do_arrow_key (int arrow_key)
268 {
269 int retval = 0;
270
271 available_char = 0x1b;
272 rl_callback_read_char ();
273
274 available_char = '[';
275 rl_callback_read_char ();
276
277 switch (arrow_key)
278 {
279 case Qt::Key_Up:
280 retval = 'A';
281 break;
282
283 case Qt::Key_Down:
284 retval = 'B';
285 break;
286
287 case Qt::Key_Right:
288 retval = 'C';
289 break;
290
291 case Qt::Key_Left:
292 retval = 'D';
293 break;
294 }
295
296 return retval;
297 }
298
299 // Retrieve a command from the history list and insert it on the
300 // current command.
301
302 void command_window::history (bool up)
303 {
304 HIST_ENTRY *entry = up ? previous_history () : next_history ();
305
306 if (entry)
307 {
308 erase_line ();
309
310 std::string prompt = rl_prompt ? rl_prompt : "";
311
312 insert_line (prompt, entry->line);
313 }
314 else if (! up)
315 erase_line ();
316
317 scroll_to_bottom ();
318 }
319
320 void command_window::erase_line (void)
321 {
322 QTextCursor cursor = textCursor ();
323
324 cursor.movePosition (QTextCursor::End);
325 cursor.select (QTextCursor::LineUnderCursor);
326 cursor.removeSelectedText ();
327
328 setTextCursor (cursor);
329 }
330
331 void command_window::insert_at_cursor (const std::string& text)
332 {
333 QTextCursor cursor = textCursor ();
334
335 cursor.insertText (QString::fromStdString (text));
336
337 setTextCursor (cursor);
338 }
339
340 void command_window::insert_line (const std::string& prompt,
341 const std::string& line)
342 {
343 beg_mark = set_mark ();
344
345 insert_at_cursor (prompt);
346
347 prompt_mark = set_mark ();
348
349 insert_at_cursor (line);
350 }
351
352 int command_window::set_mark (void)
353 {
354 return textCursor ().position ();
355 }
356
357 void command_window::insert_result (double value)
358 {
359 std::ostringstream buf;
360
361 buf << "ans = " << value << "\n";
362
363 insert_at_cursor (buf.str ());
364
365 beg_mark = set_mark ();
366
367 scroll_to_bottom ();
368 }
369
370 void command_window::scroll_to_bottom (void)
371 {
372 QTextCursor cursor = textCursor ();
373
374 cursor.movePosition (QTextCursor::End);
375
376 setTextCursor (cursor);
377 }
378
379 int main (int argc, char *argv[])
380 { 16 {
381 QApplication app (argc, argv); 17 QApplication app (argc, argv);
382 18
383 calc_interaction_window = new command_window (); 19 command_window command_window;
384 20
385 calc_interaction_window->show (); 21 command_window.show ();
386 22
387 readline_init (); 23 readline_init ();
388 24
389 int status = app.exec (); 25 int status = app.exec ();
390 26
391 readline_fini (); 27 readline_fini ();
392 28
393 return status; 29 return status;
394 } 30 }
395 } 31 }
396
397 // -- Variable: rl_getc_func_t * rl_getc_function
398 // If non-zero, Readline will call indirectly through this pointer to
399 // get a character from the input stream. By default, it is set to
400 // `rl_getc', the default Readline character input function (*note
401 // Character Input::).
402
403 // -- Variable: rl_voidfunc_t * rl_redisplay_function
404 // If non-zero, Readline will call indirectly through this pointer to
405 // update the display with the current contents of the editing buffer.
406 // By default, it is set to `rl_redisplay', the default Readline
407 // redisplay function (*note Redisplay::).
408
409 // -- Variable: rl_vintfunc_t * rl_prep_term_function
410 // If non-zero, Readline will call indirectly through this pointer to
411 // initialize the terminal. The function takes a single argument, an
412 // `int' flag that says whether or not to use eight-bit characters.
413 // By default, this is set to `rl_prep_terminal' (*note Terminal
414 // Management::).
415
416 // -- Variable: rl_voidfunc_t * rl_deprep_term_function
417 // If non-zero, Readline will call indirectly through this pointer to
418 // reset the terminal. This function should undo the effects of
419 // `rl_prep_term_function'. By default, this is set to
420 // `rl_deprep_terminal' (*note Terminal Management::).
421
422 // -- Function: void rl_callback_handler_install (const char *prompt,
423 // rl_vcpfunc_t *lhandler)
424 // Set up the terminal for readline I/O and display the initial
425 // expanded value of PROMPT. Save the value of LHANDLER to use as a
426 // function to call when a complete line of input has been entered.
427 // The function takes the text of the line as an argument.
428
429 // -- Function: void rl_callback_read_char (void)
430 // Whenever an application determines that keyboard input is
431 // available, it should call `rl_callback_read_char()', which will
432 // read the next character from the current input source. If that
433 // character completes the line, `rl_callback_read_char' will invoke
434 // the LHANDLER function saved by `rl_callback_handler_install' to
435 // process the line. Before calling the LHANDLER function, the
436 // terminal settings are reset to the values they had before calling
437 // `rl_callback_handler_install'. If the LHANDLER function returns,
438 // the terminal settings are modified for Readline's use again.
439 // `EOF' is indicated by calling LHANDLER with a `NULL' line.
440
441 // -- Function: void rl_callback_handler_remove (void)
442 // Restore the terminal to its initial state and remove the line
443 // handler. This may be called from within a callback as well as
444 // independently. If the LHANDLER installed by
445 // `rl_callback_handler_install' does not exit the program, either
446 // this function or the function referred to by the value of
447 // `rl_deprep_term_function' should be called before the program
448 // exits to reset the terminal settings.
449
450 // -- Variable: rl_compdisp_func_t * rl_completion_display_matches_hook
451 // If non-zero, then this is the address of a function to call when
452 // completing a word would normally display the list of possible
453 // matches. This function is called in lieu of Readline displaying
454 // the list. It takes three arguments: (`char **'MATCHES, `int'
455 // NUM_MATCHES, `int' MAX_LENGTH) where MATCHES is the array of
456 // matching strings, NUM_MATCHES is the number of strings in that
457 // array, and MAX_LENGTH is the length of the longest string in that
458 // array. Readline provides a convenience function,
459 // `rl_display_match_list', that takes care of doing the display to
460 // Readline's output stream. That function may be called from this
461 // hook.