GNU Octave  8.1.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
command-widget.cc
Go to the documentation of this file.
1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2021-2023 The Octave Project Developers
4 //
5 // See the file COPYRIGHT.md in the top-level directory of this
6 // distribution or <https://octave.org/copyright/>.
7 //
8 // This file is part of Octave.
9 //
10 // Octave is free software: you can redistribute it and/or modify it
11 // under the terms of the GNU General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // Octave is distributed in the hope that it will be useful, but
16 // WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU General Public License for more details.
19 //
20 // You should have received a copy of the GNU General Public License
21 // along with Octave; see the file COPYING. If not, see
22 // <https://www.gnu.org/licenses/>.
23 //
24 ////////////////////////////////////////////////////////////////////////
25 
26 #if defined (HAVE_CONFIG_H)
27 # include "config.h"
28 #endif
29 
30 #if defined (HAVE_QSCINTILLA)
31 
32 #include <QGroupBox>
33 #include <QHBoxLayout>
34 #include <QLabel>
35 #include <QLineEdit>
36 #include <QPushButton>
37 #include <QTextEdit>
38 #include <QTextBlock>
39 #include <QVBoxLayout>
40 
41 #include "command-widget.h"
42 
43 #include "cmd-edit.h"
44 #include "event-manager.h"
45 #include "gui-preferences-cs.h"
46 #include "gui-preferences-global.h"
47 #include "gui-utils.h"
48 #include "input.h"
49 #include "interpreter.h"
50 
52 
54 : QWidget (p), m_incomplete_parse (false),
55  m_prompt (QString ()),
56  m_console (new console (this, oct_qobj))
57 {
58  QPushButton *pause_button = new QPushButton (tr("Pause"), this);
59  QPushButton *stop_button = new QPushButton (tr("Stop"), this);
60  QPushButton *resume_button = new QPushButton (tr("Continue"), this);
61 
62  QGroupBox *input_group_box = new QGroupBox ();
63  QHBoxLayout *input_layout = new QHBoxLayout;
64  input_layout->addWidget (pause_button);
65  input_layout->addWidget (stop_button);
66  input_layout->addWidget (resume_button);
67  input_group_box->setLayout (input_layout);
68 
69  QVBoxLayout *main_layout = new QVBoxLayout ();
70  main_layout->addWidget (m_console);
71  main_layout->addWidget (input_group_box);
72 
73  setLayout (main_layout);
74 
75  setFocusProxy (m_console);
76 
77  connect (pause_button, &QPushButton::clicked,
79 
80  connect (resume_button, &QPushButton::clicked,
82 
83  connect (stop_button, &QPushButton::clicked,
85 
87  m_console, &console::new_command_line);
88 
89  insert_interpreter_output ("\n\n Welcome to Octave\n\n");
90 
91 }
92 
94 {
95  // The interpreter_event callback function below emits a signal.
96  // Because we don't control when that happens, use a guarded pointer
97  // so that the callback can abort if this object is no longer valid.
98 
99  QPointer<command_widget> this_cw (this);
100 
101  emit interpreter_event
102  ([=] (interpreter& interp)
103  {
104  // INTERPRETER THREAD
105 
106  // We can skip the entire callback function because it does not
107  // make any changes to the interpreter state.
108 
109  if (this_cw.isNull ())
110  return;
111 
112  event_manager& evmgr = interp.get_event_manager ();
113  input_system& input_sys = interp.get_input_system ();
114  std::string prompt = input_sys.PS1 ();
115  evmgr.update_prompt (command_editor::decode_prompt_string (prompt));
116 
117  emit new_command_line_signal ();
118  });
119 }
120 
121 void command_widget::update_prompt (const QString& prompt)
122 {
123  m_prompt = prompt;
124 }
125 
127 {
128  return m_prompt;
129 }
130 
132 {
133  m_console->append (msg);
134 }
135 
136 void command_widget::process_input_line (const QString& input_line)
137 {
138  // The interpreter_event callback function below emits a signal.
139  // Because we don't control when that happens, use a guarded pointer
140  // so that the callback can abort if this object is no longer valid.
141 
142  QPointer<command_widget> this_cw (this);
143 
144  emit interpreter_event
145  ([=] (interpreter& interp)
146  {
147  // INTERPRETER THREAD
148 
149  // If THIS_CW is no longer valid, we still want to parse and
150  // execute INPUT_LINE but we can't emit the signals associated
151  // with THIS_CW.
152 
153  interp.parse_and_execute (input_line.toStdString (),
155 
156  if (this_cw.isNull ())
157  return;
158 
159  event_manager& evmgr = interp.get_event_manager ();
160  input_system& input_sys = interp.get_input_system ();
161 
162  std::string prompt
163  = m_incomplete_parse ? input_sys.PS2 () : input_sys.PS1 ();
164 
165  evmgr.update_prompt (command_editor::decode_prompt_string (prompt));
166 
167  emit new_command_line_signal ();
168  });
169 
170 }
171 
173 {
174  // Set terminal font:
175  QFont term_font = QFont ();
176  term_font.setStyleHint (QFont::TypeWriter);
177  QString default_font = settings->value (global_mono_font).toString ();
178  term_font.setFamily
179  (settings->value (cs_font.key, default_font).toString ());
180  term_font.setPointSize
181  (settings->value (cs_font_size).toInt ());
182 
183  m_console->setFont (term_font);
184 
185  // Colors
186  int mode = settings->value (cs_color_mode).toInt ();
187  QColor fgc = settings->color_value (cs_colors[0], mode);
188  QColor bgc = settings->color_value (cs_colors[1], mode);
189 
190  m_console->setStyleSheet (QString ("color: %1; background-color:%2;")
191  .arg (fgc.name ()).arg (bgc.name ()));
192 }
193 
194 // The console itself using QScintilla.
195 // This implementation is partly based on the basic concept of
196 // "qpconsole" as proposed by user "DerManu" in the Qt-forum thread
197 // https://forum.qt.io/topic/28765/command-terminal-using-qtextedit
198 
200  : QsciScintilla (p),
201  m_command_position (-1),
202  m_cursor_position (0),
203  m_text_changed (false),
204  m_command_widget (p),
205  m_last_key_string (QString ())
206 {
207  setMargins (0);
208  setWrapMode (QsciScintilla::WrapWord);
209 
210  connect (this, SIGNAL (cursorPositionChanged (int, int)),
211  this, SLOT (cursor_position_changed (int, int)));
212 
213  connect (this, SIGNAL (textChanged (void)),
214  this, SLOT (text_changed (void)));
215 
216  connect (this, SIGNAL (modificationAttempted (void)),
217  this, SLOT (move_cursor_to_end (void)));
218 }
219 
220 // Prepare a new command line with the current prompt
221 void console::new_command_line (const QString& command)
222 {
223  if (! text (lines () -1).isEmpty ())
224  append ("\n");
225 
227 
228  int line, index;
229  getCursorPosition (&line,&index);
230  m_command_position = positionFromLineIndex (line, index);
231 
232  append_string (command);
233 }
234 
235 // Accept the current command line (or block)
237 {
238  QString input_line = text (lines () - 1);
239 
240  if (input_line.startsWith (m_command_widget->prompt ()))
241  input_line.remove(0, m_command_widget->prompt ().length ());
242 
243  input_line = input_line.trimmed ();
244 
245  append_string ("\n");
246 
247  if (input_line.isEmpty ())
248  new_command_line ();
249  else
250  m_command_widget->process_input_line (input_line);
251 }
252 
253 // Execute a command
254 void console::execute_command (const QString& command)
255 {
256  if (command.trimmed ().isEmpty ())
257  return;
258 
259  new_command_line (command);
261 }
262 
263 // Append a string and update the curdor püosition
264 void console::append_string (const QString& string)
265 {
266  setReadOnly (false);
267  append (string);
268 
269  int line, index;
270  lineIndexFromPosition (text ().length (), &line, &index);
271 
272  setCursorPosition (line, index);
273 }
274 
275 // Cursor position changed: Are we in the command line or not?
276 void console::cursor_position_changed (int line, int col)
277 {
278  m_cursor_position = positionFromLineIndex (line, col);
280  {
281  // We are in the read only area
283  {
284  setReadOnly (false);
285  insert (m_command_widget->prompt ().right (1)); // And here we have tried to remove the prompt by Backspace
286  setCursorPosition (line+1, col);
287  }
288  setReadOnly (true);
289  }
290  else
291  setReadOnly (false); // Writable area
292 
293  m_text_changed = false;
294 }
295 
296 // User attempted to type on read only mode: move cursor at end and allow
297 // editing
299 {
300  if ((! m_last_key_string.isEmpty ()) && (m_last_key_string.at (0).isPrint ()))
301  {
303  setReadOnly (true); // Avoid that changing read only text is done afterwards
304  }
305 }
306 
307 // Text has changed: is cursor still in "writable" area?
308 // This signal seems to be emitted before cursor position changed.
310 {
311  m_text_changed = true;
312 }
313 
314 // Re-implement key event
315 void console::keyPressEvent (QKeyEvent *e)
316 {
317  if (e->key () == Qt::Key_Return)
318  // On "return", accept the current command line
320  else
321  {
322  // Otherwise, store text process the expected event
323  m_last_key_string = e->text ();
324  QsciScintilla::keyPressEvent(e);
325  }
326 }
327 
329 
330 #endif
OCTAVE_END_NAMESPACE(octave)
Base class for Octave interfaces that use Qt.
static std::string decode_prompt_string(const std::string &s)
Definition: cmd-edit.cc:1278
void new_command_line_signal(const QString &command=QString())
QString prompt(void)
void interpreter_event(const fcn_callback &fcn)
console * m_console
void process_input_line(const QString &input_line)
void init_command_prompt()
void insert_interpreter_output(const QString &msg)
void interpreter_resume(void)
void notice_settings(const gui_settings *settings)
void update_prompt(const QString &prompt)
void interpreter_stop(void)
void interpreter_pause(void)
bool m_text_changed
QString m_last_key_string
void move_cursor_to_end(void)
void accept_command_line(void)
void execute_command(const QString &command)
command_widget * m_command_widget
void cursor_position_changed(int line, int col)
int m_command_position
int m_cursor_position
console(command_widget *p, base_qobject &oct_qobj)
void text_changed(void)
void new_command_line(const QString &command=QString())
void keyPressEvent(QKeyEvent *e)
void append_string(const QString &string)
Provides threadsafe access to octave.
event_manager & get_event_manager(void)
Definition: interpreter.h:328
void parse_and_execute(const std::string &input, bool &incomplete_parse)
Definition: interpreter.cc:832
input_system & get_input_system(void)
Definition: interpreter.h:263
OCTAVE_BEGIN_NAMESPACE(octave) static octave_value daspk_fcn
const gui_pref cs_colors[2 *cs_colors_count]
const gui_pref cs_color_mode("terminal/color_mode", QVariant(0))
const gui_pref cs_font_size("terminal/fontSize", QVariant(10))
const gui_pref cs_font("terminal/fontName", QVariant())
const gui_pref global_mono_font("monospace_font", global_font_family)
const QString key