view libgui/src/m-editor/file-editor.cc @ 33034:49128bdb9eb2

use explicit lambda-expression captures (bug #65318) Previously, we recommended using implicit capture by value (if possible) in all lambda expressions in Octave. However, this choice causes trouble in the transition period leading up to C++20, when the meaning changes for capturing 'this' by reference when the default capture is '='. Since all lambda expressions in Octave only need to capture a few variables it seems better and relatively easy to simply name all captured variable explicitly. (The maximum number of captured variables currently appears to be seven, including 'this', but the vast majority are capturing just one or two.) Affected files: Canvas.cc, GLCanvas.cc, QTerminal.cc, command-widget.cc, documentation.cc, files-dock-widget.cc, interpreter-qobject.cc, file-editor-tab.cc, file-editor.cc, octave-qscintilla.cc, main-window.cc, octave-dock-widget.cc, octave-qobject.cc, set-path-model.cc, settings-dialog.cc, variable-editor-model.cc, variable-editor.cc, call-stack.cc, gl2ps-print.cc, graphics.cc, input.cc, interpreter.cc, load-path.cc, mex.cc, pr-output.cc, strfns.cc, sysdep.cc, __delaunayn__.cc, audiodevinfo.cc, audioread.cc, oct-parse.yy, pt-eval.cc, Array-util.cc, Range.h, lo-sysdep.cc, lo-regexp.cc, oct-glob.cc, oct-string.cc, and url-transfer.cc.
author John W. Eaton <jwe@octave.org>
date Fri, 16 Feb 2024 14:42:54 -0500
parents 7de59b26cf79
children 775dde0cb3e5
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2011-2024 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// 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
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#if defined (HAVE_QSCINTILLA)

#include <algorithm>

#include <QApplication>
#include <QClipboard>
#include <QFile>
#include <QFileDialog>
#include <QFont>
#include <QMessageBox>
#include <QMimeData>
#include <QProcess>
#include <QPushButton>
#include <QStyle>
#include <QTabBar>
#include <QTextStream>
#include <QVBoxLayout>
#include <Qsci/qscicommandset.h>

#include "file-editor.h"
#include "gui-preferences-ed.h"
#include "gui-preferences-sc.h"
#include "gui-preferences-global.h"
#include "gui-settings.h"
#include "main-window.h"

#include "lo-sysdep.h"
#include "oct-env.h"

#include "event-manager.h"
#include "interpreter.h"
#include "oct-map.h"
#include "pt-eval.h"
#include "utils.h"

OCTAVE_BEGIN_NAMESPACE(octave)

// Functions of the the reimplemented tab widget

file_editor_tab_widget::file_editor_tab_widget (QWidget *p, file_editor *fe)
  : QTabWidget (p)
{
  tab_bar *bar = new tab_bar (this);

  connect (bar, &tab_bar::close_current_tab_signal,
           fe, &file_editor::request_close_file);

  this->setTabBar (bar);

  setTabsClosable (true);
  setUsesScrollButtons (true);
  setMovable (true);
}

tab_bar *
file_editor_tab_widget::get_tab_bar () const
{
  return qobject_cast<tab_bar *> (tabBar ());
}

std::list<file_editor_tab *>
file_editor_tab_widget::tab_list () const
{
  std::list<file_editor_tab *> retval;
  for (int i = 0; i < count (); i++)
    retval.push_back (static_cast<file_editor_tab *> (widget (i)));
  return retval;
}

// File editor

file_editor::file_editor (QWidget *p)
  : file_editor_interface (p)
{
  // Set current editing directory before construction because loaded
  // files will change ced accordingly.
  m_ced = QDir::currentPath ();

  // Set actions that are later added by the main window to null,
  // preventing access to them when they are still undefined.
  m_undo_action = nullptr;
  m_copy_action = nullptr;
  m_paste_action = nullptr;
  m_selectall_action = nullptr;

  m_find_dialog = nullptr;

  m_closed = true;
  m_no_focus = false;
  m_editor_ready = false;

  m_copy_action_enabled = false;
  m_undo_action_enabled = false;
  m_current_tab_modified = false;

  construct ();

  setVisible (false);
  setAcceptDrops (true);
  setFocusPolicy (Qt::StrongFocus);
}

void
file_editor::focusInEvent (QFocusEvent *e)
{
  // The focus is transferred to the active tab and its edit
  // area in this focus in event handler. This is to avoid
  // using focus proxies with conflicts in the proxy change
  // presumably introduced by bug
  // https://bugreports.qt.io/browse/QTBUG-61092
  reset_focus (); // Make sure editor tab with edit area get focus

  QDockWidget::focusInEvent (e);
}

// insert global actions, that should also be displayed in the editor window,
// into the editor's menu and/or toolbar
void
file_editor::insert_global_actions (QList<QAction *> shared_actions)
{
  // actions/menus that have to be added to the toolbar or the menu
  QAction *open_action = shared_actions.at (OPEN_ACTION);
  QAction *new_action = shared_actions.at (NEW_SCRIPT_ACTION);
  QAction *new_fcn_action = shared_actions.at (NEW_FUNCTION_ACTION);
  m_fileMenu->insertAction (m_mru_file_menu->menuAction (), open_action);
  m_fileMenu->insertAction (open_action, new_fcn_action);
  m_fileMenu->insertAction (new_fcn_action, new_action);
  m_tool_bar->insertAction (m_popdown_mru_action, open_action);
  m_tool_bar->insertAction (open_action, new_action);

  // actions that are additionally enabled/disabled later by the editor
  // undo
  m_undo_action = shared_actions.at (UNDO_ACTION);
  m_tool_bar->insertAction (m_redo_action, m_undo_action);
  m_edit_menu->insertAction (m_redo_action, m_undo_action);
  // select all
  m_selectall_action = shared_actions.at (SELECTALL_ACTION);
  m_edit_menu->insertAction (m_find_action, m_selectall_action);
  m_edit_menu->insertSeparator (m_find_action);
  // paste
  m_paste_action = shared_actions.at (PASTE_ACTION);
  m_tool_bar->insertAction (m_find_action, m_paste_action);
  m_edit_menu->insertAction (m_selectall_action, m_paste_action);
  m_edit_menu->insertSeparator (m_selectall_action);
  // copy
  m_copy_action = shared_actions.at (COPY_ACTION);
  m_tool_bar->insertAction (m_paste_action, m_copy_action);
  m_edit_menu->insertAction (m_paste_action, m_copy_action);
  // find files
  m_find_files_action = shared_actions.at (FIND_FILES_ACTION);
  m_edit_menu->insertAction (m_find_action, m_find_files_action);
}

void
file_editor::handle_enter_debug_mode ()
{
  gui_settings settings;

  QString sc_run = settings.sc_value (sc_edit_run_run_file);
  QString sc_cont = settings.sc_value (sc_main_debug_continue);

  if (sc_run == sc_cont)
    m_run_action->setShortcut (QKeySequence ());  // prevent ambiguous shortcuts

  m_run_action->setToolTip (tr ("Continue"));   // update tool tip

  emit enter_debug_mode_signal ();
}

void
file_editor::handle_exit_debug_mode ()
{
  gui_settings settings;
  settings.set_shortcut (m_run_action, sc_edit_run_run_file);
  m_run_action->setToolTip (tr ("Save File and Run"));  // update tool tip

  emit exit_debug_mode_signal ();
}

void
file_editor::check_actions ()
{
  // Do not include shared actions not only related to the editor
  bool have_tabs = m_tab_widget->count () > 0;

  m_edit_cmd_menu->setEnabled (have_tabs);
  m_edit_fmt_menu->setEnabled (have_tabs);
  m_edit_nav_menu->setEnabled (have_tabs);

  m_comment_selection_action->setEnabled (have_tabs);
  m_uncomment_selection_action->setEnabled (have_tabs);
  m_comment_var_selection_action->setEnabled (have_tabs);
  m_indent_selection_action->setEnabled (have_tabs);
  m_unindent_selection_action->setEnabled (have_tabs);
  m_smart_indent_line_or_selection_action->setEnabled (have_tabs);

  m_context_help_action->setEnabled (have_tabs);
  m_context_doc_action->setEnabled (have_tabs);

  m_view_editor_menu->setEnabled (have_tabs);
  m_zoom_in_action->setEnabled (have_tabs);
  m_zoom_out_action->setEnabled (have_tabs);
  m_zoom_normal_action->setEnabled (have_tabs);

  m_find_action->setEnabled (have_tabs);
  m_find_next_action->setEnabled (have_tabs);
  m_find_previous_action->setEnabled (have_tabs);
  m_print_action->setEnabled (have_tabs);

  m_run_action->setEnabled (have_tabs && m_is_octave_file);

  m_toggle_breakpoint_action->setEnabled (have_tabs && m_is_octave_file);
  m_next_breakpoint_action->setEnabled (have_tabs && m_is_octave_file);
  m_previous_breakpoint_action->setEnabled (have_tabs && m_is_octave_file);
  m_remove_all_breakpoints_action->setEnabled (have_tabs && m_is_octave_file);

  m_edit_function_action->setEnabled (have_tabs);
  m_save_action->setEnabled (have_tabs && m_current_tab_modified);
  m_save_as_action->setEnabled (have_tabs);
  m_close_action->setEnabled (have_tabs);
  m_close_all_action->setEnabled (have_tabs);
  m_close_others_action->setEnabled (have_tabs && m_tab_widget->count () > 1);
  m_sort_tabs_action->setEnabled (have_tabs && m_tab_widget->count () > 1);

  emit editor_tabs_changed_signal (have_tabs, m_is_octave_file);
}

// empty_script determines whether we have to create an empty script
// 1. At startup, when the editor has to be (really) visible
//    (Here we can not use the visibility changed signal)
// 2. When the editor becomes visible when octave is running
void
file_editor::empty_script (bool startup, bool visible)
{

  if (startup)
    m_editor_ready = true;
  else
    {
      if (! m_editor_ready)
        return;  // not yet ready but got visibility changed signals
    }

  gui_settings settings;

  if (settings.value (global_use_custom_editor.settings_key (),
                      global_use_custom_editor.def ()).toBool ())
    return;  // do not open an empty script in the external editor

  bool real_visible;

  if (startup)
    real_visible = isVisible ();
  else
    real_visible = visible;

  if (! real_visible || m_tab_widget->count () > 0)
    return;

  if (startup && ! isFloating ())
    {
      // check if editor is really visible or hidden between tabbed widgets
      QWidget *parent = parentWidget ();

      if (parent)
        {
          QList<QTabBar *> tab_list = parent->findChildren<QTabBar *>();

          bool in_tab = false;
          int i = 0;
          while ((i < tab_list.count ()) && (! in_tab))
            {
              QTabBar *tab = tab_list.at (i);
              i++;

              int j = 0;
              while ((j < tab->count ()) && (! in_tab))
                {
                  // check all tabs for the editor
                  if (tab->tabText (j) == windowTitle ())
                    {
                      // editor is in this tab widget
                      in_tab = true;
                      int top = tab->currentIndex ();
                      if (! (top > -1 && tab->tabText (top) == windowTitle ()))
                        return; // not current tab -> not visible
                    }
                  j++;
                }
            }
        }
    }

  request_new_file ("");
}

void
file_editor::restore_session (bool visible)
{

  if (! visible)
    return;

  gui_settings settings;

  //restore previous session
  if (! settings.bool_value (ed_restore_session))
    return;

  // get the data from the settings file
  QStringList sessionFileNames
    = settings.string_list_value (ed_session_names);

  QStringList session_encodings
    = settings.string_list_value (ed_session_enc);

  QStringList session_index
    = settings.string_list_value (ed_session_ind);

  QStringList session_lines
    = settings.string_list_value (ed_session_lines);

  QStringList session_bookmarks
    = settings.string_list_value (ed_session_bookmarks);

  // fill a list of the struct and sort it (depending on index)
  QList<session_data> s_data;

  bool do_encoding = (session_encodings.count () == sessionFileNames.count ());
  bool do_index = (session_index.count () == sessionFileNames.count ());
  bool do_lines = (session_lines.count () == sessionFileNames.count ());
  bool do_bookmarks = (session_bookmarks.count () == sessionFileNames.count ());

  for (int n = 0; n < sessionFileNames.count (); ++n)
    {
      QFileInfo file = QFileInfo (sessionFileNames.at (n));
      if (! file.exists ())
        continue;

      session_data item = { 0, -1, sessionFileNames.at (n),
                            QString (), QString (), QString ()
                          };
      if (do_lines)
        item.line = session_lines.at (n).toInt ();
      if (do_index)
        item.index = session_index.at (n).toInt ();
      if (do_encoding)
        item.encoding = session_encodings.at (n);
      if (do_bookmarks)
        item.bookmarks = session_bookmarks.at (n);

      s_data << item;
    }

  std::sort (s_data.begin (), s_data.end ());

  // finally open the files with the desired encoding in the desired order
  for (int n = 0; n < s_data.count (); ++n)
    request_open_file (s_data.at (n).file_name, s_data.at (n).encoding,
                       s_data.at (n).line, false, false, true, "", -1,
                       s_data.at (n).bookmarks);
}

void
file_editor::activate ()
{
  if (m_no_focus)
    return;  // No focus for the editor if external open/close request

  octave_dock_widget::activate ();

  // set focus to current tab
  reset_focus ();
}

void
file_editor::set_focus (QWidget *fet)
{
  setFocus ();

  // set focus to desired tab
  if (fet)
    m_tab_widget->setCurrentWidget (fet);
}

// function enabling/disabling the menu accelerators depending on the
// focus of the editor
void
file_editor::enable_menu_shortcuts (bool enable)
{
  // Hide or show the find dialog together with the focus of the
  // editor widget depending on the overall visibility of the find dialog.
  // Do not change internal visibility state.
  if (m_find_dialog)
    m_find_dialog->set_visible (enable);

  // Take care of the shortcuts
  QHash<QMenu *, QStringList>::const_iterator i = m_hash_menu_text.constBegin ();

  while (i != m_hash_menu_text.constEnd ())
    {
      i.key ()->setTitle (i.value ().at (! enable));
      ++i;
    }

  // when editor loses focus, enable the actions, which are always active
  // in the main window due to missing info on selected text and undo actions
  if (m_copy_action && m_undo_action)
    {
      if (enable)
        {
          m_copy_action->setEnabled (m_copy_action_enabled);
          m_undo_action->setEnabled (m_undo_action_enabled);
        }
      else
        {
          m_copy_action_enabled = m_copy_action->isEnabled ();
          m_undo_action_enabled = m_undo_action->isEnabled ();
          m_copy_action->setEnabled (true);
          m_undo_action->setEnabled (true);
        }
    }
}

// Save open files for restoring in next session
// (even if last session will not be restored next time)
// together with encoding and the tab index
void
file_editor::save_session ()
{
  gui_settings settings;

  QStringList fetFileNames;
  QStringList fet_encodings;
  QStringList fet_index;
  QStringList fet_lines;
  QStringList fet_bookmarks;

  std::list<file_editor_tab *> editor_tab_lst = m_tab_widget->tab_list ();

  for (auto editor_tab : editor_tab_lst)
    {
      QString file_name = editor_tab->file_name ();

      // Don't append unnamed files.

      if (! file_name.isEmpty ())
        {
          fetFileNames.append (file_name);
          fet_encodings.append (editor_tab->encoding ());

          QString index;
          fet_index.append (index.setNum (m_tab_widget->indexOf (editor_tab)));

          int l, c;
          editor_tab->qsci_edit_area ()->getCursorPosition (&l, &c);
          fet_lines.append (index.setNum (l + 1));

          fet_bookmarks.append (editor_tab->get_all_bookmarks ());
        }
    }

  settings.setValue (ed_session_names.settings_key (), fetFileNames);
  settings.setValue (ed_session_enc.settings_key (), fet_encodings);
  settings.setValue (ed_session_ind.settings_key (), fet_index);
  settings.setValue (ed_session_lines.settings_key (), fet_lines);
  settings.setValue (ed_session_bookmarks.settings_key (), fet_bookmarks);

  settings.sync ();
}

bool
file_editor::check_closing ()
{
  // When the application or the editor is closing and the user wants to
  // close all files, in the latter case all editor tabs are checked whether
  // they need to be saved.  During these checks tabs are not closed since
  // the user might cancel closing Octave during one of these saving dialogs.
  // Therefore, saving the session for restoring at next start is not done
  // before the application is definitely closing.

  // Save the session. Even is closing is cancelled, this would be
  // overwritten by the next attempt to close the editor
  save_session ();

  std::list<file_editor_tab *> fe_tab_lst = m_tab_widget->tab_list ();
  m_number_of_tabs = fe_tab_lst.size ();

  for (auto fe_tab : fe_tab_lst)
    {
      // Wait for all editor tabs to have saved their files if required

      connect (fe_tab, &file_editor_tab::tab_ready_to_close,
               this, &file_editor::handle_tab_ready_to_close,
               Qt::UniqueConnection);
    }

  m_closing_canceled = false;

  for (auto fe_tab : fe_tab_lst)
    {
      // If there was a cancellation, make the already saved/discarded tabs
      // recover from the exit by removing the read-only state and by
      // recovering the debugger breakpoints.  Finally return false in order
      // to cancel closing the application or the editor.

      if (fe_tab->check_file_modified (false) == QMessageBox::Cancel)
        {
          emit fetab_recover_from_exit ();

          m_closing_canceled = true;

          for (auto fet : fe_tab_lst)
            disconnect (fet, &file_editor_tab::tab_ready_to_close, 0, 0);

          return false;
        }
    }

  return true;
}

void
file_editor::handle_tab_ready_to_close ()
{
  if (m_closing_canceled)
    return;

  // FIXME: Why count down to zero here before doing anything?  Why
  // not remove and delete each tab that is ready to be closed, one
  // per invocation?

  m_number_of_tabs--;

  if (m_number_of_tabs > 0)
    return;

  // Here, the application or the editor will be closed -> store the session

  // Take care of the find dialog
  if (m_find_dialog)
    m_find_dialog->close ();

  // Finally close all the tabs and return indication that we can exit
  // the application or close the editor.
  // Closing and deleting the tabs makes the editor visible.  In case it was
  // hidden before, this state has to be restored afterwards.
  bool vis = isVisible ();

  std::list<file_editor_tab *> editor_tab_lst = m_tab_widget->tab_list ();
  for (auto editor_tab : editor_tab_lst)
    editor_tab->deleteLater ();

  m_tab_widget->clear ();

  setVisible (vis);
}

void
file_editor::request_new_file (const QString& commands)
{
  // Custom editor? If yes, we can only call the editor without passing
  // some initial contents and even without being sure a new file is opened
  if (call_custom_editor ())
    return;

  // New file isn't a file_editor_tab function since the file
  // editor tab has yet to be created and there is no object to
  // pass a signal to.  Hence, functionality is here.

  file_editor_tab *fileEditorTab = make_file_editor_tab (m_ced);
  add_file_editor_tab (fileEditorTab, "");  // new tab with empty title
  fileEditorTab->new_file (commands);       // title is updated here
  activate ();                              // focus editor and new tab
}

void
file_editor::request_close_file (bool)
{
  file_editor_tab *editor_tab
    = static_cast<file_editor_tab *> (m_tab_widget->currentWidget ());
  editor_tab->conditional_close ();
}

void
file_editor::request_close_all_files (bool)
{
  file_editor_tab *editor_tab;

  // loop over all tabs starting from last one otherwise deletion changes index
  for (int index = m_tab_widget->count ()-1; index >= 0; index--)
    {
      editor_tab = static_cast<file_editor_tab *> (m_tab_widget->widget (index));
      editor_tab->conditional_close ();
    }
}

void
file_editor::request_close_other_files (bool)
{
  file_editor_tab *editor_tab;
  QWidget *tabID = m_tab_widget->currentWidget ();

  // loop over all tabs starting from last one otherwise deletion changes index
  for (int index = m_tab_widget->count ()-1; index >= 0; index--)
    {
      if (tabID != m_tab_widget->widget (index))
        {
          editor_tab
            = static_cast<file_editor_tab *> (m_tab_widget->widget (index));
          editor_tab->conditional_close ();
        }
    }
}

void
file_editor::copy_full_file_path (bool)
{
  file_editor_tab *editor_tab
    = static_cast<file_editor_tab *> (m_tab_widget->currentWidget ());

  if (editor_tab)
    QGuiApplication::clipboard ()->setText (editor_tab->file_name ());
}

// open a file from the mru list
void
file_editor::request_mru_open_file (QAction *action)
{
  if (action)
    {
      request_open_file (action->data ().toStringList ().at (0),
                         action->data ().toStringList ().at (1));
    }
}

void
file_editor::request_print_file (bool)
{
  emit fetab_print_file (m_tab_widget->currentWidget ());
}

void
file_editor::request_redo (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_REDO);
}

