view libgui/src/variable-editor.cc @ 24021:46dc1ba54f7f

Free variable editor QMainWindow memory upon delete (bug #51954). * variable-editor.cc (~variable_editor): Delete the m_main window from memory and, consequently, m_tool_bar and m_tab_widget.
author Daniel J Sebald <daniel.sebald@ieee.org>
date Wed, 06 Sep 2017 21:54:31 -0500
parents b0e696ea6487
children ca4ab27152a9
line wrap: on
line source

/*

Copyright (C) 2015 Michael Barnes
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 <algorithm>
#include <limits>

#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QFileDialog>
#include <QHeaderView>
#include <QLabel>
#include <QMainWindow>
#include <QMenu>
#include <QPalette>
#include <QSignalMapper>
#include <QTableView>
#include <QTabWidget>
#include <QToolBar>
#include <QToolButton>
#include <QVBoxLayout>

#include "octave-qt-link.h"
#include "resource-manager.h"
#include "variable-editor.h"
#include "variable-editor-model.h"

#include "operators/ops.h"
#include "ov.h"

namespace
{
  // Helper struct to store widget pointers in "data" Tab property.
  struct table_data
  {
    table_data (QTableView *t = nullptr)
      : m_table (t)
    { }

    QTableView *m_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)

static
QString idx_to_expr (int32_t from, int32_t to)
{
  if (from == to)
    return QString ("%1").arg (from + 1);
  else
    return QString ("%1:%2").arg (from + 1).arg (to + 1);
}

variable_editor::variable_editor (QWidget *p)
  : octave_dock_widget (p),
    m_main (new QMainWindow ()),
    m_tool_bar (new QToolBar (m_main)),
    m_tab_widget (new QTabWidget (m_main)),
    m_default_width (20), m_default_height (100),
    m_add_font_height (0),
    m_autofit (false), m_autofit_max (false),
    m_use_terminal_font (true), m_alternate_rows (true),
    m_stylesheet (""), m_font (), m_sel_font (),
    m_table_colors ()
{
  // Use a MainWindow
  setObjectName ("variable_editor");
  set_title (tr ("Variable Editor"));
  setStatusTip (tr ("Edit variables."));
  setWindowIcon (QIcon (":/actions/icons/logo.png"));

  // Tool Bar
  construct_tool_bar ();
  m_main->addToolBar (m_tool_bar);

  for (int i = 0; i < resource_manager::varedit_color_chars ().length (); i++)
    m_table_colors.append (QColor (Qt::white));

  // Tab Widget
  m_tab_widget->setTabsClosable (true);
  m_tab_widget->setMovable (true);
  connect (m_tab_widget, SIGNAL (tabCloseRequested (int)),
           this, SLOT (closeTab (int)));
  m_main->setCentralWidget (m_tab_widget);

  // Main
  m_main->setParent (this);
  setWidget (m_main);

  connect (this, SIGNAL (command_requested (const QString&)),
           p, SLOT (execute_command_in_terminal (const QString&)));
}

variable_editor::~variable_editor (void)
{
  // m_tool_bar and m_tab_widget are contained within m_main
  delete m_main;
}

void
variable_editor::edit_variable (const QString& name)
{
  if (m_stylesheet.isEmpty ())
    {
      QSettings *settings = resource_manager::get_settings ();
      notice_settings (settings);
    }

  const int tab_count = m_tab_widget->count ();
  for (int i = 0; i < tab_count; ++i)
    if (m_tab_widget->tabText (i) == name)
      {
        m_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);


  table->horizontalHeader ()->setContextMenuPolicy (Qt::CustomContextMenu);
  table->verticalHeader ()->setContextMenuPolicy (Qt::CustomContextMenu);

  connect (table->horizontalHeader (),
           SIGNAL (customContextMenuRequested (const QPoint&)),
           this, SLOT (columnmenu_requested (const QPoint&)));
  connect (table->verticalHeader (),
           SIGNAL (customContextMenuRequested (const QPoint&)),
           this, SLOT (rowmenu_requested (const QPoint&)));
  connect (table, SIGNAL (customContextMenuRequested (const QPoint&)),
           this, SLOT (contextmenu_requested (const QPoint&)));
  connect (table, SIGNAL (doubleClicked (const QModelIndex&)),
           this, SLOT (double_click (const QModelIndex&)));
  connect (model, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)),
           this, SLOT (callUpdate (const QModelIndex&, const QModelIndex&)));

  vbox->addWidget (table);

  page->setProperty ("data", QVariant::fromValue (table_data (table)));
  int tab_idx = m_tab_widget->addTab (page, name);
  m_tab_widget->setCurrentIndex (tab_idx);

  if (m_tab_widget->count () == 1)
    m_tool_bar->setEnabled (true);  // This is the first tab -> enable tool bar

  if (m_autofit)
    {
      table->resizeColumnsToContents ();
      if (m_autofit_max)
        {
          int mx = 0;
          for (int i = 0; i < table->model ()->columnCount (); i++)
            {
              if (table->columnWidth (i) > mx)
                mx = table->columnWidth (i);
            }
          table->horizontalHeader ()->setDefaultSectionSize (mx);
        }
    }
  else
    {
      table->horizontalHeader ()->setDefaultSectionSize (m_default_width);
    }
  table->setFont (m_font);
  table->setStyleSheet (m_stylesheet);
  table->setAlternatingRowColors (m_alternate_rows);
#if defined (HAVE_QT4)
  table->verticalHeader ()->setResizeMode (QHeaderView::Interactive);
#else
  table->verticalHeader ()->setSectionResizeMode (QHeaderView::Interactive);
#endif
  table->verticalHeader ()->setDefaultSectionSize (m_default_height
                                                   + m_add_font_height);
}

void variable_editor::clear_data_cache (void)
{
  for (int i = 0; i < m_tab_widget->count (); ++i)
    {
      QTableView *const table = get_table_data (m_tab_widget, i).m_table;
      QAbstractItemModel *const model = table->model ();
      qobject_cast<variable_editor_model *> (model)->clear_data_cache ();
    }
}

bool
variable_editor::has_focus (void)
{
  // FIXME: This only generates exceptions in certain circumstances.
  //        Get a definitive list and eliminate the need to handle exceptions.
  if (m_tab_widget->currentIndex () == -1)
    return false;  // No tabs

  try
    {
      QTableView *view = get_table_data (m_tab_widget).m_table;
      if (view)
        return view->hasFocus ();

      return false;
    }
  catch (...)
    {
      return false;
    }

  return false;
}

QList<QColor> variable_editor::default_colors (void)
{
  QList<QColor> colorlist;

  colorlist << qApp->palette ().color (QPalette::WindowText);
  colorlist << qApp->palette ().color (QPalette::Base);
  colorlist << qApp->palette ().color (QPalette::HighlightedText);
  colorlist << qApp->palette ().color (QPalette::Highlight);
  colorlist << qApp->palette ().color (QPalette::AlternateBase);

  return colorlist;
}

QStringList variable_editor::color_names (void)
{
  QStringList output;

  output << "Foreground";
  output << "Background";
  output << "Selected Foreground";
  output << "Selected Background";
  output << "Alternate Background";

  return output;
}

void
variable_editor::callUpdate (const QModelIndex&, const QModelIndex&)
{
  if (m_autofit)
    {
      QTableView *view = get_table_data (m_tab_widget).m_table;
      view->resizeColumnsToContents ();
      if (m_autofit_max)
        {
          int mx = 0;
          for (int i = 0; i < view->model ()->columnCount (); i++)
            {
              if (view->columnWidth (i) > mx)
                mx = view->columnWidth (i);
            }
          view->horizontalHeader ()->setDefaultSectionSize (mx);
        }

    }

  emit updated ();
}

void variable_editor::notice_settings (const QSettings *settings)
{
  // FIXME: Why use object->tostring->toint?  Why not just 100?
  m_default_width = settings->value ("variable_editor/column_width",
                                     QVariant ("100")).toString ().toInt ();
  m_autofit = settings->value ("variable_editor/autofit_column_width",
                               QVariant (false)).toBool ();
  // FIXME: Magic Number 1 here, why not use enum?
  if (m_autofit)
    {
      if (settings->value ("variable_editor/autofit_type", 0).toInt () == 1)
        m_autofit_max = true;
    }

  m_default_height = settings->value ("variable_editor/row_height",
                                    QVariant ("10")).toString ().toInt ();
  m_alternate_rows = settings->value ("variable_editor/alternate_rows",
                                    QVariant (false)).toBool ();

  QList<QColor> _default_colors = resource_manager::varedit_default_colors ();
  QString class_chars = resource_manager::varedit_color_chars ();

  m_use_terminal_font = settings->value ("variable_editor/use_terminal_font", true).toBool ();

  QString font_name;
  int font_size;

  if (m_use_terminal_font)
    {
      font_name = settings->value ("terminal/fontName", "").toString ();
      font_size = settings->value ("terminal/fontSize", 10).toInt ();
    }
  else
    {
      font_name = settings->value ("variable_editor/font_name", "").toString ();
      font_size = settings->value ("variable_editor/font_size", 10).toInt ();
    }
  m_font = QFont (font_name, font_size);

  if (settings->value ("variable_editor/autofit_row_height", false).toBool ())
    {
      QFontMetrics fm (m_font);
      m_add_font_height = fm.height ();
    }
  else
    m_add_font_height = 0;

  for (int i = 0; i < class_chars.length (); i++)
    {
      QVariant default_var = _default_colors.at (i);
      QColor setting_color = settings->value ("variable_editor/color_"
                                              + class_chars.mid (i, 1),
                                              default_var).value<QColor> ();
      m_table_colors.replace (i, setting_color);
    }
  update_colors ();

  // Icon size in the toolbar
  int icon_size_settings = settings->value ("toolbar_icon_size", 0).toInt ();
  QStyle *st = style ();
  int icon_size = st->pixelMetric (QStyle::PM_ToolBarIconSize);

  // FIXME: Magic numbers.  Use enum?
  if (icon_size_settings == 1)
    icon_size = st->pixelMetric (QStyle::PM_LargeIconSize);
  else if (icon_size_settings == -1)
    icon_size = st->pixelMetric (QStyle::PM_SmallIconSize);

  m_tool_bar->setIconSize (QSize (icon_size, icon_size));
}

void
variable_editor::closeEvent (QCloseEvent *e)
{
  emit finished ();
  octave_dock_widget::closeEvent (e);
}

void
variable_editor::closeTab (int idx)
{
  if (idx < 0 || idx > m_tab_widget->count ())
    return;

  QWidget *const wdgt = m_tab_widget->widget (idx);
  m_tab_widget->removeTab (idx);
  delete wdgt;

  if (m_tab_widget->count () == 0)
    m_tool_bar->setEnabled (false);  // This was the last tab, disable tool bar.
}

void
variable_editor::contextmenu_requested (const QPoint& qpos)
{
  QTableView *view = get_table_data (m_tab_widget).m_table;
  QModelIndex index = view->indexAt (qpos);

  if (index.isValid ())
    {
      QMenu *menu = new QMenu (this);
      menu->addAction (resource_manager::icon ("edit-cut"), tr ("Cut"),
                       this, SLOT (cutClipboard ()));
      menu->addAction (resource_manager::icon ("edit-copy"), tr ("Copy"),
                       this, SLOT (copyClipboard ()));
      menu->addAction (resource_manager::icon ("edit-paste"), tr ("Paste"),
                       this, SLOT (pasteClipboard ()));
      // FIXME: better icon for paste table?
      menu->addAction (resource_manager::icon ("edit-paste"), tr ("Paste Table"),
                       this, SLOT (pasteTableClipboard ()));

      menu->addSeparator ();

      menu->addAction (resource_manager::icon ("edit-delete"), tr ("Clear"),
                       this, SLOT (clearContent ()));
      menu->addAction (resource_manager::icon ("document-new"),
                       tr ("Variable from Selection"),
                       this, SLOT (createVariable ()));
      // FIXME: addAction for sort?
      menu->addAction ( //QIcon (), FIXME: Add icon for transpose
                       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));
    }
}

void
variable_editor::columnmenu_requested (const QPoint& pt)
{
  QTableView *view = get_table_data (m_tab_widget).m_table;

  int index = view->horizontalHeader ()->logicalIndexAt (pt);

  //emit command_requested (QString ("disp ('") + QString::number (index) + "');");

  if (index < 0 || index > view->model ()->columnCount ())
    return;

  QString selection = selected_to_octave ();
  QList<int> coords = octave_to_coords (selection);

  bool nothingSelected = false;
  if (coords.isEmpty ())
    nothingSelected = true;

  bool whole_columns_selected =
    nothingSelected ? false
    : (coords[0] == 1
       && coords[1] == view->model ()->rowCount ());

  bool current_column_selected =
    nothingSelected ? false : (coords[2] <= index+1 && coords[3] > index);

  int column_selection_count =
    nothingSelected ? 0 : (coords[3] - coords[2] + 1);

  if (! whole_columns_selected || ! current_column_selected)
    {
      view->selectColumn (index);
      column_selection_count = 1;
      current_column_selected = true;
      whole_columns_selected = true;
    }

  QString column_string =
    tr (column_selection_count > 1 ? " columns" : " column");

  QMenu *menu = new QMenu (this);
  menu->addAction (resource_manager::icon ("edit-cut"),
                   tr ("Cut") + column_string,
                   this, SLOT (cutClipboard ()));
  menu->addAction (resource_manager::icon ("edit-copy"),
                   tr ("Copy") + column_string,
                   this, SLOT (copyClipboard ()));
  menu->addAction (resource_manager::icon ("edit-paste"),
                   tr ("Paste"),
                   this, SLOT (pasteClipboard ()));
  // FIXME: better icon for Paste Table?
  menu->addAction (resource_manager::icon ("edit-paste"),
                   tr ("Paste Table"),
                   this, SLOT (pasteTableClipboard ()));

  menu->addSeparator ();

  menu->addAction (resource_manager::icon ("edit-delete"),
                   tr ("Clear") + column_string,
                   this, SLOT (clearContent ()));
  menu->addAction (resource_manager::icon ("edit-delete"),
                   tr ("Delete") + column_string,
                   this, SLOT (delete_selected ()));
  menu->addAction (resource_manager::icon ("document-new"),
                   tr ("Variable from Selection"),
                   this, SLOT (createVariable ()));

  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&)));

  QPoint menupos = pt;
  menupos.setY (view->horizontalHeader ()->height ());

  menu->exec (view->mapToGlobal (menupos));
}

void
variable_editor::rowmenu_requested (const QPoint& pt)
{
  QTableView *view = get_table_data (m_tab_widget).m_table;

  int index = view->verticalHeader ()->logicalIndexAt (pt);

  //emit command_requested (QString ("disp ('") + QString::number (index) + "');");

  if (index < 0 || index > view->model ()->columnCount ())
    return;

  QString selection = selected_to_octave ();
  QList<int> coords = octave_to_coords (selection);

  bool nothingSelected;
  if (coords.isEmpty ())
    nothingSelected = true;
  else
    nothingSelected = false;

  bool whole_rows_selected =
    nothingSelected ? false
    : (coords[2] == 1
       && coords[3] == view->model ()->columnCount ());

  bool current_row_selected =
    nothingSelected ? false
    : (coords[0] <= index+1 && coords[1] > index);

  int rowselection_count = nothingSelected ? 0 : (coords[3] - coords[2] + 1);

  if (! whole_rows_selected || ! current_row_selected)
    {
      view->selectRow (index);
      rowselection_count = 1;
      current_row_selected = true;
      whole_rows_selected = true;
    }

  QString row_string = tr (rowselection_count > 1 ? " rows" : " row");

  QMenu *menu = new QMenu (this);
  menu->addAction (resource_manager::icon ("edit-cut"),
                   tr ("Cut") + row_string,
                   this, SLOT (cutClipboard ()));
  menu->addAction (resource_manager::icon ("edit-copy"),
                   tr ("Copy") + row_string,
                   this, SLOT (copyClipboard ()));
  menu->addAction (resource_manager::icon ("edit-paste"),
                   tr ("Paste"),
                   this, SLOT (pasteClipboard ()));
  // FIXME: better icon for Paste Table?
  menu->addAction (resource_manager::icon ("edit-paste"),
                   tr ("Paste Table"),
                   this, SLOT (pasteTableClipboard ()));

  menu->addSeparator ();

  menu->addAction (resource_manager::icon ("edit-delete"),
                   tr ("Clear") + row_string,
                   this, SLOT (clearContent ()));
  menu->addAction (resource_manager::icon ("edit-delete"),
                   tr ("Delete") + row_string,
                   this, SLOT (delete_selected ()));
  menu->addAction (resource_manager::icon ("document-new"),
                   tr ("Variable from Selection"),
                   this, SLOT (createVariable ()));

  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&)));

  QPoint menupos = pt;
  menupos.setX (view->verticalHeader ()->width ());
  //setY (view->verticalHeader ()->sectionPosition (index+1) +
  //             view->verticalHeader ()->sectionSize (index));

  menu->exec (view->mapToGlobal (menupos));
}

void
variable_editor::double_click (const QModelIndex& idx)
{
  QString name = m_tab_widget->tabText (m_tab_widget->currentIndex ());
  QTableView *const table = get_table_data (m_tab_widget).m_table;
  variable_editor_model *const model =
    qobject_cast<variable_editor_model *> (table->model ());
  if (model->requires_sub_editor (idx))
    {
      if (model ->editor_type_matrix (idx))
        edit_variable (name +
                       model->parens ()
                       .arg (idx.row () + 1)
                       .arg (idx.column () + 1));
      /*        emit command_requested ("openvar ('" + name +
                model->parens ()
                .arg (idx.row () + 1)
                .arg (idx.column () + 1)
                + "');");
      */

    }
}

