changeset 27298:1805f8586179

new gui dialog for modifying octaves load path (bug #43549) Originally submitted by JunWang, Edited by Rik, jwe, and Torsten. * main-window.cc (main_window): initialize new m_settings_dlg class variable; (~main_window): delete set path dialog; (handle_set_path_dialog_request): new slot for opening the path dialog; (set_global_shortcuts): set shortcut for opening new path dialog; (construct_edit_menu): add and connect related action to the edit menu; (configure_shortcuts): read the shortcut from the preferences file * main-window.h: include path dialog header, new path variable and new action for dialog * module.mk: add new files * set-path-dialog.cc: new file (set_path_dialog): constructing the dialog with widgets and buttons, connecting all action signals, adding model object; (add_dir): open file dailog to select directory to be added to the path; (rm_dir): call rm_dir method in model object for removing selected dirs; (move_dir_up, move_dir_bottom, move_dir_top, move_dir_bottom): call related model object for moving selected dirs and update selection and view accordingly; * set-path-dialog.h: new header for path dialog * set-path-model.cc: new file for path dialog model (set_path_model): data signal connections and calling construct method; (to_string): make a string from the current list of directories; (model_to_path): set the octave path to the current model data; (clear): clear the current model data; (save): save current model data using savepath; (revert) reset the model data to the state from dialog start; (revert_last): Reset the model data to the state before the latest change; (add_dir): add a directory to the current model data and path; (rm_dir): remoce a directory from current model data and path; (move_dir_up, move_dir_down, move_dir_top, move_dir_bottom): move one ore more directory entries wihtin the path; (rowCount): retiurn the size if the current model data; (data): reimplemeneted method for returning data from the model; (construct): loading the current path into the model; (update_data): slot for update data signal * set-path-model.h: model header file * shortcut-manager.cc (do_init_data): initialize shortcut for path dialog with empty key sequence
author JunWang <jstzwj@aliyun.com>
date Mon, 22 Jul 2019 23:51:01 -0400
parents 6ac42f6615bb
children 6ec7b2e73b5b
files libgui/src/main-window.cc libgui/src/main-window.h libgui/src/module.mk libgui/src/set-path-dialog.cc libgui/src/set-path-dialog.h libgui/src/set-path-model.cc libgui/src/set-path-model.h libgui/src/shortcut-manager.cc
diffstat 8 files changed, 788 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/libgui/src/main-window.cc	Sun Jul 28 21:51:26 2019 -0700
+++ b/libgui/src/main-window.cc	Mon Jul 22 23:51:01 2019 -0400
@@ -103,8 +103,8 @@
       m_doc_browser_window (nullptr), m_editor_window (nullptr),
       m_workspace_window (nullptr), m_variable_editor_window (nullptr),
       m_external_editor (new external_editor_interface (this)),
-      m_active_editor (m_external_editor),
-      m_settings_dlg (nullptr), m_find_files_dlg (nullptr),
+      m_active_editor (m_external_editor), m_settings_dlg (nullptr),
+      m_find_files_dlg (nullptr), m_set_path_dlg (nullptr),
       m_release_notes_window (nullptr), m_community_news_window (nullptr),
       m_clipboard (QApplication::clipboard ()),
       m_prevent_readline_conflicts (true), m_suppress_dbg_location (true),
@@ -236,7 +236,7 @@
 
     delete m_find_files_dlg;
     delete m_release_notes_window;
-    delete m_settings_dlg;
+    delete m_community_news_window;
     delete m_community_news_window;
   }
 
@@ -1707,6 +1707,18 @@
     focus_command_window ();  // make sure that the command window has focus
   }
 