void
file_editor::request_cut (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_CUT);
}

void
file_editor::request_context_help (bool)
{
  emit fetab_context_help (m_tab_widget->currentWidget (), false);
}

void
file_editor::request_context_doc (bool)
{
  emit fetab_context_help (m_tab_widget->currentWidget (), true);
}

void
file_editor::request_context_edit (bool)
{
  emit fetab_context_edit (m_tab_widget->currentWidget ());
}

void
file_editor::request_save_file (bool)
{
  emit fetab_save_file (m_tab_widget->currentWidget ());
}

void
file_editor::request_save_file_as (bool)
{
  emit fetab_save_file_as (m_tab_widget->currentWidget ());
}

void
file_editor::request_run_file (bool)
{
  // The interpreter_event callback function below emits a signal.
  // Because we don't control when that happens, use a guarded pointer
  // so that the callback can abort if this object is no longer valid.

  QPointer<file_editor> this_fe (this);

  emit interpreter_event
    ([this, this_fe] (interpreter& interp)
     {
       // INTERPRETER THREAD

       // If THIS_FE is no longer valid, skip the entire callback
       // function.  With the way things are implemented now, we can't
       // run the contents of a file unless the file editor and the
       // corresponding file editor tab are still valid.

       if (this_fe.isNull ())
         return;

       // Act as though this action was entered at the command propmt
       // so that the interpreter will check for updated file time
       // stamps.
       Vlast_prompt_time.stamp ();

       tree_evaluator& tw = interp.get_evaluator ();

       if (tw.in_debug_repl ())
         emit request_dbcont_signal ();
       else
         emit fetab_run_file (m_tab_widget->currentWidget ());
     });
}

void
file_editor::request_step_into_file ()
{
  emit fetab_run_file (m_tab_widget->currentWidget (), true);
}

void
file_editor::request_context_run (bool)
{
  emit fetab_context_run (m_tab_widget->currentWidget ());
}

void
file_editor::request_toggle_bookmark (bool)
{
  emit fetab_toggle_bookmark (m_tab_widget->currentWidget ());
}

void
file_editor::request_next_bookmark (bool)
{
  emit fetab_next_bookmark (m_tab_widget->currentWidget ());
}

void
file_editor::request_previous_bookmark (bool)
{
  emit fetab_previous_bookmark (m_tab_widget->currentWidget ());
}

void
file_editor::request_remove_bookmark (bool)
{
  emit fetab_remove_bookmark (m_tab_widget->currentWidget ());
}