void
variable_editor::save (void)
{
  QString name = m_tab_widget->tabText (m_tab_widget->currentIndex ());
  QString file =
    QFileDialog::getSaveFileName (this,
                                  tr ("Save Variable %1 As").arg (name),
                                  ".", 0, 0,
                                  QFileDialog::DontUseNativeDialog);
  // FIXME: Type? binary, float-binary, ascii, text, hdf5, matlab format?
  if (! file.isEmpty ())
    // FIXME: Use octave_value::save_*?
    emit command_requested (QString ("save ('%1', '%2');")
                            .arg (file)
                            .arg (name));
}

void
variable_editor::clearContent (void)
{
  // FIXME: shift?
  QTableView *view = get_table_data (m_tab_widget).m_table;
  QAbstractItemModel *model = view->model ();
  QItemSelectionModel *sel = view->selectionModel ();
  QList<QModelIndex> indices = sel->selectedIndexes ();
  for (const auto& idx : indices)
    model->setData (idx, QVariant ("0"));  // FIXME: Use [] for empty cells
}

void
variable_editor::cutClipboard (void)
{
  if (! has_focus ())
    return;

  copyClipboard ();
  clearContent ();
}

void
variable_editor::copyClipboard (void)
{
  if (! has_focus ())
    return;

  QTableView *view = get_table_data (m_tab_widget).m_table;
  QAbstractItemModel *model = view->model ();
  QItemSelectionModel *sel = view->selectionModel ();
  QList<QModelIndex> indices = sel->selectedIndexes ();
  qSort (indices);
  if (indices.isEmpty ())
    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.push_back (previous.row () != idx.row () ? '\n' : '\t');
      copy.append (model->data (idx).toString ());
      previous = idx;
    }
  copy.push_back ('\n');

  QClipboard *clipboard = QApplication::clipboard ();
  clipboard->setText (copy);
}

