# HG changeset patch
# User Rüdiger Sonderfeld <ruediger@c-plusplus.de>
# Date 1392419569 -3600
#      Sat Feb 15 00:12:49 2014 +0100
# Branch gui-release
# Node ID 021ff2b6822313656e55c9612a6e96f3f437add6
# Parent  9fbea0c395ec88a9a66b6328ef1cd45ff0e3cf27
gui: Add Variable Editor.

* liboctave/util/action-container.h (method_arg2_elem)
(method_arg3_elem, method_arg4_elem): New classes.
(add_method): New variants for two, three, and four args.

* libinterp/corefcn/octave-link.h (post_event, do_post_event):
Don't force const ref and add three and four arg variant.
(openvar, do_openvar): New methods.

* libinterp/corefcn/octave-link.cc (openvar): New function.

* libgui/src/workspace-view.h (edit_variable_signal): New signal.
(handle_contextmenu_edit): New slot.

* libgui/src/workspace-view.cc (workspace_view): Connect
`edit_variable_signal'.
(contextmenu_requested): Add "Open in Varible Editor" action.
(handle_contextmenu_edit): New slot.

* libgui/src/variable-editor.h: New file.

* libgui/src/variable-editor.cc: New file.

* libgui/src/variable-editor-model.h: New file.

* libgui/src/variable-editor-model.cc: New file.

* libgui/src/octave-qt-link.h (octave_link): New method.
(open_variable): New signal.

* libgui/src/octave-qt-link.cc (do_openvar): New method.

* libgui/src/module.mk: Add new files.

* libgui/src/main-window.h (edit_variable, edit_variable_callback):
New slots.
(variable_editor_window): New member.
(dock_widget_list): Add `variable_editor_window'.

* libgui/src/main-window.cc (main_window, construct): Initialize
`variable_editor_window'.
(construct_octave_qt_link): Connect `open_variable' signal.
(construct_window_menu): Add keybindings for variable editor and
connect signals.
(edit_variable, edit_variable_callback): New slots.

diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/main-window.cc
--- a/libgui/src/main-window.cc	Thu Feb 13 12:05:47 2014 -0500
+++ b/libgui/src/main-window.cc	Sat Feb 15 00:12:49 2014 +0100
@@ -82,6 +82,7 @@
     doc_browser_window (new documentation_dock_widget (this)),
     editor_window (create_default_editor (this)),
     workspace_window (new workspace_view (this)),
+    variable_editor_window (new variable_editor (this)),
     find_files_dlg (0),
     release_notes_window (0),
     community_news_window (0),
@@ -1220,6 +1221,8 @@
   addDockWidget (Qt::LeftDockWidgetArea, workspace_window);
   addDockWidget (Qt::LeftDockWidgetArea, history_window);
 
+  variable_editor_window->setVisible (false);
+
   int win_x = QApplication::desktop ()->width ();
   int win_y = QApplication::desktop ()->height ();
 
@@ -1370,6 +1373,22 @@
            SIGNAL (show_doc_signal (const QString &)),
            this, SLOT (handle_show_doc (const QString &)));
 
+  connect (_octave_qt_link,
+           SIGNAL (open_variable (const QString&)),
+           this,
+           SLOT (edit_variable (const QString&)));
+
+  connect (_octave_qt_link,
+           SIGNAL (set_workspace_signal
+                   (bool, const QString&, const QStringList&,
+                    const QStringList&, const QStringList&,
+                    const QStringList&, const QIntList&)),
+           this,
+           SLOT (clear_variable_editor_cache
+                 (bool, const QString&, const QStringList&,
+                  const QStringList&, const QStringList&,
+                  const QStringList&, const QIntList&)));
+
   connect (_workspace_model,
            SIGNAL (rename_variable (const QString&, const QString&)),
            this,
@@ -1652,9 +1671,14 @@
                                 (window_menu, tr ("Show Editor"), true,
                                  ctrl_shift + Qt::Key_4);
 
+  QAction *show_variable_editor_action = construct_window_menu_item
+                                        (window_menu,
+                                         tr ("Show Variable Editor"), true,
+                                         ctrl_shift + Qt::Key_5);
+
   QAction *show_documentation_action = construct_window_menu_item
                                        (window_menu, tr ("Show Documentation"),
-                                        true, ctrl_shift + Qt::Key_5);
+                                        true, ctrl_shift + Qt::Key_6);
 
   window_menu->addSeparator ();
 
@@ -1678,9 +1702,14 @@
                            (window_menu, tr ("Editor"), false,
                             ctrl + Qt::Key_4);
 
+  QAction *variable_editor_action = construct_window_menu_item
+                                    (window_menu,
+                                     tr ("Variable Editor"), false,
+                                     ctrl + Qt::Key_5);
+
   QAction *documentation_action = construct_window_menu_item
                                   (window_menu, tr ("Documentation"), false,
-                                   ctrl + Qt::Key_5);
+                                   ctrl + Qt::Key_6);
 
   window_menu->addSeparator ();
 
@@ -1719,6 +1748,12 @@
            show_editor_action, SLOT (setChecked (bool)));
 #endif
 
+  connect (show_variable_editor_action, SIGNAL (toggled (bool)),
+           variable_editor_window, SLOT (setVisible (bool)));
+
+  connect (variable_editor_window, SIGNAL (active_changed (bool)),
+           show_variable_editor_action, SLOT (setChecked (bool)));
+
   connect (show_documentation_action, SIGNAL (toggled (bool)),
            doc_browser_window, SLOT (setVisible (bool)));
 
@@ -1742,6 +1777,9 @@
            editor_window, SLOT (focus ()));
 #endif
 
+  connect (variable_editor_action, SIGNAL (triggered ()),
+           variable_editor_window, SLOT (focus ()));
+
   connect (documentation_action, SIGNAL (triggered ()),
            doc_browser_window, SLOT (focus ()));
 
@@ -2312,3 +2350,29 @@
 {
   _clipboard->clear (QClipboard::Clipboard);
 }
+
+void
+main_window::edit_variable (const QString &expr)
+{
+  variable_editor_window->edit_variable (expr);
+
+  if (! variable_editor_window->isVisible ())
+    {
+      addDockWidget (Qt::RightDockWidgetArea, variable_editor_window);
+      tabifyDockWidget (command_window, variable_editor_window);
+      variable_editor_window->show ();
+    }
+
+  variable_editor_window->raise ();
+}
+
+void
+main_window::clear_variable_editor_cache (bool, const QString&,
+                                          const QStringList&,
+                                          const QStringList&,
+                                          const QStringList&,
+                                          const QStringList&,
+                                          const QIntList&)
+{
+  variable_editor_window->clear_data_cache ();
+}
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/main-window.h
--- a/libgui/src/main-window.h	Thu Feb 13 12:05:47 2014 -0500
+++ b/libgui/src/main-window.h	Sat Feb 15 00:12:49 2014 +0100
@@ -49,6 +49,7 @@
 #include "resource-manager.h"
 #include "workspace-model.h"
 #include "workspace-view.h"
+#include "variable-editor.h"
 #include "history-dock-widget.h"
 #include "files-dock-widget.h"
 #include "terminal-dock-widget.h"
@@ -198,6 +199,14 @@
   void clipboard_has_changed (QClipboard::Mode);
   void clear_clipboard ();
 
+  // open variable_editor
+  void
+  edit_variable (const QString &name);
+
+  void
+  clear_variable_editor_cache (bool, const QString&, const QStringList&,
+                               const QStringList&, const QStringList&,
+                               const QStringList&, const QIntList&);
 
 protected:
   void closeEvent (QCloseEvent * closeEvent);