void
file_editor::request_move_match_brace (bool)
{
  emit fetab_move_match_brace (m_tab_widget->currentWidget (), false);
}

void
file_editor::request_sel_match_brace (bool)
{
  emit fetab_move_match_brace (m_tab_widget->currentWidget (), true);
}

// FIXME: What should this do with conditional breakpoints?
void
file_editor::request_toggle_breakpoint (bool)
{
  emit fetab_toggle_breakpoint (m_tab_widget->currentWidget ());
}

void
file_editor::request_next_breakpoint (bool)
{
  emit fetab_next_breakpoint (m_tab_widget->currentWidget ());
}

void
file_editor::request_previous_breakpoint (bool)
{
  emit fetab_previous_breakpoint (m_tab_widget->currentWidget ());
}

void
file_editor::request_remove_breakpoint (bool)
{
  emit fetab_remove_all_breakpoints (m_tab_widget->currentWidget ());
}

// slots for Edit->Commands actions
void
file_editor::request_delete_start_word (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_DELWORDLEFT);
}

void
file_editor::request_delete_end_word (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_DELWORDRIGHT);
}

void
file_editor::request_delete_start_line (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_DELLINELEFT);
}

void
file_editor::request_delete_end_line (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_DELLINERIGHT);
}

void
file_editor::request_delete_line (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_LINEDELETE);
}

void
file_editor::request_copy_line (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_LINECOPY);
}

void
file_editor::request_cut_line (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_LINECUT);
}

void
file_editor::request_duplicate_selection (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_SELECTIONDUPLICATE);
}

void
file_editor::request_transpose_line (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_LINETRANSPOSE);
}

void
file_editor::request_comment_selected_text (bool)
{
  emit fetab_comment_selected_text (m_tab_widget->currentWidget (), false);
}

void
file_editor::request_uncomment_selected_text (bool)
{
  emit fetab_uncomment_selected_text (m_tab_widget->currentWidget ());
}

void
file_editor::request_comment_var_selected_text (bool)
{
  emit fetab_comment_selected_text (m_tab_widget->currentWidget (), true);
}

// slots for Edit->Format actions
void
file_editor::request_upper_case (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_UPPERCASE);
}

void
file_editor::request_lower_case (bool)
{
  emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                QsciScintillaBase::SCI_LOWERCASE);
}

void
file_editor::request_indent_selected_text (bool)
{
  emit fetab_indent_selected_text (m_tab_widget->currentWidget ());
}

void
file_editor::request_unindent_selected_text (bool)
{
  emit fetab_unindent_selected_text (m_tab_widget->currentWidget ());
}

void
file_editor::request_smart_indent_line_or_selected_text ()
{
  emit fetab_smart_indent_line_or_selected_text (m_tab_widget->currentWidget ());
}

void
file_editor::request_conv_eol_windows (bool)
{
  emit fetab_convert_eol (m_tab_widget->currentWidget (),
                          QsciScintilla::EolWindows);
}
void
file_editor::request_conv_eol_unix (bool)
{
  emit fetab_convert_eol (m_tab_widget->currentWidget (),
                          QsciScintilla::EolUnix);
}

void
file_editor::request_conv_eol_mac (bool)
{
  emit fetab_convert_eol (m_tab_widget->currentWidget (),
                          QsciScintilla::EolMac);
}

// Slot for initially creating and showing the find dialog
void
file_editor::request_find (bool)
{
  // Create the dialog
  find_create ();

  // Since find_create shows the dialog without activating the widget
  // (which is reuqired in other cases) do this manually here
  m_find_dialog->activateWindow ();

  // Initiate search text from possible selection and save the initial
  // data from the dialog on the defined structure
  m_find_dialog->init_search_text ();
}

// This method creates the find dialog.

void
file_editor::find_create ()
{
  if (m_find_dialog)
    m_find_dialog->close ();

  if (isFloating ())
    m_find_dialog = new find_dialog (this, this);
  else
    m_find_dialog = new find_dialog (this, parentWidget ());

  // Add required actions
  m_find_dialog->addAction (m_find_next_action);
  m_find_dialog->addAction (m_find_previous_action);

  // Update edit area
  file_editor_tab *fet
    = static_cast<file_editor_tab *> (m_tab_widget->currentWidget ());
  m_find_dialog->update_edit_area (fet->qsci_edit_area ());

  // Icon is the same as the editor
  m_find_dialog->setWindowIcon (windowIcon ());

  // Position:  lower right of editor's position
  int xp = x () + frameGeometry ().width ();
  int yp = y () + frameGeometry ().height ();

  if (! isFloating ())
    {
      // Fix position if editor is docked

      QWidget *parent = parentWidget ();

      if  (parent)
        {
          xp = xp + parent->x ();
          yp = yp + parent->y ();
        }
    }

  if (yp < 0)
    yp = 0;

  // The size of the find dialog is considered in restore_settings
  // since its size might change depending on the options
  m_find_dialog->restore_settings (QPoint (xp, yp));

  // Set visible
  m_find_dialog->set_visible (true);
}

void
file_editor::request_find_next (bool)
{
  if (m_find_dialog)
    m_find_dialog->find_next ();
}

void
file_editor::request_find_previous (bool)
{
  if (m_find_dialog)
    m_find_dialog->find_prev ();
}

void
file_editor::request_goto_line (bool)
{
  emit fetab_goto_line (m_tab_widget->currentWidget ());
}

void
file_editor::request_completion (bool)
{
  emit fetab_completion (m_tab_widget->currentWidget ());
}

void
file_editor::handle_file_name_changed (const QString& fname,
                                       const QString& tip,
                                       bool modified)
{
  QObject *fileEditorTab = sender ();
  if (fileEditorTab)
    {
      gui_settings settings;

      for (int i = 0; i < m_tab_widget->count (); i++)
        {
          if (m_tab_widget->widget (i) == fileEditorTab)
            {
              m_tab_widget->setTabText (i, fname);
              m_tab_widget->setTabToolTip (i, tip);

              m_save_action->setEnabled (modified);
              m_current_tab_modified = modified;

              if (modified)
                m_tab_widget->setTabIcon (i, settings.icon ("document-save"));
              else
                m_tab_widget->setTabIcon (i, QIcon ());
            }
        }
    }
}

void
file_editor::handle_tab_close_request (int index)
{
  file_editor_tab *editor_tab
    = static_cast<file_editor_tab *> (m_tab_widget->widget (index));
  editor_tab->conditional_close ();
}

void
file_editor::handle_tab_remove_request ()
{
  QObject *fileEditorTab = sender ();
  if (fileEditorTab)
    {
      for (int i = 0; i < m_tab_widget->count (); i++)
        {
          if (m_tab_widget->widget (i) == fileEditorTab)
            {
              m_tab_widget->removeTab (i);

              // Deleting the sender (even with deleteLater) seems a
              // bit strange.  Is there a better way?
              fileEditorTab->deleteLater ();
              break;
            }
        }
    }
  check_actions ();

  activate ();     // focus stays in editor when tab is closed

}

// context menu of edit area
void
file_editor::active_tab_changed (int index)
{
  emit fetab_change_request (m_tab_widget->widget (index));
  activate ();
}

void
file_editor::handle_editor_state_changed (bool copy_available,
    bool is_octave_file,
    bool is_modified)
{
  // In case there is some scenario where traffic could be coming from
  // all the file editor tabs, just process info from the current active tab.
  if (sender () == m_tab_widget->currentWidget ())
    {
      m_save_action->setEnabled (is_modified);
      m_current_tab_modified = is_modified;

      if (m_copy_action)
        m_copy_action->setEnabled (copy_available);

      m_cut_action->setEnabled (copy_available);

      m_run_selection_action->setEnabled (copy_available);
      m_run_action->setEnabled (is_octave_file);
      m_is_octave_file = is_octave_file;

      emit editor_tabs_changed_signal (true, m_is_octave_file);
    }

  m_copy_action_enabled = m_copy_action->isEnabled ();
  m_undo_action_enabled = m_undo_action->isEnabled ();
}

void
file_editor::handle_mru_add_file (const QString& file_name,
                                  const QString& encoding)
{
  int index;
  while ((index = m_mru_files.indexOf (file_name)) >= 0)
    {
      m_mru_files.removeAt (index);
      m_mru_files_encodings.removeAt (index);
    }

  m_mru_files.prepend (file_name);
  m_mru_files_encodings.prepend (encoding);

  mru_menu_update ();
}

void
file_editor::check_conflict_save (const QString& saveFileName,
                                  bool remove_on_success)
{
  // Check whether this file is already open in the editor.
  file_editor_tab *tab = find_tab_widget (saveFileName);

  if (tab)
    {
      // Note: to overwrite the contents of some other file editor tab
      // with the same name requires identifying which file editor tab
      // that is (not too difficult) then closing that tab.  Of course,
      // that could trigger another dialog box if the file editor tab
      // with the same name has modifications in it.  This could become
      // somewhat confusing to the user.  For now, opt to do nothing.

      // Create a NonModal message about error.
      QMessageBox *msgBox
        = new QMessageBox (QMessageBox::Critical, tr ("Octave Editor"),
                           tr ("File not saved! A file with the selected name\n%1\n"
                               "is already open in the editor.").
                           arg (saveFileName),
                           QMessageBox::Ok, nullptr);

      msgBox->setWindowModality (Qt::NonModal);
      msgBox->setAttribute (Qt::WA_DeleteOnClose);
      msgBox->show ();

      return;
    }

  QObject *saveFileObject = sender ();
  QWidget *saveFileWidget = nullptr;

  for (int i = 0; i < m_tab_widget->count (); i++)
    {
      if (m_tab_widget->widget (i) == saveFileObject)
        {
          saveFileWidget = m_tab_widget->widget (i);
          break;
        }
    }
  if (! saveFileWidget)
    {
      // Create a NonModal message about error.
      QMessageBox *msgBox
        = new QMessageBox (QMessageBox::Critical, tr ("Octave Editor"),
                           tr ("The associated file editor tab has disappeared."),
                           QMessageBox::Ok, nullptr);

      msgBox->setWindowModality (Qt::NonModal);
      msgBox->setAttribute (Qt::WA_DeleteOnClose);
      msgBox->show ();

      return;
    }

  // Can save without conflict, have the file editor tab do so.
  emit fetab_save_file (saveFileWidget, saveFileName, remove_on_success);
}

void
file_editor::handle_insert_debugger_pointer_request (const QString& file,
    int line)
{
  request_open_file (file, QString (), line, true); // default encoding
}

void
file_editor::handle_delete_debugger_pointer_request (const QString& file,
    int line)
{
  if (! file.isEmpty ())
    {
      // Check whether this file is already open in the editor.
      file_editor_tab *tab = find_tab_widget (file);

      if (tab)
        {
          m_tab_widget->setCurrentWidget (tab);

          if (line > 0)
            emit fetab_delete_debugger_pointer (tab, line);

          emit fetab_set_focus (tab);
        }
    }
}

void
file_editor::handle_update_breakpoint_marker_request (bool insert,
    const QString& file,
    int line,
    const QString& cond)
{
  request_open_file (file, QString (), line, false, true, insert, cond);
}

void
file_editor::handle_edit_file_request (const QString& file)
{
  request_open_file (file);
}