void
variable_editor::pasteClipboard (void)
{
  // FIXME: ???
  if (! has_focus ())
    return;

  QClipboard *clipboard = QApplication::clipboard ();
  QString text = clipboard->text ();

  QTableView *view = get_table_data (m_tab_widget).m_table;
  QItemSelectionModel *sel = view->selectionModel ();
  QList<QModelIndex> indices = sel->selectedIndexes ();

  variable_editor_model *model
    = static_cast<variable_editor_model *> (view->model ());

  if (indices.isEmpty ())
    {
      if (view->size () == QSize (1,1))
        {
          model->setData (view->model ()->index (0,0), text.toDouble ());
        }
      else if (view->size () == QSize (0,0))
        {
          model->insertColumn (0);
          model->insertRow (0);
          model->setData (view->model ()->index (0,0), text.toDouble ());
        }
    }
  else
    {
      for (int i = 0; i < indices.size (); i++)
        view->model ()->setData (indices[i], text.toDouble ());
    }

  emit updated ();
}

void variable_editor::pasteTableClipboard (void)
{
  if (! has_focus ())
    return;

  QClipboard *clipboard = QApplication::clipboard ();
  QString text = clipboard->text ();

  QTableView *view = get_table_data (m_tab_widget).m_table;
  QItemSelectionModel *sel = view->selectionModel ();
  QList<QModelIndex> indices = sel->selectedIndexes ();

  variable_editor_model *model =
    static_cast<variable_editor_model *> (view->model ());

  QPoint start, end;

  QPoint tabsize = QPoint (model->rowCount (), model->columnCount ());

  if (indices.isEmpty ())
    {
      start = QPoint (0,0);
      end = tabsize;
    }
  else if (indices.size () == 1)
    {
      start = QPoint (indices[0].row (), indices[0].column ());
      end = tabsize;
    }
  else
    {
      end = QPoint (0,0);
      start = tabsize;

      for (int i = 0; i < indices.size (); i++)
        {
          if (indices[i].column () < start.y ())
            start.setY (indices[i].column ());

          if (indices[i].column () > end.y ())
            end.setY (indices[i].column ());

          if (indices[i].row () < start.x ())
            start.setX (indices[i].column ());

          if (indices[i].row () > end.x ())
            end.setX (indices[i].column ());

        }
    }

  int rownum = 0;
  int colnum = 0;

  QStringList rows = text.split ('\n');
  for (const auto& row : rows)
    {
      if (rownum > end.x () - start.x ())
        continue;

      QStringList cols = row.split ('\t');
      if (cols.isEmpty ())
        continue;

      for (const auto& col : cols)
        {
          if (col.isEmpty ())
            continue;
          if (colnum > end.y () - start.y () )
            continue;

          model->setData (model->index (rownum + start.x (),
                                        colnum + start.y ()),
                          QVariant (col));

          //          relay_command ("disp ('" + QString::number (colnum+start.y ()) + "," + QString::number (rownum+start.x ()) +"');");
          colnum++;
        }
      colnum = 0;
      rownum++;
    }

  emit updated ();
}

