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