// Slot used for signals indicating that a file was changed/renamed or
// is going to be deleted/renamed
void
file_editor::handle_file_remove (const QString& old_name,
                                 const QString& new_name)
{
  // Clear old list of file data and declare a structure for file data
  m_tmp_closed_files.clear ();
  removed_file_data f_data;

  // Preprocessing old name(s)
  QString old_name_clean = old_name.trimmed ();
  int s = old_name_clean.size ();

  if (s > 1 && old_name_clean.at (0) == QChar ('\"')
      && old_name_clean.at (s - 1) == QChar ('\"'))
    old_name_clean = old_name_clean.mid (1, s - 2);

  QStringList old_names = old_name_clean.split ("\" \"");

  // Check if new name is a file or directory
  QFileInfo newf (new_name);
  bool new_is_dir = newf.isDir ();

  // Now loop over all old files/dirs (several files by movefile ())
  for (int i = 0; i < old_names.count (); i++)
    {
      // Check if old name is a file or directory
      QFileInfo old (old_names.at (i));

      if (old.isDir ())
        {
          // Call the function which handles directories and return
          handle_dir_remove (old_names.at (i), new_name);
        }
      else
        {
          // It is a single file.  Is it open?
          file_editor_tab *editor_tab = find_tab_widget (old_names.at (i));

          if (editor_tab)
            {

              editor_tab->enable_file_watcher (false);

              // For re-enabling tracking if error while removing/renaming
              f_data.editor_tab = editor_tab;
              // For renaming into new file (if new_file is not empty)
              if (new_is_dir)
                {
                  std::string ndir = new_name.toStdString ();
                  std::string ofile = old.fileName ().toStdString ();
                  f_data.new_file_name
                    = QString::fromStdString (sys::env::make_absolute (ofile, ndir));
                }
              else
                f_data.new_file_name = new_name;

              // Add file data to list
              m_tmp_closed_files << f_data;
            }
        }
    }
}

// Slot for signal indicating that a file was renamed
void
file_editor::handle_file_renamed (bool load_new)
{
  m_no_focus = true;  // Remember for not focussing editor

  // Loop over all files that have to be handled.  Start at the end of the
  // list, otherwise the stored indexes are not correct.
  for (int i = m_tmp_closed_files.count () - 1; i >= 0; i--)
    {
      if (load_new)
        {
          // Close file (remove) or rename into new file (rename)
          if (m_tmp_closed_files.at (i).new_file_name.isEmpty ())
            m_tmp_closed_files.at (i).editor_tab->file_has_changed (QString (), true);
          else
            m_tmp_closed_files.at (i).editor_tab->set_file_name (m_tmp_closed_files.at (i).new_file_name);
        }
      else
        {
          // Something went wrong while renaming or removing:
          // Leave everything as it is but reactivate tracking
          m_tmp_closed_files.at (i).editor_tab->enable_file_watcher (true);
        }

    }

  m_no_focus = false;  // Back to normal focus

  // Clear the list of file data
  m_tmp_closed_files.clear ();
}

void
file_editor::notice_settings ()
{
  gui_settings settings;

  int size_idx = settings.int_value (global_icon_size);
  size_idx = (size_idx > 0) - (size_idx < 0) + 1;  // Make valid index from 0 to 2

  QStyle *st = style ();
  int icon_size = st->pixelMetric (global_icon_sizes[size_idx]);
  m_tool_bar->setIconSize (QSize (icon_size, icon_size));

  // Tab position and rotation
  QTabWidget::TabPosition pos
    = static_cast<QTabWidget::TabPosition> (settings.int_value (ed_tab_position));
  bool rotated = settings.bool_value (ed_tabs_rotated);

  m_tab_widget->setTabPosition (pos);

  if (rotated)
    m_tab_widget->setTabsClosable (false);  // No close buttons
    // FIXME: close buttons can not be correctly placed in rotated tabs

  // Get the tab bar and set the rotation
  int rotation = rotated;
  if (pos == QTabWidget::West)
    rotation = -rotation;

  tab_bar *bar = m_tab_widget->get_tab_bar ();
  bar->set_rotated (rotation);

  // Get suitable height of a tab related to font and icon size
  int height = 1.5*QFontMetrics (m_tab_widget->font ()).height ();
  int is = 1.5*m_tab_widget->iconSize ().height ();
  if (is > height)
    height = is;

  // Calculate possibly limited width and set the elide mode
  int chars = settings.int_value (ed_tabs_max_width);
  int width = 9999;
  if (chars > 0)
    width = chars * QFontMetrics (m_tab_widget->font ()).averageCharWidth ();

  // Get tab bar size properties for style sheet depending on rotation
  QString width_str ("width");
  QString height_str ("height");
  if ((pos == QTabWidget::West) || (pos == QTabWidget::East))
    {
      width_str = QString ("height");
      height_str = QString ("width");
    }

  QString style_sheet
      = QString ("QTabBar::tab {max-" + height_str + ": %1px;\n"
                               "max-" + width_str + ": %2px; }")
                        .arg (height).arg (width);

#if defined (Q_OS_MAC)
  // FIXME: This is a workaround for missing tab close buttons on MacOS
  // in several Qt versions (https://bugreports.qt.io/browse/QTBUG-61092)
  if (! rotated)
    {
      QString icon = global_icon_paths.at (ICON_THEME_OCTAVE) + "widget-close.png";

      QString close_button_css_mac
        ("QTabBar::close-button"
         " { image: url(" + icon + ");"
         " padding: 4px;"
         "   subcontrol-position: bottom; }\n"
         "QTabBar::close-button:hover"
         "  { background-color: #cccccc; }");

      style_sheet = style_sheet + close_button_css_mac;
    }
#endif

  m_tab_widget->setStyleSheet (style_sheet);

  bool show_it;
  show_it = settings.bool_value (ed_show_line_numbers);
  m_show_linenum_action->setChecked (show_it);
  show_it = settings.bool_value (ed_show_white_space);
  m_show_whitespace_action->setChecked (show_it);
  show_it = settings.bool_value (ed_show_eol_chars);
  m_show_eol_action->setChecked (show_it);
  show_it = settings.bool_value (ed_show_indent_guides);
  m_show_indguide_action->setChecked (show_it);
  show_it = settings.bool_value (ed_long_line_marker);
  m_show_longline_action->setChecked (show_it);

  show_it = settings.bool_value (ed_show_toolbar);
  m_show_toolbar_action->setChecked (show_it);
  m_tool_bar->setVisible (show_it);
  show_it = settings.bool_value (ed_show_edit_status_bar);
  m_show_statusbar_action->setChecked (show_it);
  show_it = settings.bool_value (ed_show_hscroll_bar);
  m_show_hscrollbar_action->setChecked (show_it);

  set_shortcuts ();

  // Find dialog with the same icon as the editor
  if (m_find_dialog)
    m_find_dialog->setWindowIcon (windowIcon ());

  // Relay signal to file editor tabs.
  emit fetab_settings_changed ();
}

void
file_editor::set_shortcuts ()
{
  // Shortcuts also available in the main window, as well as the related
  // shortcuts, are defined in main_window and added to the editor

  gui_settings settings;

  // File menu
  settings.set_shortcut (m_edit_function_action, sc_edit_file_edit_function);
  settings.set_shortcut (m_save_action, sc_edit_file_save);
  settings.set_shortcut (m_save_as_action, sc_edit_file_save_as);
  settings.set_shortcut (m_close_action, sc_edit_file_close);
  settings.set_shortcut (m_close_all_action, sc_edit_file_close_all);
  settings.set_shortcut (m_close_others_action, sc_edit_file_close_other);
  settings.set_shortcut (m_print_action, sc_edit_file_print);

  // Edit menu
  settings.set_shortcut (m_redo_action, sc_edit_edit_redo);
  settings.set_shortcut (m_cut_action, sc_edit_edit_cut);
  settings.set_shortcut (m_find_action, sc_edit_edit_find_replace);
  settings.set_shortcut (m_find_next_action, sc_edit_edit_find_next);
  settings.set_shortcut (m_find_previous_action, sc_edit_edit_find_previous);

  settings.set_shortcut (m_delete_start_word_action, sc_edit_edit_delete_start_word);
  settings.set_shortcut (m_delete_end_word_action, sc_edit_edit_delete_end_word);
  settings.set_shortcut (m_delete_start_line_action, sc_edit_edit_delete_start_line);
  settings.set_shortcut (m_delete_end_line_action, sc_edit_edit_delete_end_line);
  settings.set_shortcut (m_delete_line_action, sc_edit_edit_delete_line);
  settings.set_shortcut (m_copy_line_action, sc_edit_edit_copy_line);
  settings.set_shortcut (m_cut_line_action, sc_edit_edit_cut_line);
  settings.set_shortcut (m_duplicate_selection_action, sc_edit_edit_duplicate_selection);
  settings.set_shortcut (m_transpose_line_action, sc_edit_edit_transpose_line);
  settings.set_shortcut (m_comment_selection_action, sc_edit_edit_comment_selection);
  settings.set_shortcut (m_uncomment_selection_action, sc_edit_edit_uncomment_selection);
  settings.set_shortcut (m_comment_var_selection_action, sc_edit_edit_comment_var_selection);

  settings.set_shortcut (m_upper_case_action, sc_edit_edit_upper_case);
  settings.set_shortcut (m_lower_case_action, sc_edit_edit_lower_case);
  settings.set_shortcut (m_indent_selection_action, sc_edit_edit_indent_selection);
  settings.set_shortcut (m_unindent_selection_action, sc_edit_edit_unindent_selection);
  settings.set_shortcut (m_smart_indent_line_or_selection_action, sc_edit_edit_smart_indent_line_or_selection);
  settings.set_shortcut (m_completion_action, sc_edit_edit_completion_list);
  settings.set_shortcut (m_goto_line_action, sc_edit_edit_goto_line);
  settings.set_shortcut (m_move_to_matching_brace, sc_edit_edit_move_to_brace);
  settings.set_shortcut (m_sel_to_matching_brace, sc_edit_edit_select_to_brace);
  settings.set_shortcut (m_toggle_bookmark_action, sc_edit_edit_toggle_bookmark);
  settings.set_shortcut (m_next_bookmark_action, sc_edit_edit_next_bookmark);
  settings.set_shortcut (m_previous_bookmark_action, sc_edit_edit_previous_bookmark);
  settings.set_shortcut (m_remove_bookmark_action, sc_edit_edit_remove_bookmark);
  settings.set_shortcut (m_preferences_action, sc_edit_edit_preferences);
  settings.set_shortcut (m_styles_preferences_action, sc_edit_edit_styles_preferences);

  settings.set_shortcut (m_conv_eol_windows_action, sc_edit_edit_conv_eol_winows);
  settings.set_shortcut (m_conv_eol_unix_action,    sc_edit_edit_conv_eol_unix);
  settings.set_shortcut (m_conv_eol_mac_action,     sc_edit_edit_conv_eol_mac);

  // View menu
  settings.set_shortcut (m_show_linenum_action, sc_edit_view_show_line_numbers);
  settings.set_shortcut (m_show_whitespace_action, sc_edit_view_show_white_spaces);
  settings.set_shortcut (m_show_eol_action, sc_edit_view_show_eol_chars);
  settings.set_shortcut (m_show_indguide_action, sc_edit_view_show_ind_guides);
  settings.set_shortcut (m_show_longline_action, sc_edit_view_show_long_line);
  settings.set_shortcut (m_show_toolbar_action, sc_edit_view_show_toolbar);
  settings.set_shortcut (m_show_statusbar_action, sc_edit_view_show_statusbar);
  settings.set_shortcut (m_show_hscrollbar_action, sc_edit_view_show_hscrollbar);
  settings.set_shortcut (m_zoom_in_action, sc_edit_view_zoom_in);
  settings.set_shortcut (m_zoom_out_action, sc_edit_view_zoom_out);
  settings.set_shortcut (m_zoom_normal_action, sc_edit_view_zoom_normal);
  settings.set_shortcut (m_sort_tabs_action, sc_edit_view_sort_tabs);

  // Debug menu
  settings.set_shortcut (m_toggle_breakpoint_action, sc_edit_debug_toggle_breakpoint);
  settings.set_shortcut (m_next_breakpoint_action, sc_edit_debug_next_breakpoint);
  settings.set_shortcut (m_previous_breakpoint_action, sc_edit_debug_previous_breakpoint);
  settings.set_shortcut (m_remove_all_breakpoints_action, sc_edit_debug_remove_breakpoints);

  // Run menu
  settings.set_shortcut (m_run_action, sc_edit_run_run_file);
  settings.set_shortcut (m_run_selection_action, sc_edit_run_run_selection);

  // Help menu
  settings.set_shortcut (m_context_help_action, sc_edit_help_help_keyword);
  settings.set_shortcut (m_context_doc_action,  sc_edit_help_doc_keyword);

  // Tab navigation without menu entries
  settings.set_shortcut (m_switch_left_tab_action, sc_edit_tabs_switch_left_tab);
  settings.set_shortcut (m_switch_right_tab_action, sc_edit_tabs_switch_right_tab);
  settings.set_shortcut (m_move_tab_left_action, sc_edit_tabs_move_tab_left);
  settings.set_shortcut (m_move_tab_right_action, sc_edit_tabs_move_tab_right);
}