void
variable_editor::createVariable (void)
{
  // FIXME: unnamed1..n if exist ('unnamed', 'var')
  relay_command ("unnamed = %1");
}

void
variable_editor::transposeContent (void)
{
  QString name = m_tab_widget->tabText (m_tab_widget->currentIndex ());
  emit command_requested (QString ("%1 = %1';").arg (name));
  emit updated ();
}

void
variable_editor::up (void)
{
  QString name = m_tab_widget->tabText (m_tab_widget->currentIndex ());
  // FIXME: is there a better way?
  if (name.endsWith (')') || name.endsWith ('}'))
    {
      qDebug () << "up";
      name.remove (QRegExp ("(\\(|\\{)[^({]*(\\)|\\})$"));
      edit_variable (name);
      //emit command_requested (QString ("openvar ('%1');").arg (name));
    }
}

void
variable_editor::delete_selected (void)
{
  QTableView *view = get_table_data (m_tab_widget).m_table;
  QString selection = selected_to_octave ();
  QList<int> coords = octave_to_coords (selection);

  if (coords.isEmpty ())
    return;

  bool whole_columns_selected = coords[0] == 1
    && coords[1] == view->model ()->rowCount ();
  bool whole_rows_selected = coords[2] == 1
    && coords[3] == view->model ()->columnCount ();

  emit command_requested (QString ("disp ('")
                          + QString::number (coords[0]) + ","
                          + QString::number (coords[1]) + ","
                          + QString::number (coords[2]) + ","
                          + QString::number (coords[3]) + "');");

  // Must be deleting whole columns or whole rows, and not the whole thing.
  if (whole_columns_selected == whole_rows_selected)  // all or nothing
    return;

  if (whole_rows_selected)
    view->model ()->removeRows (coords[0], coords[1] - coords[0]);

  if (whole_columns_selected)
    view->model ()->removeColumns (coords[2], coords[3] - coords[2]);

  emit updated ();
}