@@ -278,6 +287,7 @@
   documentation_dock_widget *doc_browser_window;
   file_editor_interface *editor_window;
   workspace_view *workspace_window;
+  variable_editor *variable_editor_window;
   QList<octave_dock_widget *> dock_widget_list ()
   {
     QList<octave_dock_widget *> list = QList<octave_dock_widget *> ();
@@ -289,6 +299,8 @@
     list.append (static_cast<octave_dock_widget *> (editor_window));
 #endif
     list.append (static_cast<octave_dock_widget *> (workspace_window));
+    if (variable_editor_window)
+      list.append (static_cast<octave_dock_widget *> (variable_editor_window));
     return list;
   }
 
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/module.mk
--- a/libgui/src/module.mk	Thu Feb 13 12:05:47 2014 -0500
+++ b/libgui/src/module.mk	Sat Feb 15 00:12:49 2014 +0100
@@ -99,6 +99,8 @@
   src/moc-welcome-wizard.cc \
   src/moc-workspace-model.cc \
   src/moc-workspace-view.cc \
+  src/moc-variable-editor.cc \
+  src/moc-variable-editor-model.cc \
   src/moc-find-files-dialog.cc \
   src/moc-find-files-model.cc \
   src/qtinfo/moc-parser.cc \
@@ -140,7 +142,9 @@
   src/find-files-dialog.h \
   src/find-files-model.h \
   src/workspace-model.h \
-  src/workspace-view.h
+  src/workspace-view.h \
+  src/variable-editor.h \
+  src/variable-editor-model.h
 
 src_libgui_src_la_SOURCES = \
   src/dialog.cc \
@@ -167,7 +171,9 @@
   src/find-files-dialog.cc \
   src/find-files-model.cc \
   src/workspace-model.cc \
-  src/workspace-view.cc
+  src/workspace-view.cc \
+  src/variable-editor.cc \
+  src/variable-editor-model.cc
 
 nodist_src_libgui_src_la_SOURCES = $(octave_gui_MOC) $(octave_gui_RC)
 
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/octave-qt-link.cc
--- a/libgui/src/octave-qt-link.cc	Thu Feb 13 12:05:47 2014 -0500
+++ b/libgui/src/octave-qt-link.cc	Sat Feb 15 00:12:49 2014 +0100
@@ -552,6 +552,12 @@
 }
 
 void