+  void main_window::handle_set_path_dialog_request (void)
+  {
+    if (m_set_path_dlg)  // m_set_path_dlg is a guarded pointer!
+      return;
+
+    m_set_path_dlg = new set_path_dialog (this);
+
+    m_set_path_dlg->setModal (false);
+    m_set_path_dlg->setAttribute (Qt::WA_DeleteOnClose);
+    m_set_path_dlg->show ();
+  }
+
   void main_window::find_files (const QString& start_dir)
   {
 
@@ -1764,6 +1776,7 @@
         m_load_workspace_action->setShortcut (no_key);
         m_save_workspace_action->setShortcut (no_key);
         m_preferences_action->setShortcut (no_key);
+        m_set_path_action->setShortcut (no_key);
         m_exit_action->setShortcut (no_key);
 
         // edit menu
@@ -2407,6 +2420,9 @@
 
     edit_menu->addSeparator ();
 
+    m_set_path_action
+      = edit_menu->addAction (tr ("Set Path"));
+
     m_preferences_action
       = edit_menu->addAction (resource_manager::icon ("preferences-system"),
                               tr ("Preferences..."));
@@ -2436,6 +2452,10 @@
 
     connect (m_preferences_action, SIGNAL (triggered (void)),
              this, SLOT (process_settings_dialog_request (void)));
+
+    connect (m_set_path_action, SIGNAL (triggered (void)),
+             this, SLOT (handle_set_path_dialog_request (void)));
+
   }
 
   QAction * main_window::construct_debug_menu_item (const char *icon,
@@ -2769,7 +2789,6 @@
                                     "main_file:load_workspace");
     shortcut_manager::set_shortcut (m_save_workspace_action,
                                     "main_file:save_workspace");
-    shortcut_manager::set_shortcut (m_preferences_action, "main_file:preferences");
     shortcut_manager::set_shortcut (m_exit_action,"main_file:exit");
 
     // edit menu
@@ -2786,6 +2805,8 @@
                                     "main_edit:clear_command_window");
     shortcut_manager::set_shortcut (m_clear_workspace_action,
                                     "main_edit:clear_workspace");
+    shortcut_manager::set_shortcut (m_set_path_action, "main_edit:set_path");
+    shortcut_manager::set_shortcut (m_preferences_action, "main_edit:preferences");
 
     // debug menu
     shortcut_manager::set_shortcut (m_debug_step_over, "main_debug:step_over");
--- a/libgui/src/main-window.h	Sun Jul 28 21:51:26 2019 -0700
+++ b/libgui/src/main-window.h	Mon Jul 22 23:51:01 2019 -0400
@@ -49,6 +49,7 @@
 #include "documentation-dock-widget.h"
 #include "files-dock-widget.h"
 #include "find-files-dialog.h"
+#include "set-path-dialog.h"
 #include "history-dock-widget.h"
 #include "octave-dock-widget.h"
 #include "qt-interpreter-events.h"
@@ -198,6 +199,8 @@
 
     void handle_octave_ready ();
 
+    void handle_set_path_dialog_request (void);
+
     //! Find files dialog.
     //!@{
     void find_files (const QString& startdir = QDir::currentPath ());
@@ -330,6 +333,7 @@
     QAction *m_new_figure_action;
     QAction *m_load_workspace_action;
     QAction *m_save_workspace_action;
+    QAction *m_set_path_action;
     QAction *m_preferences_action;
     QAction *m_exit_action;
 
@@ -386,6 +390,9 @@
 
     find_files_dialog *m_find_files_dlg;
 
+    //! Set path dialog
+    QPointer<set_path_dialog> m_set_path_dlg;
+
     //! Release notes window.
 
     QWidget *m_release_notes_window;
--- a/libgui/src/module.mk	Sun Jul 28 21:51:26 2019 -0700
+++ b/libgui/src/module.mk	Mon Jul 22 23:51:01 2019 -0400
@@ -156,7 +156,9 @@
   %reldir%/moc-variable-editor-model.cc \
   %reldir%/moc-find-files-dialog.cc \
   %reldir%/moc-find-files-model.cc \
-  %reldir%/moc-octave-dock-widget.cc
+  %reldir%/moc-octave-dock-widget.cc \
+  %reldir%/moc-set-path-dialog.cc \
+  %reldir%/moc-set-path-model.cc
 
 octave_gui_MOC += \
   $(OCTAVE_GUI_SRC_MOC) \
@@ -214,7 +216,10 @@
   %reldir%/workspace-model.h \
   %reldir%/workspace-view.h \
   %reldir%/variable-editor.h \
-  %reldir%/variable-editor-model.h
+  %reldir%/variable-editor-model.h \
+  %reldir%/set-path-dialog.h \
+  %reldir%/set-path-model.h
+
 
 %canon_reldir%_%canon_reldir%_la_SOURCES = \
   %reldir%/dialog.cc \