void
variable_editor::relay_command (const QString& cmd)
{
  emit command_requested (cmd.arg (selected_to_octave ()));
}

QList<int>
variable_editor::octave_to_coords (QString& selection)
{
  // FIXME: Is this necessary or would it be quicker to clone the function that
  // gives us the QString?

  // sanity check
  if (selection.count (",") != 1)
    return QList<int> ();

  QList<int> output;
  output.clear ();
  // remove braces
  int firstbracket = std::max (selection.indexOf ("("),
                               selection.indexOf ("{"));
  selection = selection.mid (firstbracket + 1,
                             selection.length () - (firstbracket + 2));

  QString rows = selection.left (selection.indexOf (","));
  if (! rows.contains (":"))
    {
      // Only one row
      output.push_back (rows.toInt ());
      output.push_back (output.last ());
    }
  else
    {
      output.push_back (rows.left (rows.indexOf (":")).toInt ());
      output.push_back (rows.right (rows.length () - (rows.indexOf (":") + 1))
                        .toInt ());
    }

  QString cols;
  cols = selection.right (selection.length () - (selection.indexOf (",") + 1));
  if (cols.left (1) == " ")
    cols = cols.right (cols.length () - 1);

  if (! cols.contains (":"))
    {
      // Only one row
      output.push_back (cols.toInt ());
      output.push_back (output.last ());
    }
  else
    {
      output.push_back (cols.left (cols.indexOf (":")).toInt ());
      output.push_back (cols.right (cols.length () - (cols.indexOf (":") + 1))
                        .toInt ());
    }

  return output;
}