+octave_qt_link::do_openvar (const std::string &expr)
+{
+  emit open_variable (QString::fromStdString (expr));
+}
+
+void
 octave_qt_link::terminal_interrupt (void)
 {
   command_interpreter->interrupt ();  
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/octave-qt-link.h
--- a/libgui/src/octave-qt-link.h	Thu Feb 13 12:05:47 2014 -0500
+++ b/libgui/src/octave-qt-link.h	Sat Feb 15 00:12:49 2014 +0100
@@ -130,6 +130,8 @@
 
   void do_show_doc (const std::string& file);
 
+  void do_openvar (const std::string &name);
+
 private:
 
   // No copying!
@@ -185,6 +187,8 @@
 
   void show_doc_signal (const QString &file);
 
+  void open_variable (const QString &name);
+
 public slots:
 
   void terminal_interrupt (void);
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/variable-editor-model.cc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/variable-editor-model.cc	Sat Feb 15 00:12:49 2014 +0100
@@ -0,0 +1,541 @@
+/*
+
+Copyright (C) 2013 Rüdiger Sonderfeld
+Copyright (C) 2013 John W. Eaton
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "variable-editor-model.h"
+#include "octave-qt-link.h"
+
+#include <sstream>
+
+#include <QMessageBox>
+#include <QVector>
+#include <QLabel>
+#include <QDebug> //DBG
+
+#include "ov.h"
+#include "parse.h"
+
+/// Pimpl/Dpointer for variable_editor_model.
+struct variable_editor_model::impl
+{
+  struct cell
+  {
+    enum state_t {
+      avail,
+      notavail,
+      pending,
+      unset
+    };
+
+    cell ()
+      : state (unset)
+    { }
+
+    cell (state_t s)
+      : state (s)
+    { }
+
+    cell (const QString &d, const QString &s, const QString &t,
+          bool rse)
+      : state (avail), data (d), status_tip (s), tool_tip (t),
+        requires_sub_editor(rse)
+    { }
+
+    state_t state;
+    QVariant data;
+    QVariant status_tip;
+    QVariant tool_tip;
+    QVariant background;
+    bool requires_sub_editor;
+    // ...?
+  };
+
+  void set (const QModelIndex &idx, const cell &dat)
+  {
+    if (idx.isValid ())
+      table[model_to_index (idx)] = dat;
+  }
+  void set (int r, int c, const cell &dat)
+  {
+    if (0 <= r && r < _rows && 0 <= c && c <= columns ())
+      table[c * _rows + r] = dat;
+  }
+  bool is_set (const QModelIndex &idx) const
+  {
+    return idx.isValid ()
+      && table [model_to_index (idx)].state == cell::avail;
+  }
+  bool is_notavail (const QModelIndex &idx) const
+  {
+    return idx.isValid ()
+      && table [model_to_index (idx)].state == cell::notavail;
+  }
+  bool is_pending (const QModelIndex &idx) const
+  {
+    return idx.isValid ()
+      && table [model_to_index (idx)].state == cell::pending;
+  }
+  void pending (const QModelIndex &idx)
+  {
+    if (idx.isValid ())
+      table [model_to_index (idx)].state = cell::pending;
+  }
+  void notavail (int r, int c)
+  {
+    if (0 <= r && r < _rows && 0 <= c && c <= columns ())
+      table[c * _rows + r].state = cell::notavail;
+  }
+  bool requires_sub_editor (const QModelIndex &idx)
+  {
+    return idx.isValid ()
+      && table [model_to_index (idx)].requires_sub_editor;
+  }
+  void unset (int r, int c)
+  {
+    if (0 <= r && r < _rows && 0 <= c && c <= columns ())
+      table[c * _rows + r].state = cell::unset;
+  }
+  void clear ()
+  {
+    for (int i = 0; i < table.size(); ++i)
+      table[i].state = cell::unset;
+  }
+  QVariant data (const QModelIndex &idx, int role) const
+  {
+    if (idx.isValid ())
+      {
+        const int i = model_to_index (idx);
+        switch (role)
+          {
+          case Qt::DisplayRole:
+          case Qt::EditRole:
+            return table[i].data;
+          case Qt::StatusTipRole:
+            return table[i].status_tip;
+          case Qt::ToolTipRole:
+            return table[i].tool_tip;
+          case Qt::BackgroundRole:
+            return table[i].background;
+          }
+      }
+    return QVariant ();
+  }
+  octave_idx_type rows () const
+  { return _rows; }
+  octave_idx_type columns () const
+  { return _cols; }
+
+  int model_to_index (const QModelIndex &idx) const
+  {
+    return idx.column () * _rows + idx.row ();
+  }
+
+  impl (const QString &n, QLabel *l)
+    : name (n.toStdString ()),
+      _rows (0), _cols (0),
+      label (l)
+  { }
+
+  const std::string name;
+  std::string type;
+  octave_idx_type _rows;
+  octave_idx_type _cols;
+  QVector<cell> table;
+  QLabel *label;
+};
+
+variable_editor_model::variable_editor_model (const QString &expr,
+                                              QLabel *label,
+                                              QObject *p)
+  : QAbstractTableModel (p), d (new impl (expr, label))
+{
+  connect (this, SIGNAL (data_ready (int, int, const QString&,
+                                     const QString&,
+                                     int, int)),
+           this, SLOT (received_data (int, int, const QString&,
+                                      const QString&,
+                                      int, int)));
+  connect (this, SIGNAL (no_data (int, int)),
+           this, SLOT (received_no_data (int, int)));
+  connect (this, SIGNAL (unset_data (int, int)),
+           this, SLOT (received_unset_data (int, int)));
+  connect (this, SIGNAL (user_error (const QString&, const QString&)),
+           this, SLOT (received_user_error (const QString&, const QString&)));
+  connect (this, SIGNAL (initialize_data (const QString&, const QString&,
+                                          int, int)),
+           this, SLOT (received_initialize_data (const QString&,
+                                                 const QString&,
+                                                 int, int)));
+
+  clear_data_cache (); // initializes everything
+}
+
+variable_editor_model::~variable_editor_model ()
+{
+  delete d;
+}
+
+void
+variable_editor_model::clear_data_cache ()
+{
+  octave_link::post_event
+    (this, &variable_editor_model::init_from_oct, d->name);
+}
+
+bool
+variable_editor_model::requires_sub_editor (const QModelIndex &idx) const
+{
+  return d->requires_sub_editor (idx);
+}
+
+QString
+variable_editor_model::parens () const
+{
+  if (d->type == "{")
+    return "{%1, %2}";
+  return "(%1, %2)";
+}
+
+int
+variable_editor_model::rowCount (const QModelIndex&) const
+{
+  return d->rows ();
+}
+
+int
+variable_editor_model::columnCount (const QModelIndex&) const
+{
+  return d->columns ();
+}
+
+QVariant
+variable_editor_model::data (const QModelIndex &idx, int role) const
+{
+  if (idx.isValid ())
+    {
+      if (d->is_set (idx))
+        return d->data (idx, role);
+      else
+        {
+          if (! d->is_pending (idx))
+            {
+              octave_link::post_event<variable_editor_model, int, int,
+                                      const std::string&>
+                (const_cast<variable_editor_model*> (this),
+                 &variable_editor_model::get_data_oct,
+                 idx.row (), idx.column (), d->name);
+              d->pending (idx);
+            }
+          if (role == Qt::DisplayRole)
+            return QVariant (QString (d->is_notavail (idx) ? "⌛" : "✗"));
+          else
+            return QVariant ();
+        }
+    }
+  return QVariant (); // invalid
+}
+
+bool
+variable_editor_model::setData (const QModelIndex &idx, const QVariant &v,
+                                int role)
+{
+  if (idx.isValid () && role == Qt::EditRole)
+    {
+      if (v.type () != QVariant::String)
+        {
+          qDebug () << v.typeName () << " Expected String!"; // DBG
+          return false;
+        }
+      octave_link::post_event<variable_editor_model, const std::string&,
+                              int, int, std::string>
+        (this, &variable_editor_model::set_data_oct,
+         d->name, idx.row (), idx.column (),
+         v.toString ().toStdString ());
+      return true;
+    }
+  else
+    return false;
+}
+
+bool
+variable_editor_model::insertRows (int row, int count, const QModelIndex&)
+{
+  // TODO cells?
+  octave_link::post_event <variable_editor_model, const std::string&,
+                           std::string>
+    (this, &variable_editor_model::eval_oct, d->name,
+     QString ("%1 = [ %1(1:%2,:) ; zeros(%3, columns(%1)) ; %1(%2+%3:end,:) ]")
+     .arg (QString::fromStdString (d->name))
+     .arg (row)
+     .arg (count)
+     .toStdString ());
+  return true;
+}
+
+bool
+variable_editor_model::removeRows (int row, int count, const QModelIndex&)
+{
+  if (row + count > d->_rows)
+    {
+      qDebug () << "Try to remove too many rows " << d->_rows << " "
+                << count << " (" << row << ")"; //DBG
+      return false;
+    }
+  ;
+  octave_link::post_event <variable_editor_model, const std::string&,
+                           std::string>
+    (this, &variable_editor_model::eval_oct, d->name,
+     QString ("%1 (%2:%3, :) = []")
+     .arg (QString::fromStdString (d->name))
+     .arg (row)
+     .arg (row + count)
+     .toStdString ());
+  return true;
+}
+
+bool
+variable_editor_model::insertColumns (int col, int count, const QModelIndex&)
+{
+  octave_link::post_event <variable_editor_model, const std::string&,
+                           std::string>
+    (this, &variable_editor_model::eval_oct, d->name,
+     QString ("%1 = [ %1(:,1:%2) ; zeros(rows(%1), %3) %1(:,%2+%3:end) ]")
+     .arg (QString::fromStdString (d->name))
+     .arg (col)
+     .arg (count)
+     .toStdString ());
+  return true;
+}
+
+bool
+variable_editor_model::removeColumns (int col, int count, const QModelIndex&)
+{
+  if (col + count > d->_cols)
+    {
+      qDebug () << "Try to remove too many cols " << d->_cols << " "
+                << count << " (" << col << ")"; //DBG
+      return false;
+    }
+  ;
+  octave_link::post_event <variable_editor_model, const std::string&,
+                           std::string>
+    (this, &variable_editor_model::eval_oct, d->name,
+     QString ("%1 (:, %2:%3) = []")
+     .arg (QString::fromStdString (d->name))
+     .arg (col)
+     .arg (col + count)
+     .toStdString ());
+  return true;
+}
+
+Qt::ItemFlags
+variable_editor_model::flags (const QModelIndex &idx) const
+{
+  return QAbstractTableModel::flags (idx) | Qt::ItemIsEditable;
+}
+
+// private slots
+
+void
+variable_editor_model::received_data (int r, int c,
+                                      const QString &dat,
+                                      const QString &class_info,
+                                      int rows, int cols)
+{
+  // trimm data
+  const QString status_tip;
+  const QString tool_tip = class_info +
+    QString (": %1×%2").arg (rows).arg (cols);
+
+  d->set (r, c, impl::cell (dat, status_tip, tool_tip,
+                            rows != 1 || cols != 1));
+
+  QModelIndex idx = QAbstractTableModel::index (r, c);
+
+  emit dataChanged (idx, idx);
+}
+
+void
+variable_editor_model::received_no_data (int r, int c)
+{
+  d->notavail (r, c);
+}
+
+void
+variable_editor_model::received_unset_data (int r, int c)
+{
+  d->unset (r, c);
+}
+
+void
+variable_editor_model::received_user_error (const QString &title,
+                                            const QString &msg)
+{
+  QMessageBox::critical (0x0, title, msg);
+}
+
+void
+variable_editor_model::received_initialize_data (const QString &class_name,
+                                                 const QString &paren,
+                                                 int rows, int cols)
+{
+  d->type = paren.toStdString ();
+
+  const int r = d->_rows - rows;
+  if (r > 0)
+    emit beginRemoveRows (QModelIndex (), rows, d->_rows - 1);
+  else if(r < 0)
+    emit beginInsertRows (QModelIndex (), d->_rows, rows - 1);
+
+  const int c = d->_cols - cols;
+  if (c > 0)
+    emit beginRemoveColumns (QModelIndex (), cols, d->_cols - 1);
+  else if(c < 0)
+    emit beginInsertColumns (QModelIndex (), d->_cols, cols - 1);
+
+  d->_rows = rows;
+  d->_cols = cols;
+  d->table.clear ();
+  d->table.resize (rows * cols);
+
+  if (c > 0)
+    emit endRemoveColumns ();
+  else if (c < 0)
+    emit endInsertColumns ();
+
+  if (r > 0)
+    emit endRemoveRows ();
+  else if (r < 0)
+    emit endInsertRows ();
+
+  emit dataChanged (QAbstractTableModel::index (0, 0),
+                    QAbstractTableModel::index (d->_rows - 1, d->_cols - 1));
+
+  d->label->setTextFormat (Qt::PlainText);
+  QString description = QString ("%1: %2 %3×%4")
+    .arg (QString::fromStdString (d->name))
+    .arg (class_name)
+    .arg (rows)
+    .arg (cols);
+  d->label->setText (description);
+}
+
+// private
+
+void
+variable_editor_model::get_data_oct (int row, int col, const std::string &x)
+{
+  int parse_status = 0;
+  octave_value v = eval_string (x, true, parse_status);
+  if (parse_status != 0 || ! v.is_defined ())
+    {
+      emit no_data (row, col);
+      emit user_error ("Table invalid",
+                       QString ("Table expression `%1' invalid")
+                       .arg (QString::fromStdString (x)));
+      return;
+    }
+  octave_value_list ovlidx = ovl (row + 1, col + 1);
+  const octave_value elem = v.single_subsref (d->type, ovlidx);
+
+  if (elem.is_defined ())
+    {
+      std::stringstream ss;
+      elem.print (ss, true);
+      const QString dat = QString::fromStdString (ss.str ()).trimmed ();
+      const QString cname = QString::fromStdString (elem.class_name ());
+      emit data_ready (row, col, dat, cname,
+                       elem.rows (), elem.columns ());
+    }
+  else
+    emit no_data (row, col);
+}
+
+// val has to be copied!
+void
+variable_editor_model::set_data_oct (const std::string &x, int row, int col,
+                                     std::string val)
+{
+  int parse_status = 0;
+  octave_value ret = eval_string (val, true, parse_status);
+  if (parse_status != 0 || ! ret.is_defined ())
+    {
+      emit user_error ("Invalid expression",
+                       QString ("Expression `%1' invalid")
+                       .arg (QString::fromStdString (val)));
+      return;
+    }
+
+  parse_status = 0;
+  octave_value v = eval_string (x, true, parse_status);
+  if (parse_status != 0 || ! v.is_defined ())
+    {
+      emit user_error ("Table invalid",
+                       QString ("Table expression `%1' invalid")
+                       .arg (QString::fromStdString (x)));
+      return;
+    }
+
+  octave_value_list ovlidx = ovl (row + 1, col + 1);
+  std::list<octave_value_list> idxl;
+  idxl.push_back (ovlidx);
+  v.subsasgn (d->type, idxl, ret);
+  emit unset_data (row, col);
+  QModelIndex idx = QAbstractTableModel::index (row, col);
+  emit dataChanged (idx, idx);
+}
+
+void
+variable_editor_model::init_from_oct (const std::string &x)
+{
+  int parse_status = 0;
+  const octave_value ov = eval_string (x, true, parse_status);
+  if (parse_status != 0 || ! ov.is_defined ())
+    {
+      emit user_error ("Table invalid",
+                       QString ("Table expression `%1' invalid")
+                       .arg (QString::fromStdString (x)));
+      // TODO emit remove_table
+      return;
+    }
+  const QString class_name = QString::fromStdString (ov.class_name ());
+  const QString paren = ov.is_cell () ? "{" : "("; // TODO
+  const octave_idx_type rows = ov.rows ();
+  const octave_idx_type cols = ov.columns ();
+  emit initialize_data (class_name, paren, rows, cols);
+}
+
+void
+variable_editor_model::eval_oct (const std::string &name, std::string x)
+{
+  int parse_status = 0;
+  eval_string (x, true, parse_status);
+  if (parse_status != 0)
+    emit user_error ("Evaluation failed",
+                     QString ("Evaluation of `%s' failed")
+                     .arg (QString::fromStdString (x)));
+  init_from_oct (name);
+}
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/variable-editor-model.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/variable-editor-model.h	Sat Feb 15 00:12:49 2014 +0100
@@ -0,0 +1,129 @@
+/*
+
+Copyright (C) 2013 Rüdiger Sonderfeld
+Copyright (C) 2013 John W. Eaton
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#if !defined (variable_editor_model_h)
+#define variable_editor_model_h 1
+
+#include <QAbstractTableModel>
+
+class QLabel;
+
+class
+variable_editor_model : public QAbstractTableModel
+{
+  Q_OBJECT
+
+public:
+  variable_editor_model (const QString &expr,
+                         QLabel *label,
+                         QObject *p = 0);
+  ~variable_editor_model ();
+
+  int rowCount (const QModelIndex & = QModelIndex ()) const;
+  int columnCount (const QModelIndex & = QModelIndex ()) const;
+  QVariant data (const QModelIndex &idx, int role = Qt::DisplayRole) const;
+  bool setData (const QModelIndex &idx, const QVariant &v, int role = Qt::EditRole);
+  Qt::ItemFlags flags (const QModelIndex &idx) const;
+  bool insertRows (int row, int count,
+                   const QModelIndex &parent = QModelIndex());
+  bool removeRows (int row, int count,
+                   const QModelIndex &parent = QModelIndex());
+  bool insertColumns (int column, int count,
+                      const QModelIndex &parent = QModelIndex());
+  bool removeColumns (int column, int count,
+                      const QModelIndex &parent = QModelIndex());
+
+  void clear_data_cache ();
+  /// Is cell at idx complex enough to require a sub editor?
+  bool requires_sub_editor (const QModelIndex &idx) const;
+  /** Return the proper parens to access the data structure.
+   *
+   * {%1,%2} for cell and (%1,%2) for matrices.  Use QString::arg to
+   * set the index.
+   */
+  QString parens () const;
+
+  // TODO insertRows(), removeRows(), insertColumns(), and removeColumns().
+
+signals: // private
+
+  void
+  data_ready (int r, int c,
+              const QString &data,
+              const QString &class_info,
+              int rows, int cols);
+
+  void
+  no_data (int r, int c);
+
+  void
+  unset_data (int r, int c);
+
+  void
+  user_error (const QString &title, const QString &msg);
+
+  void
+  initialize_data (const QString &class_name, const QString &paren,
+                   int rows, int cols);
+
+private slots:
+
+  void
+  received_data (int r, int c,
+                 const QString &dat,
+                 const QString &class_info,
+                 int rows, int cols);
+  void
+  received_no_data (int r, int c);
+  void
+  received_unset_data (int r, int c);
+  void
+  received_user_error (const QString &title, const QString &msg);
+  void
+  received_initialize_data (const QString &class_name, const QString &paren,
+                            int rows, int cols);
+
+private:
+  /** Get data for ov(row, col)
+   * This must be executed in the octave thread!
+   */
+  void
+  get_data_oct (int row, int col, const std::string &v) /*const*/;
+
+  void
+  set_data_oct (const std::string &v, int row, int col,
+                std::string val);
+
+  void
+  init_from_oct (const std::string &x);
+
+  void
+  eval_oct (const std::string &name, std::string expr);
+
+  Q_DISABLE_COPY (variable_editor_model)
+
+  struct impl;
+  impl *d;
+};
+
+#endif
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/variable-editor.cc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/variable-editor.cc	Sat Feb 15 00:12:49 2014 +0100
@@ -0,0 +1,525 @@
+/*
+
+Copyright (C) 2013 Rüdiger Sonderfeld
+Copyright (C) 2013 John W. Eaton
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <limits>
+
+#include <QSignalMapper>
+#include <QApplication>
+#include <QFileDialog>
+#include <QMainWindow>
+#include <QVBoxLayout>
+#include <QToolButton>
+#include <QClipboard>
+#include <QTableView>
+#include <QTabWidget>
+#include <QToolBar>
+#include <QLabel>
+#include <QDebug> //DBG
+#include <QMenu>
+
+#include "variable-editor.h"
+#include "variable-editor-model.h"
+#include "octave-qt-link.h"
+
+#include "ov.h"
+#include "operators/ops.h"
+
+variable_editor::variable_editor (QWidget *p)
+  : octave_dock_widget (p),
+    main (new QMainWindow ()),
+    tool_bar (new QToolBar (main)),
+    tab_widget (new QTabWidget (main))
+{
+
+  /*
+    We use a MainWindow
+   */
+
+  setObjectName ("variable_editor");
+  setWindowTitle (tr ("Variable Editor"));
+  setStatusTip (tr ("Edit variables."));
+  setWindowIcon (QIcon (":/actions/icons/logo.png"));
+
+  // Tool Bar
+  construct_tool_bar ();
+  main->addToolBar (tool_bar);
+
+  // Tab Widget
+  tab_widget->setTabsClosable (true);
+  tab_widget->setMovable (true);
+  connect (tab_widget, SIGNAL (tabCloseRequested (int)),
+           this, SLOT (closeTab (int)));
+  main->setCentralWidget (tab_widget);
+
+  // Main
+  main->setParent (this);
+  setWidget (main);
+
+  connect (this, SIGNAL (command_requested (const QString&)),
+           p, SLOT (execute_command_in_terminal (const QString&)));
+}
+
+void
+variable_editor::construct_tool_bar ()
+{
+  tool_bar->setObjectName ("VariableEditorToolBar");
+  tool_bar->setWindowTitle (tr ("Variable Editor Toolbar"));
+
+  tool_bar->addAction
+    (QIcon (":/actions/icons/filesave.png"),
+     tr ("Save"),
+     this, SLOT (save ()));
+  tool_bar->addSeparator ();
+
+  tool_bar->addAction
+    (QIcon (":/actions/icons/editcut.png"),
+     tr ("Cut"),
+     this, SLOT (cutClipboard ()));
+  tool_bar->addAction
+    (QIcon (":/actions/icons/editcopy.png"),
+     tr ("Copy"),
+     this, SLOT (copyClipboard ()));
+  tool_bar->addAction
+    (QIcon (":/actions/icons/editpaste.png"),
+     tr ("Paste"),
+     this, SLOT (pasteClipboard ()));
+  tool_bar->addAction
+    (QIcon (":/actions/icons/editpaste.png"), // TODO
+     tr ("Paste Table"),
+     this, SLOT (pasteTableClipboard ()));
+  tool_bar->addSeparator ();
+
+  //QAction *print_action; /icons/fileprint.png
+  //tool_bar->addSeparator ();
+
+  QToolButton *plot_tool_button = new QToolButton (tool_bar);
+  plot_tool_button->setText (tr ("Plot"));
+  plot_tool_button->setIcon (QIcon (":/actions/icons/gear.png")); // TODO
+
+  plot_tool_button->setPopupMode (QToolButton::InstantPopup);
+
+  QMenu *plot_menu = new QMenu (tr ("Plot"), plot_tool_button);
+  plot_menu->setSeparatorsCollapsible (false);
+  QSignalMapper *plot_mapper = new QSignalMapper (plot_menu);
+  plot_mapper->setMapping(plot_menu->addAction ("plot",
+                                                plot_mapper,
+                                                SLOT (map ())),
+                          "figure (); plot (%1);");
+  plot_mapper->setMapping(plot_menu->addAction ("bar",
+                                                plot_mapper,
+                                                SLOT (map ())),
+                          "figure (); bar (%1);");
+  plot_mapper->setMapping(plot_menu->addAction ("stem",
+                                                plot_mapper,
+                                                SLOT (map ())),
+                          "figure (); stem (%1);");
+  plot_mapper->setMapping(plot_menu->addAction ("stairs",
+                                                plot_mapper,
+                                                SLOT (map ())),
+                          "figure (); stairs (%1);");
+  plot_mapper->setMapping(plot_menu->addAction ("area",
+                                                plot_mapper,
+                                                SLOT (map ())),
+                          "figure (); area (%1);");
+  plot_mapper->setMapping(plot_menu->addAction ("pie",
+                                                plot_mapper,
+                                                SLOT (map ())),
+                          "figure (); pie (%1);");
+  plot_mapper->setMapping(plot_menu->addAction ("hist",
+                                                plot_mapper,
+                                                SLOT (map ())),
+                          "figure (); hist (%1);");
+  connect (plot_mapper, SIGNAL (mapped (const QString &)),
+           this, SLOT (relay_command (const QString &)));
+
+  plot_tool_button->setMenu (plot_menu);
+  tool_bar->addWidget (plot_tool_button);
+  //tool_bar->addSeparator ();
+
+  //QAction *color_action;
+
+  tool_bar->addSeparator ();
+  tool_bar->addAction
+    (QIcon (":/actions/icons/arrow_up.png"),
+     tr ("Up"),
+     this, SLOT (up ()));
+}
+
+variable_editor::~variable_editor ()
+{
+}
+
+namespace
+{
+  /// Helper struct to store widget pointers in "data" Tab property.
+  struct table_data
+  {
+    table_data (QTableView *t = 0x0)
+      : table (t)
+    { }
+    QTableView *table;
+  };
+
+  table_data get_table_data (QTabWidget *w, int tidx)
+  {
+    return w->widget (tidx)->property("data").value<table_data> ();
+  }
+
+  table_data get_table_data (QTabWidget *w)
+  {
+    return get_table_data (w, w->currentIndex ());
+  }
+}
+Q_DECLARE_METATYPE (table_data)
+
+void
+variable_editor::edit_variable (const QString &name)
+{
+  const int tab_count = tab_widget->count ();
+  for (int i = 0; i < tab_count; ++i)
+    if (tab_widget->tabText (i) == name)
+      {
+        tab_widget->setCurrentIndex (i);
+        return; // already open
+      }
+
+  QWidget *page = new QWidget; // Do not set parent.
+
+  QVBoxLayout *vbox = new QVBoxLayout (page);
+  page->setLayout (vbox);
+
+  QLabel *label = new QLabel (page);
+  label->setTextFormat (Qt::PlainText);
+  label->setText (name);
+  vbox->addWidget (label);
+
+  QTableView *table = new QTableView (page);
+  variable_editor_model *model =
+    new variable_editor_model (name, label, table);
+  table->setModel (model);
+  table->setWordWrap (false);
+  table->setContextMenuPolicy (Qt::CustomContextMenu);
+  table->setSelectionMode (QAbstractItemView::ContiguousSelection);
+  connect (table, SIGNAL (customContextMenuRequested (const QPoint&)),
+           this, SLOT (contextmenu_requested (const QPoint&)));
+  connect (table, SIGNAL (doubleClicked (const QModelIndex&)),
+           this, SLOT (double_click (const QModelIndex&)));
+  vbox->addWidget (table);
+
+  page->setProperty ("data", QVariant::fromValue (table_data (table)));
+  int tab_idx = tab_widget->addTab (page, name);
+  tab_widget->setCurrentIndex (tab_idx);
+}
+
+void
+variable_editor::closeTab (int idx)
+{
+  if (idx < 0 || idx > tab_widget->count ())
+    return;
+
+  QWidget *const wdgt = tab_widget->widget (idx);
+  tab_widget->removeTab (idx);
+  delete wdgt;
+}
+
+void
+variable_editor::closeEvent (QCloseEvent *e)
+{
+  emit finished ();
+  QDockWidget::closeEvent (e);
+}
+
+
+void
+variable_editor::contextmenu_requested (const QPoint &qpos)
+{
+  QTableView *view = get_table_data (tab_widget).table;
+  QModelIndex index = view->indexAt (qpos);
+
+  if (index.isValid ())
+    {
+      QMenu *menu = new QMenu (this);
+      menu->addAction
+        (QIcon (":/actions/icons/editcut.png"),
+         tr ("Cut"),
+         this, SLOT (cutClipboard ()));
+      menu->addAction
+        (QIcon (":/actions/icons/editcopy.png"),
+         tr ("Copy"),
+         this, SLOT (copyClipboard ()));
+      menu->addAction
+        (QIcon (":/actions/icons/editpaste.png"),
+         tr ("Paste"),
+         this, SLOT (pasteClipboard ()));
+      menu->addAction
+        (QIcon (":/actions/icons/editpaste.png"), // TODO
+         tr ("Paste Table"),
+         this, SLOT (pasteTableClipboard ()));
+      menu->addSeparator ();
+
+      menu->addAction
+        (QIcon (":/actions/icons/editdelete.png"),
+         tr ("Clear"),
+         this, SLOT (clearContent ()));
+
+      // TODO delete, insert, clear
+
+      menu->addAction
+        (QIcon (":/actions/icons/filenew.png"),
+         tr ("Variable from Selection"),
+         this, SLOT (createVariable ()));
+
+      // TODO sort
+
+      menu->addAction
+        ( //QIcon (), TODO
+         tr ("Transpose"),
+         this, SLOT (transposeContent ()));
+
+      QItemSelectionModel *sel = view->selectionModel ();
+      QList<QModelIndex> indices = sel->selectedIndexes ();
+      if (! indices.isEmpty ())
+        {
+          menu->addSeparator ();
+          QSignalMapper *plot_mapper = new QSignalMapper (menu);
+          plot_mapper->setMapping(menu->addAction ("plot",
+                                                   plot_mapper,
+                                                   SLOT (map ())),
+                                  "figure (); plot (%1);");
+          plot_mapper->setMapping(menu->addAction ("bar",
+                                                   plot_mapper,
+                                                   SLOT (map ())),
+                                  "figure (); bar (%1);");
+          plot_mapper->setMapping(menu->addAction ("stem",
+                                                   plot_mapper,
+                                                   SLOT (map ())),
+                                  "figure (); stem (%1);");
+          plot_mapper->setMapping(menu->addAction ("stairs",
+                                                   plot_mapper,
+                                                   SLOT (map ())),
+                                  "figure (); stairs (%1);");
+          plot_mapper->setMapping(menu->addAction ("area",
+                                                   plot_mapper,
+                                                   SLOT (map ())),
+                                  "figure (); area (%1);");
+          plot_mapper->setMapping(menu->addAction ("pie",
+                                                   plot_mapper,
+                                                   SLOT (map ())),
+                                  "figure (); pie (%1);");
+          plot_mapper->setMapping(menu->addAction ("hist",
+                                                   plot_mapper,
+                                                   SLOT (map ())),
+                                  "figure (); hist (%1);");
+          connect (plot_mapper, SIGNAL (mapped (const QString &)),
+                   this, SLOT (relay_command (const QString &)));
+        }
+
+      menu->exec (view->mapToGlobal (qpos));
+    }
+}
+
+static
+QString idx_to_expr (octave_idx_type from, octave_idx_type to)
+{
+  if (from == to)
+    return QString ("%1").arg (from + 1);
+  else
+    return QString ("%1:%2").arg (from + 1).arg (to + 1);
+}
+
+QString
+variable_editor::selected_to_octave ()
+{
+  QString name = tab_widget->tabText (tab_widget->currentIndex ());
+  QTableView *view = get_table_data (tab_widget).table;
+  QItemSelectionModel *sel = view->selectionModel ();
+  QList<QModelIndex> indices = sel->selectedIndexes (); // it's indices!
+
+  octave_idx_type from_row = std::numeric_limits<octave_idx_type>::max ();
+  octave_idx_type to_row = 0;
+  octave_idx_type from_col = std::numeric_limits<octave_idx_type>::max ();
+  octave_idx_type to_col = 0;
+
+  foreach(QModelIndex idx, indices)
+    {
+      from_row = std::min (from_row, idx.row ());
+      to_row = std::max (to_row, idx.row ());
+      from_col = std::min (from_col, idx.column ());
+      to_col = std::max (to_col, idx.column ());
+    }
+
+  QString rows = idx_to_expr (from_row, to_row);
+  QString cols = idx_to_expr (from_col, to_col);
+
+  // TODO Cells
+  return QString ("%1 (%2, %3)").arg (name).arg (rows).arg (cols);
+}
+
+void
+variable_editor::double_click (const QModelIndex &idx)
+{
+  QString name = tab_widget->tabText (tab_widget->currentIndex ());
+  QTableView *const table = get_table_data (tab_widget).table;
+  variable_editor_model *const model =
+    qobject_cast<variable_editor_model*>(table->model ());
+  if (model->requires_sub_editor (idx))
+    {
+      emit command_requested ("openvar ('" + name +
+                              model->parens ()
+                              .arg (idx.row () + 1)
+                              .arg (idx.column () + 1)
+                              + "');");
+    }
+}
+
+void
+variable_editor::relay_command (const QString &cmd)
+{
+  emit command_requested (cmd.arg (selected_to_octave ()));
+}
+
+void
+variable_editor::save ()
+{
+  QString name = tab_widget->tabText (tab_widget->currentIndex ());
+  QString file =
+    QFileDialog::getSaveFileName (this,
+                                  tr ("Save Variable %1 As").arg (name),
+                                  ".", 0, 0,
+                                  QFileDialog::DontUseNativeDialog);
+  // TODO type? binary, float-binary, ascii, text, hdf5 matlab format?
+  if (! file.isEmpty ())
+    // TODO use octave_value::save_*?
+    emit command_requested (QString ("save ('%1', '%2');")
+                            .arg (file)
+                            .arg (name));
+}
+
+void
+variable_editor::clearContent ()
+{
+  // TODO shift?
+  QTableView *view = get_table_data (tab_widget).table;
+  QAbstractItemModel *model = view->model ();
+  QItemSelectionModel *sel = view->selectionModel ();
+  QList<QModelIndex> indices = sel->selectedIndexes ();
+  foreach (QModelIndex idx, indices)
+    model->setData(idx, QVariant ("0")); // TODO [] for cell
+}
+
+void
+variable_editor::cutClipboard ()
+{
+  copyClipboard ();
+  clearContent ();
+}
+
+void
+variable_editor::copyClipboard ()
+{
+  QTableView *view = get_table_data (tab_widget).table;
+  QAbstractItemModel *model = view->model ();
+  QItemSelectionModel *sel = view->selectionModel ();
+  QList<QModelIndex> indices = sel->selectedIndexes ();
+  qSort(indices);
+  if (indices.size () <= 0)
+    return;
+
+  // Convert selected items into TSV format and copy that.
+  // Spreadsheet tools should understand that.
+  QModelIndex previous = indices.first ();
+  QString copy = model->data (previous).toString ();
+  indices.removeFirst ();
+  foreach (QModelIndex idx, indices)
+    {
+      copy.append (previous.row () != idx.row () ? '\n' : '\t');
+      copy.append (model->data (idx).toString ());
+      previous = idx;
+    }
+  copy.append ('\n');
+
+  QClipboard *clipboard = QApplication::clipboard ();
+  clipboard->setText (copy);
+}
+
+void
+variable_editor::pasteClipboard ()
+{
+  // TODO
+}
+
+void variable_editor::pasteTableClipboard ()
+{
+  QClipboard *clipboard = QApplication::clipboard ();
+  QString text = clipboard->text ();
+  QStringList rows = text.split ('\n');
+  foreach (QString row, rows)
+    {
+      QStringList cols = text.split ('\t');
+      foreach (QString col, cols)
+        {
+          // TODO
+        }
+    }
+}
+
+void
+variable_editor::createVariable ()
+{
+  // TODO unnamed1..n if exist ('unnamed', 'var')
+  relay_command ("unnamed = %1");
+}
+
+void
+variable_editor::transposeContent ()
+{
+  QString name = tab_widget->tabText (tab_widget->currentIndex ());
+  emit command_requested (QString ("%1 = %1';").arg (name));
+}
+
+void
+variable_editor::up ()
+{
+  QString name = tab_widget->tabText (tab_widget->currentIndex ());
+  // TODO is there a better way?
+  if (name.endsWith (')') || name.endsWith ('}'))
+    {
+      qDebug () << "up";
+      name.remove (QRegExp ("(\\(|\\{)[^({]*(\\)|\\})$"));
+      emit command_requested (QString ("openvar ('%1');").arg (name));
+    }
+}
+
+void variable_editor::clear_data_cache ()
+{
+  for (int i = 0; i < tab_widget->count (); ++i)
+    {
+      QTableView *const table = get_table_data (tab_widget, i).table;
+      QAbstractItemModel *const model = table->model ();
+      qobject_cast<variable_editor_model*>(model)->clear_data_cache ();
+    }
+}
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/variable-editor.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libgui/src/variable-editor.h	Sat Feb 15 00:12:49 2014 +0100
@@ -0,0 +1,91 @@
+/*
+
+Copyright (C) 2013 Rüdiger Sonderfeld
+Copyright (C) 2013 John W. Eaton
+
+This file is part of Octave.
+
+Octave is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by the
+Free Software Foundation; either version 3 of the License, or (at your
+option) any later version.
+
+Octave is distributed in the hope that it will be useful, but WITHOUT
+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with Octave; see the file COPYING.  If not, see
+<http://www.gnu.org/licenses/>.
+
+*/
+
+#if !defined (variable_editor_h)
+#define variable_editor_h 1
+
+#include "octave-dock-widget.h"
+
+class QTabWidget;
+class QToolBar;
+class QMainWindow;
+class QTableView;
+class QModelIndex;
+
+class variable_editor : public octave_dock_widget
+{
+  Q_OBJECT
+
+public:
+
+  variable_editor (QWidget *parent = 0x0);
+
+  ~variable_editor ();
+
+  void edit_variable (const QString &name);
+
+  /// Clear all the models' data cache
+  void clear_data_cache ();
+
+protected slots:
+
+  void closeEvent (QCloseEvent *);
+
+  void closeTab (int idx);
+
+  void contextmenu_requested (const QPoint &pt);
+
+  void double_click (const QModelIndex &idx);
+
+  void save ();
+  void clearContent ();
+  void cutClipboard ();
+  void copyClipboard ();
+  void pasteClipboard ();
+  void pasteTableClipboard ();
+  void createVariable ();
+  void transposeContent ();
+  void up ();
+
+  /** Send command to Octave interpreter.
+   * %1 in CMD is replaced with the value of selected_to_octave.
+   */
+  void relay_command (const QString &cmd);
+
+signals:
+
+  void finished ();
+  void command_requested (const QString &cmd);
+
+private:
+  void construct_tool_bar ();
+
+  /// Convert selection to an Octave expression.
+  QString selected_to_octave ();
+
+  QMainWindow *main;
+  QToolBar *tool_bar;
+  QTabWidget *tab_widget;
+};
+
+#endif
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/workspace-view.cc
--- a/libgui/src/workspace-view.cc	Thu Feb 13 12:05:47 2014 -0500
+++ b/libgui/src/workspace-view.cc	Sat Feb 15 00:12:49 2014 +0100
@@ -79,6 +79,8 @@
   connect (this, SIGNAL (command_requested (const QString&)),
            p, SLOT (execute_command_in_terminal (const QString&)));
 
