1 ////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (C) 2011-2021 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 #include <QApplication>
31 #include <QClipboard>
32 #include <QCompleter>
33 #include <QLabel>
34 #include <QMenu>
35 #include <QScrollBar>
36 #include <QVBoxLayout>
37 
38 #include "gui-preferences-cs.h"
39 #include "gui-preferences-global.h"
40 #include "gui-preferences-hw.h"
41 #include "history-dock-widget.h"
42 #include "octave-qobject.h"
43 
44 #include "cmd-hist.h"
45 
46 #include "error.h"
47 
48 namespace octave
49 {
history_dock_widget(QWidget * p,base_qobject & oct_qobj)50   history_dock_widget::history_dock_widget (QWidget *p, base_qobject& oct_qobj)
51     : octave_dock_widget ("HistoryDockWidget", p, oct_qobj)
52   {
53     setStatusTip (tr ("Browse and search the command history."));
54 
55     connect (this, SIGNAL (command_create_script (const QString&)),
56              p, SLOT (new_file (const QString&)));
57 
58     connect (this, SIGNAL (information (const QString&)),
59              p, SLOT (report_status_message (const QString&)));
60 
61     connect (this, SIGNAL (command_double_clicked (const QString&)),
62              p, SLOT (execute_command_in_terminal (const QString&)));
63 
64     construct ();
65   }
66 
set_history(const QStringList & hist)67   void history_dock_widget::set_history (const QStringList& hist)
68   {
69     m_history_model->setStringList (hist);
70     m_history_list_view->scrollToBottom ();
71   }
72 
append_history(const QString & hist_entry)73   void history_dock_widget::append_history (const QString& hist_entry)
74   {
75     QStringList lst = m_history_model->stringList ();
76     lst.append (hist_entry);
77 
78     QScrollBar *scroll_bar = m_history_list_view->verticalScrollBar ();
79 
80     bool at_bottom = scroll_bar->maximum () - scroll_bar->value () < 1;
81 
82     m_history_model->setStringList (lst);
83 
84     // Scroll if slider position at bottom.
85     if (at_bottom)
86       m_history_list_view->scrollToBottom ();
87   }
88 
clear_history(void)89   void history_dock_widget::clear_history (void)
90   {
91     m_history_model->setStringList (QStringList ());
92   }
93 
save_settings(void)94   void history_dock_widget::save_settings (void)
95   {
96     resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
97     gui_settings *settings = rmgr.get_settings ();
98 
99     if (! settings)
100       return;
101 
102     settings->setValue (hw_filter_active.key, m_filter_checkbox->isChecked ());
103     settings->setValue (hw_filter_shown.key, m_filter_shown);
104 
105     QStringList mru;
106     for (int i = 0; i < m_filter->count (); i++)
107       mru.append (m_filter->itemText (i));
108     settings->setValue (hw_mru_list.key, mru);
109 
110     settings->sync ();
111 
112     octave_dock_widget::save_settings ();
113   }
114 
update_filter_history(void)115   void history_dock_widget::update_filter_history (void)
116   {
117     QString text = m_filter->currentText ();   // get current text
118     int index = m_filter->findText (text);     // and its actual index
119 
120     if (index > -1)
121       m_filter->removeItem (index);    // remove if already existing
122 
123     m_filter->insertItem (0, text);    // (re)insert at beginning
124     m_filter->setCurrentIndex (0);
125   }
126 
filter_activate(bool state)127   void history_dock_widget::filter_activate (bool state)
128   {
129     m_filter->setEnabled (state);
130     m_sort_filter_proxy_model.setDynamicSortFilter (state);
131 
132     if (state)
133       m_sort_filter_proxy_model.setFilterWildcard (m_filter->currentText ());
134     else
135       m_sort_filter_proxy_model.setFilterWildcard (QString ());
136   }
137 
ctxMenu(const QPoint & xpos)138   void history_dock_widget::ctxMenu (const QPoint& xpos)
139   {
140     QMenu menu (this);
141 
142     QModelIndex index = m_history_list_view->indexAt (xpos);
143 
144     if (index.isValid () && index.column () == 0)
145       {
146         resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
147 
148         menu.addAction (rmgr.icon ("edit-copy"), tr ("Copy"), this,
149                         SLOT (handle_contextmenu_copy (bool)));
150         menu.addAction (tr ("Evaluate"), this,
151                         SLOT (handle_contextmenu_evaluate (bool)));
152         menu.addAction (rmgr.icon ("document-new"), tr ("Create script"), this,
153                         SLOT (handle_contextmenu_create_script (bool)));
154       }
155     if (m_filter_shown)
156       menu.addAction (tr ("Hide filter"), this,
157                       SLOT (handle_contextmenu_filter ()));
158     else
159       menu.addAction (tr ("Show filter"), this,
160                       SLOT (handle_contextmenu_filter ()));
161 
162     menu.exec (m_history_list_view->mapToGlobal (xpos));
163   }
164 
handle_double_click(QModelIndex modelIndex)165   void history_dock_widget::handle_double_click (QModelIndex modelIndex)
166   {
167     emit command_double_clicked (modelIndex.data ().toString ());
168   }
169 
handle_contextmenu_copy(bool)170   void history_dock_widget::handle_contextmenu_copy (bool)
171   {
172     QString text;
173     QItemSelectionModel *selectionModel = m_history_list_view->selectionModel ();
174     QModelIndexList rows = selectionModel->selectedRows ();
175     bool prev_valid_row = false;
176     for (auto it = rows.begin (); it != rows.end (); it++)
177       {
178         if ((*it).isValid ())
179           {
180             if (prev_valid_row)
181               text += '\n';
182             text += (*it).data ().toString ();
183             prev_valid_row = true;
184           }
185       }
186     QApplication::clipboard ()->setText (text);
187   }
188 
handle_contextmenu_evaluate(bool)189   void history_dock_widget::handle_contextmenu_evaluate (bool)
190   {
191     QItemSelectionModel *selectionModel = m_history_list_view->selectionModel ();
192     QModelIndexList rows = selectionModel->selectedRows ();
193     for (auto it = rows.begin () ; it != rows.end (); it++)
194       {
195         if ((*it).isValid ())
196           emit command_double_clicked ((*it).data ().toString ());
197       }
198   }
199 
handle_contextmenu_create_script(bool)200   void history_dock_widget::handle_contextmenu_create_script (bool)
201   {
202     QString text;
203     QItemSelectionModel *selectionModel = m_history_list_view->selectionModel ();
204     QModelIndexList rows = selectionModel->selectedRows ();
205 
206     bool prev_valid_row = false;
207     for (auto it = rows.begin (); it != rows.end (); it++)
208       {
209         if ((*it).isValid ())
210           {
211             if (prev_valid_row)
212               text += '\n';
213             text += (*it).data ().toString ();
214             prev_valid_row = true;
215           }
216       }
217 
218     if (text.length () > 0)
219       emit command_create_script (text);
220   }
221 
handle_contextmenu_filter(void)222   void history_dock_widget::handle_contextmenu_filter (void)
223   {
224     m_filter_shown = ! m_filter_shown;
225     m_filter_widget->setVisible (m_filter_shown);
226   }
227 
copyClipboard(void)228   void history_dock_widget::copyClipboard (void)
229   {
230     if (m_history_list_view->hasFocus ())
231       handle_contextmenu_copy (true);
232     if (m_filter->lineEdit ()->hasFocus ()
233         && m_filter->lineEdit ()->hasSelectedText ())
234       {
235         QClipboard *clipboard = QApplication::clipboard ();
236         clipboard->setText (m_filter->lineEdit ()->selectedText ());
237       }
238   }
239 
pasteClipboard(void)240   void history_dock_widget::pasteClipboard (void)
241   {
242     if (m_filter->lineEdit ()->hasFocus ())
243       {
244         QClipboard *clipboard = QApplication::clipboard ();
245         QString str = clipboard->text ();
246         if (str.length () > 0)
247           m_filter->lineEdit ()->insert (str);
248       }
249   }
250 
selectAll(void)251   void history_dock_widget::selectAll (void)
252   {
253     if (m_filter->lineEdit ()->hasFocus ())
254       m_filter->lineEdit ()->selectAll ();
255 
256     if (m_history_list_view->hasFocus ())
257       m_history_list_view->selectAll ();
258   }
259 
handle_visibility(bool visible)260   void history_dock_widget::handle_visibility (bool visible)
261   {
262     octave_dock_widget::handle_visibility (visible);
263 
264     if (visible)
265       {
266         int filter_state = m_filter_checkbox->isChecked ();
267         filter_activate (filter_state);
268       }
269   }
270 
construct(void)271   void history_dock_widget::construct (void)
272   {
273     m_history_model = new QStringListModel ();
274     m_sort_filter_proxy_model.setSourceModel (m_history_model);
275     m_history_list_view = new QListView (this);
276     m_history_list_view->setModel (&m_sort_filter_proxy_model);
277     m_history_list_view->setAlternatingRowColors (true);
278     m_history_list_view->setEditTriggers (QAbstractItemView::NoEditTriggers);
279     m_history_list_view->setStatusTip
280       (tr ("Double-click a command to transfer it to the Command Window."));
281     m_history_list_view->setSelectionMode (QAbstractItemView::ExtendedSelection);
282     m_history_list_view->setContextMenuPolicy (Qt::CustomContextMenu);
283     connect (m_history_list_view,
284              SIGNAL (customContextMenuRequested (const QPoint &)), this,
285              SLOT (ctxMenu (const QPoint &)));
286 
287     m_filter = new QComboBox (this);
288     m_filter->setToolTip (tr ("Enter text to filter the command history"));
289     m_filter->setEditable (true);
290     m_filter->setMaxCount (MaxFilterHistory);
291     m_filter->setInsertPolicy (QComboBox::NoInsert);
292     m_filter->setSizeAdjustPolicy
293       (QComboBox::AdjustToMinimumContentsLengthWithIcon);
294     QSizePolicy sizePol (QSizePolicy::Expanding, QSizePolicy::Preferred);
295     m_filter->setSizePolicy (sizePol);
296     m_filter->completer ()->setCaseSensitivity (Qt::CaseSensitive);
297 
298     QLabel *filter_label = new QLabel (tr ("Filter"));
299 
300     m_filter_checkbox = new QCheckBox ();
301 
302     setWindowIcon (QIcon (":/actions/icons/logo.png"));
303     set_title (tr ("Command History"));
304     setWidget (new QWidget ());
305 
306     m_filter_widget = new QWidget (this);
307     QHBoxLayout *filter_layout = new QHBoxLayout ();
308     filter_layout->addWidget (filter_label);
309     filter_layout->addWidget (m_filter_checkbox);
310     filter_layout->addWidget (m_filter);
311     filter_layout->setMargin(0);
312     m_filter_widget->setLayout (filter_layout);
313 
314     QVBoxLayout *hist_layout = new QVBoxLayout ();
315     hist_layout->addWidget (m_filter_widget);
316     hist_layout->addWidget (m_history_list_view);
317 
318     hist_layout->setMargin (2);
319     hist_layout->setSpacing (0);
320     widget ()->setLayout (hist_layout);
321 
322     // Init state of the filter
323     resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
324     gui_settings *settings = rmgr.get_settings ();
325 
326     m_filter_shown
327       = settings->value (hw_filter_shown).toBool ();
328     m_filter_widget->setVisible (m_filter_shown);
329 
330     m_filter->addItems (settings->value (hw_mru_list).toStringList ());
331 
332     bool filter_state
333       = settings->value (hw_filter_active).toBool ();
334     m_filter_checkbox->setChecked (filter_state);
335     filter_activate (filter_state);
336 
337     // Connect signals and slots
338     connect (m_filter, SIGNAL (editTextChanged (const QString&)),
339              &m_sort_filter_proxy_model,
340              SLOT (setFilterWildcard (const QString&)));
341     connect (m_filter_checkbox, SIGNAL (toggled (bool)),
342              this, SLOT (filter_activate (bool)));
343     connect (m_filter->lineEdit (), SIGNAL (editingFinished (void)),
344              this, SLOT (update_filter_history (void)));
345 
346     connect (m_history_list_view, SIGNAL (doubleClicked (QModelIndex)),
347              this, SLOT (handle_double_click (QModelIndex)));
348 
349     m_history_list_view->setTextElideMode (Qt::ElideRight);
350   }
351 
notice_settings(const gui_settings * settings)352   void history_dock_widget::notice_settings (const gui_settings *settings)
353   {
354     QFont font = QFont ();
355 
356     font.setStyleHint (QFont::TypeWriter);
357     QString default_font = settings->value (global_mono_font).toString ();
358 
359     font.setFamily (settings->value (cs_font.key, default_font).toString ());
360     font.setPointSize (settings->value (cs_font_size).toInt ());
361 
362     m_history_list_view->setFont (font);
363   }
364 
365 }
366