// This slot is a reimplementation of the virtual slot in octave_dock_widget.
// We need this for creating an empty script when the editor has no open
// files and is made visible.
void
file_editor::handle_visibility (bool visible)
{
  octave_dock_widget::handle_visibility (visible);

  if (! m_editor_ready)
    return;

  if (m_closed && visible)
    {
      m_closed = false;

      restore_session (visible);
    }

  empty_script (false, visible);
}

// This slot is a reimplementation of the virtual slot in octave_dock_widget.
// We need this for updating the parent of the find dialog
void
file_editor::toplevel_change (bool)
{
  if (m_find_dialog)
    {
      // close current dialog
      m_find_dialog->close ();

      // re-create dialog with the new parent (editor or main-win)
      find_create ();
      m_find_dialog->activateWindow ();
    }
}

void
file_editor::update_octave_directory (const QString& dir)
{
  m_ced = dir;
  emit fetab_set_directory (m_ced);  // for save dialog
}

void
file_editor::copyClipboard ()
{
  if (editor_tab_has_focus ())
    emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                  QsciScintillaBase::SCI_COPY);
}

void
file_editor::pasteClipboard ()
{
  if (editor_tab_has_focus ())
    emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                  QsciScintillaBase::SCI_PASTE);
}

void
file_editor::selectAll ()
{
  if (editor_tab_has_focus ())
    emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                  QsciScintillaBase::SCI_SELECTALL);
}

void
file_editor::do_undo ()
{
  if (editor_tab_has_focus ())
    emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                  QsciScintillaBase::SCI_UNDO);
}

// Open a file, if not already open, and mark the current execution location
// and/or a breakpoint with condition cond.
void
file_editor::request_open_file (const QString& openFileName,
                                const QString& encoding,
                                int line, bool debug_pointer,
                                bool breakpoint_marker, bool insert,
                                const QString& cond, int index,
                                const QString& bookmarks)
{
  gui_settings settings;

  if (settings.bool_value (global_use_custom_editor))
    {
      // Custom editor
      if (debug_pointer || breakpoint_marker)
        return;   // Do not call custom editor during debugging

      if (call_custom_editor (openFileName, line))
        return;   // Custom editor called
    }

  bool show_dbg_file = settings.bool_value (ed_show_dbg_file);

  if (openFileName.isEmpty ())
    {
      // This happens if edit is called without an argument
      // Open editor with empty edit area instead (as new file would do)
      request_new_file ("");
    }
  else
    {
      // Check whether this file is already open in the editor.
      file_editor_tab *tab = find_tab_widget (openFileName);

      if (tab)
        {
          m_tab_widget->setCurrentWidget (tab);

          if (line > 0)
            {
              if (insert)
                emit fetab_goto_line (tab, line);

              if (debug_pointer)
                emit fetab_insert_debugger_pointer (tab, line);

              if (breakpoint_marker)
                emit fetab_do_breakpoint_marker (insert, tab, line, cond);
            }

          if (show_dbg_file && ! ((breakpoint_marker || debug_pointer)
                                  && is_editor_console_tabbed ()))
            {
              emit fetab_set_focus (tab);
              activate ();
            }
        }
      else
        {
          if (! show_dbg_file && (breakpoint_marker  || debug_pointer))
            return;   // Do not open a file for showing dbg markers

          if (breakpoint_marker && ! insert)
            return;   // Never open a file when removing breakpoints

          file_editor_tab *fileEditorTab = nullptr;
          // Reuse <unnamed> tab if it hasn't yet been modified.
          bool reusing = false;
          tab = find_tab_widget ("");
          if (tab)
            {
              fileEditorTab = tab;
              if (fileEditorTab->qsci_edit_area ()->isModified ())
                fileEditorTab = nullptr;
              else
                reusing = true;
            }

          // If <unnamed> was absent or modified, create a new tab.
          if (! fileEditorTab)
            fileEditorTab = make_file_editor_tab ();

          fileEditorTab->set_encoding (encoding);
          QString result = fileEditorTab->load_file (openFileName);
          if (result == "")
            {
              // Supply empty title then have the file_editor_tab update
              // with full or short name.
              if (! reusing)
                add_file_editor_tab (fileEditorTab, "", index);
              fileEditorTab->update_window_title (false);
              // file already loaded, add file to mru list here
              QFileInfo file_info = QFileInfo (openFileName);
              handle_mru_add_file (file_info.canonicalFilePath (),
                                   encoding);

              if (line > 0)
                {
                  if (insert)
                    emit fetab_goto_line (fileEditorTab, line);

                  if (debug_pointer)
                    emit fetab_insert_debugger_pointer (fileEditorTab,
                                                        line);
                  if (breakpoint_marker)
                    emit fetab_do_breakpoint_marker (insert, fileEditorTab,
                                                     line, cond);
                }
            }
          else
            {
              if (! reusing)
                {
                  delete fileEditorTab;
                  fileEditorTab = nullptr;
                }

              if (QFile::exists (openFileName))
                {
                  // File not readable:
                  // create a NonModal message about error.
                  QMessageBox *msgBox
                    = new QMessageBox (QMessageBox::Critical,
                                       tr ("Octave Editor"),
                                       tr ("Could not open file\n%1\nfor reading: %2.").
                                       arg (openFileName).arg (result),
                                       QMessageBox::Ok, this);

                  msgBox->setWindowModality (Qt::NonModal);
                  msgBox->setAttribute (Qt::WA_DeleteOnClose);
                  msgBox->show ();
                }
              else
                {
                  // File does not exist, should it be created?
                  bool create_file = true;
                  QMessageBox *msgBox;

                  if (! settings.bool_value (ed_create_new_file))
                    {
                      msgBox = new QMessageBox (QMessageBox::Question,
                                                tr ("Octave Editor"),
                                                tr ("File\n%1\ndoes not exist. "
                                                    "Do you want to create it?").arg (openFileName),
                                                QMessageBox::NoButton, nullptr);
                      QPushButton *create_button =
                        msgBox->addButton (tr ("Create"), QMessageBox::YesRole);
                      msgBox->addButton (tr ("Cancel"), QMessageBox::RejectRole);
                      msgBox->setDefaultButton (create_button);
                      msgBox->exec ();

                      QAbstractButton *clicked_button = msgBox->clickedButton ();
                      if (clicked_button != create_button)
                        create_file = false;

                      delete msgBox;
                    }

                  if (create_file)
                    {
                      // create the file and call the editor again
                      QFile file (openFileName);
                      if (! file.open (QIODevice::WriteOnly))
                        {
                          // error opening the file
                          msgBox = new QMessageBox (QMessageBox::Critical,
                                                    tr ("Octave Editor"),
                                                    tr ("Could not open file\n%1\nfor writing: %2.").
                                                    arg (openFileName).arg (file.errorString ()),
                                                    QMessageBox::Ok, this);

                          msgBox->setWindowModality (Qt::NonModal);
                          msgBox->setAttribute (Qt::WA_DeleteOnClose);
                          msgBox->show ();
                        }
                      else
                        {
                          file.close ();
                          request_open_file (openFileName);
                        }
                    }
                }
            }

          if (! bookmarks.isEmpty ())
            {
              // Restore bookmarks
              for (const auto& bms : bookmarks.split (','))
                {
                  int bm = bms.toInt ();
                  if (fileEditorTab)
                    fileEditorTab->qsci_edit_area ()->markerAdd (bm, marker::bookmark);
                }
            }

          if (! ((breakpoint_marker || debug_pointer) && is_editor_console_tabbed ()))
            {
              // update breakpoint pointers, really show editor
              // and the current editor tab
              if (fileEditorTab)
                fileEditorTab->update_breakpoints ();
              activate ();
              emit file_loaded_signal ();
            }
        }
    }
}

void
file_editor::request_preferences (bool)
{
  emit request_settings_dialog ("editor");
}

void
file_editor::request_styles_preferences (bool)
{
  emit request_settings_dialog ("editor_styles");
}

void
file_editor::show_line_numbers (bool)
{
  toggle_preference (ed_show_line_numbers);
}

void
file_editor::show_white_space (bool)
{
  toggle_preference (ed_show_white_space);
}

void
file_editor::show_eol_chars (bool)
{
  toggle_preference (ed_show_eol_chars);
}

void
file_editor::show_indent_guides (bool)
{
  toggle_preference (ed_show_indent_guides);
}

void
file_editor::show_long_line (bool)
{
  toggle_preference (ed_long_line_marker);
}

void
file_editor::show_toolbar (bool)
{
  toggle_preference (ed_show_toolbar);
}

void
file_editor::show_statusbar (bool)
{
  toggle_preference (ed_show_edit_status_bar);
}

void
file_editor::show_hscrollbar (bool)
{
  toggle_preference (ed_show_hscroll_bar);
}

void
file_editor::zoom_in (bool)
{
  emit fetab_zoom_in (m_tab_widget->currentWidget ());
}

void
file_editor::zoom_out (bool)
{
  emit fetab_zoom_out (m_tab_widget->currentWidget ());
}

void
file_editor::zoom_normal (bool)
{
  emit fetab_zoom_normal (m_tab_widget->currentWidget ());
}

void
file_editor::create_context_menu (QMenu *menu)
{
  // remove all standard actions from scintilla
  QList<QAction *> all_actions = menu->actions ();

  for (auto *a : all_actions)
    menu->removeAction (a);

  // add editor's actions with icons and customized shortcuts
  menu->addAction (m_cut_action);
  menu->addAction (m_copy_action);
  menu->addAction (m_paste_action);
  menu->addSeparator ();
  menu->addAction (m_selectall_action);
  menu->addSeparator ();
  menu->addAction (m_find_files_action);
  menu->addAction (m_find_action);
  menu->addAction (m_find_next_action);
  menu->addAction (m_find_previous_action);
  menu->addSeparator ();
  menu->addMenu (m_edit_cmd_menu);
  menu->addMenu (m_edit_fmt_menu);
  menu->addMenu (m_edit_nav_menu);
  menu->addSeparator ();
  menu->addAction (m_run_selection_action);
}

void
file_editor::edit_status_update (bool undo, bool redo)
{
  if (m_undo_action)
    m_undo_action->setEnabled (undo);
  m_redo_action->setEnabled (redo);
}

// handler for the close event
void
file_editor::closeEvent (QCloseEvent *e)
{
  gui_settings settings;

  if (settings.bool_value (ed_hiding_closes_files))
    {
      if (check_closing ())
        {
          // All tabs are closed without cancelling,
          // store closing state for restoring session when shown again.
          // Editor is closing when session data is stored in preferences
          m_closed = true;
          e->ignore ();
        }
      else
        {
          e->ignore ();
          return;
        }
    }
  else
    e->accept ();

  octave_dock_widget::closeEvent (e);
}