+  connect (this, SIGNAL (edit_variable_signal (const QString&)),
+           p, SLOT (edit_variable (const QString&)));
 }
 
 workspace_view::~workspace_view (void)
@@ -135,6 +137,9 @@
           rename->setToolTip (tr ("Only top-level symbols may be renamed."));
         }
 
+      menu.addAction (tr ("Open in Variable Editor"), this,
+                      SLOT (handle_contextmenu_edit ()));
+
       menu.addSeparator ();
 
       menu.addAction ("disp (" + var_name + ")", this,
@@ -202,6 +207,25 @@
 }
 
 void
+workspace_view::handle_contextmenu_edit (void)
+{
+  QModelIndex index = view->currentIndex ();
+
+  if (index.isValid ())
+    {
+      index = index.sibling (index.row (), 0);
+
+      QAbstractItemModel *m = view->model ();
+
+      QMap<int, QVariant> item_data = m->itemData (index);
+
+      QString var_name = item_data[0].toString ();
+
+      emit edit_variable_signal (var_name);
+    }
+}
+
+void
 workspace_view::handle_contextmenu_disp (void)
 {
   relay_contextmenu_command ("disp");
diff -r 9fbea0c395ec -r 021ff2b68223 libgui/src/workspace-view.h
--- a/libgui/src/workspace-view.h	Thu Feb 13 12:05:47 2014 -0500
+++ b/libgui/src/workspace-view.h	Sat Feb 15 00:12:49 2014 +0100
@@ -52,6 +52,9 @@
   /** signal that user had requested a command on a variable */
   void command_requested (const QString& cmd);
 
+  /// signal that user wants to edit a variable
+  void edit_variable_signal (const QString&);
+
 protected:
 
   void closeEvent (QCloseEvent *event);
@@ -63,6 +66,7 @@
   // context menu slots
   void handle_contextmenu_copy (void);
   void handle_contextmenu_rename (void);
+  void handle_contextmenu_edit (void);
   void handle_contextmenu_disp (void);
   void handle_contextmenu_plot (void);
   void handle_contextmenu_stem (void);
diff -r 9fbea0c395ec -r 021ff2b68223 libinterp/corefcn/octave-link.cc
--- a/libinterp/corefcn/octave-link.cc	Thu Feb 13 12:05:47 2014 -0500
+++ b/libinterp/corefcn/octave-link.cc	Sat Feb 15 00:12:49 2014 +0100
@@ -447,5 +447,24 @@
   return retval;
 }
 
