view libgui/src/m-editor/file-editor.cc @ 31649:deb553ac2c54

maint: Merge stable to default.
author John W. Eaton <jwe@octave.org>
date Tue, 06 Dec 2022 15:45:27 -0500
parents 431f80aba37a 29d734430e5f
children 1f6bdbb5531b
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2011-2022 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 "octave-qobject.h"
#include "octave-qtutils.h"
#include "shortcut-manager.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 (void) const
  {
    return qobject_cast<tab_bar *> (tabBar ());
  }

  std::list<file_editor_tab *>
  file_editor_tab_widget::tab_list (void) 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, base_qobject& oct_qobj)
    : file_editor_interface (p, oct_qobj)
  {
    // 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 = false;
    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 (void)
  {
    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 (void)
  {
    shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();
    scmgr.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 (void)
  {
    // 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.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 (void)
  {
    gui_settings settings;

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

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

    QStringList session_encodings
      = settings.value (ed_session_enc).toStringList ();

    QStringList session_index
      = settings.value (ed_session_ind).toStringList ();

    QStringList session_lines
      = settings.value (ed_session_lines).toStringList ();

    QStringList session_bookmarks
      = settings.value (ed_session_bookmarks).toStringList ();

    // 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 (void)
  {
    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 (void)
  {
    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.key, fetFileNames);
    settings.setValue (ed_session_enc.key, fet_encodings);
    settings.setValue (ed_session_ind.key, fet_index);
    settings.setValue (ed_session_lines.key, fet_lines);
    settings.setValue (ed_session_bookmarks.key, fet_bookmarks);

    settings.sync ();
  }

  bool file_editor::check_closing (void)
  {
    // 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 (void)
  {
    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)
  {
    emit interpreter_event
      ([=] (interpreter& interp)
       {
         // INTERPRETER THREAD

         // 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 (void)
  {
    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 (void)
  {
    gui_settings settings;

    int size_idx = settings.value (global_icon_size).toInt ();
    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.value (ed_tab_position).toInt ());
    bool rotated = settings.value (ed_tabs_rotated).toBool ();

    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.value (ed_tabs_max_width).toInt ();
    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.value (ed_show_line_numbers).toBool ();
    m_show_linenum_action->setChecked (show_it);
    show_it = settings.value (ed_show_white_space).toBool ();
    m_show_whitespace_action->setChecked (show_it);
    show_it = settings.value (ed_show_eol_chars).toBool ();
    m_show_eol_action->setChecked (show_it);
    show_it = settings.value (ed_show_indent_guides).toBool ();
    m_show_indguide_action->setChecked (show_it);
    show_it = settings.value (ed_long_line_marker).toBool ();
    m_show_longline_action->setChecked (show_it);

    show_it = settings.value (ed_show_toolbar).toBool ();
    m_show_toolbar_action->setChecked (show_it);
    m_tool_bar->setVisible (show_it);
    show_it = settings.value (ed_show_edit_status_bar).toBool ();
    m_show_statusbar_action->setChecked (show_it);
    show_it = settings.value (ed_show_hscroll_bar).toBool ();
    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 (void)
  {
    // Shortcuts also available in the main window, as well as the related
    // shortcuts, are defined in main_window and added to the editor

    shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager ();

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

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

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

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

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

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

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

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

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

    // Tab navigation without menu entries
    scmgr.set_shortcut (m_switch_left_tab_action, sc_edit_tabs_switch_left_tab);
    scmgr.set_shortcut (m_switch_right_tab_action, sc_edit_tabs_switch_right_tab);
    scmgr.set_shortcut (m_move_tab_left_action, sc_edit_tabs_move_tab_left);
    scmgr.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 ();
      }

    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 (void)
  {
    if (editor_tab_has_focus ())
      emit fetab_scintilla_command (m_tab_widget->currentWidget (),
                                    QsciScintillaBase::SCI_COPY);
  }

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

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

  void file_editor::do_undo (void)
  {
    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.value (global_use_custom_editor).toBool ())
      {
        // 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.value (ed_show_dbg_file).toBool ();

    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
              {
                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 read: %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.value (ed_create_new_file).toBool ())
                      {
                        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 write: %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.value (ed_hiding_closes_files).toBool ())
      {
        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 (void)
  {
    // 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 (void)
  {
    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.value (ed_mru_file_list).toStringList ();
    m_mru_files_encodings = settings.value (ed_mru_file_encodings)
                            .toStringList ();

    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 (void)));

    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 (void)),
                    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 (void)),
                    m_tab_widget->get_tab_bar ());

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

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

    m_move_tab_right_action
      = add_action (nullptr, "", SLOT (move_tab_right (void)),
                    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->setMargin (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 (void)
  {
    // 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 (void)
  {
    // 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 (m_octave_qobj, directory);

    // signals from the qscintilla edit area
    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 (void)));

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

    // 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, [=] (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->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 (void)
  {
    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.key,  m_mru_files);
    settings.setValue (ed_mru_file_encodings.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.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.value (preference).toBool ();
    settings.setValue (preference.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 (void)
  {
    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 (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