1 /*
2
3 Copyright (C) 2012-2019 Michael Goffioul.
4 Copyright (C) 2012-2019 Jacob Dawid.
5
6 This file is part of QTerminal.
7
8 This program is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not,
20 see <https://www.gnu.org/licenses/>.
21
22 */
23
24 #if defined (HAVE_CONFIG_H)
25 # include "config.h"
26 #endif
27
28 #include <QKeySequence>
29 #include <QWidget>
30 #include <QStringList>
31 #include <QColor>
32 #include <QList>
33 #include <QMenu>
34 #include <QClipboard>
35 #include <QApplication>
36 #include <QAction>
37
38 #include "gui-preferences-global.h"
39 #include "gui-preferences-cs.h"
40 #include "gui-preferences-sc.h"
41 #include "octave-qobject.h"
42 #include "resource-manager.h"
43
44 #include "QTerminal.h"
45 #if defined (Q_OS_WIN32)
46 # include "win32/QWinTerminalImpl.h"
47 #else
48 # include "unix/QUnixTerminalImpl.h"
49 #endif
50
51 QTerminal *
create(octave::base_qobject & oct_qobj,QWidget * xparent)52 QTerminal::create (octave::base_qobject& oct_qobj, QWidget *xparent)
53 {
54 #if defined (Q_OS_WIN32)
55 QTerminal *terminal = new QWinTerminalImpl (xparent);
56 #else
57 QTerminal *terminal = new QUnixTerminalImpl (xparent);
58 #endif
59
60 // FIXME: this function should probably be called from or part of the
61 // QTerminal constructor, but I think that would mean some major
62 // surgery because then the constructor for QTerminal and the derived
63 // Unix- and Windows-specific versions would need access to the
64 // base_qobject object, or the design would have to change significantly.
65
66 terminal->construct (oct_qobj, xparent);
67
68 return terminal;
69 }
70
71 // slot for disabling the interrupt action when terminal loses focus
72 void
set_global_shortcuts(bool focus_out)73 QTerminal::set_global_shortcuts (bool focus_out)
74 {
75 if (focus_out)
76 {
77 _interrupt_action->setShortcut (QKeySequence ());
78 _nop_action->setShortcut (QKeySequence ());
79 }
80 else
81 {
82 _interrupt_action->setShortcut
83 (QKeySequence (Qt::ControlModifier | Qt::Key_C));
84
85 _nop_action->setShortcut
86 (QKeySequence (Qt::ControlModifier | Qt::Key_D));
87 }
88 }
89
90 // slot for the terminal's context menu
91 void
handleCustomContextMenuRequested(const QPoint & at)92 QTerminal::handleCustomContextMenuRequested (const QPoint& at)
93 {
94 QClipboard * cb = QApplication::clipboard ();
95 QString selected_text = selectedText();
96 bool has_selected_text = ! selected_text.isEmpty ();
97
98 _edit_action->setVisible (false);
99 m_edit_selected_action->setVisible (false);
100 m_help_selected_action->setVisible (false);
101 m_doc_selected_action->setVisible (false);
102
103 #if defined (Q_OS_WIN32)
104 // include this when in windows because there is no filter for
105 // detecting links and error messages yet
106 if (has_selected_text)
107 {
108 QRegExp file ("(?:[ \\t]+)(\\S+) at line (\\d+) column (?:\\d+)");
109
110 int pos = file.indexIn (selected_text);
111
112 if (pos > -1)
113 {
114 QString file_name = file.cap (1);
115 QString line = file.cap (2);
116
117 _edit_action->setVisible (true);
118 _edit_action->setText (tr ("Edit %1 at line %2")
119 .arg (file_name).arg (line));
120
121 QStringList data;
122 data << file_name << line;
123 _edit_action->setData (data);
124 }
125 }
126 #endif
127
128 if (has_selected_text)
129 {
130 QRegExp expr (".*\b*(\\w+)\b*.*");
131
132 int pos = expr.indexIn (selected_text);
133
134 if (pos > -1)
135 {
136 QString expr_found = expr.cap (1);
137
138 m_edit_selected_action->setVisible (true);
139 m_edit_selected_action->setText (tr ("Edit %1").arg (expr_found));
140 m_edit_selected_action->setData (expr_found);
141
142 m_help_selected_action->setVisible (true);
143 m_help_selected_action->setText (tr ("Help on %1").arg (expr_found));
144 m_help_selected_action->setData (expr_found);
145
146 m_doc_selected_action->setVisible (true);
147 m_doc_selected_action->setText (tr ("Documentation on %1")
148 .arg (expr_found));
149 m_doc_selected_action->setData (expr_found);
150 }
151 }
152
153 _paste_action->setEnabled (cb->text().length() > 0);
154 _copy_action->setEnabled (has_selected_text);
155 _run_selection_action->setVisible (has_selected_text);
156
157 // Get the actions of any hotspots the filters may have found
158 QList<QAction*> actions = get_hotspot_actions (at);
159 if (actions.length ())
160 _contextMenu->addSeparator ();
161 for (int i = 0; i < actions.length (); i++)
162 _contextMenu->addAction (actions.at(i));
163
164 // Finally, show the context menu
165 _contextMenu->exec (mapToGlobal (at));
166
167 // Cleaning up, remove actions of the hotspot
168 for (int i = 0; i < actions.length (); i++)
169 _contextMenu->removeAction (actions.at(i));
170 }
171
172 // slot for running the selected code
173 void
run_selection()174 QTerminal::run_selection ()
175 {
176 QStringList commands = selectedText ().split (QRegExp ("[\r\n]"),
177 #if defined (HAVE_QT_SPLITBEHAVIOR_ENUM)
178 Qt::SkipEmptyParts);
179 #else
180 QString::SkipEmptyParts);
181 #endif
182 for (int i = 0; i < commands.size (); i++)
183 emit execute_command_in_terminal_signal (commands.at (i));
184
185 }
186
187 // slot for edit files in error messages
188 void
edit_file()189 QTerminal::edit_file ()
190 {
191 QString file = _edit_action->data ().toStringList ().at (0);
192 int line = _edit_action->data ().toStringList ().at (1).toInt ();
193
194 emit edit_mfile_request (file,line);
195 }
196
197 // slot for edit selected function names
edit_selected()198 void QTerminal::edit_selected ()
199 {
200 QString file = m_edit_selected_action->data ().toString ();
201
202 emit edit_mfile_request (file,0);
203 }
204
205 // slot for showing help on selected epxression
help_on_expression()206 void QTerminal::help_on_expression ()
207 {
208 QString expr = m_help_selected_action->data ().toString ();
209
210 emit execute_command_in_terminal_signal ("help " + expr);
211 }
212
213 // slot for showing documentation on selected epxression
doc_on_expression()214 void QTerminal::doc_on_expression ()
215 {
216 QString expr = m_doc_selected_action->data ().toString ();
217
218 emit show_doc_signal (expr);
219 }
220
221 void
notice_settings(const gui_settings * settings)222 QTerminal::notice_settings (const gui_settings *settings)
223 {
224 if (! settings)
225 return;
226
227 // Set terminal font:
228 QFont term_font = QFont ();
229 term_font.setStyleHint (QFont::TypeWriter);
230 QString default_font = settings->value (global_mono_font).toString ();
231 term_font.setFamily
232 (settings->value (cs_font.key, default_font).toString ());
233 term_font.setPointSize
234 (settings->value (cs_font_size).toInt ());
235 setTerminalFont (term_font);
236
237 QFontMetrics metrics (term_font);
238 setMinimumSize (metrics.maxWidth ()*16, metrics.height ()*3);
239
240 QString cursor_type
241 = settings->value (cs_cursor).toString ();
242
243 bool cursor_blinking;
244 if (settings->contains (global_cursor_blinking.key))
245 cursor_blinking = settings->value (global_cursor_blinking).toBool ();
246 else
247 cursor_blinking = settings->value (cs_cursor_blinking).toBool ();
248
249 for (int ct = IBeamCursor; ct <= UnderlineCursor; ct++)
250 {
251 if (cursor_type.toStdString () == cs_cursor_types[ct])
252 {
253 setCursorType ((CursorType) ct, cursor_blinking);
254 break;
255 }
256 }
257
258 bool cursorUseForegroundColor
259 = settings->value (cs_cursor_use_fgcol).toBool ();
260
261 setForegroundColor
262 (settings->value (cs_colors[0].key, cs_colors[0].def).value<QColor> ());
263
264 setBackgroundColor
265 (settings->value (cs_colors[1].key, cs_colors[1].def).value<QColor> ());
266
267 setSelectionColor
268 (settings->value (cs_colors[2].key, cs_colors[2].def).value<QColor> ());
269
270 setCursorColor (cursorUseForegroundColor,
271 settings->value (cs_colors[3].key, cs_colors[3].def).value<QColor> ());
272
273 setScrollBufferSize (settings->value (cs_hist_buffer).toInt ());
274
275 // If the Copy shortcut is Ctrl+C, then the Copy action also emits
276 // a signal for interrupting the current code executed by the worker.
277 // If the Copy shortcut is not Ctrl+C, an extra interrupt action is
278 // set up for emitting the interrupt signal.
279
280 QString sc = settings->sc_value (sc_main_edit_copy);
281
282 // Dis- or enable extra interrupt action depending on the Copy shortcut
283 bool extra_ir_action
284 = (sc != QKeySequence (Qt::ControlModifier | Qt::Key_C).toString ());
285
286 _interrupt_action->setEnabled (extra_ir_action);
287 has_extra_interrupt (extra_ir_action);
288
289 // check whether shortcut Ctrl-D is in use by the main-window
290 bool ctrld = settings->value (sc_main_ctrld).toBool ();
291 _nop_action->setEnabled (! ctrld);
292 }
293
294 void
construct(octave::base_qobject & oct_qobj,QWidget * xparent)295 QTerminal::construct (octave::base_qobject& oct_qobj, QWidget *xparent)
296 {
297 octave::resource_manager& rmgr = oct_qobj.get_resource_manager ();
298
299 // context menu
300 setContextMenuPolicy (Qt::CustomContextMenu);
301
302 _contextMenu = new QMenu (this);
303
304 _copy_action
305 = _contextMenu->addAction (rmgr.icon ("edit-copy"), tr ("Copy"), this,
306 SLOT (copyClipboard ()));
307
308 _paste_action
309 = _contextMenu->addAction (rmgr.icon ("edit-paste"), tr ("Paste"), this,
310 SLOT (pasteClipboard ()));
311
312 _contextMenu->addSeparator ();
313
314 _selectall_action
315 = _contextMenu->addAction (tr ("Select All"), this, SLOT (selectAll ()));
316
317 _run_selection_action
318 = _contextMenu->addAction (tr ("Run Selection"), this,
319 SLOT (run_selection ()));
320
321 m_edit_selected_action
322 = _contextMenu->addAction (tr ("Edit selection"), this,
323 SLOT (edit_selected ()));
324 m_help_selected_action
325 = _contextMenu->addAction (tr ("Help on selection"), this,
326 SLOT (help_on_expression ()));
327 m_doc_selected_action
328 = _contextMenu->addAction (tr ("Documentation on selection"), this,
329 SLOT (doc_on_expression ()));
330
331 _edit_action = _contextMenu->addAction (tr (""), this, SLOT (edit_file ()));
332
333 _contextMenu->addSeparator ();
334
335 _contextMenu->addAction (tr ("Clear Window"), parent (),
336 SLOT (handle_clear_command_window_request ()));
337
338 connect (this, SIGNAL (customContextMenuRequested (QPoint)),
339 this, SLOT (handleCustomContextMenuRequested (QPoint)));
340
341 connect (this, SIGNAL (report_status_message (const QString&)),
342 xparent, SLOT (report_status_message (const QString&)));
343
344 connect (this, SIGNAL (show_doc_signal (const QString&)),
345 xparent, SLOT (handle_show_doc (const QString&)));
346
347 connect (this, SIGNAL (edit_mfile_request (const QString&, int)),
348 xparent, SLOT (edit_mfile (const QString&, int)));
349
350 connect (this, SIGNAL (execute_command_in_terminal_signal (const QString&)),
351 xparent, SLOT (execute_command_in_terminal (const QString&)));
352
353 connect (xparent, SIGNAL (settings_changed (const gui_settings *)),
354 this, SLOT (notice_settings (const gui_settings *)));
355
356 connect (xparent, SIGNAL (init_terminal_size_signal ()),
357 this, SLOT (init_terminal_size ()));
358
359 connect (xparent, SIGNAL (copyClipboard_signal ()),
360 this, SLOT (copyClipboard ()));
361
362 connect (xparent, SIGNAL (pasteClipboard_signal ()),
363 this, SLOT (pasteClipboard ()));
364
365 connect (xparent, SIGNAL (selectAll_signal ()),
366 this, SLOT (selectAll ()));
367
368 // extra interrupt action
369 _interrupt_action = new QAction (this);
370 addAction (_interrupt_action);
371
372 _interrupt_action->setShortcut
373 (QKeySequence (Qt::ControlModifier + Qt::Key_C));
374
375 connect (_interrupt_action, SIGNAL (triggered ()),
376 this, SLOT (terminal_interrupt ()));
377
378 // dummy (nop) action catching Ctrl-D in terminal, no connection
379 _nop_action = new QAction (this);
380 addAction (_nop_action);
381
382 _nop_action->setShortcut (QKeySequence (Qt::ControlModifier + Qt::Key_D));
383 }
384