GNU Octave  3.8.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 Copyright (C) 2011-2013 Jacob Dawid
4 
5 This file is part of Octave.
6 
7 Octave is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by the
9 Free Software Foundation; either version 3 of the License, or (at your
10 option) any later version.
11 
12 Octave is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Octave; see the file COPYING. If not, see
19 <http://www.gnu.org/licenses/>.
20 
21 */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #ifdef HAVE_QSCINTILLA
28 
29 #if defined (HAVE_QSCI_QSCILEXEROCTAVE_H)
30 #define HAVE_LEXER_OCTAVE
31 #include <Qsci/qscilexeroctave.h>
32 #elif defined (HAVE_QSCI_QSCILEXERMATLAB_H)
33 #define HAVE_LEXER_MATLAB
34 #include <Qsci/qscilexermatlab.h>
35 #endif
36 #include <Qsci/qscilexercpp.h>
37 #include <Qsci/qscilexerbash.h>
38 #include <Qsci/qscilexerperl.h>
39 #include <Qsci/qscilexerbatch.h>
40 #include <Qsci/qscilexerdiff.h>
41 #include <Qsci/qsciprinter.h>
42 #include "resource-manager.h"
43 #include <QApplication>
44 #include <QFileDialog>
45 #include <QMessageBox>
46 #include <QTextStream>
47 #include <QVBoxLayout>
48 #include <QInputDialog>
49 #include <QPrintDialog>
50 
51 #include "file-editor-tab.h"
52 #include "file-editor.h"
53 
54 #include "debug.h"
55 #include "octave-qt-link.h"
56 #include "version.h"
57 
58 // Make parent null for the file editor tab so that warning
59 // WindowModal messages don't affect grandparents.
60 file_editor_tab::file_editor_tab (const QString& directory_arg)
61 {
62  QString directory = directory_arg;
63  _lexer_apis = 0;
64  _app_closing = false;
65 
66  // Make sure there is a slash at the end of the directory name
67  // for identification when saved later.
68  if (directory.count () && directory.at (directory.count () - 1) != '/')
69  directory.append ("/");
70 
71  _file_name = directory;
72  _file_system_watcher.setObjectName ("_qt_autotest_force_engine_poller");
73 
74  _edit_area = new octave_qscintilla (this);
75  // Connect signal for command execution to a slot of this tab which in turn
76  // emits a signal connected to the main window.
77  // Direct connection is not possible because tab's parent is null.
78  connect (_edit_area,
79  SIGNAL (execute_command_in_terminal_signal (const QString&)),
80  this,
81  SLOT (execute_command_in_terminal (const QString&)));
82 
83  connect (_edit_area,
84  SIGNAL (cursorPositionChanged (int, int)),
85  this,
86  SLOT (handle_cursor_moved (int,int)));
87 
88  // create statusbar for row/col indicator
89  _status_bar = new QStatusBar (this);
90 
91  _row_indicator = new QLabel ("", this);
92  _row_indicator->setMinimumSize (30,0);
93  QLabel *row_label = new QLabel (tr ("Line:"), this);
94  _col_indicator = new QLabel ("", this);
95  _col_indicator->setMinimumSize (25,0);
96  QLabel *col_label = new QLabel (tr ("Col:"), this);
97  _status_bar->addPermanentWidget (row_label, 0);
98  _status_bar->addPermanentWidget (_row_indicator, 0);
99  _status_bar->addPermanentWidget (col_label, 0);
100  _status_bar->addPermanentWidget (_col_indicator, 0);
101 
102  // Leave the find dialog box out of memory until requested.
103  _find_dialog = 0;
104  _find_dialog_is_visible = false;
105 
106  // symbols
107  _edit_area->setMarginType (1, QsciScintilla::SymbolMargin);
108  _edit_area->setMarginSensitivity (1, true);
109  _edit_area->markerDefine (QsciScintilla::RightTriangle, bookmark);
110  _edit_area->markerDefine (QPixmap (":/actions/icons/redled.png"),
111  breakpoint);
112  _edit_area->markerDefine (QPixmap (":/actions/icons/bookmark.png"),
114 
115  connect (_edit_area, SIGNAL (marginClicked (int, int,
116  Qt::KeyboardModifiers)),
117  this, SLOT (handle_margin_clicked (int, int,
118  Qt::KeyboardModifiers)));
119 
120  // line numbers
121  _edit_area->setMarginsForegroundColor (QColor (96, 96, 96));
122  _edit_area->setMarginsBackgroundColor (QColor (232, 232, 220));
123  _edit_area->setMarginType (2, QsciScintilla::TextMargin);
124 
125  // code folding
126  _edit_area->setMarginType (3, QsciScintilla::SymbolMargin);
127  _edit_area->setFolding (QsciScintilla::BoxedTreeFoldStyle , 3);
128 
129  // other features
130  _edit_area->setBraceMatching (QsciScintilla::StrictBraceMatch);
131  _edit_area->setAutoIndent (true);
132  _edit_area->setIndentationWidth (2);
133  _edit_area->setIndentationsUseTabs (false);
134 
135  _edit_area->setUtf8 (true);
136 
137  // auto completion
138  _edit_area->autoCompleteFromAll ();
139  _edit_area->setAutoCompletionSource (QsciScintilla::AcsAll);
140 
141  QVBoxLayout *edit_area_layout = new QVBoxLayout ();
142  edit_area_layout->addWidget (_edit_area);
143  edit_area_layout->addWidget (_status_bar);
144  edit_area_layout->setMargin (0);
145  setLayout (edit_area_layout);
146 
147  // connect modified signal
148  connect (_edit_area, SIGNAL (modificationChanged (bool)),
149  this, SLOT (update_window_title (bool)));
150 
151  connect (_edit_area, SIGNAL (copyAvailable (bool)),
152  this, SLOT (handle_copy_available (bool)));
153 
154  connect (&_file_system_watcher, SIGNAL (fileChanged (const QString&)),
155  this, SLOT (file_has_changed (const QString&)));
156 
157  QSettings *settings = resource_manager::get_settings ();
158  if (settings)
159  notice_settings (settings);
160 }
161 
163 {
164  // Destroy items attached to _edit_area.
165  QsciLexer *lexer = _edit_area->lexer ();
166  if (lexer)
167  {
168  delete lexer;
169  _edit_area->setLexer (0);
170  }
171  if (_find_dialog)
172  {
173  delete _find_dialog;
174  _find_dialog = 0;
175  }
176 
177  // Destroy _edit_area.
178  delete _edit_area;
179 }
180 
181 void
182 file_editor_tab::closeEvent (QCloseEvent *e)
183 {
184  // ignore close event if file is not saved and user cancels
185  // closing this window
186  if (check_file_modified () == QMessageBox::Cancel)
187  e->ignore ();
188  else
189  e->accept ();
190 }
191 
192 void
193 file_editor_tab::execute_command_in_terminal (const QString& command)
194 {
195  emit execute_command_in_terminal_signal (command); // connected to main window
196 }
197 
198 void
199 file_editor_tab::set_file_name (const QString& fileName)
200 {
201  // update tracked file if we really have a file on disk
202  QStringList trackedFiles = _file_system_watcher.files ();
203  if (!trackedFiles.isEmpty ())
204  _file_system_watcher.removePath (_file_name);
205  if (!fileName.isEmpty ())
206  _file_system_watcher.addPath (fileName);
207  _file_name = fileName;
208 
209  // update lexer after _file_name change
210  update_lexer ();
211 
212  // update the file editor with current editing directory
214  // add the new file to the mru list
215 
216  emit mru_add_file (_file_name);
217 }
218 
219 // valid_file_name (file): checks whether "file" names a file
220 // by default, "file" is empty, then _file_name is checked
221 bool
222 file_editor_tab::valid_file_name (const QString& file)
223 {
224  QString file_name;
225  if (file.isEmpty ())
226  file_name = _file_name;
227  else
228  file_name = file;
229  return (! file_name.isEmpty ()
230  && file_name.at (file_name.count () - 1) != '/');
231 }
232 
233 void
235  Qt::KeyboardModifiers state)
236 {
237  if (margin == 1)
238  {
239  unsigned int markers_mask = _edit_area->markersAtLine (line);
240 
241  if (state & Qt::ControlModifier)
242  {
243  if (markers_mask && (1 << bookmark))
244  _edit_area->markerDelete (line, bookmark);
245  else
246  _edit_area->markerAdd (line, bookmark);
247  }
248  else
249  {
250  if (markers_mask && (1 << breakpoint))
252  else
253  request_add_breakpoint (line);
254  }
255  }
256 }
257 
258 void
260 {
261  if (_lexer_apis)
262  _lexer_apis->cancelPreparation (); // stop preparing if apis exists
263 
264  QsciLexer *lexer = _edit_area->lexer ();
265  delete lexer;
266  lexer = 0;
267 
268  if (_file_name.endsWith (".m")
269  || _file_name.endsWith ("octaverc"))
270  {
271 #if defined (HAVE_LEXER_OCTAVE)
272  lexer = new QsciLexerOctave ();
273 #elif defined (HAVE_LEXER_MATLAB)
274  lexer = new QsciLexerMatlab ();
275 #endif
276  }
277 
278  if (! lexer)
279  {
280  if (_file_name.endsWith (".c")
281  || _file_name.endsWith (".cc")
282  || _file_name.endsWith (".cpp")
283  || _file_name.endsWith (".cxx")
284  || _file_name.endsWith (".c++")
285  || _file_name.endsWith (".h")
286  || _file_name.endsWith (".hh")
287  || _file_name.endsWith (".hpp")
288  || _file_name.endsWith (".h++"))
289  {
290  lexer = new QsciLexerCPP ();
291  }
292  else if (_file_name.endsWith (".pl"))
293  {
294  lexer = new QsciLexerPerl ();
295  }
296  else if (_file_name.endsWith (".bat"))
297  {
298  lexer = new QsciLexerBatch ();
299  }
300  else if (_file_name.endsWith (".diff"))
301  {
302  lexer = new QsciLexerDiff ();
303  }
304  else if (! valid_file_name ())
305  {
306  // new, no yet named file: let us assume it is octave
307 #if defined (HAVE_LEXER_OCTAVE)
308  lexer = new QsciLexerOctave ();
309 #elif defined (HAVE_LEXER_MATLAB)
310  lexer = new QsciLexerMatlab ();
311 #else
312  lexer = new QsciLexerBash ();
313 #endif
314  }
315  else
316  {
317  // other or no extension
318  lexer = new QsciLexerBash ();
319  }
320  }
321 
322  _lexer_apis = new QsciAPIs(lexer);
323  if (_lexer_apis)
324  {
325  // get path to prepared api info
326  QDesktopServices desktopServices;
327  QString prep_apis_path
328  = desktopServices.storageLocation (QDesktopServices::HomeLocation)
329  + "/.config/octave/" + QString(OCTAVE_VERSION) + "/qsci/";
330  _prep_apis_file = prep_apis_path + lexer->lexer () + ".pap";
331 
332  if (!_lexer_apis->loadPrepared (_prep_apis_file))
333  {
334  // no prepared info loaded, prepare and save if possible
335 
336  // create raw apis info
337  QString keyword;
338  QStringList keyword_list;
339  int i,j;
340  for (i=1; i<=3; i++) // test the first 5 keyword sets
341  {
342  keyword = QString(lexer->keywords (i)); // get list
343  keyword_list = keyword.split (QRegExp ("\\s+")); // split
344  for (j = 0; j < keyword_list.size (); j++) // add to API
345  _lexer_apis->add (keyword_list.at (j));
346  }
347 
348  // dsiconnect slot for saving prepared info if already connected
349  disconnect (_lexer_apis, SIGNAL (apiPreparationFinished ()), 0, 0);
350  // check whether path for prepared info exists or can be created
351  if (QDir("/").mkpath (prep_apis_path))
352  {
353  // path exists, apis info can be saved there
354  connect (_lexer_apis, SIGNAL (apiPreparationFinished ()),
355  this, SLOT (save_apis_info ()));
356  }
357  _lexer_apis->prepare (); // prepare apis info
358  }
359  }
360 
361  QSettings *settings = resource_manager::get_settings ();
362  if (settings)
363  lexer->readSettings (*settings);
364 
365  _edit_area->setLexer (lexer);
366 
367  // fix line number width with respect to the font size of the lexer
368  if (settings->value ("editor/showLineNumbers", true).toBool ())
370  else
371  _edit_area->setMarginWidth (2,0);
372 
373 }
374 
375 void
377 {
378  _lexer_apis->savePrepared (_prep_apis_file);
379 }
380 
381 QString
382 file_editor_tab::comment_string (const QString& lexer)
383 {
384  if (lexer == "octave" || lexer == "matlab")
385  return QString("%");
386  else if (lexer == "perl" || lexer == "bash" || lexer == "diff")
387  return QString("#");
388  else if (lexer == "cpp")
389  return ("//");
390  else if (lexer == "batch")
391  return ("REM ");
392  else
393  return ("%"); // should never happen
394 }
395 
396 // slot for fetab_set_focus: sets the focus to the current edit area
397 void
399 {
400  if (ID != this)
401  return;
402  _edit_area->setFocus ();
403 }
404 
405 void
406 file_editor_tab::undo (const QWidget *ID)
407 {
408  if (ID != this)
409  return;
410 
411  _edit_area->undo ();
412 }
413 
414 void
415 file_editor_tab::redo (const QWidget *ID)
416 {
417  if (ID != this)
418  return;
419 
420  _edit_area->redo ();
421 }
422 
423 void
424 file_editor_tab::copy (const QWidget *ID)
425 {
426  if (ID != this)
427  return;
428 
429  _edit_area->copy ();
430 }
431 
432 void
433 file_editor_tab::cut (const QWidget *ID)
434 {
435  if (ID != this)
436  return;
437 
438  _edit_area->cut ();
439 }
440 
441 void
443 {
444  if (ID != this)
445  return;
446 
447  _edit_area->paste ();
448 }
449 
450 void
451 file_editor_tab::context_help (const QWidget *ID, bool doc)
452 {
453  if (ID != this)
454  return;
455 
457 }
458 
459 void
461 {
462  if (ID != this)
463  return;
464 
466 }
467 
468 void
470 {
471  if (ID != this)
472  return;
473 
475 }
476 void
477 
478 file_editor_tab::save_file (const QWidget *ID, const QString& fileName,
479  bool remove_on_success)
480 {
481  if (ID != this)
482  return;
483 
484  save_file (fileName, remove_on_success);
485 }
486 
487 void
489 {
490  if (ID != this)
491  return;
492 
493  save_file_as ();
494 }
495 
496 void
498 {
499  if (ID != this)
500  return;
501 
502  QsciPrinter *printer = new QsciPrinter (QPrinter::HighResolution);
503 
504  QPrintDialog printDlg (printer, this);
505 
506  if (printDlg.exec () == QDialog::Accepted)
507  printer->printRange (_edit_area);
508 
509  delete printer;
510 }
511 
512 void
514 {
515  if (ID != this)
516  return;
517 
518  if (_edit_area->isModified ())
520 
521  QFileInfo info (_file_name);
522  emit run_file_signal (info);
523 }
524 
525 void
527 {
528  if (ID != this)
529  return;
530 
532 }
533 
534 void
536 {
537  if (ID != this)
538  return;
539 
540  int line, cur;
541  _edit_area->getCursorPosition (&line, &cur);
542 
543  if (_edit_area->markersAtLine (line) && (1 << bookmark))
544  _edit_area->markerDelete (line, bookmark);
545  else
546  _edit_area->markerAdd (line, bookmark);
547 }
548 
549 void
551 {
552  if (ID != this)
553  return;
554 
555  int line, cur;
556  _edit_area->getCursorPosition (&line, &cur);
557 
558  if (_edit_area->markersAtLine (line) && (1 << bookmark))
559  line++; // we have a breakpoint here, so start search from next line
560 
561  int nextline = _edit_area->markerFindNext (line, (1 << bookmark));
562 
563  _edit_area->setCursorPosition (nextline, 0);
564 }
565 
566 void
568 {
569  if (ID != this)
570  return;
571 
572  int line, cur;
573  _edit_area->getCursorPosition (&line, &cur);
574 
575  if (_edit_area->markersAtLine (line) && (1 << bookmark))
576  line--; // we have a breakpoint here, so start search from prev line
577 
578  int prevline = _edit_area->markerFindPrevious (line, (1 << bookmark));
579 
580  _edit_area->setCursorPosition (prevline, 0);
581 }
582 
583 void
585 {
586  if (ID != this)
587  return;
588 
589  _edit_area->markerDeleteAll (bookmark);
590 }
591 
592 void
593 file_editor_tab::add_breakpoint_callback (const bp_info& info)
594 {
595  bp_table::intmap line_info;
596  line_info[0] = info.line;
597 
598  if (octave_qt_link::file_in_path (info.file, info.dir))
599  bp_table::add_breakpoint (info.function_name, line_info);
600 }
601 
602 void
604 {
605  bp_table::intmap line_info;
606  line_info[0] = info.line;
607 
608  if (octave_qt_link::file_in_path (info.file, info.dir))
609  bp_table::remove_breakpoint (info.function_name, line_info);
610 }
611 
612 void
614 {
615  if (octave_qt_link::file_in_path (info.file, info.dir))
616  bp_table::remove_all_breakpoints_in_file (info.function_name, true);
617 }
618 
619 void
621 {
622  QFileInfo file_info (_file_name);
623  QString dir = file_info.absolutePath ();
624  QString function_name = file_info.fileName ();
625 
626  // We have to cut off the suffix, because octave appends it.
627  function_name.chop (file_info.suffix ().length () + 1);
628 
629  bp_info info (_file_name, dir, function_name, line+1);
630 
633 }
634 
635 void
637 {
638  QFileInfo file_info (_file_name);
639  QString dir = file_info.absolutePath ();
640  QString function_name = file_info.fileName ();
641 
642  // We have to cut off the suffix, because octave appends it.
643  function_name.chop (file_info.suffix ().length () + 1);
644 
645  bp_info info (_file_name, dir, function_name, line+1);
646 
649 }
650 
651 void
653 {
654  if (ID != this)
655  return;
656 
657  int line, cur;
658  _edit_area->getCursorPosition (&line, &cur);
659 
660  if (_edit_area->markersAtLine (line) && (1 << breakpoint))
662  else
663  request_add_breakpoint (line);
664 }
665 
666 void
668 {
669  if (ID != this)
670  return;
671 
672  int line, cur;
673  _edit_area->getCursorPosition (&line, &cur);
674 
675  if (_edit_area->markersAtLine (line) && (1 << breakpoint))
676  line++; // we have a breakpoint here, so start search from next line
677 
678  int nextline = _edit_area->markerFindNext (line, (1 << breakpoint));
679 
680  _edit_area->setCursorPosition (nextline, 0);
681 }
682 
683 void
685 {
686  if (ID != this)
687  return;
688 
689  int line, cur, prevline;
690  _edit_area->getCursorPosition (&line, &cur);
691 
692  if (_edit_area->markersAtLine (line) && (1 << breakpoint))
693  line--; // we have a breakpoint here, so start search from prev line
694 
695  prevline = _edit_area->markerFindPrevious (line, (1 << breakpoint));
696 
697  _edit_area->setCursorPosition (prevline, 0);
698 }
699 
700 void
702 {
703  if (ID != this)
704  return;
705 
706  QFileInfo file_info (_file_name);
707  QString dir = file_info.absolutePath ();
708  QString function_name = file_info.fileName ();
709 
710  // We have to cut off the suffix, because octave appends it.
711  function_name.chop (file_info.suffix ().length () + 1);
712 
713  bp_info info (_file_name, dir, function_name, 0);
714 
717 }
718 
719 void
721 {
722  if (ID != this)
723  return;
724 
726 }
727 
728 void
730 {
731  if (ID != this)
732  return;
733 
734  do_comment_selected_text (false);
735 }
736 
737 void
739 {
740  // Find dialog is going to hide. Save location of window for
741  // when it is reshown.
742  _find_dialog_geometry = _find_dialog->geometry ();
743  _find_dialog_is_visible = false;
744 }
745 
746 void
747 file_editor_tab::find (const QWidget *ID)
748 {
749  if (ID != this)
750  return;
751 
752  // The find_dialog feature doesn't need a slot for return info.
753  // Rather than Qt::DeleteOnClose, let the find feature hang about
754  // in case it contains useful information like previous searches
755  // and so on. Perhaps one find dialog for the whole editor is
756  // better, but individual find dialogs has the nice feature of
757  // retaining position per file editor tabs, which can be undocked.
758 
759  if (!_find_dialog)
760  {
762  qobject_cast<QWidget *> (sender ()));
763  connect (_find_dialog, SIGNAL (finished (int)),
764  this, SLOT (handle_find_dialog_finished (int)));
765  _find_dialog->setWindowModality (Qt::NonModal);
766  _find_dialog_geometry = _find_dialog->geometry ();
767  }
768 
769  if (!_find_dialog->isVisible ())
770  {
771  _find_dialog->setGeometry (_find_dialog_geometry);
772  _find_dialog->show ();
774  }
775 
776  _find_dialog->activateWindow ();
778 
779 }
780 
781 void
782 file_editor_tab::goto_line (const QWidget *ID, int line)
783 {
784  if (ID != this)
785  return;
786 
787  if (line <= 0) // ask for desired line
788  {
789  bool ok = false;
790  int index;
791  _edit_area->getCursorPosition (&line, &index);
792  line = QInputDialog::getInt (_edit_area, tr ("Goto line"),
793  tr ("Line number"), line+1, 1,
794  _edit_area->lines (), 1, &ok);
795  if (ok)
796  {
797  _edit_area->setCursorPosition (line-1, 0);
799  }
800  }
801  else // go to given line without dialog
802  _edit_area->setCursorPosition (line-1, 0);
803 }
804 
805 
806 void
808 {
809  QString comment_str = comment_string (_edit_area->lexer ()->lexer ());
810  _edit_area->beginUndoAction ();
811 
812  if (_edit_area->hasSelectedText ())
813  {
814  int lineFrom, lineTo, colFrom, colTo;
815  _edit_area->getSelection (&lineFrom, &colFrom, &lineTo, &colTo);
816 
817  if (colTo == 0) // the beginning of last line is not selected
818  lineTo--; // stop at line above
819 
820  for (int i = lineFrom; i <= lineTo; i++)
821  {
822  if (comment)
823  _edit_area->insertAt (comment_str, i, 0);
824  else
825  {
826  QString line (_edit_area->text (i));
827  if (line.startsWith (comment_str))
828  {
829  _edit_area->setSelection (i, 0, i, comment_str.length ());
830  _edit_area->removeSelectedText ();
831  }
832  }
833  }
834  //set selection on (un)commented section
835  _edit_area->setSelection (lineFrom, 0, lineTo,
836  _edit_area->text (lineTo).length ());
837  }
838  else
839  {
840  int cpline, col;
841  _edit_area->getCursorPosition (&cpline, &col);
842  if (comment)
843  _edit_area->insertAt (comment_str, cpline, 0);
844  else
845  {
846  QString line (_edit_area->text (cpline));
847  if (line.startsWith (comment_str))
848  {
849  _edit_area->setSelection (cpline, 0, cpline, comment_str.length ());
850  _edit_area->removeSelectedText ();
851  }
852  }
853  }
854  _edit_area->endUndoAction ();
855 }
856 
857 void
859 {
860  QString title ("");
861  QString tooltip ("");
862 
863  if (! valid_file_name ())
864  title = tr ("<unnamed>");
865  else
866  {
867  if (_long_title)
868  title = _file_name;
869  else
870  {
871  QFileInfo file (_file_name);
872  title = file.fileName ();
873  tooltip = _file_name;
874  }
875  }
876 
877  if (modified)
878  emit file_name_changed (title.prepend ("* "), tooltip);
879  else
880  emit file_name_changed (title, tooltip);
881 }
882 
883 void
885 {
886  _copy_available = enableCopy;
887  emit editor_state_changed (_copy_available, QDir::cleanPath (_file_name));
888 }
889 
890 // show_dialog: shows a modal or non modal dialog depeding on the closing
891 // of the app
892 void
894 {
895  dlg->setAttribute (Qt::WA_DeleteOnClose);
896  if (_app_closing)
897  dlg->exec ();
898  else
899  {
900  dlg->setWindowModality (Qt::WindowModal);
901  dlg->show ();
902  }
903 }
904 
905 int
907 {
908  int decision = QMessageBox::Yes;
909  if (_edit_area->isModified ())
910  {
911  activateWindow ();
912  raise ();
913  // File is modified but not saved, ask user what to do. The file
914  // editor tab can't be made parent because it may be deleted depending
915  // upon the response. Instead, change the _edit_area to read only.
916  QMessageBox::StandardButtons buttons = QMessageBox::Save |
917  QMessageBox::Discard;
918  QString available_actions;
919 
920  if (_app_closing)
921  available_actions = tr ("Do you want to save or discard the changes?");
922  else
923  {
924  buttons = buttons | QMessageBox::Cancel; // cancel is allowed
925  available_actions
926  = tr ("Do you want to cancel closing, save or discard the changes?");
927  }
928 
929  QString file;
930  if (valid_file_name ())
931  file = _file_name;
932  else
933  file = tr ("<unnamed>");
934 
935  QMessageBox* msgBox
936  = new QMessageBox (QMessageBox::Warning, tr ("Octave Editor"),
937  tr ("The file\n"
938  "%1\n"
939  "is about to be closed but has been modified.\n"
940  "%2").
941  arg (file). arg (available_actions),
942  buttons, qobject_cast<QWidget *> (parent ()));
943 
944  msgBox->setDefaultButton (QMessageBox::Save);
945  _edit_area->setReadOnly (true);
946  connect (msgBox, SIGNAL (finished (int)),
947  this, SLOT (handle_file_modified_answer (int)));
948 
949  show_dialog (msgBox);
950 
951  return QMessageBox::Cancel;
952  }
953  else
954  {
955  // Nothing was modified, just remove from editor.
956  emit tab_remove_request ();
957  }
958 
959  return decision;
960 }
961 
962 void
964 {
965  if (decision == QMessageBox::Save)
966  {
967  // Save file, then remove from editor.
968  save_file (_file_name, true);
969  }
970  else if (decision == QMessageBox::Discard)
971  {
972  // User doesn't want to save, just remove from editor.
973  emit tab_remove_request ();
974  }
975  else
976  {
977  // User canceled, allow editing again.
978  _edit_area->setReadOnly (false);
979  }
980 }
981 
982 void
983 file_editor_tab::set_modified (bool modified)
984 {
985  _edit_area->setModified (modified);
986 }
987 
988 QString
989 file_editor_tab::load_file (const QString& fileName)
990 {
991  // get the absolute path
992  QFileInfo file_info = QFileInfo (fileName);
993  QString file_to_load;
994  if (file_info.exists ())
995  file_to_load = file_info.canonicalFilePath ();
996  else
997  file_to_load = fileName;
998  QFile file (file_to_load);
999  if (!file.open (QFile::ReadOnly))
1000  return file.errorString ();
1001 
1002  QTextStream in (&file);
1003  QApplication::setOverrideCursor (Qt::WaitCursor);
1004  _edit_area->setText (in.readAll ());
1005  QApplication::restoreOverrideCursor ();
1006 
1007  _copy_available = false; // no selection yet available
1008  set_file_name (file_to_load);
1009  update_window_title (false); // window title (no modification)
1010  _edit_area->setModified (false); // loaded file is not modified yet
1011 
1012  return QString ();
1013 }
1014 
1015 void
1016 file_editor_tab::new_file (const QString &commands)
1017 {
1018  update_window_title (false); // window title (no modification)
1019  _edit_area->setText (commands);
1020  _edit_area->setModified (false); // new file is not modified yet
1021 }
1022 
1023 void
1024 file_editor_tab::save_file (const QString& saveFileName, bool remove_on_success)
1025 {
1026  // If it is a new file with no name, signal that saveFileAs
1027  // should be performed.
1028  if (! valid_file_name (saveFileName))
1029  {
1030  save_file_as (remove_on_success);
1031  return;
1032  }
1033  // get the absolute path (if existing)
1034  QFileInfo file_info = QFileInfo (saveFileName);
1035  QString file_to_save;
1036  if (file_info.exists ())
1037  file_to_save = file_info.canonicalFilePath ();
1038  else
1039  file_to_save = saveFileName;
1040  QFile file (file_to_save);
1041 
1042  // stop watching file
1043  QStringList trackedFiles = _file_system_watcher.files ();
1044  if (trackedFiles.contains (file_to_save))
1045  _file_system_watcher.removePath (file_to_save);
1046 
1047  // open the file for writing
1048  if (!file.open (QIODevice::WriteOnly))
1049  {
1050  // Unsuccessful, begin watching file again if it was being
1051  // watched previously.
1052  if (trackedFiles.contains (file_to_save))
1053  _file_system_watcher.addPath (file_to_save);
1054 
1055  // Create a NonModal message about error.
1056  QMessageBox* msgBox
1057  = new QMessageBox (QMessageBox::Critical,
1058  tr ("Octave Editor"),
1059  tr ("Could not open file %1 for write:\n%2.").
1060  arg (file_to_save).arg (file.errorString ()),
1061  QMessageBox::Ok, 0);
1062  show_dialog (msgBox);
1063 
1064  return;
1065  }
1066 
1067  // save the contents into the file
1068  QTextStream out (&file);
1069  QApplication::setOverrideCursor (Qt::WaitCursor);
1070  out << _edit_area->text ();
1071  out.flush ();
1072  QApplication::restoreOverrideCursor ();
1073  file.flush ();
1074  file.close ();
1075 
1076  // file exists now
1077  file_info = QFileInfo (file);
1078  file_to_save = file_info.canonicalFilePath ();
1079 
1080  // save file name after closing file as set_file_name starts watching again
1081  set_file_name (file_to_save); // make absolute
1082 
1083  // set the window title to actual file name (not modified)
1084  update_window_title (false);
1085 
1086  // files is save -> not modified
1087  _edit_area->setModified (false);
1088 
1089  if (remove_on_success)
1090  {
1091  emit tab_remove_request ();
1092  return; // Don't touch member variables after removal
1093  }
1094 }
1095 
1096 void
1097 file_editor_tab::save_file_as (bool remove_on_success)
1098 {
1099  // Simply put up the file chooser dialog box with a slot connection
1100  // then return control to the system waiting for a file selection.
1101 
1102  // If the tab is removed in response to a QFileDialog signal, the tab
1103  // can't be a parent.
1104  QFileDialog* fileDialog;
1105  if (remove_on_success)
1106  {
1107  // If tab is closed, "this" cannot be parent in which case modality
1108  // has no effect. Disable editing instead.
1109  _edit_area->setReadOnly (true);
1110  fileDialog = new QFileDialog ();
1111  }
1112  else
1113  fileDialog = new QFileDialog (this);
1114 
1115  // Giving trouble under KDE (problem is related to Qt signal handling on unix,
1116  // see https://bugs.kde.org/show_bug.cgi?id=260719 ,
1117  // it had/has no effect on Windows, though)
1118  fileDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1119 
1120  if (valid_file_name ())
1121  {
1122  fileDialog->selectFile (_file_name);
1123  }
1124  else
1125  {
1126  fileDialog->selectFile ("");
1127 
1128  if (_file_name.isEmpty ())
1129  fileDialog->setDirectory (QDir::currentPath ());
1130  else
1131  {
1132  // The file name is actually the directory name from the
1133  // constructor argument.
1134  fileDialog->setDirectory (_file_name);
1135  }
1136  }
1137 
1138  fileDialog->setNameFilter (tr ("Octave Files (*.m);;All Files (*)"));
1139  fileDialog->setDefaultSuffix ("m");
1140  fileDialog->setAcceptMode (QFileDialog::AcceptSave);
1141  fileDialog->setViewMode (QFileDialog::Detail);
1142 
1143  if (remove_on_success)
1144  {
1145  connect (fileDialog, SIGNAL (fileSelected (const QString&)),
1146  this, SLOT (handle_save_file_as_answer_close (const QString&)));
1147 
1148  connect (fileDialog, SIGNAL (rejected ()),
1149  this, SLOT (handle_save_file_as_answer_cancel ()));
1150  }
1151  else
1152  {
1153  connect (fileDialog, SIGNAL (fileSelected (const QString&)),
1154  this, SLOT (handle_save_file_as_answer (const QString&)));
1155  }
1156 
1157  show_dialog (fileDialog);
1158 }
1159 
1160 void
1161 file_editor_tab::handle_save_file_as_answer (const QString& saveFileName)
1162 {
1163  if (saveFileName == _file_name)
1164  {
1165  // same name as actual file, save it as "save" would do
1166  save_file (saveFileName);
1167  }
1168  else
1169  {
1170  // Have editor check for conflict, do not delete tab after save.
1171  emit editor_check_conflict_save (saveFileName, false);
1172  }
1173 }
1174 
1175 void
1176 file_editor_tab::handle_save_file_as_answer_close (const QString& saveFileName)
1177 {
1178  // saveFileName == _file_name can not happen, because we only can get here
1179  // when we close a tab and _file_name is not a valid file name yet
1180 
1181  // Have editor check for conflict, delete tab after save.
1182  emit editor_check_conflict_save (saveFileName, true);
1183 }
1184 
1185 void
1187 {
1188  // User canceled, allow editing again.
1189  _edit_area->setReadOnly (false);
1190 }
1191 
1192 void
1193 file_editor_tab::file_has_changed (const QString&)
1194 {
1195  // Prevent popping up multiple message boxes when the file has
1196  // been changed multiple times by temporarily removing from the
1197  // file watcher.
1198  QStringList trackedFiles = _file_system_watcher.files ();
1199  if (!trackedFiles.isEmpty ())
1200  _file_system_watcher.removePath (_file_name);
1201 
1202  if (QFile::exists (_file_name))
1203  {
1204  // Create a WindowModal message that blocks the edit area
1205  // by making _edit_area parent.
1206  QMessageBox* msgBox
1207  = new QMessageBox (QMessageBox::Warning,
1208  tr ("Octave Editor"),
1209  tr ("It seems that \'%1\' has been modified by another application. Do you want to reload it?").
1210  arg (_file_name),
1211  QMessageBox::Yes | QMessageBox::No, this);
1212 
1213  connect (msgBox, SIGNAL (finished (int)),
1214  this, SLOT (handle_file_reload_answer (int)));
1215 
1216  msgBox->setWindowModality (Qt::WindowModal);
1217  msgBox->setAttribute (Qt::WA_DeleteOnClose);
1218  msgBox->show ();
1219  }
1220  else
1221  {
1222  QString modified = "";
1223  if (_edit_area->isModified ())
1224  modified = tr ("\n\nWarning: The contents in the editor is modified!");
1225 
1226  // Create a WindowModal message. The file editor tab can't be made
1227  // parent because it may be deleted depending upon the response.
1228  // Instead, change the _edit_area to read only.
1229  QMessageBox* msgBox
1230  = new QMessageBox (QMessageBox::Warning, tr ("Octave Editor"),
1231  tr ("It seems that the file\n"
1232  "%1\n"
1233  "has been deleted or renamed. Do you want to save it now?%2").
1234  arg (_file_name).arg (modified),
1235  QMessageBox::Save | QMessageBox::Close, 0);
1236 
1237  _edit_area->setReadOnly (true);
1238 
1239  connect (msgBox, SIGNAL (finished (int)),
1240  this, SLOT (handle_file_resave_answer (int)));
1241 
1242  msgBox->setWindowModality (Qt::WindowModal);
1243  msgBox->setAttribute (Qt::WA_DeleteOnClose);
1244  msgBox->show ();
1245  }
1246 }
1247 
1248 void
1249 file_editor_tab::notice_settings (const QSettings *settings)
1250 {
1251  // QSettings pointer is checked before emitting.
1252 
1253  update_lexer ();
1254 
1255  //highlight current line color
1256  QVariant default_var = QColor (240, 240, 240);
1257  QColor setting_color = settings->value ("editor/highlight_current_line_color",
1258  default_var).value<QColor> ();
1259  _edit_area->setCaretLineBackgroundColor (setting_color);
1260  _edit_area->setCaretLineVisible
1261  (settings->value ("editor/highlightCurrentLine", true).toBool ());
1262 
1263  if (settings->value ("editor/codeCompletion", true).toBool ()) // auto compl.
1264  {
1265  bool match_keywords = settings->value
1266  ("editor/codeCompletion_keywords",true).toBool ();
1267  bool match_document = settings->value
1268  ("editor/codeCompletion_document",true).toBool ();
1269 
1270  QsciScintilla::AutoCompletionSource source = QsciScintilla::AcsNone;
1271  if (match_keywords)
1272  if (match_document)
1273  source = QsciScintilla::AcsAll;
1274  else
1275  source = QsciScintilla::AcsAPIs;
1276  else if (match_document)
1277  source = QsciScintilla::AcsDocument;
1278  _edit_area->setAutoCompletionSource (source);
1279 
1280  _edit_area->setAutoCompletionReplaceWord
1281  (settings->value ("editor/codeCompletion_replace",false).toBool ());
1282  _edit_area->setAutoCompletionCaseSensitivity
1283  (settings->value ("editor/codeCompletion_case",true).toBool ());
1284  _edit_area->setAutoCompletionThreshold
1285  (settings->value ("editor/codeCompletion_threshold",2).toInt ());
1286  }
1287  else
1288  _edit_area->setAutoCompletionThreshold (-1);
1289 
1290  if (settings->value ("editor/show_white_space",false).toBool ())
1291  if (settings->value ("editor/show_white_space_indent",false).toBool ())
1292  _edit_area->setWhitespaceVisibility (QsciScintilla::WsVisibleAfterIndent);
1293  else
1294  _edit_area->setWhitespaceVisibility (QsciScintilla::WsVisible);
1295  else
1296  _edit_area->setWhitespaceVisibility (QsciScintilla::WsInvisible);
1297 
1298  if (settings->value ("editor/showLineNumbers", true).toBool ())
1299  {
1300  _edit_area->setMarginLineNumbers (2, true);
1301  auto_margin_width ();
1302  connect (_edit_area, SIGNAL (linesChanged ()),
1303  this, SLOT (auto_margin_width ()));
1304  }
1305  else
1306  {
1307  _edit_area->setMarginLineNumbers (2, false);
1308  disconnect (_edit_area, SIGNAL (linesChanged ()), 0, 0);
1309  }
1310 
1311  _edit_area->setAutoIndent
1312  (settings->value ("editor/auto_indent",true).toBool ());
1313  _edit_area->setTabIndents
1314  (settings->value ("editor/tab_indents_line",false).toBool ());
1315  _edit_area->setBackspaceUnindents
1316  (settings->value ("editor/backspace_unindents_line",false).toBool ());
1317  _edit_area->setIndentationGuides
1318  (settings->value ("editor/show_indent_guides",false).toBool ());
1319 
1320  _edit_area->setTabWidth
1321  (settings->value ("editor/tab_width",2).toInt ());
1322 
1323  _long_title = settings->value ("editor/longWindowTitle", false).toBool ();
1324  update_window_title (_edit_area->isModified ());
1325 
1326 }
1327 
1328 void
1330 {
1331  _edit_area->setMarginWidth (2, "1"+QString::number (_edit_area->lines ()));
1332 }
1333 
1334 void
1335 file_editor_tab::conditional_close (const QWidget *ID, bool app_closing)
1336 {
1337  if (ID != this)
1338  return;
1339 
1340  _app_closing = app_closing;
1341  close ();
1342 }
1343 
1344 void
1346 {
1347  if (ID != this)
1348  {
1349  // Widget may be going out of focus. If so, record location.
1350  if (_find_dialog)
1351  {
1352  if (_find_dialog->isVisible ())
1353  {
1354  _find_dialog_geometry = _find_dialog->geometry ();
1355  _find_dialog->hide ();
1356  }
1357  }
1358  return;
1359  }
1360 
1362  {
1363  _find_dialog->setGeometry (_find_dialog_geometry);
1364  _find_dialog->show ();
1365  }
1366 
1367  emit editor_state_changed (_copy_available, QDir::cleanPath (_file_name));
1368 }
1369 
1370 void
1372 {
1373  // A zero (null pointer) means that all file editor tabs
1374  // should respond, otherwise just the desired file editor tab.
1375  if (ID != this && ID != 0)
1376  return;
1377 
1378  // Unnamed files shouldn't be transmitted.
1379  if (!_file_name.isEmpty ())
1380  emit add_filename_to_list (_file_name, this);
1381 }
1382 
1383 void
1385 {
1386  if (decision == QMessageBox::Yes)
1387  {
1388  // reload: file is readded to the file watcher in set_file_name ()
1390  }
1391  else
1392  {
1393  // do not reload: readd to the file watche
1394  _file_system_watcher.addPath (_file_name);
1395  }
1396 }
1397 
1398 void
1400 {
1401  // check decision of user in dialog
1402  if (decision == QMessageBox::Save)
1403  {
1404  save_file (_file_name); // readds file to watcher in set_file_name ()
1405  _edit_area->setReadOnly (false); // delete read only flag
1406  }
1407  else
1408  {
1409  // Definitely close the file.
1410  // Set modified to false to prevent the dialog box when the close event
1411  // is posted. If the user cancels the close in this dialog the tab is
1412  // left open with a non-existing file.
1413  _edit_area->setModified (false);
1414  close ();
1415  }
1416 }
1417 
1418 void
1420 {
1421  if (ID != this || ID == 0)
1422  return;
1423 
1424  if (line > 0)
1425  {
1426  _edit_area->markerAdd (line-1, debugger_position);
1428  }
1429 }
1430 
1431 void
1433 {
1434  if (ID != this || ID == 0)
1435  return;
1436 
1437  if (line > 0)
1438  _edit_area->markerDelete (line-1, debugger_position);
1439 }
1440 
1441 void
1442 file_editor_tab::do_breakpoint_marker (bool insert, const QWidget *ID, int line)
1443 {
1444  if (ID != this || ID == 0)
1445  return;
1446 
1447  if (line > 0)
1448  {
1449  if (insert)
1450  _edit_area->markerAdd (line-1, breakpoint);
1451  else
1452  _edit_area->markerDelete (line-1, breakpoint);
1453  }
1454 }
1455 
1456 
1457 void
1459 {
1460  long int visible_lines
1461  = _edit_area->SendScintilla (QsciScintillaBase::SCI_LINESONSCREEN);
1462 
1463  if (visible_lines > 2)
1464  {
1465  int line, index;
1466  _edit_area->getCursorPosition (&line, &index);
1467 
1468  int first_line = _edit_area->firstVisibleLine ();
1469  first_line = first_line + (line - first_line - (visible_lines-1)/2);
1470 
1471  _edit_area->SendScintilla (2613,first_line); // SCI_SETFIRSTVISIBLELINE
1472  }
1473 }
1474 
1475 void
1476 file_editor_tab::handle_cursor_moved (int line, int col)
1477 {
1478  _row_indicator->setNum (line+1);
1479  _col_indicator->setNum (col+1);
1480 }
1481 
1482 #endif