+DEFUN (openvar, args, ,
+       "-*- texinfo -*-\n\
+@deftypefn  {Built-in Function} openvar (@var{name})\n\
+Open the variable @var{name} in the GUI Variable Editor.\n\
+@end deftypefn")
+{
+  octave_value retval;
 
+  if (args.length () == 1)
+    {
+      std::string name = args (0).string_value ();
+      if (! error_state)
+        octave_link::openvar (name);
+      else
+        error ("invalid arguments");
+    }
+  else
+    print_usage ();
 
+  return retval;
+}
diff -r 9fbea0c395ec -r 021ff2b68223 libinterp/corefcn/octave-link.h
--- a/libinterp/corefcn/octave-link.h	Thu Feb 13 12:05:47 2014 -0500
+++ b/libinterp/corefcn/octave-link.h	Sat Feb 15 00:12:49 2014 +0100
@@ -97,13 +97,6 @@
   }
 
   template <class T, class A>
-  static void post_event (T *obj, void (T::*method) (A), A arg)
-  {
-    if (enabled ())
-      instance->do_post_event (obj, method, arg);
-  }
-
-  template <class T, class A>
   static void post_event (T *obj, void (T::*method) (const A&), const A& arg)
   {
     if (enabled ())
@@ -111,11 +104,30 @@
   }
 
   template <class T, class A, class B>