void
file_editor::dragEnterEvent (QDragEnterEvent *e)
{
  if (e->mimeData ()->hasUrls ())
    {
      e->acceptProposedAction ();
    }
}

void
file_editor::dropEvent (QDropEvent *e)
{
  if (e->mimeData ()->hasUrls ())
    {
      for (const auto& url : e->mimeData ()->urls ())
        request_open_file (url.toLocalFile ());
    }
}

bool
file_editor::is_editor_console_tabbed ()
{
  // FIXME: is there a way to do this job that doesn't require casting
  // the parent to a main_window object?

  main_window *w = dynamic_cast<main_window *> (parentWidget ());

  if (w)
    {
      QList<QDockWidget *> w_list = w->tabifiedDockWidgets (this);
      QDockWidget *console =
        static_cast<QDockWidget *> (w->get_dock_widget_list ().at (0));

      for (int i = 0; i < w_list.count (); i++)
        {
          if (w_list.at (i) == console)
            return true;
        }
    }

  return false;
}

void
file_editor::construct ()
{
  QWidget *editor_widget = new QWidget (this);

  // FIXME: what was the intended purpose of this unused variable?
  // QStyle *editor_style = QApplication::style ();

  // Menu bar: do not set it native, required in macOS and Ubuntu Unity (Qt5)
  // for a visible menu bar in the editor widget.  This property is ignored
  // on other platforms.
  m_menu_bar = new QMenuBar (editor_widget);
  m_menu_bar->setNativeMenuBar (false);

  m_tool_bar = new QToolBar (editor_widget);
  m_tool_bar->setMovable (true);

  m_tab_widget = new file_editor_tab_widget (editor_widget, this);

  // the mru-list and an empty array of actions

  gui_settings settings;

  m_mru_files = settings.string_list_value (ed_mru_file_list);
  m_mru_files_encodings = settings.string_list_value (ed_mru_file_encodings);

  if (m_mru_files_encodings.count () != m_mru_files.count ())
    {
      // encodings don't have the same count -> do not use them!
      m_mru_files_encodings = QStringList ();
      for (int i = 0; i < m_mru_files.count (); i++)
        m_mru_files_encodings << QString ();
    }

  for (int i = 0; i < MaxMRUFiles; ++i)
    {
      m_mru_file_actions[i] = new QAction (this);
      m_mru_file_actions[i]->setVisible (false);
    }

  // menu bar

  // file menu

  m_fileMenu = add_menu (m_menu_bar, tr ("&File"));

  // new and open menus are inserted later by the main window
  m_mru_file_menu = new QMenu (tr ("&Recent Editor Files"), m_fileMenu);
  for (int i = 0; i < MaxMRUFiles; ++i)
    m_mru_file_menu->addAction (m_mru_file_actions[i]);
  m_fileMenu->addMenu (m_mru_file_menu);

  m_fileMenu->addSeparator ();

  m_edit_function_action
    = add_action (m_fileMenu,
                  tr ("&Edit Function"),
                  SLOT (request_context_edit (bool)));

  m_fileMenu->addSeparator ();

  m_save_action
    = add_action (m_fileMenu, settings.icon ("document-save"),
                  tr ("&Save File"), SLOT (request_save_file (bool)));

  m_save_as_action
    = add_action (m_fileMenu, settings.icon ("document-save-as"),
                  tr ("Save File &As..."),
                  SLOT (request_save_file_as (bool)));

  m_fileMenu->addSeparator ();

  m_close_action
    = add_action (m_fileMenu, settings.icon ("window-close", false),
                  tr ("&Close"), SLOT (request_close_file (bool)));

  m_close_all_action
    = add_action (m_fileMenu, settings.icon ("window-close", false),
                  tr ("Close All"), SLOT (request_close_all_files (bool)));

  m_close_others_action
    = add_action (m_fileMenu, settings.icon ("window-close", false),
                  tr ("Close Other Files"),
                  SLOT (request_close_other_files (bool)));

  m_fileMenu->addSeparator ();

  m_print_action
    = add_action (m_fileMenu, settings.icon ("document-print"),
                  tr ("Print..."), SLOT (request_print_file (bool)));

  // edit menu (undo, copy, paste and select all later via main window)

  m_edit_menu = add_menu (m_menu_bar, tr ("&Edit"));

  m_redo_action
    = add_action (m_edit_menu, settings.icon ("edit-redo"),
                  tr ("&Redo"), SLOT (request_redo (bool)));
  m_redo_action->setEnabled (false);

  m_edit_menu->addSeparator ();

  m_cut_action
    = add_action (m_edit_menu, settings.icon ("edit-cut"),
                  tr ("Cu&t"), SLOT (request_cut (bool)));
  m_cut_action->setEnabled (false);

  m_find_action
    = add_action (m_edit_menu, settings.icon ("edit-find-replace"),
                  tr ("&Find and Replace..."), SLOT (request_find (bool)));

  m_find_next_action
    = add_action (m_edit_menu, tr ("Find &Next"),
                  SLOT (request_find_next (bool)));

  m_find_previous_action
    = add_action (m_edit_menu, tr ("Find &Previous"),
                  SLOT (request_find_previous (bool)));

  m_edit_menu->addSeparator ();

  m_edit_cmd_menu = m_edit_menu->addMenu (tr ("&Commands"));

  m_delete_line_action
    = add_action (m_edit_cmd_menu, tr ("Delete Line"),
                  SLOT (request_delete_line (bool)));

  m_copy_line_action
    = add_action (m_edit_cmd_menu, tr ("Copy Line"),
                  SLOT (request_copy_line (bool)));

  m_cut_line_action
    = add_action (m_edit_cmd_menu, tr ("Cut Line"),
                  SLOT (request_cut_line (bool)));

  m_edit_cmd_menu->addSeparator ();

  m_delete_start_word_action
    = add_action (m_edit_cmd_menu, tr ("Delete to Start of Word"),
                  SLOT (request_delete_start_word (bool)));

  m_delete_end_word_action
    = add_action (m_edit_cmd_menu, tr ("Delete to End of Word"),
                  SLOT (request_delete_end_word (bool)));

  m_delete_start_line_action
    = add_action (m_edit_cmd_menu, tr ("Delete to Start of Line"),
                  SLOT (request_delete_start_line (bool)));

  m_delete_end_line_action
    = add_action (m_edit_cmd_menu, tr ("Delete to End of Line"),
                  SLOT (request_delete_end_line (bool)));

  m_edit_cmd_menu->addSeparator ();

  m_duplicate_selection_action
    = add_action (m_edit_cmd_menu, tr ("Duplicate Selection/Line"),
                  SLOT (request_duplicate_selection (bool)));

  m_transpose_line_action
    = add_action (m_edit_cmd_menu, tr ("Transpose Line"),
                  SLOT (request_transpose_line (bool)));

  m_edit_cmd_menu->addSeparator ();

  m_completion_action
    = add_action (m_edit_cmd_menu, tr ("&Show Completion List"),
                  SLOT (request_completion (bool)));

  m_edit_fmt_menu = m_edit_menu->addMenu (tr ("&Format"));

  m_upper_case_action
    = add_action (m_edit_fmt_menu, tr ("&Uppercase Selection"),
                  SLOT (request_upper_case (bool)));

  m_lower_case_action
    = add_action (m_edit_fmt_menu, tr ("&Lowercase Selection"),
                  SLOT (request_lower_case (bool)));

  m_edit_fmt_menu->addSeparator ();

  m_comment_selection_action
    = add_action (m_edit_fmt_menu, tr ("&Comment"),
                  SLOT (request_comment_selected_text (bool)));

  m_uncomment_selection_action
    = add_action (m_edit_fmt_menu, tr ("&Uncomment"),
                  SLOT (request_uncomment_selected_text (bool)));

  m_comment_var_selection_action
    = add_action (m_edit_fmt_menu, tr ("Comment (Choosing String)"),
                  SLOT (request_comment_var_selected_text (bool)));

  m_edit_fmt_menu->addSeparator ();

  m_indent_selection_action
    = add_action (m_edit_fmt_menu, tr ("&Indent Selection Rigidly"),
                  SLOT (request_indent_selected_text (bool)));

  m_unindent_selection_action
    = add_action (m_edit_fmt_menu, tr ("&Unindent Selection Rigidly"),
                  SLOT (request_unindent_selected_text (bool)));

  m_smart_indent_line_or_selection_action
    = add_action (m_edit_fmt_menu, tr ("Indent Code"),
                  SLOT (request_smart_indent_line_or_selected_text ()));

  m_edit_fmt_menu->addSeparator ();

  m_conv_eol_windows_action
    = add_action (m_edit_fmt_menu,
                  tr ("Convert Line Endings to &Windows (CRLF)"),
                  SLOT (request_conv_eol_windows (bool)));

  m_conv_eol_unix_action
    = add_action (m_edit_fmt_menu, tr ("Convert Line Endings to &Unix (LF)"),
                  SLOT (request_conv_eol_unix (bool)));

  m_conv_eol_mac_action
    = add_action (m_edit_fmt_menu,
                  tr ("Convert Line Endings to Legacy &Mac (CR)"),
                  SLOT (request_conv_eol_mac (bool)));

  m_edit_nav_menu = m_edit_menu->addMenu (tr ("Navi&gation"));

  m_goto_line_action
    = add_action (m_edit_nav_menu, tr ("Go &to Line..."),
                  SLOT (request_goto_line (bool)));

  m_edit_cmd_menu->addSeparator ();

  m_move_to_matching_brace
    = add_action (m_edit_nav_menu, tr ("Move to Matching Brace"),
                  SLOT (request_move_match_brace (bool)));

  m_sel_to_matching_brace
    = add_action (m_edit_nav_menu, tr ("Select to Matching Brace"),
                  SLOT (request_sel_match_brace (bool)));

  m_edit_nav_menu->addSeparator ();

  m_next_bookmark_action
    = add_action (m_edit_nav_menu, tr ("&Next Bookmark"),
                  SLOT (request_next_bookmark (bool)));

  m_previous_bookmark_action
    = add_action (m_edit_nav_menu, tr ("Pre&vious Bookmark"),
                  SLOT (request_previous_bookmark (bool)));

  m_toggle_bookmark_action
    = add_action (m_edit_nav_menu, tr ("Toggle &Bookmark"),
                  SLOT (request_toggle_bookmark (bool)));

  m_remove_bookmark_action
    = add_action (m_edit_nav_menu, tr ("&Remove All Bookmarks"),
                  SLOT (request_remove_bookmark (bool)));

  m_edit_menu->addSeparator ();

  m_preferences_action
    = add_action (m_edit_menu, settings.icon ("preferences-system"),
                  tr ("&Preferences..."),
                  SLOT (request_preferences (bool)));

  m_styles_preferences_action
    = add_action (m_edit_menu, settings.icon ("preferences-system"),
                  tr ("&Styles Preferences..."),
                  SLOT (request_styles_preferences (bool)));

  // view menu

  QMenu *view_menu = add_menu (m_menu_bar, tr ("&View"));

  m_view_editor_menu = view_menu->addMenu (tr ("&Editor"));

  m_show_linenum_action
    = add_action (m_view_editor_menu, tr ("Show &Line Numbers"),
                  SLOT (show_line_numbers (bool)));
  m_show_linenum_action->setCheckable (true);

  m_show_whitespace_action
    = add_action (m_view_editor_menu, tr ("Show &Whitespace Characters"),
                  SLOT (show_white_space (bool)));
  m_show_whitespace_action->setCheckable (true);

  m_show_eol_action
    = add_action (m_view_editor_menu, tr ("Show Line &Endings"),
                  SLOT (show_eol_chars (bool)));
  m_show_eol_action->setCheckable (true);

  m_show_indguide_action
    = add_action (m_view_editor_menu, tr ("Show &Indentation Guides"),
                  SLOT (show_indent_guides (bool)));
  m_show_indguide_action->setCheckable (true);

  m_show_longline_action
    = add_action (m_view_editor_menu, tr ("Show Long Line &Marker"),
                  SLOT (show_long_line (bool)));
  m_show_longline_action->setCheckable (true);

  m_view_editor_menu->addSeparator ();

  m_show_toolbar_action
    = add_action (m_view_editor_menu, tr ("Show &Toolbar"),
                  SLOT (show_toolbar (bool)));
  m_show_toolbar_action->setCheckable (true);

  m_show_statusbar_action
    = add_action (m_view_editor_menu, tr ("Show &Statusbar"),
                  SLOT (show_statusbar (bool)));
  m_show_statusbar_action->setCheckable (true);

  m_show_hscrollbar_action
    = add_action (m_view_editor_menu, tr ("Show &Horizontal Scrollbar"),
                  SLOT (show_hscrollbar (bool)));
  m_show_hscrollbar_action->setCheckable (true);

  view_menu->addSeparator ();

  m_zoom_in_action
    = add_action (view_menu, settings.icon ("view-zoom-in"), tr ("Zoom &In"),
                  SLOT (zoom_in (bool)));

  m_zoom_out_action
    = add_action (view_menu, settings.icon ("view-zoom-out"),
                  tr ("Zoom &Out"), SLOT (zoom_out (bool)));

  m_zoom_normal_action
    = add_action (view_menu, settings.icon ("view-zoom-original"),
                  tr ("&Normal Size"), SLOT (zoom_normal (bool)));

  view_menu->addSeparator ();

  m_sort_tabs_action
    = add_action (view_menu, tr ("&Sort Tabs Alphabetically"),
                  SLOT (sort_tabs_alph ()),
                  m_tab_widget->get_tab_bar ());

  m_menu_bar->addMenu (view_menu);

  // debug menu

  m_debug_menu = add_menu (m_menu_bar, tr ("&Debug"));

  m_toggle_breakpoint_action
    = add_action (m_debug_menu, settings.icon ("bp-toggle"),
                  tr ("Toggle &Breakpoint"),
                  SLOT (request_toggle_breakpoint (bool)));

  m_next_breakpoint_action
    = add_action (m_debug_menu, settings.icon ("bp-next"),
                  tr ("&Next Breakpoint"),
                  SLOT (request_next_breakpoint (bool)));

  m_previous_breakpoint_action
    = add_action (m_debug_menu, settings.icon ("bp-prev"),
                  tr ("Pre&vious Breakpoint"),
                  SLOT (request_previous_breakpoint (bool)));

  m_remove_all_breakpoints_action
    = add_action (m_debug_menu, settings.icon ("bp-rm-all"),
                  tr ("&Remove All Breakpoints"),
                  SLOT (request_remove_breakpoint (bool)));

  m_debug_menu->addSeparator ();

  // The other debug actions will be added by the main window.

  // run menu

  QMenu *_run_menu = add_menu (m_menu_bar, tr ("&Run"));

  m_run_action
    = add_action (_run_menu,
                  settings.icon ("system-run"),
                  tr ("Save File and Run/Continue"),
                  SLOT (request_run_file (bool)));

  m_run_selection_action
    = add_action (_run_menu,
                  tr ("Run &Selection"),
                  SLOT (request_context_run (bool)));
  m_run_selection_action->setEnabled (false);

  // help menu

  QMenu *_help_menu = add_menu (m_menu_bar, tr ("&Help"));

  m_context_help_action
    = add_action (_help_menu,
                  tr ("&Help on Keyword"),
                  SLOT (request_context_help (bool)));

  m_context_doc_action
    = add_action (_help_menu,
                  tr ("&Documentation on Keyword"),
                  SLOT (request_context_doc (bool)));

  // tab navigation (no menu, only actions; slots in tab_bar)

  m_switch_left_tab_action
    = add_action (nullptr, "", SLOT (switch_left_tab ()),
                  m_tab_widget->get_tab_bar ());

  m_switch_right_tab_action
    = add_action (nullptr, "", SLOT (switch_right_tab ()),
                  m_tab_widget->get_tab_bar ());

  m_move_tab_left_action
    = add_action (nullptr, "", SLOT (move_tab_left ()),
                  m_tab_widget->get_tab_bar ());

  m_move_tab_right_action
    = add_action (nullptr, "", SLOT (move_tab_right ()),
                  m_tab_widget->get_tab_bar ());

  // toolbar

  // popdown menu with mru files
  QToolButton *popdown_button = new QToolButton ();
  popdown_button->setToolTip (tr ("Recent Files"));
  popdown_button->setMenu (m_mru_file_menu);
  popdown_button->setPopupMode (QToolButton::InstantPopup);
  popdown_button->setArrowType (Qt::DownArrow);
  popdown_button->setToolButtonStyle (Qt::ToolButtonTextOnly);

  // new and open actions are inserted later from main window
  m_popdown_mru_action = m_tool_bar->addWidget (popdown_button);
  m_tool_bar->addAction (m_save_action);
  m_tool_bar->addAction (m_save_as_action);
  m_tool_bar->addAction (m_print_action);
  m_tool_bar->addSeparator ();
  // m_undo_action: later via main window
  m_tool_bar->addAction (m_redo_action);
  m_tool_bar->addSeparator ();
  m_tool_bar->addAction (m_cut_action);
  // m_copy_action: later via the main window
  // m_paste_action: later via the main window
  m_tool_bar->addAction (m_find_action);
  //m_tool_bar->addAction (m_find_next_action);
  //m_tool_bar->addAction (m_find_previous_action);
  m_tool_bar->addSeparator ();
  m_tool_bar->addAction (m_run_action);
  m_tool_bar->addSeparator ();
  m_tool_bar->addAction (m_toggle_breakpoint_action);
  m_tool_bar->addAction (m_previous_breakpoint_action);
  m_tool_bar->addAction (m_next_breakpoint_action);
  m_tool_bar->addAction (m_remove_all_breakpoints_action);

  // layout
  QVBoxLayout *vbox_layout = new QVBoxLayout ();
  vbox_layout->addWidget (m_menu_bar);
  vbox_layout->addWidget (m_tool_bar);
  vbox_layout->addWidget (m_tab_widget);
  vbox_layout->setContentsMargins (0, 0, 0, 0);
  vbox_layout->setSpacing (0);
  editor_widget->setLayout (vbox_layout);
  setWidget (editor_widget);

  // Create the basic context menu of the tab bar with editor actions.
  // Actions for selecting an tab are added when the menu is activated.
  tab_bar *bar = m_tab_widget->get_tab_bar ();
  QMenu *ctx_men = bar->get_context_menu ();
  ctx_men->addSeparator ();
  ctx_men->addAction (m_close_action);
  ctx_men->addAction (m_close_all_action);
  ctx_men->addAction (m_close_others_action);
  ctx_men->addSeparator ();
  ctx_men->addAction (m_sort_tabs_action);
  add_action (ctx_men, tr ("Copy Full File &Path"),
              SLOT (copy_full_file_path (bool)), this);

  // signals
  connect (m_mru_file_menu, &QMenu::triggered,
           this, &file_editor::request_mru_open_file);

  mru_menu_update ();

  connect (m_tab_widget, &file_editor_tab_widget::tabCloseRequested,
           this, &file_editor::handle_tab_close_request);

  connect (m_tab_widget, &file_editor_tab_widget::currentChanged,
           this, &file_editor::active_tab_changed);

  resize (500, 400);
  set_title (tr ("Editor"));

  check_actions ();
}

