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