-  static void post_event (T *obj, void (T::*method) (const A&, const B&),
-                          const A& arg_a, const B& arg_b)
+  static void post_event (T *obj, void (T::*method) (A, B),
+                          A arg_a, B arg_b)
   {
     if (enabled ())
-      instance->do_post_event (obj, method, arg_a, arg_b);
+      instance->do_post_event<T, A, B> (obj, method, arg_a, arg_b);
+  }
+
+  template <class T, class A, class B, class C>
+  static void post_event (T *obj,
+                          void (T::*method) (A, B, C),
+                          A arg_a, B arg_b, C arg_c)
+  {
+    if (enabled ())
+      instance->do_post_event<T, A, B, C> (obj, method, arg_a, arg_b, arg_c);
+  }
+
+  template <class T, class A, class B, class C, class D>
+  static void post_event (T *obj,
+                          void (T::*method) (A, B, C, D),
+                          A arg_a, B arg_b, C arg_c, D arg_d)
+  {
+    if (enabled ())
+      instance->do_post_event<T, A, B, C, D>
+        (obj, method, arg_a, arg_b, arg_c, arg_d);
   }
 
   static void entered_readline_hook (void)
@@ -340,6 +352,18 @@
 
   }
 
+  static bool
+  openvar (const std::string &name)
+  {
+    if (enabled ())
+      {
+        instance->do_openvar (name);
+        return true;
+      }
+    else
+      return false;
+  }
+
 private:
 
   static octave_link *instance;