// Slot when autocompletion list was cancelled
void
file_editor::handle_autoc_cancelled ()
{
  // List was cancelled but somehow still active and blocking the
  // edit area from accepting shortcuts. Only after another keypress
  // shortcuts and lists are working againnas expected. This is
  // probably caused by qt bug https://bugreports.qt.io/browse/QTBUG-83720
  // Hack: Accept the list, which is hidden but still active
  //       and undo the text insertion, if any

  file_editor_tab *f = reset_focus ();
  octave_qscintilla *qsci = f->qsci_edit_area ();

  int line, col;
  qsci->getCursorPosition (&line, &col);
  int l1 = qsci->lineLength (line); // Current line length

  // Accept autocompletion
  qsci->SendScintilla (QsciScintillaBase::SCI_AUTOCCOMPLETE);

  // Was text inserted? If yes, undo
  if (qsci->text (line).length () - l1)
    qsci->undo ();
}

file_editor_tab *
file_editor::reset_focus ()
{
  // Reset the focus of the tab and the related edit area
  file_editor_tab *f
    = static_cast<file_editor_tab *> (m_tab_widget->currentWidget ());
  emit fetab_set_focus (f);
  return f;
}

file_editor_tab *
file_editor::make_file_editor_tab (const QString& directory)
{
  file_editor_tab *f = new file_editor_tab (directory);

  // signals from the qscintilla edit area
  connect (f->qsci_edit_area (), &octave_qscintilla::show_symbol_tooltip_signal,
           this, &file_editor::show_symbol_tooltip_signal);

  connect (f->qsci_edit_area (), &octave_qscintilla::status_update,
           this, &file_editor::edit_status_update);

  connect (f->qsci_edit_area (), &octave_qscintilla::create_context_menu_signal,
           this, &file_editor::create_context_menu);

  connect (f->qsci_edit_area (),
           SIGNAL (SCN_AUTOCCOMPLETED (const char *, int, int, int)),
           this, SLOT (reset_focus ()));

  connect (f->qsci_edit_area (), SIGNAL (SCN_AUTOCCANCELLED ()),
           this, SLOT (handle_autoc_cancelled ()));

  // signals from the qscintilla edit area
  connect (this, &file_editor::enter_debug_mode_signal,
           f->qsci_edit_area (), &octave_qscintilla::handle_enter_debug_mode);

  connect (this, &file_editor::exit_debug_mode_signal,
           f->qsci_edit_area (), &octave_qscintilla::handle_exit_debug_mode);

  // Signals from the file editor_tab
  connect (f, &file_editor_tab::autoc_closed,
           this, &file_editor::reset_focus);

  connect (f, &file_editor_tab::file_name_changed,
           this, &file_editor::handle_file_name_changed);

  connect (f, &file_editor_tab::editor_state_changed,
           this, &file_editor::handle_editor_state_changed);

  connect (f, &file_editor_tab::tab_remove_request,
           this, &file_editor::handle_tab_remove_request);

  connect (f, &file_editor_tab::editor_check_conflict_save,
           this, &file_editor::check_conflict_save);

  connect (f, &file_editor_tab::mru_add_file,
           this, &file_editor::handle_mru_add_file);

  connect (f, &file_editor_tab::request_open_file,
           this, [this] (const QString& fname, const QString& encoding) { request_open_file (fname, encoding); });

  connect (f, &file_editor_tab::edit_area_changed,
           this, &file_editor::edit_area_changed);

  connect (f, &file_editor_tab::set_focus_editor_signal,
           this, &file_editor::set_focus);

  // Signals from the file_editor or main-win non-trivial operations
  connect (this, &file_editor::fetab_settings_changed,
           f, [f] () { f->notice_settings (); });

  connect (this, &file_editor::fetab_change_request,
           f, &file_editor_tab::change_editor_state);

  connect (this, QOverload<const QWidget *, const QString&, bool>::of (&file_editor::fetab_save_file),
           f, QOverload<const QWidget *, const QString&, bool>::of (&file_editor_tab::save_file));

  // Signals from the file_editor trivial operations
  connect (this, &file_editor::fetab_recover_from_exit,
           f, &file_editor_tab::recover_from_exit);

  connect (this, &file_editor::fetab_set_directory,
           f, &file_editor_tab::set_current_directory);

  connect (this, &file_editor::fetab_zoom_in,
           f, &file_editor_tab::zoom_in);
  connect (this, &file_editor::fetab_zoom_out,
           f, &file_editor_tab::zoom_out);
  connect (this, &file_editor::fetab_zoom_normal,
           f, &file_editor_tab::zoom_normal);

  connect (this, &file_editor::fetab_context_help,
           f, &file_editor_tab::context_help);

  connect (this, &file_editor::fetab_context_edit,
           f, &file_editor_tab::context_edit);

  connect (this, QOverload<const QWidget *>::of (&file_editor::fetab_save_file),
           f, QOverload<const QWidget *>::of (&file_editor_tab::save_file));

  connect (this, &file_editor::fetab_save_file_as,
           f, QOverload<const QWidget *>::of (&file_editor_tab::save_file_as));

  connect (this, &file_editor::fetab_print_file,
           f, &file_editor_tab::print_file);

  connect (this, &file_editor::fetab_run_file,
           f, &file_editor_tab::run_file);

  connect (this, &file_editor::fetab_context_run,
           f, &file_editor_tab::context_run);

  connect (this, &file_editor::fetab_toggle_bookmark,
           f, &file_editor_tab::toggle_bookmark);

  connect (this, &file_editor::fetab_next_bookmark,
           f, &file_editor_tab::next_bookmark);

  connect (this, &file_editor::fetab_previous_bookmark,
           f, &file_editor_tab::previous_bookmark);

  connect (this, &file_editor::fetab_remove_bookmark,
           f, &file_editor_tab::remove_bookmark);

  connect (this, &file_editor::fetab_toggle_breakpoint,
           f, &file_editor_tab::toggle_breakpoint);

  connect (this, &file_editor::fetab_next_breakpoint,
           f, &file_editor_tab::next_breakpoint);

  connect (this, &file_editor::fetab_previous_breakpoint,
           f, &file_editor_tab::previous_breakpoint);

  connect (this, &file_editor::fetab_remove_all_breakpoints,
           f, &file_editor_tab::remove_all_breakpoints);

  connect (this, &file_editor::fetab_scintilla_command,
           f, &file_editor_tab::scintilla_command);

  connect (this, &file_editor::fetab_comment_selected_text,
           f, &file_editor_tab::comment_selected_text);

  connect (this, &file_editor::fetab_uncomment_selected_text,
           f, &file_editor_tab::uncomment_selected_text);

  connect (this, &file_editor::fetab_indent_selected_text,
           f, &file_editor_tab::indent_selected_text);

  connect (this, &file_editor::fetab_unindent_selected_text,
           f, &file_editor_tab::unindent_selected_text);

  connect (this, &file_editor::fetab_smart_indent_line_or_selected_text,
           f, &file_editor_tab::smart_indent_line_or_selected_text);

  connect (this, &file_editor::fetab_convert_eol,
           f, &file_editor_tab::convert_eol);

  connect (this, &file_editor::fetab_goto_line,
           f, &file_editor_tab::goto_line);

  connect (this, &file_editor::fetab_move_match_brace,
           f, &file_editor_tab::move_match_brace);

  connect (this, &file_editor::fetab_completion,
           f, &file_editor_tab::show_auto_completion);

  connect (this, &file_editor::fetab_set_focus,
           f, &file_editor_tab::set_focus);

  connect (this, &file_editor::fetab_insert_debugger_pointer,
           f, &file_editor_tab::insert_debugger_pointer);

  connect (this, &file_editor::fetab_delete_debugger_pointer,
           f, &file_editor_tab::delete_debugger_pointer);

  connect (this, &file_editor::fetab_do_breakpoint_marker,
           f, &file_editor_tab::do_breakpoint_marker);

  connect (this, &file_editor::update_gui_lexer_signal,
           f, &file_editor_tab::update_lexer_settings);

  // Convert other signals from the edit area and tab to editor signals.

  connect (f->qsci_edit_area (), &octave_qscintilla::execute_command_in_terminal_signal,
           this, &file_editor::execute_command_in_terminal_signal);

  connect (f->qsci_edit_area (), &octave_qscintilla::focus_console_after_command_signal,
           this, &file_editor::focus_console_after_command_signal);

  connect (f, &file_editor_tab::run_file_signal,
           this, &file_editor::run_file_signal);

  connect (f, &file_editor_tab::edit_mfile_request,
           this, &file_editor::edit_mfile_request);

  connect (f, &file_editor_tab::debug_quit_signal,
           this, &file_editor::debug_quit_signal);

  // Any interpreter_event signal from a file_editor_tab_widget is
  // handled the same as for the parent main_window object.

  connect (f, QOverload<const fcn_callback&>::of (&file_editor_tab::interpreter_event),
           this, QOverload<const fcn_callback&>::of (&file_editor::interpreter_event));

  connect (f, QOverload<const meth_callback&>::of (&file_editor_tab::interpreter_event),
           this, QOverload<const meth_callback&>::of (&file_editor::interpreter_event));

  return f;
}