QString
variable_editor::selected_to_octave (void)
{
  QString name = m_tab_widget->tabText (m_tab_widget->currentIndex ());
  QTableView *view = get_table_data (m_tab_widget).m_table;
  QItemSelectionModel *sel = view->selectionModel ();

  if (! sel->hasSelection ())
    return name;  // Nothing selected

  QList<QModelIndex> indices = sel->selectedIndexes ();  // it's indices!

  int32_t from_row = std::numeric_limits<int32_t>::max ();
  int32_t to_row = 0;
  int32_t from_col = std::numeric_limits<int32_t>::max ();
  int32_t to_col = 0;

  for (const auto& 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);

  // FIXME: Cells?
  return QString ("%1 (%2, %3)").arg (name).arg (rows).arg (cols);
}

/// Also updates the font
void variable_editor::update_colors (void)
{
  m_stylesheet = "";
  m_stylesheet += "QTableView::item{ foreground-color: "
    + m_table_colors[0].name () +" }";
  m_stylesheet += "QTableView::item{ background-color: "
    + m_table_colors[1].name () +" }";
  m_stylesheet += "QTableView::item{ selection-color: "
    + m_table_colors[2].name () +" }";
  m_stylesheet += "QTableView::item:selected{ background-color: "
    + m_table_colors[3].name () +" }";
  if (m_table_colors.length () > 4 && m_alternate_rows)
    {
      m_stylesheet += "QTableView::item:alternate{ background-color: "
        + m_table_colors[4].name () +" }";
      m_stylesheet += "QTableView::item:alternate:selected{ background-color: "
        + m_table_colors[3].name () +" }";
    }

  if (m_tab_widget->count () < 1)
    return;

  for (int i=0; i < m_tab_widget->count (); i++)
    {
      QTableView *view = get_table_data (m_tab_widget).m_table;
      view->setAlternatingRowColors (m_alternate_rows);
      view->setStyleSheet (m_stylesheet);
      view->setFont (m_font);
    }

}