@@ -385,6 +409,30 @@
     gui_event_queue.add_method (obj, method, arg);
   }
 
+  template <class T, class A, class B>
+  void do_post_event (T *obj, void (T::*method) (A, B),
+                      A arg_a, B arg_b)
+  {
+    gui_event_queue.add_method<T, A, B>
+      (obj, method, arg_a, arg_b);
+  }
+
+  template <class T, class A, class B, class C>
+    void do_post_event (T *obj, void (T::*method) (A, B, C),
+                      A arg_a, B arg_b, C arg_c)
+  {
+    gui_event_queue.add_method<T, A, B, C>
+      (obj, method, arg_a, arg_b, arg_c);
+  }
+
+  template <class T, class A, class B, class C, class D>
+  void do_post_event (T *obj, void (T::*method) (A, B, C, D),
+                      A arg_a, B arg_b, C arg_c, D arg_d)
+  {
+    gui_event_queue.add_method<T, A, B, C, D>
+      (obj, method, arg_a, arg_b, arg_c, arg_d);
+  }
+
   void do_entered_readline_hook (void) { }
   void do_finished_readline_hook (void) { }
 
@@ -463,6 +511,8 @@
   virtual void do_show_preferences (void) = 0;
 
   virtual void do_show_doc (const std::string &file) = 0;
