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