view libgui/src/m-editor/file-editor-tab.cc @ 15848:424edeca3c66

Redo portions of file editor to use more signals/slots rather than casting. * file-editor-tab.cc, file-editor-tab.h (file_editor_tab::~file_editor_tab): Add. Delete lexer to prevent memory leak. Delete _edit_area to prevent memory leak. (file_editor_tab::conditional_close): Add. Simple slot that uses QWidget pointer as unique ID, not for function call. (file_editor_tab::file_name_query): Add. Simple slot that signals file name to whomever is connected. (file_editor_tab::find, file_editor_tab : public QWidget): Removed use of exec() and keep a pointer to the find_dialog as a member. Toggle hide()/show() via a connected slot to toggle visibility as desired. * file-editor.cc, file-editor.h, file-editor-tab.cc file-editor-tab.h (file_editor : public file_editor_interface, file_editor_tab : public QWidget, file_editor_tab::file_editor_tab, file_editor_tab::closeEvent, file_editor_tab::load_file, file_editor_tab::new_file, file_editor_tab::run_file): Remove _file_editor pointer member from file_editor_tab and rid file_editor::get_main_window from file_editor. There should be no need for such information about higher-level hierarchy inside lower-level objects. (file_editor::request_open_file, file_editor_tab::open_file): Move QFileDialog to file_editor::request_open_file and delete file_editor_tab::open_file since most of the remaining functionality is in file_editor_tab::load_file. (file_editor::active_editor_tab): Deleted. (file_editor::fetab_change_request, file_editor_tab::change_editor_state): Added to initiate a request for the editor tab to change focus. (file_editor_tab::editor_state_changed): Added arguments to pass the copy status and the directory path so that editor doesn't have to call functions for such information. (file_editor::handle_editor_state_changed): Add copying directory of the file_editor_tab to the current editing directory. (file_editor::check_conflict_save, file_editor_tab::editor_check_conflict_save, file_editor_tab::save_file_as, file_editor_tab::handle_save_file_as_answer): Moved a portion of the save-file-as dialog mechanism to the file_editor where all file names can be obtained to check for conflict with already open files. The new signal indicates to the editor that a name check be done, and in turn the editor signals the tab to save the file. * main-window.cc, file-editor.cc, file-editor.h, file-editor-interface.h (file_editor::terminal, file_editor : public file_editor_interface, file_editor_interface : public QDockWidget): Since file_editor_tab no longer looks up to main_window, remove _main_window and _terminal from file_editor and file_editor_interface, as well as file_editor::terminal. * file-editor-tab.cc (file_editor_tab::file_has_changed): Make the dialog boxes non-modal and use slots to handle user answers. (file_editor_tab::closeEvent): Remove portion that accesses upper hierarchy member functions, can find better approaches. (file_editor_tab::file_editor_tab): Make there no parent for QsciScintilla so that window modality can be set to individual editor. * file-editor-tab.cc, file-editor.cc (file_editor_tab::load_file): Use show() member rather than exec() and set modality to window so that rest of application may function. Return a QString with message rather than a boolean. * file-editor-tab.cc, (file_editor_tab::file_has_changed): Remove static variable alreadyAsking. Multiple file_editor_tabs are using this code so do not want to block recognition of multiple file having changed on disk (bug #37406). Instead, simply stop tracking via the file watcher. (file_editor_tab::save_file, file_editor_tab::save_file_as, file_editor_tab::handle_save_file_as_answer, file_editor_tab::handle_save_file_as_answer_close): Added a remove_on_success variable. Changed the QFileDialog to WindowModal and created slots to handle file selected signal and finished signal. Signal/slot connects vary based upon remove_on_success setting. (file_editor_tab::check_file_modified): Changed the QFileDialog to NonModal and attach some slots. Editor tab can't be parent in case deleted, so use read-only state of the editor area. * file-editor-tab.h (file_editor_tab : public QWidget): New signals for file_editor for tab and file name management. (file_editor_tab::get_file_name): Delete. * file-editor.h (file_editor : public file_editor_interface): Make QStringList sessionFileNames a member of file_editor so that it can retain data between file_editor_tab signals. Also can be used for checking precense of filenames and prevent opening multiple copies (bug #36869) Added signals for file editor tabs--settings_changed, fetab_close_request, and query_file_name. Three new slots for tab and file name management. * file-editor-interface.h, file-editor.h (file_editor_interface::add_file_editor_tab, file_editor::add_file_editor_tab): Made the text name for the tab an input variable. * file-editor.cc (file_editor::~file_editor): Replace dynamic_cast with simple signal querying all file editor tabs for file names which end up in savedSessionTabs. (file_editor::handle_file_name_changed): Dynamic cast not necessary since QObject and QWidget are compatible. (file_editor::handle_tab_close_request): Replace dynamic_cast with signal to request file_editor_tab with associated tabID tag should close. (file_editor::handle_tab_remove_request): Rename of handle_tab_close_request. Instead of dynamic cast, loop through pointers comparing QWidget* to QObject*, if same tag remove tab via index and also delete which fixes a memory leak. (file_editor::handle_add_filename_to_list): Simple slot that uses append() of the list member functions. (file_editor::notice_settings): Rather than dynamic cast, emit signal for the file_editor_tabs. (file_editor::add_file_editor_tab): New variety of connections for improved flow. (file_editor::request_open_file): Given error messages are made WindowModal, the tab shouldn't be delete if file open is not successful. The file_editor_tab takes care of that. (file_editor::request_open_file): Added check and message box for the requested file already open in editor. For the non-interactive overloaded version, open a message dialog box to tell the user file not found, e.g., could not find file in the settings when launched. (file_editor::request_open_file): Inquire file names and update list before checking for existence of files. Supply empty title to editor tab then have file_editor_tab update name. * file-editor-tab.h, file-editor-tab.cc, file-editor.cc (file_editor_tab::run_file): New signal process_octave_code. (file_editor::add_file_editor_tab): Connect signal process_octave_code to file_editor's parent's slot handle_command_double_clicked.
author Daniel J Sebald <daniel.sebald@ieee.org>
date Sun, 23 Dec 2012 14:33:48 -0600
parents 63dd6c30b294
children e55a64f49346
line wrap: on
line source

/*

Copyright (C) 2011-2012 Jacob Dawid

This file is part of Octave.

Octave is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

Octave is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING.  If not, see
<http://www.gnu.org/licenses/>.

*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <Qsci/qsciapis.h>
// Not available in the Debian repos yet!
// #include <Qsci/qscilexeroctave.h>
#include "lexer-octave-gui.h"
#include <Qsci/qscilexercpp.h>
#include <Qsci/qscilexerbash.h>
#include <Qsci/qscilexerperl.h>
#include <Qsci/qscilexerbatch.h>
#include <Qsci/qscilexerdiff.h>
#include "resource-manager.h"
#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QTextStream>
#include <QVBoxLayout>

#include "file-editor-tab.h"
#include "file-editor.h"
#include "octave-link.h"

#include "debug.h"
#include "oct-env.h"

// Make parent null for the file editor tab so that warning
// WindowModal messages don't affect grandparents.
file_editor_tab::file_editor_tab (QString directory)
{
  // Make sure there is a slash at the end of the directory name
  // for identification when saved later.
  if (directory.count () && directory.at (directory.count () - 1) != '/')
    directory.append ("/");
  _file_name = directory;
  _edit_area = new QsciScintilla (this);

  // Leave the find dialog box out of memory until requested.
  _find_dialog = 0;
  _find_dialog_is_visible = false;

  // symbols
  _edit_area->setMarginType (1, QsciScintilla::SymbolMargin);
  _edit_area->setMarginSensitivity (1, true);
  _edit_area->markerDefine (QsciScintilla::RightTriangle, bookmark);
  _edit_area->markerDefine (QPixmap (":/actions/icons/redled.png"),
                            breakpoint);
  _edit_area->markerDefine (QPixmap (":/actions/icons/arrow_right.png"),
                            debugger_position);

  connect (_edit_area, SIGNAL (marginClicked (int, int,
                                              Qt::KeyboardModifiers)),
           this, SLOT (handle_margin_clicked (int, int,
                                              Qt::KeyboardModifiers)));

  // line numbers
  _edit_area->setMarginsForegroundColor(QColor(96,96,96));
  _edit_area->setMarginsBackgroundColor(QColor(232,232,220));
  _edit_area->setMarginType (2, QsciScintilla::TextMargin);

  // code folding
  _edit_area->setMarginType (3, QsciScintilla::SymbolMargin);
  _edit_area->setFolding (QsciScintilla::BoxedTreeFoldStyle , 3);

  //highlight current line color
  _edit_area->setCaretLineBackgroundColor(QColor(245,245,245));

  // other features
  _edit_area->setBraceMatching (QsciScintilla::StrictBraceMatch);
  _edit_area->setAutoIndent (true);
  _edit_area->setIndentationWidth (2);
  _edit_area->setIndentationsUseTabs (false);

  _edit_area->setUtf8 (true);

  // auto completion
  _edit_area->autoCompleteFromAll ();
  _edit_area->setAutoCompletionSource(QsciScintilla::AcsAll);

  QVBoxLayout *edit_area_layout = new QVBoxLayout ();
  edit_area_layout->addWidget (_edit_area);
  edit_area_layout->setMargin (0);
  setLayout (edit_area_layout);

  // connect modified signal
  connect (_edit_area, SIGNAL (modificationChanged (bool)),
           this, SLOT (update_window_title (bool)));
  connect (_edit_area, SIGNAL (copyAvailable (bool)),
           this, SLOT (handle_copy_available (bool)));
  connect (&_file_system_watcher, SIGNAL (fileChanged (QString)),
           this, SLOT (file_has_changed (QString)));

  notice_settings ();
}

file_editor_tab::~file_editor_tab ()
{
  // Destroy items attached to _edit_area.
  QsciLexer *lexer = _edit_area->lexer ();
  if (lexer)
    {
      delete lexer;
      _edit_area->setLexer(0);
    }
  if (_find_dialog)
    {
      delete _find_dialog;
      _find_dialog = 0;
    }

  // Destroy _edit_area.
  delete _edit_area;
}

void
file_editor_tab::closeEvent (QCloseEvent *e)
{
  // ignore close event if file is not saved and user cancels
  // closing this window
  if (check_file_modified ("Close File",
                           QMessageBox::Cancel) == QMessageBox::Cancel)
    {
      e->ignore ();
    }
  else
    {
      e->accept();
    }
}

void
file_editor_tab::set_file_name (const QString& fileName)
{
  // update tracked file if we really have a file on disk
  QStringList trackedFiles = _file_system_watcher.files ();
  if (!trackedFiles.isEmpty ())
    _file_system_watcher.removePath (_file_name);
  if (!fileName.isEmpty ())
    _file_system_watcher.addPath (fileName);
  _file_name = fileName;

  // update lexer after _file_name change
  update_lexer ();

  // update the file editor with current editing directory
  emit editor_state_changed (_copy_available, QDir::cleanPath (_file_name));
}

void
file_editor_tab::handle_margin_clicked(int margin, int line,
                                       Qt::KeyboardModifiers state)
{
  if (margin == 1)
    {
      unsigned int markers_mask = _edit_area->markersAtLine (line);

      if (state & Qt::ControlModifier)
        {
          if (markers_mask && (1 << bookmark))
            _edit_area->markerDelete(line,bookmark);
          else
            _edit_area->markerAdd(line,bookmark);
        }
      else
        {
          if (markers_mask && (1 << breakpoint))
            {
              request_remove_breakpoint (line);
            }
          else
            {
              request_add_breakpoint (line);
            }
        }
    }
}

void
file_editor_tab::update_lexer ()
{
  QsciLexer *lexer = _edit_area->lexer ();
  delete lexer;

  if (_file_name.endsWith (".m") || _file_name.endsWith (".M"))
    {
      lexer = new lexer_octave_gui ();
    }
  else if (_file_name.endsWith (".c")
           || _file_name.endsWith (".cc")
           || _file_name.endsWith (".cpp")
           || _file_name.endsWith (".cxx")
           || _file_name.endsWith (".c++")
           || _file_name.endsWith (".h")
           || _file_name.endsWith (".hh")
           || _file_name.endsWith (".hpp")
           || _file_name.endsWith (".h++"))
    {
      lexer = new QsciLexerCPP ();
    }
  else if (_file_name.endsWith (".pl"))
    {
      lexer = new QsciLexerPerl ();
    }
  else if (_file_name.endsWith (".bat"))
    {
      lexer = new QsciLexerBatch ();
    }
  else if (_file_name.endsWith (".diff"))
    {
      lexer = new QsciLexerDiff ();
    }
  else // Default to bash lexer.
    {
      lexer = new QsciLexerBash ();
    }

  QSettings *settings = resource_manager::get_settings ();

  // Editor font (default or from settings)
  if (settings)
    lexer->setDefaultFont (QFont (
                                  settings->value ("editor/fontName",
                                                   "Courier").toString (),
                                  settings->value ("editor/fontSize",
                                                   10).toInt ()));

  // TODO: Autoindent not working as it should
  lexer->setAutoIndentStyle (QsciScintilla::AiMaintain ||
                             QsciScintilla::AiOpening  ||
                             QsciScintilla::AiClosing);

  _edit_area->setLexer (lexer);
}

void
file_editor_tab::undo (const QWidget* ID)
{
  if (ID != this)
    return;

  _edit_area->undo ();
}

void
file_editor_tab::redo (const QWidget* ID)
{
  if (ID != this)
    return;

  _edit_area->redo ();
}

void
file_editor_tab::copy (const QWidget* ID)
{
  if (ID != this)
    return;

  _edit_area->copy ();
}

void
file_editor_tab::cut (const QWidget* ID)
{
  if (ID != this)
    return;

  _edit_area->cut ();
}

void
file_editor_tab::paste (const QWidget* ID)
{
  if (ID != this)
    return;

  _edit_area->paste ();
}

void
file_editor_tab::save_file (const QWidget* ID)
{
  if (ID != this)
    return;

  save_file (_file_name);
}
void

file_editor_tab::save_file (const QWidget* ID, const QString& fileName, bool remove_on_success)
{
  if (ID != this)
    return;

  save_file (fileName, remove_on_success);
}

void
file_editor_tab::save_file_as (const QWidget* ID)
{
  if (ID != this)
    return;

  save_file_as ();
}

void
file_editor_tab::run_file_callback (void)
{
  // Maybe someday we will do something here?
}

void
file_editor_tab::run_file (const QWidget* ID)
{
  if (ID != this)
    return;

  if (_edit_area->isModified ())
    save_file (_file_name);

  QFileInfo file_info (_file_name);
  QString path = file_info.absolutePath ();
  QString current_path
    = QString::fromStdString (octave_link::last_working_directory ());
  QString function_name = file_info.fileName ();

  // We have to cut off the suffix, because octave appends it.
  function_name.chop (file_info.suffix ().length () + 1);
  emit process_octave_code (QString ("cd \'%1\'\n%2\n")
                    .arg(path).arg (function_name));
 
  // TODO: Sending a run event crashes for long scripts. Find out why.
  // octave_link::post_event
  //   (this, &file_editor_tab::run_file_callback, _file_name.toStdString ()));
}

void
file_editor_tab::toggle_bookmark (const QWidget* ID)
{
  if (ID != this)
    return;

  int line, cur;
  _edit_area->getCursorPosition (&line,&cur);
  if ( _edit_area->markersAtLine (line) && (1 << bookmark) )
    _edit_area->markerDelete (line, bookmark);
  else
    _edit_area->markerAdd (line, bookmark);
}

void
file_editor_tab::next_bookmark (const QWidget* ID)
{
  if (ID != this)
    return;

  int line, cur, nextline;
  _edit_area->getCursorPosition (&line, &cur);
  if ( _edit_area->markersAtLine (line) && (1 << bookmark) )
    line++; // we have a breakpoint here, so start search from next line
  nextline = _edit_area->markerFindNext (line, (1 << bookmark));
  _edit_area->setCursorPosition (nextline, 0);
}

void
file_editor_tab::previous_bookmark (const QWidget* ID)
{
  if (ID != this)
    return;

  int line, cur, prevline;
  _edit_area->getCursorPosition (&line, &cur);
  if ( _edit_area->markersAtLine (line) && (1 << bookmark) )
    line--; // we have a breakpoint here, so start search from prev line
  prevline = _edit_area->markerFindPrevious (line, (1 << bookmark));
  _edit_area->setCursorPosition (prevline, 0);
}

void
file_editor_tab::remove_bookmark (const QWidget* ID)
{
  if (ID != this)
    return;

  _edit_area->markerDeleteAll (bookmark);
}

void
file_editor_tab::add_breakpoint_callback (const bp_info& info)
{
  bp_table::intmap intmap;
  intmap[0] = info.line + 1;

  std::string previous_directory = octave_env::get_current_directory ();
  octave_env::chdir (info.path);
  intmap = bp_table::add_breakpoint (info.function_name, intmap);
  octave_env::chdir (previous_directory);

  if (intmap.size () > 0)
    {
    // FIXME -- Check file.
      _edit_area->markerAdd (info.line, breakpoint);
    }
}

void
file_editor_tab::remove_breakpoint_callback (const bp_info& info)
{
  bp_table::intmap intmap;
  intmap[0] = info.line;

  std::string previous_directory = octave_env::get_current_directory ();
  octave_env::chdir (info.path);
  bp_table::remove_breakpoint (info.function_name, intmap);
  octave_env::chdir (previous_directory);

  // FIXME -- check result
  bool success = true;

  if (success)
    {
      // FIXME -- check file.
      _edit_area->markerDelete (info.line, breakpoint);
    }
}

void
file_editor_tab::remove_all_breakpoints_callback (const bp_info& info)
{
  bp_table::intmap intmap;
  std::string previous_directory = octave_env::get_current_directory ();
  octave_env::chdir (info.path);
  intmap = bp_table::remove_all_breakpoints_in_file (info.function_name, true);
  octave_env::chdir (previous_directory);

  if (intmap.size() > 0)
    _edit_area->markerDeleteAll (breakpoint);
}

void
file_editor_tab::request_add_breakpoint (int line)
{
  QFileInfo file_info (_file_name);
  QString path = file_info.absolutePath ();
  QString function_name = file_info.fileName ();

  // We have to cut off the suffix, because octave appends it.
  function_name.chop (file_info.suffix ().length () + 1);

  bp_info info (path, function_name, line);

  octave_link::post_event
    (this, &file_editor_tab::add_breakpoint_callback, info);
}

void
file_editor_tab::request_remove_breakpoint (int line)
{
  QFileInfo file_info (_file_name);
  QString path = file_info.absolutePath ();
  QString function_name = file_info.fileName ();

  // We have to cut off the suffix, because octave appends it.
  function_name.chop (file_info.suffix ().length () + 1);

  bp_info info (path, function_name, line);

  octave_link::post_event
    (this, &file_editor_tab::remove_breakpoint_callback, info);
}

void
file_editor_tab::toggle_breakpoint (const QWidget* ID)
{
  if (ID != this)
    return;

  int line, cur;
  _edit_area->getCursorPosition (&line, &cur);
  if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
    request_remove_breakpoint (line);
  else
    request_add_breakpoint (line);
}

void
file_editor_tab::next_breakpoint (const QWidget* ID)
{
  if (ID != this)
    return;

  int line, cur, nextline;
  _edit_area->getCursorPosition (&line, &cur);
  if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
    line++; // we have a breakpoint here, so start search from next line
  nextline = _edit_area->markerFindNext (line, (1 << breakpoint));
  _edit_area->setCursorPosition (nextline, 0);
}

void
file_editor_tab::previous_breakpoint (const QWidget* ID)
{
  if (ID != this)
    return;

  int line, cur, prevline;
  _edit_area->getCursorPosition (&line, &cur);
  if ( _edit_area->markersAtLine (line) && (1 << breakpoint) )
    line--; // we have a breakpoint here, so start search from prev line
  prevline = _edit_area->markerFindPrevious (line, (1 << breakpoint));
  _edit_area->setCursorPosition (prevline, 0);
}

void
file_editor_tab::remove_all_breakpoints (const QWidget* ID)
{
  if (ID != this)
    return;

  QFileInfo file_info (_file_name);
  QString path = file_info.absolutePath ();
  QString function_name = file_info.fileName ();

  // We have to cut off the suffix, because octave appends it.
  function_name.chop (file_info.suffix ().length () + 1);

  bp_info info (path, function_name, 0);

  octave_link::post_event
    (this, &file_editor_tab::remove_all_breakpoints_callback, info);
}

void
file_editor_tab::comment_selected_text (const QWidget* ID)
{
  if (ID != this)
    return;

  do_comment_selected_text (true);
}

void
file_editor_tab::uncomment_selected_text (const QWidget* ID)
{
  if (ID != this)
    return;

  do_comment_selected_text (false);
}

void
file_editor_tab::handle_find_dialog_finished (int)
{
  // Find dialog is going to hide.  Save location of window for
  // when it is reshown.
  _find_dialog_geometry = _find_dialog->geometry ();
  _find_dialog_is_visible = false;
}

void
file_editor_tab::find (const QWidget* ID)
{
  if (ID != this)
    return;

  // The find_dialog feature doesn't need a slot for return info.
  // Rather than Qt::DeleteOnClose, let the find feature hang about
  // in case it contains useful information like previous searches
  // and so on.  Perhaps one find dialog for the whole editor is
  // better, but individual find dialogs has the nice feature of
  // retaining position per file editor tabs, which can be undocked.

  if (!_find_dialog)
    {
       _find_dialog = new find_dialog (_edit_area);
      connect (_find_dialog, SIGNAL (finished (int)),
               this, SLOT (handle_find_dialog_finished (int)));
      _find_dialog->setWindowModality (Qt::NonModal);
      _find_dialog_geometry = _find_dialog->geometry ();
    }

  if (!_find_dialog->isVisible ())
    {
      _find_dialog->setGeometry (_find_dialog_geometry);
      _find_dialog->show ();
      _find_dialog_is_visible = true;
    }

  _find_dialog->activateWindow ();
}

void
file_editor_tab::do_comment_selected_text (bool comment)
{
  if ( _edit_area->hasSelectedText() )
    {
      int lineFrom, lineTo, colFrom, colTo, i;
      _edit_area->getSelection (&lineFrom,&colFrom,&lineTo,&colTo);
      if ( colTo == 0 )  // the beginning of last line is not selected
        lineTo--;        // stop at line above
      _edit_area->beginUndoAction ();
      for ( i=lineFrom; i<=lineTo; i++ )
        {
          if ( comment )
            _edit_area->insertAt("%",i,0);
          else
            {
              QString line(_edit_area->text(i));
              if ( line.startsWith("%") )
                {
                  _edit_area->setSelection(i,0,i,1);
                  _edit_area->removeSelectedText();
                }
            }
        }
      _edit_area->endUndoAction ();
    }
}

void
file_editor_tab::update_window_title (bool modified)
{
  QString title ("");
  if (_file_name.isEmpty () || _file_name.at (_file_name.count () - 1) == '/')
    title = UNNAMED_FILE;
  else
    title = _file_name;
  if ( !_long_title )
    {
      QFileInfo file(_file_name);
      title = file.fileName();
    }

  if ( modified )
    {
      emit file_name_changed (title.prepend("* "));
    }
  else
    emit file_name_changed (title);
}

void
file_editor_tab::handle_copy_available(bool enableCopy)
{
  _copy_available = enableCopy;
  emit editor_state_changed (_copy_available, QDir::cleanPath (_file_name));
}

int
file_editor_tab::check_file_modified (const QString& msg, int cancelButton)
{
  int decision = QMessageBox::Yes;
  if (_edit_area->isModified ())
    {
      // File is modified but not saved, ask user what to do.  The file
      // editor tab can't be made parent because it may be deleted depending
      // upon the response.  Instead, change the _edit_area to read only.
      QMessageBox* msgBox = new QMessageBox (
              QMessageBox::Warning, tr ("Octave Editor"),
              tr ("The file \'%1\' has been modified. Do you want to save the changes?").
              arg (_file_name), QMessageBox::Yes | QMessageBox::No, 0);
      _edit_area->setReadOnly (true);
      connect (msgBox, SIGNAL (finished (int)),
               this, SLOT (handle_file_modified_answer (int)));
      msgBox->setWindowModality (Qt::NonModal);
      msgBox->setAttribute (Qt::WA_DeleteOnClose);
      msgBox->show ();
      return (QMessageBox::Cancel);
    }
  else
    {
      // Nothing was modified, just remove from editor.
      emit tab_remove_request ();
    }
 
  return (decision);
}

void
file_editor_tab::handle_file_modified_answer (int decision)
{
  if (decision == QMessageBox::Yes)
    {
      // Save file, then remove from editor.
      save_file (_file_name, true);
    }
  else if (decision == QMessageBox::No)
    {
      // User doesn't want to save, just remove from editor.
      emit tab_remove_request ();
    }
  else
    {
      // User canceled, allow editing again.
      _edit_area->setReadOnly (false);
    }
}

void
file_editor_tab::set_modified (bool modified)
{
  _edit_area->setModified (modified);
}

QString
file_editor_tab::load_file(const QString& fileName)
{
  QFile file (fileName);
  if (!file.open (QFile::ReadOnly))
    {
      return file.errorString ();
    }

  QTextStream in (&file);
  QApplication::setOverrideCursor (Qt::WaitCursor);
  _edit_area->setText (in.readAll ());
  QApplication::restoreOverrideCursor ();

  set_file_name (fileName);
  update_window_title (false); // window title (no modification)
  _edit_area->setModified (false); // loaded file is not modified yet

  return QString ();
}

void
file_editor_tab::new_file ()
{
  update_window_title (false); // window title (no modification)
  _edit_area->setText ("");
  _edit_area->setModified (false); // new file is not modified yet
}

void
file_editor_tab::save_file (const QString& saveFileName, bool remove_on_success)
{
  // If it is a new file with no name, signal that saveFileAs
  // should be performed.
  if (saveFileName.isEmpty () || saveFileName.at (saveFileName.count () - 1) == '/')
     {
      save_file_as (remove_on_success);
      return;
    }

  // stop watching file
  QStringList trackedFiles = _file_system_watcher.files ();
  if (!trackedFiles.isEmpty ())
    _file_system_watcher.removePath (saveFileName);

  // open the file for writing
  QFile file (saveFileName);
  if (!file.open (QIODevice::WriteOnly))
    {
      // Unsuccessful, begin watching file again if it was being
      // watched previously.
      if (trackedFiles.contains (saveFileName))
        _file_system_watcher.addPath (saveFileName);

      // Create a NonModal message about error.
      QMessageBox* msgBox = new QMessageBox (
              QMessageBox::Critical, tr ("Octave Editor"),
              tr ("Could not open file %1 for write:\n%2.").
              arg (saveFileName).arg (file.errorString ()),
              QMessageBox::Ok, 0);
      msgBox->setWindowModality (Qt::NonModal);
      msgBox->setAttribute (Qt::WA_DeleteOnClose);
      msgBox->show ();
      return;
    }

  // save the contents into the file
  QTextStream out (&file);
  QApplication::setOverrideCursor (Qt::WaitCursor);
  out << _edit_area->text ();
  QApplication::restoreOverrideCursor ();
  file.close();

  // save file name after closing file as set_file_name starts watching again
  set_file_name (saveFileName);
  // set the window title to actual file name (not modified)
  update_window_title (false);
  // files is save -> not modified
  _edit_area->setModified (false);

  if (remove_on_success)
    {
      emit tab_remove_request ();
      return;  // Don't touch member variables after removal
    }
}

void
file_editor_tab::save_file_as (bool remove_on_success)
{
  // Simply put up the file chooser dialog box with a slot connection
  // then return control to the system waiting for a file selection.

  // If the tab is removed in response to a QFileDialog signal, the tab
  // can't be a parent.
  QFileDialog* fileDialog;
  if (remove_on_success)
    {
      // If tab is closed, "this" cannot be parent in which case modality
      // has no effect.  Disable editing instead.
      _edit_area->setReadOnly (true);
      fileDialog = new QFileDialog ();
    }
  else
    fileDialog = new QFileDialog (this);

  if (!_file_name.isEmpty () && _file_name.at (_file_name.count () - 1) != '/')
    {
      fileDialog->selectFile (_file_name);
    }
  else
    {
      fileDialog->selectFile ("");
      if (_file_name.isEmpty ())
        {
          fileDialog->setDirectory (QDir::currentPath ());
        }
      else
        {
          // The file name is actually the directory name from the
          // constructor argument.
          fileDialog->setDirectory (_file_name);
        }
    }
  fileDialog->setNameFilter (SAVE_FILE_FILTER);
  fileDialog->setDefaultSuffix ("m");
  fileDialog->setAcceptMode (QFileDialog::AcceptSave);
  fileDialog->setViewMode (QFileDialog::Detail);
  if (remove_on_success)
    {
      connect (fileDialog, SIGNAL (fileSelected (const QString&)),
               this, SLOT (handle_save_file_as_answer_close (const QString&)));
      connect (fileDialog, SIGNAL (rejected ()),
               this, SLOT (handle_save_file_as_answer_cancel ()));
    }
  else
    {
      connect (fileDialog, SIGNAL (fileSelected (const QString&)),
               this, SLOT (handle_save_file_as_answer (const QString&)));
    }
  fileDialog->setWindowModality (Qt::WindowModal);
  fileDialog->setAttribute (Qt::WA_DeleteOnClose);
  fileDialog->show ();
}

void
file_editor_tab::message_duplicate_file_name (const QString& saveFileName)
{
  // Could overwrite the file here (and tell user the file was
  // overwritten), but the user could have unintentionally
  // selected the same name not intending to overwrite.

  // Create a NonModal message about error.
  QMessageBox* msgBox = new QMessageBox (
          QMessageBox::Critical, tr ("Octave Editor"),
          tr ("File not saved!  You've selected a file name\n\n     %1\n\nwhich is the same as the current file name.  Use ""Save"" to overwrite.  (Could allow overwriting, with message, if that is what folks want.)").
          arg (saveFileName),
          QMessageBox::Ok, 0);
  msgBox->setWindowModality (Qt::NonModal);
  msgBox->setAttribute (Qt::WA_DeleteOnClose);
  msgBox->show ();
}

void
file_editor_tab::handle_save_file_as_answer (const QString& saveFileName)
{
  if (saveFileName == _file_name)
    {
      message_duplicate_file_name (saveFileName);
      // Nothing done, allow editing again.
      _edit_area->setReadOnly (false);
    }
  else
    {
      // Have editor check for conflict, do not delete tab after save.
      emit editor_check_conflict_save (saveFileName, false);
    }
}

void
file_editor_tab::handle_save_file_as_answer_close (const QString& saveFileName)
{
  if (saveFileName == _file_name)
    {
      message_duplicate_file_name (saveFileName);
      // Nothing done, allow editing again.
      _edit_area->setReadOnly (false);
    }
  else
    {
      // Have editor check for conflict, delete tab after save.
      emit editor_check_conflict_save (saveFileName, true);
    }
}

void
file_editor_tab::handle_save_file_as_answer_cancel ()
{
  // User canceled, allow editing again.
  _edit_area->setReadOnly (false);
}

void
file_editor_tab::file_has_changed (const QString&)
{
  // Prevent popping up multiple message boxes when the file has
  // been changed multiple times by temporarily removing from the
  // file watcher.
  QStringList trackedFiles = _file_system_watcher.files ();
  if (!trackedFiles.isEmpty ())
    _file_system_watcher.removePath (_file_name);

  if (QFile::exists (_file_name))
    {
      // Create a WindowModal message that blocks the edit area
      // by making _edit_area parent.
      QMessageBox* msgBox = new QMessageBox (
              QMessageBox::Warning, tr ("Octave Editor"),
              tr ("It seems that \'%1\' has been modified by another application. Do you want to reload it?").
              arg (_file_name), QMessageBox::Yes | QMessageBox::No, this);
      connect (msgBox, SIGNAL (finished (int)),
               this, SLOT (handle_file_reload_answer (int)));
      msgBox->setWindowModality (Qt::WindowModal);
      msgBox->setAttribute (Qt::WA_DeleteOnClose);
      msgBox->show ();
    }
  else
    {
      // Create a WindowModal message that blocks the edit area
      // by making _edit_area parent.
      QMessageBox* msgBox = new QMessageBox (
              QMessageBox::Warning, tr ("Octave Editor"),
              tr ("It seems that \'%1\' has been deleted or renamed. Do you want to save it now?").
              arg (_file_name), QMessageBox::Save | QMessageBox::Close, this);
      connect (msgBox, SIGNAL (finished (int)),
               this, SLOT (handle_file_resave_answer (int)));
      msgBox->setWindowModality (Qt::WindowModal);
      msgBox->setAttribute (Qt::WA_DeleteOnClose);
      msgBox->show ();
    }
}

void
file_editor_tab::notice_settings ()
{
  QSettings *settings = resource_manager::get_settings ();

  if (settings==NULL)
    return; // this shouldn't happen!

  _edit_area->setCaretLineVisible(settings->value ("editor/highlightCurrentLine",true).toBool ());

  if (settings->value ("editor/codeCompletion",true).toBool ())
    _edit_area->setAutoCompletionThreshold (1);
  else
    _edit_area->setAutoCompletionThreshold (-1);

  QFont font( settings->value ("editor/fontName","Courier").toString () ,
              settings->value ("editor/fontSize",10).toInt () );
  if (settings->value ("editor/showLineNumbers",true).toBool ())
    {
      _edit_area->setMarginLineNumbers (2, true);
      _edit_area->setMarginsFont( font );
      QFontMetrics metrics( font );
      _edit_area->setMarginWidth(2, metrics.width("9999"));
    }
  else
    {
      _edit_area->setMarginLineNumbers (2, false);
      _edit_area->setMarginWidth(2, 0);
    }

  update_lexer ();

  _long_title = settings->value ("editor/longWindowTitle",false).toBool ();

  update_window_title (false);
}

void
file_editor_tab::conditional_close (const QWidget* ID)
{
  if (ID != this)
    return;

  close ();
}

void
file_editor_tab::change_editor_state (const QWidget* ID)
{
  if (ID != this)
    {
      // Widget may be going out of focus.  If so, record location.
      if (_find_dialog)
        {
          if (_find_dialog->isVisible ())
            {
              _find_dialog_geometry = _find_dialog->geometry ();
              _find_dialog->hide ();
            }
        }
      return;
    }

  if (_find_dialog && _find_dialog_is_visible)
    {
      _find_dialog->setGeometry (_find_dialog_geometry);
      _find_dialog->show ();
    }
  emit editor_state_changed (_copy_available, QDir::cleanPath (_file_name));
}

void
file_editor_tab::file_name_query (const QWidget* ID)
{
  // A zero (null pointer) means that all file editor tabs
  // should respond, otherwise just the desired file editor tab.
  if (ID != this && ID != 0)
    return;

  // Unnamed files shouldn't be transmitted.
  if (!_file_name.isEmpty ())
    emit add_filename_to_list (_file_name);
}

void
file_editor_tab::handle_file_reload_answer (int decision)
{
  if (decision == QMessageBox::Yes)
    {
      load_file (_file_name);
    }

  // Start watching file once again.
  _file_system_watcher.addPath (_file_name);
}

void
file_editor_tab::handle_file_resave_answer (int decision)
{
  if (decision == QMessageBox::Save)
    {
      save_file (_file_name);
    }
  else
    {
      if (close ())
        {
          emit tab_remove_request ();
          return;  // Don't touch member variables after removal
        }
    }

  // Start watching file once again.
  _file_system_watcher.addPath (_file_name);
}

void
file_editor_tab::set_debugger_position (int line)
{
  _edit_area->markerDeleteAll (debugger_position);
  if (line > 0)
    {
      _edit_area->markerAdd (line, debugger_position);
    }
}