GNU Octave  4.0.0
A high-level interpreted language, primarily intended for numerical computations, mostly compatible with Matlab
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
file-editor-tab.cc
Go to the documentation of this file.
1 
2 /*
3 
4 Copyright (C) 2011-2015 Jacob Dawid
5 
6 This file is part of Octave.
7 
8 Octave is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the
10 Free Software Foundation; either version 3 of the License, or (at your
11 option) any later version.
12 
13 Octave is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with Octave; see the file COPYING. If not, see
20 <http://www.gnu.org/licenses/>.
21 
22 */
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #ifdef HAVE_QSCINTILLA
29 
30 #if defined (HAVE_QSCI_QSCILEXEROCTAVE_H)
31 #define HAVE_LEXER_OCTAVE
32 #include <Qsci/qscilexeroctave.h>
33 #elif defined (HAVE_QSCI_QSCILEXERMATLAB_H)
34 #define HAVE_LEXER_MATLAB
35 #include <Qsci/qscilexermatlab.h>
36 #endif
37 #include <Qsci/qscilexercpp.h>
38 #include <Qsci/qscilexerbash.h>
39 #include <Qsci/qscilexerperl.h>
40 #include <Qsci/qscilexerbatch.h>
41 #include <Qsci/qscilexerdiff.h>
42 #include <Qsci/qsciprinter.h>
43 #include "resource-manager.h"
44 #include <QApplication>
45 #include <QFileDialog>
46 #include <QMessageBox>
47 #include <QTextStream>
48 #include <QVBoxLayout>
49 #include <QInputDialog>
50 #include <QPrintDialog>
51 #include <QDateTime>
52 
53 #include "file-editor-tab.h"
54 #include "file-editor.h"
55 #include "octave-txt-lexer.h"
56 
57 #include "file-ops.h"
58 
59 #include "debug.h"
60 #include "octave-qt-link.h"
61 #include "version.h"
62 #include "utils.h"
63 #include "defaults.h"
64 #include <oct-map.h>
65 
66 bool file_editor_tab::_cancelled = false;
67 
68 // Make parent null for the file editor tab so that warning
69 // WindowModal messages don't affect grandparents.
70 file_editor_tab::file_editor_tab (const QString& directory_arg)
71 {
72  _lexer_apis = 0;
73  _is_octave_file = true;
74 
75  _ced = directory_arg;
76 
77  _file_name = "";
78  _file_system_watcher.setObjectName ("_qt_autotest_force_engine_poller");
79 
80  _edit_area = new octave_qscintilla (this);
81 
82  connect (_edit_area, SIGNAL (cursorPositionChanged (int, int)),
83  this, SLOT (handle_cursor_moved (int,int)));
84 
85  connect (_edit_area, SIGNAL (context_menu_edit_signal (const QString&)),
86  this, SLOT (handle_context_menu_edit (const QString&)));
87 
88  // create statusbar for row/col indicator and eol mode
89  _status_bar = new QStatusBar (this);
90 
91  // eol mode
92  QLabel *eol_label = new QLabel (tr ("eol:"), this);
93  _eol_indicator = new QLabel ("",this);
94  QFontMetrics fm = eol_label->fontMetrics ();
95  _eol_indicator->setMinimumSize (5*fm.averageCharWidth (),0);
96  _status_bar->addPermanentWidget (eol_label, 0);
97  _status_bar->addPermanentWidget (_eol_indicator, 0);
98 
99  // row- and col-indicator
100  _row_indicator = new QLabel ("", this);
101  _row_indicator->setMinimumSize (5*fm.averageCharWidth (),0);
102  QLabel *row_label = new QLabel (tr ("line:"), this);
103  _col_indicator = new QLabel ("", this);
104  _col_indicator->setMinimumSize (4*fm.averageCharWidth (),0);
105  QLabel *col_label = new QLabel (tr ("col:"), this);
106  _status_bar->addPermanentWidget (row_label, 0);
107  _status_bar->addPermanentWidget (_row_indicator, 0);
108  _status_bar->addPermanentWidget (col_label, 0);
109  _status_bar->addPermanentWidget (_col_indicator, 0);
110 
111  // Leave the find dialog box out of memory until requested.
112  _find_dialog = 0;
113  _find_dialog_is_visible = false;
114 
115  // symbols
116  _edit_area->setMarginType (1, QsciScintilla::SymbolMargin);
117  _edit_area->setMarginSensitivity (1, true);
118  _edit_area->markerDefine (QsciScintilla::RightTriangle, bookmark);
119  _edit_area->setMarkerBackgroundColor (QColor (0,0,232), bookmark);
120  _edit_area->markerDefine (QsciScintilla::Circle, breakpoint);
121  _edit_area->setMarkerBackgroundColor (QColor (192,0,0), breakpoint);
122  _edit_area->markerDefine (QsciScintilla::RightTriangle, debugger_position);
123  _edit_area->setMarkerBackgroundColor (QColor (255,255,0), debugger_position);
124 
125  connect (_edit_area, SIGNAL (marginClicked (int, int,
126  Qt::KeyboardModifiers)),
127  this, SLOT (handle_margin_clicked (int, int,
128  Qt::KeyboardModifiers)));
129 
130  // line numbers
131  _edit_area->setMarginsForegroundColor (QColor (96, 96, 96));
132  _edit_area->setMarginsBackgroundColor (QColor (232, 232, 220));
133  _edit_area->setMarginType (2, QsciScintilla::TextMargin);
134 
135  // other features
136  _edit_area->setBraceMatching (QsciScintilla::StrictBraceMatch);
137  _edit_area->setAutoIndent (true);
138  _edit_area->setIndentationWidth (2);
139  _edit_area->setIndentationsUseTabs (false);
140 
141  _edit_area->setUtf8 (true);
142 
143  // auto completion
144  _edit_area->SendScintilla (QsciScintillaBase::SCI_AUTOCSETCANCELATSTART, false);
145 
146  QVBoxLayout *edit_area_layout = new QVBoxLayout ();
147  edit_area_layout->addWidget (_edit_area);
148  edit_area_layout->addWidget (_status_bar);
149  edit_area_layout->setMargin (0);
150  setLayout (edit_area_layout);
151 
152  // connect modified signal
153  connect (_edit_area, SIGNAL (modificationChanged (bool)),
154  this, SLOT (update_window_title (bool)));
155 
156  connect (_edit_area, SIGNAL (copyAvailable (bool)),
157  this, SLOT (handle_copy_available (bool)));
158 
159  connect (&_file_system_watcher, SIGNAL (fileChanged (const QString&)),
160  this, SLOT (file_has_changed (const QString&)));
161 
162  QSettings *settings = resource_manager::get_settings ();
163  if (settings)
164  notice_settings (settings, true);
165 }
166 
168 {
169  // Destroy items attached to _edit_area.
170  QsciLexer *lexer = _edit_area->lexer ();
171  if (lexer)
172  {
173  delete lexer;
174  _edit_area->setLexer (0);
175  }
176  if (_find_dialog)
177  {
178  delete _find_dialog;
179  _find_dialog = 0;
180  }
181 
182  // Destroy _edit_area.
183  delete _edit_area;
184 }
185 
186 void
187 file_editor_tab::closeEvent (QCloseEvent *e)
188 {
189  _cancelled = false; // prevent unwanted interaction of previous
190  // exits of octave which were canceled by the user
191 
192  if (check_file_modified () == QMessageBox::Cancel)
193  { // ignore close event if file is not saved and user cancels
194  // closing this window
195  e->ignore ();
196  }
197  else
198  {
199  e->accept ();
200  emit tab_remove_request ();
201  }
202 }
203 
204 void
205 file_editor_tab::set_current_directory (const QString& dir)
206 {
207  _ced = dir;
208 }
209 
210 void
211 file_editor_tab::handle_context_menu_edit (const QString& word_at_cursor)
212 {
213  // search for a subfunction in actual file (this is done at first because
214  // octave finds this function before other with same name in the search path
215  QRegExp rxfun1 ("^[\t ]*function[^=]+=[\t ]*"
216  + word_at_cursor + "[\t ]*\\([^\\)]*\\)[\t ]*$");
217  QRegExp rxfun2 ("^[\t ]*function[\t ]+"
218  + word_at_cursor + "[\t ]*\\([^\\)]*\\)[\t ]*$");
219  QRegExp rxfun3 ("^[\t ]*function[\t ]+"
220  + word_at_cursor + "[\t ]*$");
221  QRegExp rxfun4 ("^[\t ]*function[^=]+=[\t ]*"
222  + word_at_cursor + "[\t ]*$");
223 
224  int pos_fct = -1;
225  QStringList lines = _edit_area->text ().split ("\n");
226 
227  int line;
228  for (line = 0; line < lines.count (); line++)
229  {
230  if ((pos_fct = rxfun1.indexIn (lines.at (line))) != -1)
231  break;
232  if ((pos_fct = rxfun2.indexIn (lines.at (line))) != -1)
233  break;
234  if ((pos_fct = rxfun3.indexIn (lines.at (line))) != -1)
235  break;
236  if ((pos_fct = rxfun4.indexIn (lines.at (line))) != -1)
237  break;
238  }
239 
240  if (pos_fct > -1)
241  { // reg expr. found: it is an internal function
242  _edit_area->setCursorPosition (line, pos_fct);
243  _edit_area->SendScintilla (2613, line); // SCI_SETFIRSTVISIBLELINE
244  return;
245  }
246 
247  // Is it a regular function within the search path? (Call __which__)
248  octave_value_list fct = F__which__ (ovl (word_at_cursor.toStdString ()),0);
249  octave_map map = fct(0).map_value ();
250 
251  QString type = QString::fromStdString (
252  map.contents ("type").data ()[0].string_value ());
253  QString name = QString::fromStdString (
254  map.contents ("name").data ()[0].string_value ());
255 
256  QString message = QString ();
257  QString filename = QString ();
258 
259  if (type == QString("built-in function"))
260  { // built in function: can't edit
261  message = tr ("%1 is a built-in function");
262  }
263  else if (type.isEmpty ())
264  {
265  // function not known to octave -> try directory of edited file
266  // get directory
267  QDir dir;
268  if (_file_name.isEmpty ())
269  dir = _ced;
270  else
271  dir = QDir (QFileInfo (_file_name).canonicalPath ());
272 
273  // function not known to octave -> try directory of edited file
274  QFileInfo file = QFileInfo (dir, word_at_cursor + ".m");
275 
276  if (file.exists ())
277  {
278  filename = file.canonicalFilePath (); // local file exists
279  }
280  else
281  { // local file does not exist -> try private directory
282  file = QFileInfo (_file_name);
283  file = QFileInfo (QDir (file.canonicalPath () + "/private"),
284  word_at_cursor + ".m");
285 
286  if (file.exists ())
287  {
288  filename = file.canonicalFilePath (); // private function exists
289  }
290  else
291  {
292  message = tr ("Can not find function %1"); // no file found
293  }
294  }
295  }
296 
297  if (! message.isEmpty ())
298  {
299  QMessageBox *msgBox
300  = new QMessageBox (QMessageBox::Critical,
301  tr ("Octave Editor"),
302  message.arg (name),
303  QMessageBox::Ok, this);
304 
305  msgBox->setWindowModality (Qt::NonModal);
306  msgBox->setAttribute (Qt::WA_DeleteOnClose);
307  msgBox->show ();
308  return;
309  }
310 
311  if ( filename.isEmpty ())
312  filename = QString::fromStdString (
313  map.contents ("file").data ()[0].string_value ());
314 
315  if (! filename.endsWith (".m"))
316  filename.append (".m");
317 
318  emit request_open_file (filename);
319 }
320 
321 void
322 file_editor_tab::set_file_name (const QString& fileName)
323 {
324  // update tracked file if we really have a file on disk
325  QStringList trackedFiles = _file_system_watcher.files ();
326  if (!trackedFiles.isEmpty ())
327  _file_system_watcher.removePath (_file_name);
328  if (!fileName.isEmpty ())
329  _file_system_watcher.addPath (fileName);
330  _file_name = fileName;
331 
332  // update lexer after _file_name change
333  update_lexer ();
334 
335  // update the file editor with current editing directory
337 
338  // add the new file to the mru list
339  emit mru_add_file (_file_name);
340 }
341 
342 // valid_file_name (file): checks whether "file" names a file
343 // by default, "file" is empty, then _file_name is checked
344 bool
345 file_editor_tab::valid_file_name (const QString& file)
346 {
347  if (file.isEmpty ())
348  {
349  if (_file_name.isEmpty ())
350  return false;
351  else
352  return true;
353  }
354 
355  return true;
356 }
357 
358 void
359 file_editor_tab::handle_margin_clicked (int margin, int line,
360  Qt::KeyboardModifiers state)
361 {
362  if (margin == 1)
363  {
364  unsigned int markers_mask = _edit_area->markersAtLine (line);
365 
366  if (state & Qt::ControlModifier)
367  {
368  if (markers_mask && (1 << bookmark))
369  _edit_area->markerDelete (line, bookmark);
370  else
371  _edit_area->markerAdd (line, bookmark);
372  }
373  else
374  {
375  if (markers_mask && (1 << breakpoint))
377  else
378  request_add_breakpoint (line);
379  }
380  }
381 }
382 
383 void
385 {
386  if (_lexer_apis)
387  _lexer_apis->cancelPreparation (); // stop preparing if apis exists
388 
389  QsciLexer *lexer = _edit_area->lexer ();
390  delete lexer;
391  lexer = 0;
392 
393  _is_octave_file = false;
394 
395  if (_file_name.endsWith (".m")
396  || _file_name.endsWith ("octaverc"))
397  {
398 #if defined (HAVE_LEXER_OCTAVE)
399  lexer = new QsciLexerOctave ();
400 #elif defined (HAVE_LEXER_MATLAB)
401  lexer = new QsciLexerMatlab ();
402 #else
403  lexer = new octave_txt_lexer ();
404 #endif
405  _is_octave_file = true;
406  }
407 
408  if (! lexer)
409  {
410  if (_file_name.endsWith (".c")
411  || _file_name.endsWith (".cc")
412  || _file_name.endsWith (".cpp")
413  || _file_name.endsWith (".cxx")
414  || _file_name.endsWith (".c++")
415  || _file_name.endsWith (".h")
416  || _file_name.endsWith (".hh")
417  || _file_name.endsWith (".hpp")
418  || _file_name.endsWith (".h++"))
419  {
420  lexer = new QsciLexerCPP ();
421  }
422  else if (_file_name.endsWith (".pl"))
423  {
424  lexer = new QsciLexerPerl ();
425  }
426  else if (_file_name.endsWith (".bat"))
427  {
428  lexer = new QsciLexerBatch ();
429  }
430  else if (_file_name.endsWith (".diff"))
431  {
432  lexer = new QsciLexerDiff ();
433  }
434  else if (_file_name.endsWith (".sh"))
435  {
436  lexer = new QsciLexerBash ();
437  }
438  else if (! valid_file_name ())
439  {
440  // new, no yet named file: let us assume it is octave
441 #if defined (HAVE_LEXER_OCTAVE)
442  lexer = new QsciLexerOctave ();
443  _is_octave_file = true;
444 #elif defined (HAVE_LEXER_MATLAB)
445  lexer = new QsciLexerMatlab ();
446  _is_octave_file = true;
447 #else
448  lexer = new octave_txt_lexer ();
449 #endif
450  }
451  else
452  {
453  // other or no extension
454  lexer = new octave_txt_lexer ();
455  }
456  }
457 
458  QSettings *settings = resource_manager::get_settings ();
459 
460  // build information for auto completion (APIs)
461  _lexer_apis = new QsciAPIs(lexer);
462 
463  if (_lexer_apis)
464  {
465  bool update_apis_file = false; // flag, whether update of apis files
466 
467  // get path to prepared api info
468  QDesktopServices desktopServices;
469  QString prep_apis_path
470  = desktopServices.storageLocation (QDesktopServices::HomeLocation)
471  + "/.config/octave/" + QString(OCTAVE_VERSION) + "/qsci/";
472 
473  // get settings which infos are used for octave
474  bool octave_builtins = settings->value (
475  "editor/codeCompletion_octave_builtins", true).toBool ();
476  bool octave_functions = settings->value (
477  "editor/codeCompletion_octave_functions", true).toBool ();
478 
479  if (_is_octave_file)
480  {
481  // octave file: keywords are always used
482  _prep_apis_file = prep_apis_path + lexer->lexer () + "_k";
483 
484  if (octave_builtins)
485  _prep_apis_file = _prep_apis_file + "b"; // use builtins, too
486 
487  if (octave_functions)
488  _prep_apis_file = _prep_apis_file + "f"; // use keywords, too
489 
490  _prep_apis_file = _prep_apis_file + ".pap"; // final name of apis file
491 
492  // check whether the APIs info needs to be prepared and saved
493  QFileInfo apis_file = QFileInfo (_prep_apis_file);
494  update_apis_file = ! apis_file.exists (); // flag whether apis file needs update
495 
496  // function list depends on installed packages: check mod. date
497  if (! update_apis_file && octave_functions)
498  {
499  // check whether package file is newer than apis_file
500  QDateTime apis_date = apis_file.lastModified ();
501 
502  // compare to local package list
503  // FIXME: How to get user chosen location?
504  QFileInfo local_pkg_list = QFileInfo (
505  desktopServices.storageLocation (QDesktopServices::HomeLocation)
506  + "/.octave_packages");
507  if (local_pkg_list.exists ()
508  & (apis_date < local_pkg_list.lastModified ()) )
509  update_apis_file = true;
510 
511  // compare to global package list
512  // FIXME: How to get user chosen location?
513  QFileInfo global_pkg_list = QFileInfo (
515  + "/share/octave/octave_packages");
516  if (global_pkg_list.exists ()
517  && (apis_date < global_pkg_list.lastModified ()) )
518  update_apis_file = true;
519  }
520  }
521  else // no octave file, just add extension
522  {
523  _prep_apis_file = prep_apis_path + lexer->lexer () + ".pap";
524  }
525 
526  if (update_apis_file || !_lexer_apis->loadPrepared (_prep_apis_file))
527  {
528  // no prepared info loaded, prepare and save if possible
529 
530  // create raw apis info
531  QString keyword;
532  QStringList keyword_list;
533  int i,j;
534 
535  if (_is_octave_file)
536  {
537  // octave: get keywords from internal informations depending on
538  // user preferences
539 
540  // keywords are always used
541  add_octave_apis (F__keywords__ ()); // add new entries
542 
543  if (octave_builtins)
544  add_octave_apis (F__builtins__ ()); // add new entries
545 
546  if (octave_functions)
547  add_octave_apis (F__list_functions__ ()); // add new entries
548 
549  }
550  else
551  {
552 
553  _prep_apis_file = prep_apis_path + lexer->lexer () + ".pap";
554 
555  for (i=1; i<=3; i++) // test the first 5 keyword sets
556  {
557  keyword = QString(lexer->keywords (i)); // get list
558  keyword_list = keyword.split (QRegExp ("\\s+")); // split
559  for (j = 0; j < keyword_list.size (); j++) // add to API
560  _lexer_apis->add (keyword_list.at (j));
561  }
562  }
563 
564  // dsiconnect slot for saving prepared info if already connected
565  disconnect (_lexer_apis, SIGNAL (apiPreparationFinished ()), 0, 0);
566  // check whether path for prepared info exists or can be created
567  if (QDir("/").mkpath (prep_apis_path))
568  {
569  // path exists, apis info can be saved there
570  connect (_lexer_apis, SIGNAL (apiPreparationFinished ()),
571  this, SLOT (save_apis_info ()));
572  }
573  _lexer_apis->prepare (); // prepare apis info
574  }
575  }
576 
577  lexer->readSettings (*settings);
578 
579  _edit_area->setLexer (lexer);
580  _edit_area->setCaretForegroundColor (lexer->color (0));
581  _edit_area->setIndentationGuidesForegroundColor (lexer->color (0));
582 
583  // fix line number width with respect to the font size of the lexer
584  if (settings->value ("editor/showLineNumbers", true).toBool ())
586  else
587  _edit_area->setMarginWidth (2,0);
588 
589 }
590 
591 // function for adding entries to the octave lexer's APIs
592 void
594 {
595  octave_value keys = key_ovl(0);
596  Cell key_list = keys.cell_value ();
597 
598  for (int idx = 0; idx < key_list.numel (); idx++)
599  _lexer_apis->add (QString (key_list.elem (idx).string_value ().data ()));
600 }
601 
602 void
604 {
605  _lexer_apis->savePrepared (_prep_apis_file);
606 }
607 
608 QString
609 file_editor_tab::comment_string (const QString& lexer)
610 {
611  if (lexer == "octave" || lexer == "matlab")
612  return QString("%");
613  else if (lexer == "perl" || lexer == "bash" || lexer == "diff")
614  return QString("#");
615  else if (lexer == "cpp")
616  return ("//");
617  else if (lexer == "batch")
618  return ("REM ");
619  else
620  return ("%"); // should never happen
621 }
622 
623 // slot for fetab_set_focus: sets the focus to the current edit area
624 void
626 {
627  if (ID != this)
628  return;
629  _edit_area->setFocus ();
630 }
631 
632 void
633 file_editor_tab::context_help (const QWidget *ID, bool doc)
634 {
635  if (ID != this)
636  return;
637 
639 }
640 
641 void
643 {
644  if (ID != this)
645  return;
646 
648 }
649 
650 void
652 {
653  if (_cancelled)
654  return;
655 
656  if (check_file_modified () == QMessageBox::Cancel)
657  _cancelled = true;
658 }
659 
660 void
662 {
663  if (ID != this)
664  return;
665 
667 }
668 
669 void
670 file_editor_tab::save_file (const QWidget *ID, const QString& fileName,
671  bool remove_on_success)
672 {
673  if (ID != this)
674  return;
675 
676  save_file (fileName, remove_on_success);
677 }
678 
679 void
681 {
682  if (ID != this)
683  return;
684 
685  save_file_as ();
686 }
687 
688 void
690 {
691  if (ID != this)
692  return;
693 
694  QsciPrinter *printer = new QsciPrinter (QPrinter::HighResolution);
695 
696  QPrintDialog printDlg (printer, this);
697 
698  if (printDlg.exec () == QDialog::Accepted)
699  printer->printRange (_edit_area);
700 
701  delete printer;
702 }
703 
704 void
706 {
707  if (ID != this)
708  return;
709 
710  if (_edit_area->isModified () | ! valid_file_name ())
711  {
712  save_file (_file_name); // save file dialog
713  if (! valid_file_name ())
714  return; // still invalid file name: "save as" was cancelled
715  }
716 
717  QFileInfo info (_file_name);
718  emit run_file_signal (info);
719 }
720 
721 void
723 {
724  if (ID != this)
725  return;
726 
728 }
729 
730 void
732 {
733  if (ID != this)
734  return;
735 
736  int line, cur;
737  _edit_area->getCursorPosition (&line, &cur);
738 
739  if (_edit_area->markersAtLine (line) && (1 << bookmark))
740  _edit_area->markerDelete (line, bookmark);
741  else
742  _edit_area->markerAdd (line, bookmark);
743 }
744 
745 void
747 {
748  if (ID != this)
749  return;
750 
751  int line, cur;
752  _edit_area->getCursorPosition (&line, &cur);
753 
754  if (_edit_area->markersAtLine (line) && (1 << bookmark))
755  line++; // we have a breakpoint here, so start search from next line
756 
757  int nextline = _edit_area->markerFindNext (line, (1 << bookmark));
758 
759  _edit_area->setCursorPosition (nextline, 0);
760 }
761 
762 void
764 {
765  if (ID != this)
766  return;
767 
768  int line, cur;
769  _edit_area->getCursorPosition (&line, &cur);
770 
771  if (_edit_area->markersAtLine (line) && (1 << bookmark))
772  line--; // we have a breakpoint here, so start search from prev line
773 
774  int prevline = _edit_area->markerFindPrevious (line, (1 << bookmark));
775 
776  _edit_area->setCursorPosition (prevline, 0);
777 }
778 
779 void
781 {
782  if (ID != this)
783  return;
784 
785  _edit_area->markerDeleteAll (bookmark);
786 }
787 
788 void
789 file_editor_tab::add_breakpoint_callback (const bp_info& info)
790 {
791  bp_table::intmap line_info;
792  line_info[0] = info.line;
793 
794  if (octave_qt_link::file_in_path (info.file, info.dir))
795  bp_table::add_breakpoint (info.function_name, line_info);
796 }
797 
798 void
800 {
801  bp_table::intmap line_info;
802  line_info[0] = info.line;
803 
804  if (octave_qt_link::file_in_path (info.file, info.dir))
805  bp_table::remove_breakpoint (info.function_name, line_info);
806 }
807 
808 void
810 {
811  if (octave_qt_link::file_in_path (info.file, info.dir))
812  bp_table::remove_all_breakpoints_in_file (info.function_name, true);
813 }
814 
815 file_editor_tab::bp_info::bp_info (const QString& fname, int l)
816  : line (l), file (fname.toStdString ())
817 {
818  QFileInfo file_info (fname);
819 
820  QString q_dir = file_info.absolutePath ();
821  QString q_function_name = file_info.fileName ();
822 
823  // We have to cut off the suffix, because octave appends it.
824  q_function_name.chop (file_info.suffix ().length () + 1);
825 
826  dir = q_dir.toStdString ();
827  function_name = q_function_name.toStdString ();
828 
829  // Is the last component of DIR @foo? If so, strip it and prepend it
830  // to the name of the function.
831 
832  size_t pos = dir.rfind (file_ops::dir_sep_chars ());
833 
834  if (pos != std::string::npos && pos < dir.length () - 1)
835  {
836  if (dir[pos+1] == '@')
837  {
838  function_name = file_ops::concat (dir.substr (pos+1), function_name);
839 
840  dir = dir.substr (0, pos);
841  }
842  }
843 }
844 
845 void
847 {
848  bp_info info (_file_name, line+1);
849 
852 }
853 
854 void
856 {
857  bp_info info (_file_name, line+1);
858 
861 }
862 
863 void
865 {
866  if (ID != this)
867  return;
868 
869  int line, cur;
870  _edit_area->getCursorPosition (&line, &cur);
871 
872  if (_edit_area->markersAtLine (line) && (1 << breakpoint))
874  else
875  request_add_breakpoint (line);
876 }
877 
878 void
880 {
881  if (ID != this)
882  return;
883 
884  int line, cur;
885  _edit_area->getCursorPosition (&line, &cur);
886 
887  if (_edit_area->markersAtLine (line) && (1 << breakpoint))
888  line++; // we have a breakpoint here, so start search from next line
889 
890  int nextline = _edit_area->markerFindNext (line, (1 << breakpoint));
891 
892  _edit_area->setCursorPosition (nextline, 0);
893 }
894 
895 void
897 {
898  if (ID != this)
899  return;
900 
901  int line, cur, prevline;
902  _edit_area->getCursorPosition (&line, &cur);
903 
904  if (_edit_area->markersAtLine (line) && (1 << breakpoint))
905  line--; // we have a breakpoint here, so start search from prev line
906 
907  prevline = _edit_area->markerFindPrevious (line, (1 << breakpoint));
908 
909  _edit_area->setCursorPosition (prevline, 0);
910 }
911 
912 void
914 {
915  if (ID != this)
916  return;
917 
918  bp_info info (_file_name);
919 
922 }
923 
924 void
925 file_editor_tab::scintilla_command (const QWidget *ID, unsigned int sci_msg)
926 {
927  if (ID != this)
928  return;
929 
930  _edit_area->SendScintilla (sci_msg);
931 }
932 
933 void
935 {
936  if (ID != this)
937  return;
938 
940 }
941 
942 void
944 {
945  if (ID != this)
946  return;
947 
948  do_comment_selected_text (false);
949 }
950 
951 void
953 {
954  if (ID != this)
955  return;
956 
958 }
959 
960 void
962 {
963  if (ID != this)
964  return;
965 
966  do_indent_selected_text (false);
967 }
968 
969 void
970 file_editor_tab::convert_eol (const QWidget *ID, QsciScintilla::EolMode eol_mode)
971 {
972  if (ID != this)
973  return;
974 
975  _edit_area->convertEols (eol_mode);
976  _edit_area->setEolMode (eol_mode);
978 }
979 
980 void
982 {
983  if (ID != this)
984  return;
985 
986  _edit_area->zoomIn (1);
988 }
989 
990 void
992 {
993  if (ID != this)
994  return;
995 
996  _edit_area->zoomOut (1);
998 }
999 
1000 void
1002 {
1003  if (ID != this)
1004  return;
1005 
1006  _edit_area->zoomTo (0);
1007  auto_margin_width ();
1008 }
1009 
1010 
1011 void
1013 {
1014  // Find dialog is going to hide. Save location of window for
1015  // when it is reshown.
1016  _find_dialog_geometry = _find_dialog->geometry ();
1017  _find_dialog_is_visible = false;
1018 }
1019 
1020 void
1021 file_editor_tab::find (const QWidget *ID)
1022 {
1023  if (ID != this)
1024  return;
1025 
1026  // The find_dialog feature doesn't need a slot for return info.
1027  // Rather than Qt::DeleteOnClose, let the find feature hang about
1028  // in case it contains useful information like previous searches
1029  // and so on. Perhaps one find dialog for the whole editor is
1030  // better, but individual find dialogs has the nice feature of
1031  // retaining position per file editor tabs, which can be undocked.
1032 
1033  if (!_find_dialog)
1034  {
1036  qobject_cast<QWidget *> (sender ()));
1037  connect (_find_dialog, SIGNAL (finished (int)),
1038  this, SLOT (handle_find_dialog_finished (int)));
1039  _find_dialog->setWindowModality (Qt::NonModal);
1040  _find_dialog_geometry = _find_dialog->geometry ();
1041  }
1042  else if (!_find_dialog->isVisible ())
1043  {
1044  _find_dialog->setGeometry (_find_dialog_geometry);
1045  QPoint p = _find_dialog->pos ();
1046  _find_dialog->move(p.x ()+10, p.y ()+10);
1047  }
1048 
1049  _find_dialog->show ();
1050  _find_dialog_is_visible = true;
1051  _find_dialog->activateWindow ();
1053 
1054 }
1055 
1056 void
1057 file_editor_tab::goto_line (const QWidget *ID, int line)
1058 {
1059  if (ID != this)
1060  return;
1061 
1062  if (line <= 0) // ask for desired line
1063  {
1064  bool ok = false;
1065  int index;
1066  _edit_area->getCursorPosition (&line, &index);
1067  line = QInputDialog::getInt (_edit_area, tr ("Goto line"),
1068  tr ("Line number"), line+1, 1,
1069  _edit_area->lines (), 1, &ok);
1070  if (ok)
1071  {
1072  _edit_area->setCursorPosition (line-1, 0);
1074  }
1075  }
1076  else // go to given line without dialog
1077  _edit_area->setCursorPosition (line-1, 0);
1078 }
1079 
1080 void
1081 file_editor_tab::move_match_brace (const QWidget *ID, bool select)
1082 {
1083  if (ID != this)
1084  return;
1085 
1086  if (select)
1087  _edit_area->selectToMatchingBrace ();
1088  else
1089  _edit_area->moveToMatchingBrace ();
1090 }
1091 
1092 void
1094 {
1095  if (ID != this)
1096  return;
1097 
1098  QsciScintilla::AutoCompletionSource s = _edit_area->autoCompletionSource ();
1099  switch (s)
1100  {
1101  case QsciScintilla::AcsAll:
1102  _edit_area->autoCompleteFromAll ();
1103  break;
1104 
1105  case QsciScintilla::AcsAPIs:
1106  _edit_area->autoCompleteFromAPIs ();
1107  break;
1108 
1109  case QsciScintilla::AcsDocument:
1110  _edit_area->autoCompleteFromDocument ();
1111  break;
1112 
1113  case QsciScintilla::AcsNone:
1114  break;
1115  }
1116 }
1117 
1118 void
1120 {
1121  // TODO
1122  _edit_area->beginUndoAction ();
1123 
1124  if (_edit_area->hasSelectedText ())
1125  {
1126  int lineFrom, lineTo, colFrom, colTo;
1127  _edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo);
1128 
1129  if (colTo == 0) // the beginning of last line is not selected
1130  lineTo--; // stop at line above
1131 
1132  for (int i = lineFrom; i <= lineTo; i++)
1133  {
1134  if (indent)
1135  _edit_area->indent (i);
1136  else
1137  _edit_area->unindent (i);
1138  }
1139  //set selection on (un)indented section
1140  _edit_area->setSelection (lineFrom, 0, lineTo,
1141  _edit_area->text (lineTo).length ());
1142  }
1143  else
1144  {
1145  int cpline, col;
1146  _edit_area->getCursorPosition (&cpline, &col);
1147  if (indent)
1148  _edit_area->indent (cpline);
1149  else
1150  _edit_area->unindent (cpline);
1151  }
1152 
1153  _edit_area->endUndoAction ();
1154 }
1155 
1156 void
1158 {
1159  QString comment_str = comment_string (_edit_area->lexer ()->lexer ());
1160  _edit_area->beginUndoAction ();
1161 
1162  if (_edit_area->hasSelectedText ())
1163  {
1164  int lineFrom, lineTo, colFrom, colTo;
1165  _edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo);
1166 
1167  if (colTo == 0) // the beginning of last line is not selected
1168  lineTo--; // stop at line above
1169 
1170  for (int i = lineFrom; i <= lineTo; i++)
1171  {
1172  if (comment)
1173  _edit_area->insertAt (comment_str, i, 0);
1174  else
1175  {
1176  QString line (_edit_area->text (i));
1177  if (line.startsWith (comment_str))
1178  {
1179  _edit_area->setSelection (i, 0, i, comment_str.length ());
1180  _edit_area->removeSelectedText ();
1181  }
1182  }
1183  }
1184  //set selection on (un)commented section
1185  _edit_area->setSelection (lineFrom, 0, lineTo,
1186  _edit_area->text (lineTo).length ());
1187  }
1188  else
1189  {
1190  int cpline, col;
1191  _edit_area->getCursorPosition (&cpline, &col);
1192  if (comment)
1193  _edit_area->insertAt (comment_str, cpline, 0);
1194  else
1195  {
1196  QString line (_edit_area->text (cpline));
1197  if (line.startsWith (comment_str))
1198  {
1199  _edit_area->setSelection (cpline, 0, cpline, comment_str.length ());
1200  _edit_area->removeSelectedText ();
1201  }
1202  }
1203  }
1204  _edit_area->endUndoAction ();
1205 }
1206 
1207 void
1209 {
1210  QString title ("");
1211  QString tooltip ("");
1212 
1213  if (! valid_file_name ())
1214  title = tr ("<unnamed>");
1215  else
1216  {
1217  if (_long_title)
1218  title = _file_name;
1219  else
1220  {
1221  QFileInfo file (_file_name);
1222  title = file.fileName ();
1223  tooltip = _file_name;
1224  }
1225  }
1226 
1227  if (modified)
1228  emit file_name_changed (title.prepend ("* "), tooltip);
1229  else
1230  emit file_name_changed (title, tooltip);
1231 }
1232 
1233 void
1235 {
1236  _copy_available = enableCopy;
1238 }
1239 
1240 // show_dialog: shows a modal or non modal dialog depending on input arg
1241 void
1242 file_editor_tab::show_dialog (QDialog *dlg, bool modal)
1243 {
1244  dlg->setAttribute (Qt::WA_DeleteOnClose);
1245  if (modal)
1246  dlg->exec ();
1247  else
1248  {
1249  dlg->setWindowModality (Qt::NonModal);
1250  dlg->show ();
1251  }
1252 }
1253 
1254 int
1256 {
1257  int decision = QMessageBox::Yes;
1258  if (_edit_area->isModified ())
1259  {
1260  // File is modified but not saved, ask user what to do. The file
1261  // editor tab can't be made parent because it may be deleted depending
1262  // upon the response. Instead, change the _edit_area to read only.
1263  QMessageBox::StandardButtons buttons = QMessageBox::Save |
1264  QMessageBox::Discard |
1265  QMessageBox::Cancel;
1266 
1267  // For now, just a warning message about closing a tab that has been
1268  // modified seems sufficient. Exit-condition-specific messages could
1269  // be achieved by making 'available_actions' a function input string.
1270  QString available_actions =
1271  tr ("Do you want to cancel closing, save or discard the changes?");
1272 
1273  QString file;
1274  if (valid_file_name ())
1275  file = _file_name;
1276  else
1277  file = tr ("<unnamed>");
1278 
1279  QMessageBox* msgBox
1280  = new QMessageBox (QMessageBox::Warning, tr ("Octave Editor"),
1281  tr ("The file\n\n"
1282  " %1\n\n"
1283  "is about to be closed but has been modified. "
1284  "%2").
1285  arg (file). arg (available_actions),
1286  buttons, qobject_cast<QWidget *> (parent ()));
1287 
1288  msgBox->setDefaultButton (QMessageBox::Save);
1289  _edit_area->setReadOnly (true);
1290  connect (msgBox, SIGNAL (finished (int)),
1291  this, SLOT (handle_file_modified_answer (int)));
1292 
1293  show_dialog (msgBox, true);
1294 
1295  if (_cancelled)
1296  return QMessageBox::Cancel;
1297  else
1298  return decision;
1299  }
1300  else
1301  {
1302  // Nothing was modified. Leave tab present in case user
1303  // decides to cancel some point further along.
1304  }
1305 
1306  return decision;
1307 }
1308 
1309 void
1311 {
1312  if (decision == QMessageBox::Save)
1313  {
1314  // Save file, but do not remove from editor.
1315  save_file (_file_name, false);
1316  }
1317  else if (decision == QMessageBox::Discard)
1318  {
1319  // User doesn't want to save, leave tab and remove subsequently.
1320  }
1321  else
1322  {
1323  // User canceled, allow editing again.
1324  _edit_area->setReadOnly (false);
1325  _cancelled = true;
1326  }
1327 }
1328 
1329 void
1330 file_editor_tab::set_modified (bool modified)
1331 {
1332  _edit_area->setModified (modified);
1333 }
1334 
1335 QString
1336 file_editor_tab::load_file (const QString& fileName)
1337 {
1338  // get the absolute path
1339  QFileInfo file_info = QFileInfo (fileName);
1340  QString file_to_load;
1341  if (file_info.exists ())
1342  file_to_load = file_info.canonicalFilePath ();
1343  else
1344  file_to_load = fileName;
1345  QFile file (file_to_load);
1346  if (!file.open (QFile::ReadOnly))
1347  return file.errorString ();
1348 
1349  QTextStream in (&file);
1350  in.setCodec("UTF-8");
1351  QApplication::setOverrideCursor (Qt::WaitCursor);
1352  _edit_area->setText (in.readAll ());
1353  _edit_area->setEolMode (detect_eol_mode ());
1354  QApplication::restoreOverrideCursor ();
1355 
1356  _copy_available = false; // no selection yet available
1357  set_file_name (file_to_load);
1358  update_window_title (false); // window title (no modification)
1359  _edit_area->setModified (false); // loaded file is not modified yet
1360 
1362 
1363  return QString ();
1364 }
1365 
1366 QsciScintilla::EolMode
1368 {
1369  QByteArray text = _edit_area->text ().toAscii ();
1370 
1371  QByteArray eol_lf = QByteArray (1,0x0a);
1372  QByteArray eol_cr = QByteArray (1,0x0d);
1373  QByteArray eol_crlf = eol_cr;
1374  eol_crlf.append (eol_lf);
1375 
1376  int count_crlf = text.count (eol_crlf);
1377  int count_lf = text.count (eol_lf) - count_crlf; // isolated lf
1378  int count_cr = text.count (eol_cr) - count_crlf; // isolated cr;
1379 
1380  // get default from OS or from settings
1381 #if defined (Q_OS_WIN32)
1382  int os_eol_mode = QsciScintilla::EolWindows;
1383 #elif defined (Q_OS_MAC)
1384  int os_eol_mode = QsciScintilla::EolMac;
1385 #else
1386  int os_eol_mode = QsciScintilla::EolUnix;
1387 #endif
1388  QSettings *settings = resource_manager::get_settings ();
1389  QsciScintilla::EolMode eol_mode = static_cast<QsciScintilla::EolMode> (
1390  settings->value("editor/default_eol_mode",os_eol_mode).toInt ());
1391 
1392  int count_max = 0;
1393 
1394  if (count_crlf > count_max)
1395  {
1396  eol_mode = QsciScintilla::EolWindows;
1397  count_max = count_crlf;
1398  }
1399  if (count_lf > count_max)
1400  {
1401  eol_mode = QsciScintilla::EolUnix;
1402  count_max = count_lf;
1403  }
1404  if (count_cr > count_max)
1405  {
1406  eol_mode = QsciScintilla::EolMac;
1407  count_max = count_cr;
1408  }
1409 
1410  return eol_mode;
1411 }
1412 
1413 void
1415 {
1416  switch (_edit_area->eolMode ())
1417  {
1418  case QsciScintilla::EolWindows:
1419  _eol_indicator->setText ("CRLF");
1420  break;
1421  case QsciScintilla::EolMac:
1422  _eol_indicator->setText ("CR");
1423  break;
1424  case QsciScintilla::EolUnix:
1425  _eol_indicator->setText ("LF");
1426  break;
1427  }
1428 }
1429 
1430 void
1431 file_editor_tab::new_file (const QString &commands)
1432 {
1433  update_window_title (false); // window title (no modification)
1434 
1435  QSettings *settings = resource_manager::get_settings ();
1436 
1437  // set the eol mode from the settings or depending on the OS if the entry is
1438  // missing in the settings
1439 #if defined (Q_OS_WIN32)
1440  int eol_mode = QsciScintilla::EolWindows;
1441 #elif defined (Q_OS_MAC)
1442  int eol_mode = QsciScintilla::EolMac;
1443 #else
1444  int eol_mode = QsciScintilla::EolUnix;
1445 #endif
1446  _edit_area->setEolMode (
1447  static_cast<QsciScintilla::EolMode> (
1448  settings->value("editor/default_eol_mode",eol_mode).toInt ()));
1449 
1451 
1452  update_lexer ();
1453 
1454  _edit_area->setText (commands);
1455  _edit_area->setModified (false); // new file is not modified yet
1456 }
1457 
1458 void
1459 file_editor_tab::save_file (const QString& saveFileName, bool remove_on_success)
1460 {
1461  // If it is a new file with no name, signal that saveFileAs
1462  // should be performed.
1463  if (! valid_file_name (saveFileName))
1464  {
1465  save_file_as (remove_on_success);
1466  return;
1467  }
1468  // get the absolute path (if existing)
1469  QFileInfo file_info = QFileInfo (saveFileName);
1470  QString file_to_save;
1471  if (file_info.exists ())
1472  file_to_save = file_info.canonicalFilePath ();
1473  else
1474  file_to_save = saveFileName;
1475  QFile file (file_to_save);
1476 
1477  // stop watching file
1478  QStringList trackedFiles = _file_system_watcher.files ();
1479  if (trackedFiles.contains (file_to_save))
1480  _file_system_watcher.removePath (file_to_save);
1481 
1482  // open the file for writing
1483  if (!file.open (QIODevice::WriteOnly))
1484  {
1485  // Unsuccessful, begin watching file again if it was being
1486  // watched previously.
1487  if (trackedFiles.contains (file_to_save))
1488  _file_system_watcher.addPath (file_to_save);
1489 
1490  // Create a NonModal message about error.
1491  QMessageBox* msgBox
1492  = new QMessageBox (QMessageBox::Critical,
1493  tr ("Octave Editor"),
1494  tr ("Could not open file %1 for write:\n%2.").
1495  arg (file_to_save).arg (file.errorString ()),
1496  QMessageBox::Ok, 0);
1497  show_dialog (msgBox, false);
1498 
1499  return;
1500  }
1501 
1502  // save the contents into the file
1503  QTextStream out (&file);
1504  out.setCodec("UTF-8");
1505  QApplication::setOverrideCursor (Qt::WaitCursor);
1506  out << _edit_area->text ();
1507  out.flush ();
1508  QApplication::restoreOverrideCursor ();
1509  file.flush ();
1510  file.close ();
1511 
1512  // file exists now
1513  file_info = QFileInfo (file);
1514  file_to_save = file_info.canonicalFilePath ();
1515 
1516  // save file name after closing file as set_file_name starts watching again
1517  set_file_name (file_to_save); // make absolute
1518 
1519  // set the window title to actual file name (not modified)
1520  update_window_title (false);
1521 
1522  // files is save -> not modified
1523  _edit_area->setModified (false);
1524 
1525  if (remove_on_success)
1526  {
1527  emit tab_remove_request ();
1528  return; // Don't touch member variables after removal
1529  }
1530 }
1531 
1532 void
1533 file_editor_tab::save_file_as (bool remove_on_success)
1534 {
1535  // Simply put up the file chooser dialog box with a slot connection
1536  // then return control to the system waiting for a file selection.
1537 
1538  // If the tab is removed in response to a QFileDialog signal, the tab
1539  // can't be a parent.
1540  QFileDialog* fileDialog;
1541  if (remove_on_success)
1542  {
1543  // If tab is closed, "this" cannot be parent in which case modality
1544  // has no effect. Disable editing instead.
1545  _edit_area->setReadOnly (true);
1546  fileDialog = new QFileDialog ();
1547  }
1548  else
1549  fileDialog = new QFileDialog (this);
1550 
1551  // Giving trouble under KDE (problem is related to Qt signal handling on unix,
1552  // see https://bugs.kde.org/show_bug.cgi?id=260719 ,
1553  // it had/has no effect on Windows, though)
1554  fileDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1555 
1556  // get the dialog's layout for adding extra elements
1557  QGridLayout *dialog_layout = dynamic_cast<QGridLayout*> (fileDialog->layout ());
1558  int rows = dialog_layout->rowCount ();
1559 
1560  // define a new grid layout with the extra elements
1561  QGridLayout *extra = new QGridLayout (fileDialog);
1562  QSpacerItem *spacer = new QSpacerItem (1,1,QSizePolicy::Expanding,
1563  QSizePolicy::Fixed);
1564  QFrame *separator = new QFrame (fileDialog);
1565  separator->setFrameShape (QFrame::HLine); // horizontal line as separator
1566  separator->setFrameStyle (QFrame::Sunken);
1567 
1568  // combo box for choosing new line ending chars
1569  QLabel *label_eol = new QLabel (tr ("Line Endings:"));
1570  QComboBox *combo_eol = new QComboBox ();
1571  combo_eol->addItem ("Windows (CRLF)"); // ensure the same order as in
1572  combo_eol->addItem ("Mac (CR)"); // the settings dialog
1573  combo_eol->addItem ("Unix (LF)");
1574  _save_as_desired_eol = _edit_area->eolMode (); // init with current eol
1575  combo_eol->setCurrentIndex (_save_as_desired_eol);
1576 
1577  // track changes in the combo box
1578  connect (combo_eol, SIGNAL (currentIndexChanged (int)),
1579  this, SLOT (handle_combo_eol_current_index (int)));
1580 
1581  // build the extra grid layout
1582  extra->addWidget (separator,0,0,1,3);
1583  extra->addWidget (label_eol,1,0);
1584  extra->addWidget (combo_eol,1,1);
1585  extra->addItem (spacer, 1,2);
1586 
1587  // and add the extra grid layout to the dialog's layout
1588  dialog_layout->addLayout (extra,rows,0,1,dialog_layout->columnCount ());
1589 
1590  // add the possible filters and the default suffix
1591  QStringList filters;
1592  filters << tr ("Octave Files (*.m)")
1593  << tr ("All Files (*)");
1594  fileDialog->setNameFilters (filters);
1595  fileDialog->setDefaultSuffix ("m");
1596 
1597  if (valid_file_name ())
1598  {
1599  fileDialog->selectFile (_file_name);
1600  QFileInfo file_info (_file_name);
1601  if (file_info.suffix () != "m")
1602  { // it is not an octave file
1603  fileDialog->selectNameFilter (filters.at (1)); // "All Files"
1604  fileDialog->setDefaultSuffix (""); // no default suffix
1605  }
1606  }
1607  else
1608  {
1609  fileDialog->selectFile ("");
1610  fileDialog->setDirectory (_ced);
1611 
1612  // propose a name corresponding to the function name
1613  QString fname = get_function_name ();
1614  if (! fname.isEmpty ())
1615  fileDialog->selectFile (fname + ".m");
1616  }
1617 
1618  fileDialog->setAcceptMode (QFileDialog::AcceptSave);
1619  fileDialog->setViewMode (QFileDialog::Detail);
1620 
1621  connect (fileDialog, SIGNAL (filterSelected (const QString&)),
1622  this, SLOT (handle_save_as_filter_selected (const QString&)));
1623 
1624  if (remove_on_success)
1625  {
1626  connect (fileDialog, SIGNAL (fileSelected (const QString&)),
1627  this, SLOT (handle_save_file_as_answer_close (const QString&)));
1628 
1629  connect (fileDialog, SIGNAL (rejected ()),
1630  this, SLOT (handle_save_file_as_answer_cancel ()));
1631  }
1632  else
1633  {
1634  connect (fileDialog, SIGNAL (fileSelected (const QString&)),
1635  this, SLOT (handle_save_file_as_answer (const QString&)));
1636  }
1637 
1638  show_dialog (fileDialog, ! valid_file_name ());
1639 }
1640 
1641 void
1643 {
1644  _save_as_desired_eol = static_cast<QsciScintilla::EolMode> (index);
1645 }
1646 
1647 void
1649 {
1650  QFileDialog *file_dialog = qobject_cast<QFileDialog *> (sender ());
1651 
1652  QRegExp rx ("\\*\\.([^ ^\\)]*)[ \\)]"); // regexp for suffix in filter
1653  int index = rx.indexIn (filter,0); // get first suffix in filter
1654 
1655  if (index > -1)
1656  file_dialog->setDefaultSuffix (rx.cap (1)); // found a suffix, set default
1657  else
1658  file_dialog->setDefaultSuffix (""); // not found, clear default
1659 }
1660 
1661 bool
1662 file_editor_tab::check_valid_identifier (QString file_name)
1663 {
1664  QFileInfo file = QFileInfo (file_name);
1665  QString base_name = file.baseName ();
1666 
1667  if ((file.suffix () == "m")
1668  && (! valid_identifier (base_name.toStdString ())))
1669  {
1670  int ans = QMessageBox::question (0, tr ("Octave Editor"),
1671  tr ("\"%1\"\n"
1672  "is not a valid identifier.\n\n"
1673  "If you keep this file name, you will not be able to\n"
1674  "call your script using its name as an Octave command.\n\n"
1675  "Do you want to choose another name?").arg (base_name),
1676  QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
1677 
1678  if (ans == QMessageBox::Yes)
1679  return true;
1680  }
1681 
1682  return false;
1683 }
1684 
1685 void
1686 file_editor_tab::handle_save_file_as_answer (const QString& saveFileName)
1687 {
1688  if (_save_as_desired_eol != _edit_area->eolMode ())
1690 
1691  if (saveFileName == _file_name)
1692  {
1693  // same name as actual file, save it as "save" would do
1694  save_file (saveFileName);
1695  }
1696  else
1697  {
1698  // Have editor check for conflict, do not delete tab after save.
1699  if (check_valid_identifier (saveFileName))
1700  save_file_as (false);
1701  else
1702  emit editor_check_conflict_save (saveFileName, false);
1703  }
1704 }
1705 
1706 void
1707 file_editor_tab::handle_save_file_as_answer_close (const QString& saveFileName)
1708 {
1709  if (_save_as_desired_eol != _edit_area->eolMode ())
1710  {
1711  _edit_area->setReadOnly (false); // was set to read-only in save_file_as
1713  _edit_area->setReadOnly (true); // restore read-only mode
1714  }
1715 
1716  // saveFileName == _file_name can not happen, because we only can get here
1717  // when we close a tab and _file_name is not a valid file name yet
1718 
1719  // Have editor check for conflict, delete tab after save.
1720  if (check_valid_identifier (saveFileName))
1721  save_file_as (true);
1722  else
1723  emit editor_check_conflict_save (saveFileName, true);
1724 }
1725 
1726 void
1728 {
1729  // User canceled, allow editing again.
1730  _edit_area->setReadOnly (false);
1731 }
1732 
1733 void
1734 file_editor_tab::file_has_changed (const QString&)
1735 {
1736  // Prevent popping up multiple message boxes when the file has
1737  // been changed multiple times by temporarily removing from the
1738  // file watcher.
1739  QStringList trackedFiles = _file_system_watcher.files ();
1740  if (!trackedFiles.isEmpty ())
1741  _file_system_watcher.removePath (_file_name);
1742 
1743  if (QFile::exists (_file_name))
1744  {
1746 
1748 
1749  else
1750  {
1751  // Create a WindowModal message that blocks the edit area
1752  // by making _edit_area parent.
1753  QMessageBox* msgBox
1754  = new QMessageBox (QMessageBox::Warning,
1755  tr ("Octave Editor"),
1756  tr ("It seems that \'%1\' has been modified by another application. Do you want to reload it?").
1757  arg (_file_name),
1758  QMessageBox::Yes | QMessageBox::No, this);
1759 
1760  connect (msgBox, SIGNAL (finished (int)),
1761  this, SLOT (handle_file_reload_answer (int)));
1762 
1763  msgBox->setWindowModality (Qt::WindowModal);
1764  msgBox->setAttribute (Qt::WA_DeleteOnClose);
1765  msgBox->show ();
1766  }
1767  }
1768  else
1769  {
1770  QString modified = "";
1771  if (_edit_area->isModified ())
1772  modified = tr ("\n\nWarning: The contents in the editor is modified!");
1773 
1774  // Create a WindowModal message. The file editor tab can't be made
1775  // parent because it may be deleted depending upon the response.
1776  // Instead, change the _edit_area to read only.
1777  QMessageBox* msgBox
1778  = new QMessageBox (QMessageBox::Warning, tr ("Octave Editor"),
1779  tr ("It seems that the file\n"
1780  "%1\n"
1781  "has been deleted or renamed. Do you want to save it now?%2").
1782  arg (_file_name).arg (modified),
1783  QMessageBox::Save | QMessageBox::Close, 0);
1784 
1785  _edit_area->setReadOnly (true);
1786 
1787  connect (msgBox, SIGNAL (finished (int)),
1788  this, SLOT (handle_file_resave_answer (int)));
1789 
1790  msgBox->setWindowModality (Qt::WindowModal);
1791  msgBox->setAttribute (Qt::WA_DeleteOnClose);
1792  msgBox->show ();
1793  }
1794 }
1795 
1796 void
1797 file_editor_tab::notice_settings (const QSettings *settings, bool init)
1798 {
1799  // QSettings pointer is checked before emitting.
1800 
1801  if (! init)
1802  update_lexer ();
1803 
1804  // code folding
1805  if (settings->value ("editor/code_folding",true).toBool ())
1806  {
1807  _edit_area->setMarginType (3, QsciScintilla::SymbolMargin);
1808  _edit_area->setFolding (QsciScintilla::BoxedTreeFoldStyle , 3);
1809  }
1810  else
1811  {
1812  _edit_area->setFolding (QsciScintilla::NoFoldStyle, 3);
1813  }
1814 
1815  // status bar
1816  if (settings->value ("editor/show_edit_status_bar",true).toBool ())
1817  _status_bar->show ();
1818  else
1819  _status_bar->hide ();
1820 
1821  //highlight current line color
1822  QVariant default_var = QColor (240, 240, 240);
1823  QColor setting_color = settings->value ("editor/highlight_current_line_color",
1824  default_var).value<QColor> ();
1825  _edit_area->setCaretLineBackgroundColor (setting_color);
1826  _edit_area->setCaretLineVisible
1827  (settings->value ("editor/highlightCurrentLine", true).toBool ());
1828 
1829  bool match_keywords = settings->value
1830  ("editor/codeCompletion_keywords",true).toBool ();
1831  bool match_document = settings->value
1832  ("editor/codeCompletion_document",true).toBool ();
1833 
1834  QsciScintilla::AutoCompletionSource source = QsciScintilla::AcsNone;
1835  if (match_keywords)
1836  if (match_document)
1837  source = QsciScintilla::AcsAll;
1838  else
1839  source = QsciScintilla::AcsAPIs;
1840  else if (match_document)
1841  source = QsciScintilla::AcsDocument;
1842  _edit_area->setAutoCompletionSource (source);
1843 
1844  _edit_area->setAutoCompletionReplaceWord
1845  (settings->value ("editor/codeCompletion_replace",false).toBool ());
1846  _edit_area->setAutoCompletionCaseSensitivity
1847  (settings->value ("editor/codeCompletion_case",true).toBool ());
1848 
1849  if (settings->value ("editor/codeCompletion", true).toBool ())
1850  _edit_area->setAutoCompletionThreshold
1851  (settings->value ("editor/codeCompletion_threshold",2).toInt ());
1852  else
1853  _edit_area->setAutoCompletionThreshold (-1);
1854 
1855  if (settings->value ("editor/show_white_space",false).toBool ())
1856  if (settings->value ("editor/show_white_space_indent",false).toBool ())
1857  _edit_area->setWhitespaceVisibility (QsciScintilla::WsVisibleAfterIndent);
1858  else
1859  _edit_area->setWhitespaceVisibility (QsciScintilla::WsVisible);
1860  else
1861  _edit_area->setWhitespaceVisibility (QsciScintilla::WsInvisible);
1862 
1863  _edit_area->setEolVisibility (
1864  settings->value("editor/show_eol_chars",false).toBool ());
1865 
1866  if (settings->value ("editor/showLineNumbers", true).toBool ())
1867  {
1868  _edit_area->setMarginLineNumbers (2, true);
1869  auto_margin_width ();
1870  connect (_edit_area, SIGNAL (linesChanged ()),
1871  this, SLOT (auto_margin_width ()));
1872  }
1873  else
1874  {
1875  _edit_area->setMarginLineNumbers (2, false);
1876  disconnect (_edit_area, SIGNAL (linesChanged ()), 0, 0);
1877  }
1878 
1879  _edit_area->setAutoIndent
1880  (settings->value ("editor/auto_indent",true).toBool ());
1881  _edit_area->setTabIndents
1882  (settings->value ("editor/tab_indents_line",false).toBool ());
1883  _edit_area->setBackspaceUnindents
1884  (settings->value ("editor/backspace_unindents_line",false).toBool ());
1885  _edit_area->setIndentationGuides
1886  (settings->value ("editor/show_indent_guides",false).toBool ());
1887  _edit_area->setIndentationsUseTabs
1888  (settings->value ("editor/indent_uses_tabs",false).toBool ());
1889  _edit_area->setIndentationWidth
1890  (settings->value ("editor/indent_width",2).toInt ());
1891 
1892  _edit_area->setTabWidth
1893  (settings->value ("editor/tab_width",2).toInt ());
1894 
1895  _edit_area->SendScintilla (QsciScintillaBase::SCI_SETHSCROLLBAR,
1896  settings->value ("editor/show_hscroll_bar",true).toBool ());
1897  _edit_area->SendScintilla (QsciScintillaBase::SCI_SETSCROLLWIDTH,-1);
1898  _edit_area->SendScintilla (QsciScintillaBase::SCI_SETSCROLLWIDTHTRACKING,true);
1899 
1900  _long_title = settings->value ("editor/longWindowTitle", false).toBool ();
1901  update_window_title (_edit_area->isModified ());
1902 
1903  _edit_area->setEdgeColumn (
1904  settings->value ("editor/long_line_column",80).toInt ());
1905  if (settings->value ("editor/long_line_marker",true).toBool ())
1906  _edit_area->setEdgeMode (QsciScintilla::EdgeLine);
1907  else
1908  _edit_area->setEdgeMode (QsciScintilla::EdgeNone);
1909 
1910  // reload changed files
1912  settings->value ("editor/always_reload_changed_files",false).toBool ();
1913 }
1914 
1915 void
1917 {
1918  _edit_area->setMarginWidth (2, "1"+QString::number (_edit_area->lines ()));
1919 }
1920 
1921 // the following close request was changed from a signal slot into a
1922 // normal function because we need the return value from close whether
1923 // the tab really was closed (for canceling exiting octave).
1924 // When emitting a signal, only the return value from the last slot
1925 // goes back to the sender
1926 bool
1928 {
1929  return close ();
1930 }
1931 
1932 void
1934 {
1935  if (ID != this)
1936  {
1937  // Widget may be going out of focus. If so, record location.
1938  if (_find_dialog)
1939  {
1940  if (_find_dialog->isVisible ())
1941  {
1942  _find_dialog_geometry = _find_dialog->geometry ();
1943  _find_dialog->hide ();
1944  }
1945  }
1946  return;
1947  }
1948 
1950  {
1951  _find_dialog->setGeometry (_find_dialog_geometry);
1952  QPoint p = _find_dialog->pos ();
1953  _find_dialog->move(p.x ()+10, p.y ()+10);
1954  _find_dialog->show ();
1955  }
1956 
1958 }
1959 
1960 void
1962 {
1963  // A zero (null pointer) means that all file editor tabs
1964  // should respond, otherwise just the desired file editor tab.
1965  if (ID != this && ID != 0)
1966  return;
1967 
1968  // Unnamed files shouldn't be transmitted.
1969  if (!_file_name.isEmpty ())
1970  emit add_filename_to_list (_file_name, this);
1971 }
1972 
1973 void
1975 {
1976  if (decision == QMessageBox::Yes)
1977  {
1978  // reload: file is readded to the file watcher in set_file_name ()
1980  }
1981  else
1982  {
1983  // do not reload: readd to the file watche
1984  _file_system_watcher.addPath (_file_name);
1985  }
1986 }
1987 
1988 void
1990 {
1991  // check decision of user in dialog
1992  if (decision == QMessageBox::Save)
1993  {
1994  save_file (_file_name); // readds file to watcher in set_file_name ()
1995  _edit_area->setReadOnly (false); // delete read only flag
1996  }
1997  else
1998  {
1999  // Definitely close the file.
2000  // Set modified to false to prevent the dialog box when the close event
2001  // is posted. If the user cancels the close in this dialog the tab is
2002  // left open with a non-existing file.
2003  _edit_area->setModified (false);
2004  close ();
2005  }
2006 }
2007 
2008 void
2010 {
2011  if (ID != this || ID == 0)
2012  return;
2013 
2014  if (line > 0)
2015  {
2016  _edit_area->markerAdd (line-1, debugger_position);
2018  }
2019 }
2020 
2021 void
2023 {
2024  if (ID != this || ID == 0)
2025  return;
2026 
2027  if (line > 0)
2028  _edit_area->markerDelete (line-1, debugger_position);
2029 }
2030 
2031 void
2032 file_editor_tab::do_breakpoint_marker (bool insert, const QWidget *ID, int line)
2033 {
2034  if (ID != this || ID == 0)
2035  return;
2036 
2037  if (line > 0)
2038  {
2039  if (insert)
2040  _edit_area->markerAdd (line-1, breakpoint);
2041  else
2042  _edit_area->markerDelete (line-1, breakpoint);
2043  }
2044 }
2045 
2046 
2047 void
2049 {
2050  long int visible_lines
2051  = _edit_area->SendScintilla (QsciScintillaBase::SCI_LINESONSCREEN);
2052 
2053  if (visible_lines > 2)
2054  {
2055  int line, index;
2056  _edit_area->getCursorPosition (&line, &index);
2057 
2058  int first_line = _edit_area->firstVisibleLine ();
2059  first_line = first_line + (line - first_line - (visible_lines-1)/2);
2060 
2061  _edit_area->SendScintilla (2613,first_line); // SCI_SETFIRSTVISIBLELINE
2062  }
2063 }
2064 
2065 void
2066 file_editor_tab::handle_cursor_moved (int line, int col)
2067 {
2068  if (_edit_area->SendScintilla (QsciScintillaBase::SCI_AUTOCACTIVE))
2069  show_auto_completion (this);
2070 
2071  _row_indicator->setNum (line+1);
2072  _col_indicator->setNum (col+1);
2073 }
2074 
2075 QString
2077 {
2078  QRegExp rxfun1 ("^[\t ]*function[^=]+=([^\\(]+)\\([^\\)]*\\)[\t ]*$");
2079  QRegExp rxfun2 ("^[\t ]*function[\t ]+([^\\(]+)\\([^\\)]*\\)[\t ]*$");
2080  QRegExp rxfun3 ("^[\t ]*function[^=]+=[\t ]*([^\\s]+)[\t ]*$");
2081  QRegExp rxfun4 ("^[\t ]*function[\t ]+([^\\s]+)[\t ]*$");
2082 
2083  QStringList lines = _edit_area->text ().split ("\n");
2084 
2085  for (int i = 0; i < lines.count (); i++)
2086  {
2087  if (rxfun1.indexIn (lines.at (i)) != -1)
2088  return rxfun1.cap (1).remove (QRegExp("[ \t]*"));
2089  else if (rxfun2.indexIn (lines.at (i)) != -1)
2090  return rxfun2.cap (1).remove (QRegExp("[ \t]*"));
2091  else if (rxfun3.indexIn (lines.at (i)) != -1)
2092  return rxfun3.cap (1).remove (QRegExp("[ \t]*"));
2093  else if (rxfun4.indexIn (lines.at (i)) != -1)
2094  return rxfun4.cap (1).remove (QRegExp("[ \t]*"));
2095  }
2096 
2097  return QString ();
2098 }
2099 
2100 #endif
void handle_file_resave_answer(int decision)
QStatusBar * _status_bar
void file_has_changed(const QString &fileName)
const Cell & contents(const_iterator p) const
Definition: oct-map.h:314
OCTINTERP_API octave_value_list F__keywords__(const octave_value_list &=octave_value_list(), int=0)
Definition: help.cc:1205
void closeEvent(QCloseEvent *event)
void update_window_title(bool modified)
void auto_margin_width()
Definition: Cell.h:35
void remove_breakpoint_callback(const bp_info &info)
void convert_eol(const QWidget *ID, QsciScintilla::EolMode)
void previous_bookmark(const QWidget *ID)
void indent_selected_text(const QWidget *ID)
void context_help(const QWidget *ID, bool)
void add_filename_to_list(const QString &, QWidget *)
void set_file_name(const QString &fileName)
bp_info(const QString &fname, int l=0)
void find(const QWidget *ID)
static uint32_t state[624]
Definition: randmtzig.c:188
find_dialog * _find_dialog
QFileSystemWatcher _file_system_watcher
octave_idx_type numel(void) const
Number of elements in the array.
Definition: Array.h:275
void handle_save_file_as_answer_close(const QString &fileName)
void zoom_normal(const QWidget *ID)
void handle_combo_eol_current_index(int index)
void zoom_out(const QWidget *ID)
void file_name_query(const QWidget *ID)
void toggle_breakpoint(const QWidget *ID)
void add_breakpoint_callback(const bp_info &info)
QString fromStdString(const std::string &s)
void handle_copy_available(bool enableCopy)
void request_add_breakpoint(int line)
void do_breakpoint_marker(bool insert, const QWidget *ID, int line=-1)
void remove_all_breakpoints(const QWidget *ID)
T & elem(octave_idx_type n)
Definition: Array.h:380
void handle_cursor_moved(int line, int col)
void init_search_text()
OCTINTERP_API octave_value_list F__builtins__(const octave_value_list &=octave_value_list(), int=0)
Definition: help.cc:1217
void update_eol_indicator()
void handle_find_dialog_finished(int decision)
void check_modified_file(void)
void save_apis_info()
void save_file_as(const QWidget *ID)
Cell cell_value(void) const
Definition: ov.cc:1566
void context_help_doc(bool)
static std::string concat(const std::string &, const std::string &)
Definition: file-ops.cc:360
void message(const char *name, const char *fmt,...)
Definition: error.cc:380
void set_focus(const QWidget *ID)
void file_name_changed(const QString &fileName, const QString &toolTip)
QLabel * _eol_indicator
#define lexer
Definition: oct-parse.cc:170
bool valid_identifier(const char *s)
Definition: utils.cc:77
void insert_debugger_pointer(const QWidget *ID, int line=-1)
std::string string_value(bool force=false) const
Definition: ov.h:897
void notice_settings(const QSettings *settings, bool init=false)
std::string Voctave_home
Definition: defaults.cc:58
void print_file(const QWidget *ID)
MArray< double > filter(MArray< double > &, MArray< double > &, MArray< double > &, int dim)
std::map< int, int > intmap
Definition: debug.h:48
void change_editor_state(const QWidget *ID)
void center_current_line()
void goto_line(const QWidget *ID, int line=-1)
const T * data(void) const
Definition: Array.h:479
void do_comment_selected_text(bool comment)
void request_remove_breakpoint(int line)
void handle_margin_clicked(int line, int margin, Qt::KeyboardModifiers state)
void next_bookmark(const QWidget *ID)
void save_file(const QWidget *ID)
QLabel * _col_indicator
void move_match_brace(const QWidget *ID, bool select)
void context_run(const QWidget *ID)
void remove_bookmark(const QWidget *ID)
void unindent_selected_text(const QWidget *ID)
static intmap remove_all_breakpoints_in_file(const std::string &fname, bool silent=false)
Definition: debug.h:77
void set_current_directory(const QString &dir)
void handle_file_modified_answer(int decision)
static QSettings * get_settings(void)
void handle_context_menu_edit(const QString &)
void handle_file_reload_answer(int decision)
void delete_debugger_pointer(const QWidget *ID, int line=-1)
QString get_function_name()
octave_qscintilla * _edit_area
#define OCTAVE_VERSION
Definition: main.cc:46
void show_dialog(QDialog *dlg, bool modal)
QLabel * _row_indicator
double arg(double x)
Definition: lo-mappers.h:37
void toggle_bookmark(const QWidget *ID)
bool check_valid_identifier(QString file_name)
void handle_save_file_as_answer(const QString &fileName)
void context_edit(const QWidget *ID)
static std::string dir_sep_chars(void)
Definition: file-ops.h:68
void do_indent_selected_text(bool indent)
octave_value_list ovl(const octave_value &a0)
Definition: oct-obj.h:178
void remove_all_breakpoints_callback(const bp_info &info)
void next_breakpoint(const QWidget *ID)
bool _always_reload_changed_files
QString comment_string(const QString &)
void handle_save_as_filter_selected(const QString &filter)
void mru_add_file(const QString &file_name)
QsciAPIs * _lexer_apis
void previous_breakpoint(const QWidget *ID)
QsciScintilla::EolMode detect_eol_mode()
~file_editor_tab(void)
int check_file_modified()
void uncomment_selected_text(const QWidget *ID)
void add_octave_apis(octave_value_list key_ovl)
void handle_save_file_as_answer_cancel()
static int remove_breakpoint(const std::string &fname="", const intmap &lines=intmap())
Definition: debug.h:69
OCTINTERP_API octave_value_list F__which__(const octave_value_list &=octave_value_list(), int=0)
Definition: help.cc:1298
bool conditional_close(void)
void editor_check_conflict_save(const QString &saveFileName, bool remove_on_success)
static intmap add_breakpoint(const std::string &fname="", const intmap &lines=intmap())
Definition: debug.h:61
QString load_file(const QString &fileName)
QsciScintilla::EolMode _save_as_desired_eol
void show_auto_completion(const QWidget *ID)
void scintilla_command(const QWidget *, unsigned int)
void zoom_in(const QWidget *ID)
void run_file(const QWidget *ID)
void comment_selected_text(const QWidget *ID)
std::string toStdString(const QString &s)
void set_modified(bool modified=true)
static bool _cancelled
void new_file(const QString &commands=QString())
void run_file_signal(const QFileInfo &info)
void editor_state_changed(bool copy_available, bool is_octave_file)
void request_open_file(const QString &)
bool valid_file_name(const QString &file=QString())
OCTINTERP_API octave_value_list F__list_functions__(const octave_value_list &=octave_value_list(), int=0)
Definition: help.cc:1373
file_editor_tab(const QString &directory="")