void
variable_editor::construct_tool_bar (void)
{
  m_tool_bar->setObjectName ("VariableEditorToolBar");
  m_tool_bar->setWindowTitle (tr ("Variable Editor Toolbar"));

  m_tool_bar->addAction (resource_manager::icon ("document-save"), tr ("Save"),
                         this, SLOT (save ()));
  m_tool_bar->addSeparator ();

  m_tool_bar->addAction (resource_manager::icon ("edit-cut"), tr ("Cut"),
                         this, SLOT (cutClipboard ()));
  m_tool_bar->addAction (resource_manager::icon ("edit-copy"), tr ("Copy"),
                         this, SLOT (copyClipboard ()));
  m_tool_bar->addAction (resource_manager::icon ("edit-paste"), tr ("Paste"),
                         this, SLOT (pasteClipboard ()));
  // FIXME: Different icon for Paste Table?
  m_tool_bar->addAction (resource_manager::icon ("edit-paste"), tr ("Paste Table"),
                         this, SLOT (pasteTableClipboard ()));
  m_tool_bar->addSeparator ();

  // FIXME: Add a print item?
  //QAction *print_action; /icons/fileprint.png
  //m_tool_bar->addSeparator ();

  QToolButton *plot_tool_button = new QToolButton (m_tool_bar);
  plot_tool_button->setText (tr ("Plot"));
  plot_tool_button->setIcon (resource_manager::icon ("applications-system"));

  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);
  m_tool_bar->addWidget (plot_tool_button);

  m_tool_bar->addSeparator ();
  m_tool_bar->addAction (QIcon (resource_manager::icon ("go-up")), tr ("Up"),
                         this, SLOT (up ()));

  m_tool_bar->setEnabled (false);  // Disabled when no tab is present
}