+
+  virtual void do_openvar (const std::string &name) = 0;
 };
 
 #endif // OCTAVELINK_H
diff -r 9fbea0c395ec -r 021ff2b68223 liboctave/util/action-container.h
--- a/liboctave/util/action-container.h	Thu Feb 13 12:05:47 2014 -0500
+++ b/liboctave/util/action-container.h	Sat Feb 15 00:12:49 2014 +0100
@@ -184,6 +184,91 @@
     method_crefarg_elem operator = (const method_crefarg_elem&);
   };
 
+  /// An element for calling a member function with two arguments
+  template <class T, class A, class B>
+  class method_arg2_elem : public elem
+  {
+  public:
+    method_arg2_elem (T *obj, void (T::*method) (A, B),
+                      A arg_a, B arg_b)
+      : e_obj (obj), e_method (method),
+        e_arg_a (arg_a), e_arg_b (arg_b) { }
+
+    void run (void) { (e_obj->*e_method) (e_arg_a, e_arg_b); }
+
+  private:
+
+    T *e_obj;
+    void (T::*e_method) (A, B);
+    A e_arg_a;
+    B e_arg_b;
+
+    // No copying!
+
+    method_arg2_elem (const method_arg2_elem&);
+
+    method_arg2_elem operator = (const method_arg2_elem&);
+  };
+
+  /// An element for calling a member function with three arguments
+  template <class T, class A, class B, class C>
+  class method_arg3_elem : public elem
+  {
+  public:
+    method_arg3_elem (T *obj, void (T::*method) (A, B, C),
+                      A arg_a, B arg_b, C arg_c)
+      : e_obj (obj), e_method (method),
+        e_arg_a (arg_a), e_arg_b (arg_b), e_arg_c (arg_c)
+    { }
+
+    void run (void) { (e_obj->*e_method) (e_arg_a, e_arg_b, e_arg_c); }
+
+  private:
+
+    T *e_obj;
+    void (T::*e_method) (A, B, C);
+    A e_arg_a;
+    B e_arg_b;
+    C e_arg_c;
+
+    // No copying!
+
+    method_arg3_elem (const method_arg3_elem&);
+
+    method_arg3_elem operator = (const method_arg3_elem&);
+  };
+
+  /// An element for calling a member function with three arguments
+  template <class T, class A, class B, class C, class D>
+  class method_arg4_elem : public elem
+  {
+  public:
+    method_arg4_elem (T *obj, void (T::*method) (A, B, C, D),
+                      A arg_a, B arg_b, C arg_c, D arg_d)
+      : e_obj (obj), e_method (method),
+        e_arg_a (arg_a), e_arg_b (arg_b), e_arg_c (arg_c), e_arg_d (arg_d)
+    { }
+
+    void run (void) {
+      (e_obj->*e_method) (e_arg_a, e_arg_b, e_arg_c, e_arg_d);
+    }
+
+  private:
+
+    T *e_obj;
+    void (T::*e_method) (A, B, C, D);
+    A e_arg_a;
+    B e_arg_b;
+    C e_arg_c;
+    D e_arg_d;
+
+    // No copying!
+
+    method_arg4_elem (const method_arg4_elem&);
+
+    method_arg4_elem operator = (const method_arg4_elem&);
+  };
+
   // An element that stores arbitrary variable, and restores it.
 
   template <class T>
@@ -275,6 +360,33 @@
     add (new method_crefarg_elem<T, A> (obj, method, arg));
   }
 
+  // Call to T::method (A, B).
+  template <class T, class A, class B>
+  void add_method (T *obj, void (T::*method) (A, B),
+                   A arg_a, B arg_b)
+  {
+    add (new method_arg2_elem<T, A, B> (obj, method, arg_a, arg_b));
+  }
+
+  // Call to T::method (A, B, C).
+  template <class T, class A, class B, class C>
+  void add_method (T *obj, void (T::*method) (A, B, C),
+                   A arg_a, B arg_b, C arg_c)
+  {
+    add (new method_arg3_elem<T, A, B, C> (obj, method, arg_a,
+                                           arg_b, arg_c));
+  }
+
+  // Call to T::method (A, B, C, D).
+  template <class T, class A, class B, class C, class D>
+  void add_method (T *obj, void (T::*method) (A, B, C, D),
+                   A arg_a, B arg_b,
+                   C arg_c, D arg_d)
+  {
+    add (new method_arg4_elem<T, A, B, C, D> (obj, method, arg_a,
+                                              arg_b, arg_c, arg_d));
+  }
+
   // Call to delete (T*).
 
   template <class T>