@@ -249,7 +254,9 @@
   %reldir%/workspace-model.cc \
   %reldir%/workspace-view.cc \
   %reldir%/variable-editor.cc \
-  %reldir%/variable-editor-model.cc
+  %reldir%/variable-editor-model.cc \
+  %reldir%/set-path-dialog.cc \
+  %reldir%/set-path-model.cc
 
 nodist_%canon_reldir%_%canon_reldir%_la_SOURCES = \
   $(octave_gui_MOC) \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/set-path-dialog.cc	Mon Jul 22 23:51:01 2019 -0400
@@ -0,0 +1,240 @@
+/*
+
+Copyright (C) 2019 JunWang
+
+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
+
+#include <QPushButton>
+#include <QDialogButtonBox>
+#include <QGridLayout>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QLineEdit>
+#include <QComboBox>
+#include <QCheckBox>
+#include <QHeaderView>
+#include <QListView>
+#include <QFileDialog>
+#include <QStatusBar>
+#include <QIcon>
+#include <QFileInfo>
+#include <QTimer>
+#include <QDirIterator>
+#include <QTextStream>
+#include <QGroupBox>
+#include <QFileDialog>
+
+#include "set-path-dialog.h"
+#include "set-path-model.h"
+#include "resource-manager.h"
+
+namespace octave
+{
+  set_path_dialog::set_path_dialog (QWidget *parent)
+    : QDialog (parent)
+  {
+    setWindowTitle (tr ("Set Path"));
+
+    m_info_label = new QLabel (tr ("All changes take effect immediately."));
+
+    m_add_folder_button = new QPushButton (tr ("Add Folder..."));
+    m_move_to_top_button = new QPushButton (tr ("Move to Top"));
+    m_move_to_bottom_button = new QPushButton (tr ("Move to Bottom"));
+    m_move_up_button = new QPushButton (tr ("Move Up"));
+    m_move_down_button = new QPushButton (tr ("Move Down"));
+    m_remove_button = new QPushButton (tr ("Remove"));
+
+    m_save_button = new QPushButton (tr ("Save"));
+    m_revert_button = new QPushButton (tr ("Revert"));
+    m_revert_last_button = new QPushButton (tr ("Revert Last"));
+
+    m_save_button->setFocus ();
+
+    connect (m_add_folder_button, SIGNAL (clicked (void)),
+             this, SLOT (add_dir (void)));
+
+    connect (m_remove_button, SIGNAL (clicked (void)),
+             this, SLOT (rm_dir (void)));
+
+    connect (m_move_to_top_button, SIGNAL (clicked (void)),
+             this, SLOT (move_dir_top (void)));
+
+    connect (m_move_to_bottom_button, SIGNAL (clicked (void)),
+             this, SLOT (move_dir_bottom (void)));
+
+    connect (m_move_up_button, SIGNAL (clicked (void)),
+             this, SLOT (move_dir_up (void)));
+
+    connect (m_move_down_button, SIGNAL (clicked (void)),
+             this, SLOT (move_dir_down (void)));
+
+    set_path_model *model = new set_path_model (this);
+
+    connect (m_save_button, SIGNAL (clicked (void)),
+             model, SLOT (save (void)));
+
+    connect (m_revert_button, SIGNAL (clicked (void)),
+             model, SLOT (revert (void)));
+
+    connect (m_revert_last_button, SIGNAL (clicked (void)),
+             model, SLOT (revert_last (void)));
+
+    m_path_list = new QListView (this);
+    m_path_list->setWordWrap (false);
+    m_path_list->setModel (model);
+    m_path_list->setSelectionBehavior (QAbstractItemView::SelectRows);
+    m_path_list->setSelectionMode (QAbstractItemView::ExtendedSelection);
+    m_path_list->setAlternatingRowColors (true);
+
+    // layout everything
+    QDialogButtonBox *button_box = new QDialogButtonBox (Qt::Horizontal);
+    button_box->addButton (m_save_button, QDialogButtonBox::ActionRole);
+
+    // add dialog close button
+    m_close_button = button_box->addButton (QDialogButtonBox::Close);
+    connect (button_box, SIGNAL (rejected (void)), this, SLOT (close (void)));
+
+    button_box->addButton (m_revert_last_button, QDialogButtonBox::ActionRole);
+    button_box->addButton (m_revert_button, QDialogButtonBox::ActionRole);
+
+    // path edit options
+    QDialogButtonBox *path_edit_layout = new QDialogButtonBox (Qt::Vertical);
+    path_edit_layout->addButton (m_add_folder_button, QDialogButtonBox::ActionRole);
+    path_edit_layout->addButton (m_move_to_top_button, QDialogButtonBox::ActionRole);
+    path_edit_layout->addButton (m_move_up_button, QDialogButtonBox::ActionRole);
+    path_edit_layout->addButton (m_move_down_button, QDialogButtonBox::ActionRole);
+    path_edit_layout->addButton (m_move_to_bottom_button, QDialogButtonBox::ActionRole);
+    path_edit_layout->addButton (m_remove_button, QDialogButtonBox::ActionRole);
+
+    // main layout
+    QHBoxLayout *main_hboxlayout = new QHBoxLayout;
+    main_hboxlayout->addWidget(path_edit_layout);
+    main_hboxlayout->addWidget(m_path_list);
+
+    QGridLayout *main_layout = new QGridLayout;
+    main_layout->addWidget (m_info_label, 0, 0);
+    main_layout->addLayout (main_hboxlayout, 1, 0);
+    main_layout->addWidget (button_box,2, 0);
+
+    setLayout (main_layout);
+  }
+
+  set_path_dialog::~set_path_dialog (void)
+  {
+  }
+
+  void set_path_dialog::add_dir(void)
+  {
+    QString dir
+      = QFileDialog::getExistingDirectory (this, tr ("Open Directory"),
+                                           "",
+                                           (QFileDialog::ShowDirsOnly
+                                            | QFileDialog::DontResolveSymlinks));
+    set_path_model *m = static_cast<set_path_model *> (m_path_list->model ());
+    m->add_dir (dir);
+  }
+
+  void set_path_dialog::rm_dir (void)
+  {
+    set_path_model *m = static_cast<set_path_model *> (m_path_list->model ());
+    QItemSelectionModel *selmodel = m_path_list->selectionModel ();
+    QModelIndexList indexlist = selmodel->selectedIndexes();
+    m->rm_dir (indexlist);
+
+    selmodel->clearSelection ();
+  }
+
+  void set_path_dialog::move_dir_up (void)
+  {
+    set_path_model *m = static_cast<set_path_model *> (m_path_list->model ());
+    QItemSelectionModel *selmodel = m_path_list->selectionModel ();
+    QModelIndexList indexlist = selmodel->selectedIndexes();
+    m->move_dir_up (indexlist);
+
+    // Update selection and view
+    selmodel->clearSelection ();
+    int min_row = m->rowCount () - 1;
+    for (int i = 0; i < indexlist.length (); i++)
+      {
+        int new_row = std::max (indexlist.at (i).row () - 1, 0);
+        min_row = std::min (min_row, new_row);
+        selmodel->select (m->index (new_row), QItemSelectionModel::Select);
+      }
+
+    m_path_list->scrollTo (m->index (min_row));
+  }
+
+  void set_path_dialog::move_dir_down (void)
+  {
+    set_path_model *m = static_cast<set_path_model *> (m_path_list->model ());
+    QItemSelectionModel *selmodel = m_path_list->selectionModel ();
+    QModelIndexList indexlist = selmodel->selectedIndexes();
+    m->move_dir_down (indexlist);
+
+    // Update selection and view
+    selmodel->clearSelection ();
+    int max_row = 0;
+    for (int i = 0; i < indexlist.length (); i++)
+      {
+        int new_row = std::min (indexlist.at (i).row () + 1, m->rowCount () - 1);
+        max_row = std::max (max_row, new_row);
+        selmodel->select (m->index (new_row), QItemSelectionModel::Select);
+      }
+
+    m_path_list->scrollTo (m->index (max_row));
+  }
+
+  void set_path_dialog::move_dir_top (void)
+  {
+    set_path_model *m = static_cast<set_path_model *> (m_path_list->model ());
+    QItemSelectionModel *selmodel = m_path_list->selectionModel ();
+    QModelIndexList indexlist = selmodel->selectedIndexes();
+    m->move_dir_top (indexlist);
+
+    // Update selection and view
+    selmodel->clearSelection ();
+    for (int i = 0; i < indexlist.length (); i++)
+      selmodel->select (m->index (i), QItemSelectionModel::Select);
+
+    m_path_list->scrollTo (m->index (0));
+  }
+
+  void set_path_dialog::move_dir_bottom (void)
+  {
+    set_path_model *m = static_cast<set_path_model *> (m_path_list->model ());
+    QItemSelectionModel *selmodel = m_path_list->selectionModel ();
+    QModelIndexList indexlist = selmodel->selectedIndexes();
+    m->move_dir_bottom (indexlist);
+
+    // Update selection and view
+    selmodel->clearSelection ();
+    int row_count = m->rowCount ();
+    for (int i = 0; i < indexlist.length (); i++)
+      selmodel->select (m->index (row_count - 1 - i),
+                        QItemSelectionModel::Select);
+
+    m_path_list->scrollTo (m->index (row_count - 1));
+  }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/set-path-dialog.h	Mon Jul 22 23:51:01 2019 -0400
@@ -0,0 +1,80 @@
+/*
+
+Copyright (C) 2019 JunWang
+
+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 (octave_set_path_dialog_h)
+#define octave_set_path_dialog_h 1
+
+#include <QDialog>
+#include <QModelIndex>
+#include <QFileInfo>
+
+class QLabel;
+class QPushButton;
+class QListView;
+class QVBoxLayout;
+class QHBoxLayout;
+
+namespace octave
+{
+  class set_path_dialog : public QDialog
+  {
+    Q_OBJECT
+
+  public:
+
+    set_path_dialog (QWidget *parent = nullptr);
+
+    virtual ~set_path_dialog (void);
+
+  private slots:
+
+    void add_dir(void);
+
+    void rm_dir (void);
+
+    void move_dir_up (void);
+
+    void move_dir_down (void);
+
+    void move_dir_top (void);
+
+    void move_dir_bottom (void);
+
+  private:
+
+    QLabel *m_info_label;
+    QPushButton *m_save_button;
+    QPushButton *m_close_button;
+    QPushButton *m_revert_button;
+    QPushButton *m_revert_last_button;
+
+    QListView *m_path_list;
+
+    QPushButton *m_add_folder_button;
+    QPushButton *m_move_to_top_button;
+    QPushButton *m_move_to_bottom_button;
+    QPushButton *m_move_up_button;
+    QPushButton *m_move_down_button;
+    QPushButton *m_remove_button;
+  };
+}
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/set-path-model.cc	Mon Jul 22 23:51:01 2019 -0400
@@ -0,0 +1,329 @@
+/*
+
+Copyright (C) 2019 JunWang
+
+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
+
+#include <iostream>
+
+#include <string>
+#include <algorithm>
+
+#include <QFileIconProvider>
+#include <QtAlgorithms>
+#include <QMessageBox>
+
+#include "pathsearch.h"
+
+#include "event-manager.h"
+#include "interpreter-private.h"
+#include "interpreter.h"
+#include "load-path.h"
+
+#include "set-path-model.h"
+
+namespace octave
+{
+  set_path_model::set_path_model (QObject *p)
+    : QAbstractListModel (p)
+  {
+    connect (this, SIGNAL (update_data_signal (const QStringList&)),
+             this, SLOT (update_data (const QStringList&)));
+
+    construct ();
+  }
+
+  std::string set_path_model::to_string (void)
+  {
+    std::string path_sep = directory_path::path_sep_str ();
+
+    std::string path_str;
+
+    QStringList::iterator it = m_dirs.begin ();
+
+    while (it < m_dirs.end ())
+      {
+        if (it != m_dirs.begin ())
+          path_str += path_sep;
+        path_str += it->toStdString ();
+        ++it;
+      }
+
+    return path_str;
+  }
+
+  void set_path_model::model_to_path (void)
+  {
+    event_manager& evmgr
+      = __get_event_manager__ ("set_path_model::model_to_path");
+
+    std::string path_str = to_string ();
+
+    evmgr.post_event
+      ([path_str] (void)
+       {
+         // INTERPRETER THREAD
+
+         load_path& lp = __get_load_path__ ("set_path_model::model_to_path");
+
+         lp.set (path_str);
+
+       });
+  }
+
+  void set_path_model::clear (void)
+  {
+    beginResetModel ();
+
+    m_dirs.clear ();
+
+    endResetModel ();
+  }
+
+  void set_path_model::save (void)
+  {
+    model_to_path ();
+
+    event_manager& evmgr = __get_event_manager__ ("set_path_model::save");
+
+    evmgr.post_event
+      ([] (void)
+       {
+         // INTERPRETER THREAD
+
+         interpreter& interp = __get_interpreter__ ("set_path_model::save");
+
+         interp.feval ("savepath");
+       });
+  }
+
+  void set_path_model::revert (void)
+  {
+    clear ();
+
+    beginInsertRows (QModelIndex (), 0, m_orig_dirs.size () - 1);
+      m_dirs = m_orig_dirs;
+    endInsertRows ();
+
+    model_to_path ();
+  }
+
+  void set_path_model::revert_last (void)
+  {
+    clear ();
+
+    beginInsertRows (QModelIndex (), 0, m_last_dirs.size () - 1);
+      m_dirs = m_last_dirs;
+    endInsertRows ();
+
+    model_to_path ();
+  }
+
+  void set_path_model::add_dir (const QString& p)
+  {
+    m_last_dirs = m_dirs;
+
+    beginInsertRows (QModelIndex (), m_dirs.size (), m_dirs.size ());
+
+    QList<QString>::Iterator it = m_dirs.begin();
+
+    m_dirs.insert (it, p);
+
+    endInsertRows ();
+
+    model_to_path ();
+  }
+
+  void set_path_model::rm_dir (const QModelIndexList& indices)
+  {
+    m_last_dirs = m_dirs;
+
+    for (int i = indices.size () - 1; i >= 0; i--)
+      {
+        const QModelIndex& idx = indices.at (i);
+
+        beginRemoveRows (idx, idx.row (), idx.row ());
+          m_dirs.removeAt (idx.row ());
+        endRemoveRows ();
+      }
+
+    model_to_path ();
+  }
+
+  void set_path_model::move_dir_up (const QModelIndexList& indices)
+  {
+    m_last_dirs = m_dirs;
+
+    for (int i = 0; i < indices.size (); i++)
+      {
+        const QModelIndex& idx = indices.at (i);
+
+        if (idx.row () == 0 )
+          continue; //  already at top position
+
+        beginMoveRows (idx, idx.row (), idx.row (),
+                       this->index (idx.row () - 1), idx.row () - 1);
+
+          m_dirs.move (idx.row (), idx.row () - 1);
+
+        endMoveRows ();
+      }
+
+    model_to_path ();
+  }
+
+  void set_path_model::move_dir_down (const QModelIndexList& indices)
+  {
+    m_last_dirs = m_dirs;
+
+    for (int i = indices.size () - 1; i >= 0; i--)
+      {
+        const QModelIndex& idx = indices.at (i);
+        int bottom = m_dirs.size () - 1;
+
+        if (idx.row () >= bottom)
+          continue; //  already at bottom position
+
+        beginMoveRows (idx, idx.row (), idx.row (),
+                       this->index (idx.row () + 1), idx.row () + 1);
+
+          m_dirs.move (idx.row (), idx.row () + 1);
+
+        endMoveRows ();
+      }
+
+    model_to_path ();
+  }
+
+  void set_path_model::move_dir_top (const QModelIndexList& indices)
+  {
+    m_last_dirs = m_dirs;
+
+    for (int i = 0; i < indices.size (); i++)
+      {
+        const QModelIndex& idx = indices.at (i);
+
+        if (idx.row () == i)
+          continue; //  already at target position
+
+        beginMoveRows (idx, idx.row (), idx.row (), this->index (i), i);
+
+          m_dirs.move (idx.row (), i);
+
+        endMoveRows ();
+      }
+
+    model_to_path ();
+  }
+
+  void set_path_model::move_dir_bottom (const QModelIndexList& indices)
+  {
+    m_last_dirs = m_dirs;
+
+    for (int i = 0; i < indices.size (); i++)
+      {
+        const QModelIndex& idx = indices.at (i);
+        int target = m_dirs.size () - 1 - i;
+
+        if (idx.row () == target)
+          continue; //  already at target position
+
+        beginMoveRows (idx, idx.row (), idx.row (),
+                       this->index (target), target);
+
+          m_dirs.move (idx.row (), target);
+
+        endMoveRows ();
+      }
+
+    model_to_path ();
+  }
+
+  int set_path_model::rowCount (const QModelIndex&) const
+  {
+    return m_dirs.size ();
+  }
+
+  QVariant set_path_model::data (const QModelIndex& idx, int role) const
+  {
+    QVariant retval;
+    if (idx.isValid ())
+      {
+        switch (role)
+          {
+          case Qt::DisplayRole:
+            retval = QVariant (m_dirs[idx.row ()]);
+            break;
+
+          case Qt::DecorationRole:
+            retval = QVariant (QIcon ());
+            break;
+
+          case Qt::SizeHintRole:
+            retval = QVariant (QSize (10, 20));
+            break;
+          }
+      }
+
+    return retval;
+  }
+
+  void set_path_model::construct (void)
+  {
+    event_manager& evmgr = __get_event_manager__ ("set_path_model::construct");
+
+    evmgr.post_event
+      ([this] (void)
+       {
+         load_path& lp = __get_load_path__ ("set_path_model::construct");
+
+         std::list<std::string> dir_list = lp.dir_list ();
+
+         QStringList qs_dir_list;
+
+         for (const auto& dir : dir_list)
+           qs_dir_list << QString::fromStdString (dir);
+
+         emit update_data_signal (qs_dir_list);
+       });
+  }
+
+  void set_path_model::update_data (const QStringList& dirs)
+  {
+    m_dirs = dirs;
+
+    m_dirs.removeAll (".");
+
+    if (m_orig_dirs.isEmpty ())
+      {
+        // first time update
+        m_orig_dirs = m_dirs;
+        m_last_dirs = m_dirs;
+      }
+
+    int numel = m_dirs.size ();
+
+    emit dataChanged (QAbstractListModel::index (0, 0),
+                      QAbstractListModel::index (numel-1, 0));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/set-path-model.h	Mon Jul 22 23:51:01 2019 -0400
@@ -0,0 +1,96 @@
+/*
+
+Copyright (C) 2019 JunWang
+
+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 (octave_set_path_model_h)
+#define octave_set_path_model_h 1
+
+#include <QAbstractListModel>
+#include <QStringList>
+#include <QList>
+#include <QFileInfo>
+#include <QIcon>
+
+namespace octave
+{
+  class set_path_model : public QAbstractListModel
+  {
+    Q_OBJECT
+
+  public:
+
+    set_path_model (QObject *p = nullptr);
+
+    ~set_path_model (void) = default;
+
+    void clear (void);
+
+    void add_dir (const QString& p);
+
+    void rm_dir (const QModelIndexList& indices);
+
+    void move_dir_up (const QModelIndexList& indices);
+
+    void move_dir_down (const QModelIndexList& indices);
+
+    void move_dir_top (const QModelIndexList& indices);
+
+    void move_dir_bottom (const QModelIndexList& indices);
+
+    std::string to_string (void);
+
+    // Overloaded Qt methods
+
+    void model_to_path (void);
+
+    int rowCount (const QModelIndex& p = QModelIndex ()) const;
+
+    QVariant data (const QModelIndex& idx, int role) const;
+
+  public slots:
+
+    void save (void);
+
+    void revert (void);
+
+    void revert_last (void);
+
+  signals:
+
+    void update_data_signal (const QStringList& dirs);
+
+  private slots:
+
+    void update_data (const QStringList& dirs);
+
+  private:
+
+    void construct (void);
+
+    QStringList m_dirs;
+
+    QStringList m_orig_dirs;
+
+    QStringList m_last_dirs;
+  };
+}
+
+#endif
--- a/libgui/src/shortcut-manager.cc	Sun Jul 28 21:51:26 2019 -0700
+++ b/libgui/src/shortcut-manager.cc	Mon Jul 22 23:51:01 2019 -0400
@@ -262,6 +262,7 @@
     init (tr ("Clear Command History"), "main_edit:clear_history",
           QKeySequence ());
     init (tr ("Clear Workspace"), "main_edit:clear_workspace", QKeySequence ());
+    init (tr ("Set Path"), "main_edit:set_path", QKeySequence ());
     init (tr ("Preferences"), "main_edit:preferences", QKeySequence ());
 
     // debug