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 <QDialog>
31 #include <QDir>
32 #include <QIcon>
33 #include <QMetaType>
34 #include <QPushButton>
35 #include <QStringList>
36 
37 #include "dialog.h"
38 #include "gui-preferences-ed.h"
39 #include "octave-qobject.h"
40 #include "qt-interpreter-events.h"
41 #include "qt-utils.h"
42 
43 #include "localcharset-wrapper.h"
44 #include "oct-env.h"
45 #include "str-vec.h"
46 
47 #include "builtin-defun-decls.h"
48 #include "error.h"
49 #include "interpreter-private.h"
50 #include "load-path.h"
51 #include "oct-map.h"
52 #include "octave.h"
53 #include "ov.h"
54 #include "syminfo.h"
55 #include "utils.h"
56 
57 Q_DECLARE_METATYPE (octave_value)
58 Q_DECLARE_METATYPE (octave::symbol_info_list)
59 Q_DECLARE_METATYPE (octave::fcn_callback)
60 Q_DECLARE_METATYPE (octave::meth_callback)
61 
62 namespace octave
63 {
64   static QStringList
make_qstring_list(const std::list<std::string> & lst)65   make_qstring_list (const std::list<std::string>& lst)
66   {
67     QStringList retval;
68 
69     for (const auto& s : lst)
70       retval.append (QString::fromStdString (s));
71 
72     return retval;
73   }
74 
75   static QStringList
make_filter_list(const event_manager::filter_list & lst)76   make_filter_list (const event_manager::filter_list& lst)
77   {
78     QStringList retval;
79 
80     // We have pairs of data, first being the list of extensions
81     // exta;exb;extc etc second the name to use as filter name
82     // (optional).  Qt wants a list of filters in the format of
83     // 'FilterName (space separated exts)'.
84 
85     for (const auto& ext_name : lst)
86       {
87         QString ext = QString::fromStdString (ext_name.first);
88         QString name = QString::fromStdString (ext_name.second);
89 
90         // Strip out extensions from name and replace ';' with spaces in list.
91 
92         name.replace (QRegExp (R"(\(.*\))"), "");
93         ext.replace (";", " ");
94 
95         if (name.isEmpty ())
96           {
97             // No name field.  Build one from the extensions.
98             name = ext.toUpper () + " Files";
99           }
100 
101         retval.append (name + " (" + ext + ')');
102       }
103 
104     return retval;
105   }
106 
qt_interpreter_events(base_qobject & oct_qobj)107   qt_interpreter_events::qt_interpreter_events (base_qobject& oct_qobj)
108     : interpreter_events (), m_octave_qobj (oct_qobj),
109       m_uiwidget_creator (oct_qobj), m_result (), m_mutex (),
110       m_waitcondition ()
111   {
112     qRegisterMetaType<QIntList> ("QIntList");
113     qRegisterMetaType<QFloatList> ("QFloatList");
114 
115     qRegisterMetaType<octave_value> ("octave_value");
116     qRegisterMetaType<symbol_info_list> ("symbol_info_list");
117 
118     qRegisterMetaType<fcn_callback> ("fcn_callback");
119     qRegisterMetaType<meth_callback> ("meth_callback");
120 
121     connect (this, SIGNAL (confirm_shutdown_signal (void)),
122              this, SLOT (confirm_shutdown_octave (void)));
123 
124     connect (this, SIGNAL (get_named_icon_signal (const QString&)),
125              this, SLOT (get_named_icon_slot (const QString&)));
126 
127     connect (this,
128              SIGNAL (gui_preference_signal (const QString&, const QString&)),
129              this,
130              SLOT (gui_preference_slot (const QString&, const QString&)));
131   }
132 
133   std::list<std::string>
file_dialog(const filter_list & filter,const std::string & title,const std::string & filename,const std::string & dirname,const std::string & multimode)134   qt_interpreter_events::file_dialog (const filter_list& filter,
135                                       const std::string& title,
136                                       const std::string& filename,
137                                       const std::string& dirname,
138                                       const std::string& multimode)
139   {
140     QStringList lst
141       = m_uiwidget_creator.file_dialog (make_filter_list (filter),
142                                         QString::fromStdString (title),
143                                         QString::fromStdString (filename),
144                                         QString::fromStdString (dirname),
145                                         QString::fromStdString (multimode));
146 
147     std::list<std::string> retval;
148 
149     for (const auto& s : lst)
150       retval.push_back (s.toStdString ());
151 
152     return retval;
153   }
154 
155   std::list<std::string>
input_dialog(const std::list<std::string> & prompt,const std::string & title,const std::list<float> & nr,const std::list<float> & nc,const std::list<std::string> & defaults)156   qt_interpreter_events::input_dialog (const std::list<std::string>& prompt,
157                                        const std::string& title,
158                                        const std::list<float>& nr,
159                                        const std::list<float>& nc,
160                                        const std::list<std::string>& defaults)
161   {
162     QStringList lst
163       = m_uiwidget_creator.input_dialog (make_qstring_list (prompt),
164                                          QString::fromStdString (title),
165                                          std_list_to_qt_list<float> (nr),
166                                          std_list_to_qt_list<float> (nc),
167                                          make_qstring_list (defaults));
168     std::list<std::string> retval;
169 
170     for (const auto& s : lst)
171       retval.push_back (s.toStdString ());
172 
173     return retval;
174   }
175 
176   std::pair<std::list<int>, int>
list_dialog(const std::list<std::string> & list,const std::string & mode,int width,int height,const std::list<int> & initial,const std::string & name,const std::list<std::string> & prompt,const std::string & ok_string,const std::string & cancel_string)177   qt_interpreter_events::list_dialog (const std::list<std::string>& list,
178                                       const std::string& mode,
179                                       int width, int height,
180                                       const std::list<int>& initial,
181                                       const std::string& name,
182                                       const std::list<std::string>& prompt,
183                                       const std::string& ok_string,
184                                       const std::string& cancel_string)
185   {
186     QPair<QIntList, int> result
187       = m_uiwidget_creator.list_dialog (make_qstring_list (list),
188                                         QString::fromStdString (mode),
189                                         width, height,
190                                         std_list_to_qt_list<int> (initial),
191                                         QString::fromStdString (name),
192                                         make_qstring_list (prompt),
193                                         QString::fromStdString (ok_string),
194                                         QString::fromStdString (cancel_string));
195 
196     QIntList& lst = result.first;
197     return std::pair<std::list<int>, int> (std::list<int> (lst.begin (),
198                                                            lst.end ()),
199                                            result.second);
200   }
201 
202   std::string
question_dialog(const std::string & msg,const std::string & title,const std::string & btn1,const std::string & btn2,const std::string & btn3,const std::string & btndef)203   qt_interpreter_events::question_dialog (const std::string& msg,
204                                           const std::string& title,
205                                           const std::string& btn1,
206                                           const std::string& btn2,
207                                           const std::string& btn3,
208                                           const std::string& btndef)
209   {
210     QString icon = "quest";
211     QStringList buttons;
212     QStringList role;
213 
214     // Must use ResetRole which is left-aligned for all OS and WM.
215     role << "ResetRole" << "ResetRole" << "ResetRole";
216 
217     buttons << QString::fromStdString (btn1);
218     if (btn2 == "")
219       role.removeAt (0);
220     else
221       buttons << QString::fromStdString (btn2);
222     buttons << QString::fromStdString (btn3);
223 
224     QString answer
225       = m_uiwidget_creator.message_dialog (QString::fromStdString (msg),
226                                            QString::fromStdString (title),
227                                            icon, buttons,
228                                            QString::fromStdString (btndef),
229                                            role);
230 
231     return answer.toStdString ();
232   }
233 
update_path_dialog(void)234   void qt_interpreter_events::update_path_dialog (void)
235   {
236     emit update_path_dialog_signal ();
237   }
238 
show_preferences(void)239   void qt_interpreter_events::show_preferences (void)
240   {
241     emit show_preferences_signal ();
242   }
243 
apply_preferences(void)244   void qt_interpreter_events::apply_preferences (void)
245   {
246     emit apply_new_settings ();
247   }
248 
show_doc(const std::string & file)249   void qt_interpreter_events::show_doc (const std::string& file)
250   {
251     emit show_doc_signal (QString::fromStdString (file));
252   }
253 
edit_file(const std::string & file)254   bool qt_interpreter_events::edit_file (const std::string& file)
255   {
256     emit edit_file_signal (QString::fromStdString (file));
257 
258     return true;
259   }
260 
edit_variable(const std::string & expr,const octave_value & val)261   void qt_interpreter_events::edit_variable (const std::string& expr,
262                                              const octave_value& val)
263   {
264     emit edit_variable_signal (QString::fromStdString (expr), val);
265   }
266 
confirm_shutdown(void)267   bool qt_interpreter_events::confirm_shutdown (void)
268   {
269     QMutexLocker autolock (&m_mutex);
270 
271     emit confirm_shutdown_signal ();
272 
273     // Wait for result.
274     wait ();
275 
276     return m_result.toBool ();
277   }
278 
prompt_new_edit_file(const std::string & file)279   bool qt_interpreter_events::prompt_new_edit_file (const std::string& file)
280   {
281     resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
282     gui_settings *settings = rmgr.get_settings ();
283 
284     if (! settings || settings->value (ed_create_new_file).toBool ())
285       return true;
286 
287     std::string abs_fname = sys::env::make_absolute (file);
288 
289     QStringList btn;
290     QStringList role;
291     role << "YesRole" << "RejectRole";
292     btn << tr ("Create") << tr ("Cancel");
293 
294     QString answer = m_uiwidget_creator.message_dialog
295       (tr ("File\n%1\ndoes not exist. Do you want to create it?").
296        arg (QString::fromStdString (abs_fname)),
297        tr ("Octave Editor"), "quest", btn, tr ("Create"), role);
298 
299     return (answer == tr ("Create"));
300   }
301 
302   // Prompt to allow file to be run by setting cwd (or if
303   // addpath_option==true, alternatively setting the path).
304 
305   int
debug_cd_or_addpath_error(const std::string & file,const std::string & dir,bool addpath_option)306   qt_interpreter_events::debug_cd_or_addpath_error (const std::string& file,
307                                                     const std::string& dir,
308                                                     bool addpath_option)
309   {
310     int retval = -1;
311 
312     QString qdir = QString::fromStdString (dir);
313     QString qfile = QString::fromStdString (file);
314     QString msg
315       = (addpath_option
316          ? tr ("The file %1 does not exist in the load path.  To run or debug the function you are editing, you must either change to the directory %2 or add that directory to the load path.").arg (qfile).arg (qdir)
317          : tr ("The file %1 is shadowed by a file with the same name in the load path. To run or debug the function you are editing, change to the directory %2.").arg (qfile).arg (qdir));
318 
319     QString title = tr ("Change Directory or Add Directory to Load Path");
320 
321     QString cd_txt = tr ("&Change Directory");
322     QString addpath_txt = tr ("&Add Directory to Load Path");
323     QString cancel_txt = tr ("Cancel");
324 
325     QStringList btn;
326     QStringList role;
327     btn << cd_txt;
328     role << "YesRole";
329     if (addpath_option)
330       {
331         btn << addpath_txt;
332         role << "AcceptRole";
333       }
334     btn << cancel_txt;
335     role << "RejectRole";
336 
337     QString result
338       = m_uiwidget_creator.message_dialog (msg, title, "quest", btn,
339                                            cancel_txt, role);
340 
341     if (result == cd_txt)
342       retval = 1;
343     else if (result == addpath_txt)
344       retval = 2;
345 
346     return retval;
347   }
348 
get_named_icon(const std::string & name)349   uint8NDArray qt_interpreter_events::get_named_icon (const std::string& name)
350   {
351     QMutexLocker autolock (&m_mutex);
352 
353     emit get_named_icon_signal (QString::fromStdString (name));
354 
355     // Wait for result.
356     wait ();
357 
358     uint8NDArray empty_img;
359 
360     QIcon icon = m_result.value<QIcon> ();
361 
362     if (icon.isNull ())
363       return empty_img;
364 
365     QImage img = icon.pixmap (QSize (32, 32)).toImage ();
366 
367     if (img.format () != QImage::Format_ARGB32_Premultiplied)
368       return empty_img;
369 
370     dim_vector dims (img.height (), img.width (), 4);
371 
372     uint8NDArray retval (dims, 0);
373 
374     uint8_t *bits = img.bits ();
375 
376     for (int i = 0; i < img.height (); i++)
377       {
378         for (int j = 0; j < img.width (); j++)
379           {
380             retval(i,j,2) = bits[0];
381             retval(i,j,1) = bits[1];
382             retval(i,j,0) = bits[2];
383             retval(i,j,3) = bits[3];
384 
385             bits += 4;
386           }
387       }
388 
389     return retval;
390   }
391 
get_named_icon_slot(const QString & name)392   void qt_interpreter_events::get_named_icon_slot (const QString& name)
393   {
394     QMutexLocker autolock (&m_mutex);
395 
396     resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
397     m_result = QVariant::fromValue (rmgr.icon (name));
398 
399     wake_all ();
400   }
401 
402   std::string
gui_preference(const std::string & key,const std::string & value)403   qt_interpreter_events::gui_preference (const std::string& key,
404                                          const std::string& value)
405   {
406     QString pref_value;
407 
408     QMutexLocker autolock (&m_mutex);
409 
410     // Emit the signal for changing or getting a preference
411     emit gui_preference_signal (QString::fromStdString (key),
412                                 QString::fromStdString (value));
413 
414     // Wait for response (pref_value).
415     wait ();
416 
417     QString pref = m_result.toString ();
418 
419     return pref.toStdString ();
420   }
421 
copy_image_to_clipboard(const std::string & file)422   bool qt_interpreter_events::copy_image_to_clipboard (const std::string& file)
423   {
424     emit copy_image_to_clipboard_signal (QString::fromStdString (file), true);
425 
426     return true;
427   }
428 
focus_window(const std::string win_name)429   void qt_interpreter_events::focus_window (const std::string win_name)
430   {
431     emit focus_window_signal (QString::fromStdString (win_name));
432   }
433 
execute_command_in_terminal(const std::string & command)434   void qt_interpreter_events::execute_command_in_terminal
435   (const std::string& command)
436   {
437     emit execute_command_in_terminal_signal (QString::fromStdString (command));
438   }
439 
register_doc(const std::string & file)440   void qt_interpreter_events::register_doc (const std::string& file)
441   {
442     emit register_doc_signal (QString::fromStdString (file));
443   }
444 
unregister_doc(const std::string & file)445   void qt_interpreter_events::unregister_doc (const std::string& file)
446   {
447     emit unregister_doc_signal (QString::fromStdString (file));
448   }
449 
directory_changed(const std::string & dir)450   void qt_interpreter_events::directory_changed (const std::string& dir)
451   {
452     emit directory_changed_signal (QString::fromStdString (dir));
453   }
454 
file_remove(const std::string & old_name,const std::string & new_name)455   void qt_interpreter_events::file_remove (const std::string& old_name,
456                                            const std::string& new_name)
457   {
458     QMutexLocker autolock (&m_mutex);
459 
460     // Emit the signal for the editor for closing the file if it is open
461     emit file_remove_signal (QString::fromStdString (old_name),
462                              QString::fromStdString (new_name));
463 
464     // Wait for file removal to complete before continuing.
465     wait ();
466   }
467 
file_renamed(bool load_new)468   void qt_interpreter_events::file_renamed (bool load_new)
469   {
470     emit file_renamed_signal (load_new);
471   }
472 
set_workspace(bool top_level,bool debug,const symbol_info_list & syminfo,bool update_variable_editor)473   void qt_interpreter_events::set_workspace (bool top_level, bool debug,
474                                              const symbol_info_list& syminfo,
475                                              bool update_variable_editor)
476   {
477     if (! top_level && ! debug)
478       return;
479 
480     emit set_workspace_signal (top_level, debug, syminfo);
481 
482     if (update_variable_editor)
483       emit refresh_variable_editor_signal ();
484   }
485 
clear_workspace(void)486   void qt_interpreter_events::clear_workspace (void)
487   {
488     emit clear_workspace_signal ();
489   }
490 
set_history(const string_vector & hist)491   void qt_interpreter_events::set_history (const string_vector& hist)
492   {
493     QStringList qt_hist;
494 
495     for (octave_idx_type i = 0; i < hist.numel (); i++)
496       qt_hist.append (QString::fromStdString (hist[i]));
497 
498     emit set_history_signal (qt_hist);
499   }
500 
append_history(const std::string & hist_entry)501   void qt_interpreter_events::append_history (const std::string& hist_entry)
502   {
503     emit append_history_signal (QString::fromStdString (hist_entry));
504   }
505 
clear_history(void)506   void qt_interpreter_events::clear_history (void)
507   {
508     emit clear_history_signal ();
509   }
510 
pre_input_event(void)511   void qt_interpreter_events::pre_input_event (void)
512   { }
513 
post_input_event(void)514   void qt_interpreter_events::post_input_event (void)
515   { }
516 
enter_debugger_event(const std::string &,const std::string & fcn_file_name,int line)517   void qt_interpreter_events::enter_debugger_event (const std::string& /*fcn_name*/,
518                                                     const std::string& fcn_file_name,
519                                                     int line)
520   {
521     if (fcn_file_name.empty ())
522       return;
523 
524     insert_debugger_pointer (fcn_file_name, line);
525 
526     emit enter_debugger_signal ();
527   }
528 
529   void
execute_in_debugger_event(const std::string & file,int line)530   qt_interpreter_events::execute_in_debugger_event (const std::string& file,
531                                                     int line)
532   {
533     delete_debugger_pointer (file, line);
534   }
535 
exit_debugger_event(void)536   void qt_interpreter_events::exit_debugger_event (void)
537   {
538     emit exit_debugger_signal ();
539   }
540 
541   // Display (if @insert true) or remove the appropriate symbol for a breakpoint
542   // in @file at @line with condition @cond.
update_breakpoint(bool insert,const std::string & file,int line,const std::string & cond)543   void qt_interpreter_events::update_breakpoint (bool insert,
544                                                  const std::string& file,
545                                                  int line,
546                                                  const std::string& cond)
547   {
548     emit update_breakpoint_marker_signal (insert, QString::fromStdString (file),
549                                           line, QString::fromStdString (cond));
550   }
551 
552   void
insert_debugger_pointer(const std::string & file,int line)553   qt_interpreter_events::insert_debugger_pointer (const std::string& file,
554                                                   int line)
555   {
556     emit insert_debugger_pointer_signal (QString::fromStdString (file), line);
557   }
558 
559   void
delete_debugger_pointer(const std::string & file,int line)560   qt_interpreter_events::delete_debugger_pointer (const std::string& file,
561                                                   int line)
562   {
563     emit delete_debugger_pointer_signal (QString::fromStdString (file), line);
564   }
565 
566   void
confirm_shutdown_octave(void)567   qt_interpreter_events::confirm_shutdown_octave (void)
568   {
569     QMutexLocker autolock (&m_mutex);
570 
571     m_result = m_octave_qobj.confirm_shutdown ();
572 
573     wake_all ();
574   }
575 
576   // If VALUE is empty, return current value of preference named by KEY.
577   //
578   // If VALUE is not empty, set preference named by KEY to VALUE return
579   // previous value.
580   //
581   // FIXME: should we have separate get and set functions?  With only
582   // one, we don't allow a preference value to be set to the empty
583   // string.
584 
585   void
gui_preference_slot(const QString & key,const QString & value)586   qt_interpreter_events::gui_preference_slot (const QString& key,
587                                               const QString& value)
588   {
589     QMutexLocker autolock (&m_mutex);
590 
591     resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
592     gui_settings *settings = rmgr.get_settings ();
593 
594     QString read_value = settings->value (key).toString ();
595 
596     // Some preferences need extra handling
597     QString adjusted_value = gui_preference_adjust (key, value);
598 
599     if (! adjusted_value.isEmpty () && (read_value != adjusted_value))
600       {
601         // Change settings only for new, non-empty values
602         settings->setValue (key, QVariant (adjusted_value));
603 
604         emit settings_changed (settings, true);   // true: changed by worker
605       }
606 
607     m_result = read_value;
608 
609     wake_all ();
610   }
611 
612   QString
gui_preference_adjust(const QString & key,const QString & value)613   qt_interpreter_events::gui_preference_adjust (const QString& key,
614                                                 const QString& value)
615   {
616     // Immediately return if no new value is given.
617 
618     if (value.isEmpty ())
619       return value;
620 
621     QString adjusted_value = value;
622 
623     // Not all encodings are available.  Encodings are uppercase and do
624     // not use CPxxx but IBMxxx or WINDOWS-xxx.
625 
626     if (key == ed_default_enc.key)
627       {
628         adjusted_value = adjusted_value.toUpper ();
629 
630         QStringList codecs;
631         resource_manager& rmgr = m_octave_qobj.get_resource_manager ();
632         rmgr.get_codecs (&codecs);
633 
634         QRegExp re ("^CP(\\d+)$");
635 
636         if (adjusted_value == "SYSTEM")
637           adjusted_value =
638             QString ("SYSTEM (") +
639             QString (octave_locale_charset_wrapper ()).toUpper () +
640             QString (")");
641         else if (re.indexIn (adjusted_value) > -1)
642           {
643             if (codecs.contains ("IBM" + re.cap (1)))
644               adjusted_value = "IBM" + re.cap (1);
645             else if (codecs.contains ("WINDOWS-" + re.cap (1)))
646               adjusted_value = "WINDOWS-" + re.cap (1);
647             else
648               adjusted_value.clear ();
649           }
650         else if (! codecs.contains (adjusted_value))
651           adjusted_value.clear ();
652       }
653 
654     return adjusted_value;
655   }
656 }
657