void
file_editor::add_file_editor_tab (file_editor_tab *f, const QString& fn,
                                  int index)
{
  if (index == -1)
    m_tab_widget->addTab (f, fn);
  else
    m_tab_widget->insertTab (index, f, fn);

  m_tab_widget->setCurrentWidget (f);

  check_actions ();
}

void
file_editor::mru_menu_update ()
{
  int num_files = qMin (m_mru_files.size (), int (MaxMRUFiles));

  // configure and show active actions of mru-menu
  for (int i = 0; i < num_files; ++i)
    {
      QString text = QString ("&%1 %2").
        arg ((i+1) % int (MaxMRUFiles)).arg (m_mru_files.at (i));
      m_mru_file_actions[i]->setText (text);

      QStringList action_data;
      action_data << m_mru_files.at (i) << m_mru_files_encodings.at (i);
      m_mru_file_actions[i]->setData (action_data);

      m_mru_file_actions[i]->setVisible (true);
    }

  // hide unused mru-menu entries
  for (int j = num_files; j < MaxMRUFiles; ++j)
    m_mru_file_actions[j]->setVisible (false);

  // delete entries in string-list beyond MaxMRUFiles
  while (m_mru_files.size () > MaxMRUFiles)
    {
      m_mru_files.removeLast ();
      m_mru_files_encodings.removeLast ();
    }

  // save actual mru-list in settings

  gui_settings settings;

  settings.setValue (ed_mru_file_list.settings_key (),  m_mru_files);
  settings.setValue (ed_mru_file_encodings.settings_key (),  m_mru_files_encodings);

  settings.sync ();
}

bool
file_editor::call_custom_editor (const QString& file_name, int line)
{
  // Check if the user wants to use a custom file editor.

  gui_settings settings;

  if (settings.value (global_use_custom_editor.settings_key (),
                      global_use_custom_editor.def ()).toBool ())
    {
      // use the external editor interface for handling the call
      emit request_open_file_external (file_name, line);

      if (line < 0 && ! file_name.isEmpty ())
        handle_mru_add_file (QFileInfo (file_name).canonicalFilePath (),
                             QString ());

      return true;
    }

  return false;
}

void
file_editor::toggle_preference (const gui_pref& preference)
{
  gui_settings settings;

  bool old = settings.bool_value (preference);
  settings.setValue (preference.settings_key (), ! old);
  notice_settings ();
}

// Function for closing the files in a removed directory
void
file_editor::handle_dir_remove (const QString& old_name,
                                const QString& new_name)
{
  QDir old_dir (old_name);
  removed_file_data f_data;

  std::list<file_editor_tab *> editor_tab_lst = m_tab_widget->tab_list ();

  for (auto editor_tab : editor_tab_lst)
    {
      QString file_name = editor_tab->file_name ();

      if (file_name.isEmpty ())
        continue;   // Nothing to do, no valid file name

      // Get abs. file path and its path relative to the removed directory
      QString rel_path_to_file = old_dir.relativeFilePath (file_name);
      QString abs_path_to_file = old_dir.absoluteFilePath (file_name);

      // Test whether the file is located within the directory that will
      // be removed.  For this, two conditions must be met:
      // 1. The path of the file rel. to the dir is not equal to the
      //    its absolute one.
      //    If both are equal, then there is no relative path and removed
      //    directory and file are on different drives (e.g. on windows)
      // 2. The (real) relative path does not start with "../", i.e.,
      //    the file can be reached from the directory by descending only
      if ((rel_path_to_file != abs_path_to_file)
          && (rel_path_to_file.left (3) != QString ("../")))
        {
          // The currently considered file is included in the
          // removed/renamed diectory: remeber it
          if (editor_tab)
            {
              editor_tab->enable_file_watcher (false);
              f_data.editor_tab = editor_tab;

              // Add the new file path and the encoding for later reloading
              // if new_name is given
              if (! new_name.isEmpty ())
                {
                  QDir new_dir (new_name);
                  QString append_to_new_dir;
                  if (new_dir.exists ())
                    {
                      // The new directory already exists (movefile was used).
                      // This means, we have to add the name (not the path)
                      // of the old dir and the relative path to the file
                      // to new dir.
                      append_to_new_dir
                        = old_dir.dirName () + "/" + rel_path_to_file;
                    }
                  else
                    append_to_new_dir = rel_path_to_file;

                  f_data.new_file_name
                    = new_dir.absoluteFilePath (append_to_new_dir);
                }
              else
                f_data.new_file_name = ""; // no new name, just removing this file

              // Store data in list for later reloading
              m_tmp_closed_files << f_data;
            }
        }
    }
}

bool
file_editor::editor_tab_has_focus ()
{
  QWidget *foc_w = focusWidget ();
  if (foc_w && foc_w->inherits ("octave::octave_qscintilla"))
    return true;
  return false;
}

// Check whether this file is already open in the editor.
file_editor_tab *
file_editor::find_tab_widget (const QString& file)
{
  std::string std_file = file.toStdString ();

  std::list<file_editor_tab *> fe_tab_lst = m_tab_widget->tab_list ();

  for (auto fe_tab : fe_tab_lst)
    {
      QString tab_file = fe_tab->file_name ();

      // We check file == tab_file because
      //
      //   same_file ("", "")
      //
      // is false

      if (sys::same_file (std_file, tab_file.toStdString ())
          || file == tab_file)
        return fe_tab;
    }

  return nullptr;
}

QAction *
file_editor::add_action (QMenu *menu, const QString& text,
                         const char *member,
                         QWidget *receiver)
{
  return add_action (menu, QIcon (), text, member, receiver);
}

QAction *
file_editor::add_action (QMenu *menu, const QIcon& icon,
                         const QString& text, const char *member,
                         QWidget *receiver)
{
  QAction *a;
  QWidget *r = this;

  if (receiver != nullptr)
    r = receiver;

  if (menu)
    a = menu->addAction (icon, text, r, member);
  else
    {
      a = new QAction (this);
      connect (a, SIGNAL (triggered ()), r, member);
    }

  addAction (a);  // important for shortcut context
  a->setShortcutContext (Qt::WidgetWithChildrenShortcut);

  return a;
}

QMenu *
file_editor::add_menu (QMenuBar *p, QString name)
{
  QMenu *menu = p->addMenu (name);

  QString base_name = name;  // get a copy
  // replace intended '&' ("&&") by a temp. string
  base_name.replace ("&&", "___octave_amp_replacement___");
  // remove single '&' (shortcut)
  base_name.remove ("&");
  // restore intended '&'
  base_name.replace ("___octave_amp_replacement___", "&&");

  // remember names with and without shortcut
  m_hash_menu_text[menu] = QStringList () << name << base_name;

  return menu;
}

OCTAVE_END_NAMESPACE(octave)

#endif