view libgui/src/variable-editor-model.cc @ 24644:e04a56630c8a

variable-editor-model.cc: Fix missing parenthesis preventing compilation. * variable-editor-model.cc (type_is_editable): Fix missing parenthesis preventing compilation.
author Rik <rik@octave.org>
date Fri, 26 Jan 2018 19:50:04 -0800
parents 78aff6f14227
children 456b486ae5e6
line wrap: on
line source

/*

Copyright (C) 2013-2017 John W. Eaton
Copyright (C) 2015 Michael Barnes
Copyright (C) 2013 RĂ¼diger Sonderfeld

This file is part of Octave.

Octave is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.

Octave is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING.  If not, see
<https://www.gnu.org/licenses/>.

*/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <sstream>

#include <QDebug>
#include <QLabel>
#include <QMessageBox>
#include <QString>
#include <QVector>

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

#include "ov.h"
#include "parse.h"
#include "utils.h"
#include "variables.h"

// Pimpl/Dpointer for variable_editor_model.

static QString
make_label (const std::string& name, const octave_value& val)
{
  QString lbl_txt = QString::fromStdString (name);

  if (val.is_defined ())
    {
      if (! lbl_txt.isEmpty ())
        lbl_txt += " ";

      dim_vector dv = val.dims ();

      lbl_txt += ("["
                  + QString::fromStdString (dv.str ())
                  + " "
                  + QString::fromStdString (val.class_name ())
                  + "]");
    }
  else
    lbl_txt += " [undefined]";

  return lbl_txt;
}

static void
get_rows_and_columns (const octave_value& val, int& rows, int& cols)
{
  rows = val.rows ();
  cols = val.columns ();
}

struct variable_editor_model::impl
{
  struct cell
  {
    cell (void) : m_defined (false) { }

    cell (const octave_value& val, int r, int c)
      : m_defined (true), m_data ("no display"), m_status_tip ("status"),
        m_tool_tip ("tip"), m_requires_sub_editor (false),
        m_editor_type (sub_none)
    {
      if (val.iscell ())
        {
          Cell cval = val.cell_value ();

          octave_value ov = cval(r,c);
          dim_vector dv = ov.dims ();

          m_requires_sub_editor = true;

          m_data = make_label ("", ov);
        }
      else
        {
          m_data = QString::fromStdString (val.edit_display (r, c));
        }
    }

    cell (const QString& d, const QString& s, const QString& t,
          bool rse, sub_editor_types edtype)
      : m_defined (true), m_data (d), m_status_tip (s), m_tool_tip (t),
        m_requires_sub_editor (rse), m_editor_type (edtype)
    { }

    bool m_defined;

    QVariant m_data;

    QVariant m_status_tip;

    QVariant m_tool_tip;

    QVariant m_background;

    bool m_requires_sub_editor;

    sub_editor_types m_editor_type;

    // FIXME: Other variables needed?
  };

  impl (void) = delete;

  impl (const QString& name, const octave_value& val, QLabel *label)
    : m_name (name.toStdString ()), m_value (val),
      m_rows (0), m_cols (0), m_table (), m_label (label),
      m_validity (true), m_validtext (make_label (m_name, m_value))
  {
  }

  impl (const impl&) = delete;

  impl& operator = (const impl&) = delete;

  int size (void) const { return m_table.size (); }
  int rows (void) const { return m_rows; }
  int columns (void) const { return m_cols; }

  int index (int r, int c) const { return c * m_rows + r; }
  int index (const QModelIndex& idx) const
  {
    return index (idx.row (), idx.column ());
  }

  cell& elem (int i) { return m_table[i]; }
  cell& elem (int r, int c) { return elem (index (r, c)); }
  cell& elem (const QModelIndex& idx) { return elem (index (idx)); }

  const cell& elem (int i) const { return m_table[i]; }
  const cell& elem (int r, int c) const { return elem (index (r, c)); }
  const cell& elem (const QModelIndex& idx) const { return elem (index (idx)); }

  void update (const QModelIndex& idx)
  {
    if (is_defined (idx))
      return;

    if (idx.isValid ())
      {
        int r = idx.row ();
        int c = idx.column ();

        cell edit_cell (m_value, r, c);

        set (r, c, edit_cell);
      }
  }

  octave_value value_at (const QModelIndex& idx) const
  {
    if (! m_value.iscell ())
      return octave_value ();

    Cell cval = m_value.cell_value ();

    return cval.elem (idx.row (), idx.column ());
  }

  void set (const QModelIndex& idx, const cell& dat)
  {
    if (idx.isValid ())
      elem (idx) = dat;
  }

  void set (int r, int c, const cell& dat)
  {
    if (0 <= r && r < rows () && 0 <= c && c <= columns ())
      elem (r, c) = dat;
  }

  bool is_defined (int r, int c) const { return elem (r, c).m_defined; }

  bool is_defined (const QModelIndex& idx) const
  {
    return (idx.isValid () && elem (idx).m_defined);
  }

  bool requires_sub_editor (const QModelIndex& idx)
  {
    return (idx.isValid () && elem (idx).m_requires_sub_editor);
  }

  sub_editor_types sub_editor_type (const QModelIndex& idx)
  {
    return (idx.isValid () ? elem (idx).m_editor_type : sub_none);
  }

  void clear (int i) { elem (i).m_defined = false; }
  void clear (int r, int c) { clear (index (r, c)); }
  void clear (const QModelIndex& idx) { clear (index (idx)); }

  void clear (void)
  {
    for (int i = 0; i < size (); ++i)
      clear (i);
  }

  void reset (const octave_value& val)
  {
    m_validity = false;

    m_table.clear ();

    int r = 0;
    int c = 0;

    m_value = val;

    if (m_value.is_defined ())
      {
        m_validity = true;

        get_rows_and_columns (m_value, r, c);
      }

    m_rows = r;
    m_cols = c;

    m_table.resize (r * c);

    m_label->setTextFormat (Qt::PlainText);

    m_validtext = make_label (m_name, m_value);
  }

  void invalidate (void)
  {
    reset (octave_value ());
  }

  QVariant data (const QModelIndex& idx, int role)
  {
    update (idx);

    if (idx.isValid ())
      {
        switch (role)
          {
          case Qt::DisplayRole:
          case Qt::EditRole:
            return elem (idx).m_data;

          case Qt::StatusTipRole:
            return elem (idx).m_status_tip;

          case Qt::ToolTipRole:
            return elem (idx).m_tool_tip;

          case Qt::BackgroundRole:
            return elem (idx).m_background;
          }
      }

    return QVariant ();
  }

  const std::string m_name;

  octave_value m_value;

  // Using QVector limits the size to int.
  int m_rows;
  int m_cols;

  QVector<cell> m_table;

  QLabel *m_label;

  bool m_validity;

  QString m_validtext;
};

variable_editor_model::variable_editor_model (const QString& expr,
                                              const octave_value& val,
                                              QLabel *label,
                                              QObject *parent)
  : QAbstractTableModel (parent), m_parent (parent),
    m_d (new impl (expr, val, label))
{
  connect (this, SIGNAL (user_error_signal (const QString&, const QString&)),
           this, SLOT (user_error (const QString&, const QString&)));

  connect (this, SIGNAL (update_data_signal (const octave_value&)),
           this, SLOT (update_data (const octave_value&)));

  connect (this, SIGNAL (data_error_signal (const QString&)),
           this, SLOT (data_error (const QString&)));

  connect (this, SIGNAL (clear_data_cell_signal (int, int)),
           this, SLOT (clear_data_cell (int, int)));

  connect (this, SIGNAL (resize_columns_signal (void)),
           parent, SLOT (resizeColumnsToContents (void)));

  if (! type_is_editable (val))
    return;

  // Initializes everything.

  int rows = 0;
  int cols = 0;

  get_rows_and_columns (val, rows, cols);

  m_d->reset (val);

  beginInsertRows (QModelIndex (), 0, rows-1);
  endInsertRows ();

  beginInsertColumns (QModelIndex (), 0, cols-1);
  endInsertColumns ();
}

variable_editor_model::~variable_editor_model (void)
{
  delete m_d;
}

octave_value
variable_editor_model::value_at (const QModelIndex& idx) const
{
  return m_d->value_at (idx);
}

int
variable_editor_model::rowCount (const QModelIndex&) const
{
  return m_d->m_validity ? m_d->rows () : 1;
}

int
variable_editor_model::columnCount (const QModelIndex&) const
{
  return m_d->m_validity ? m_d->columns () : 1;
}

QVariant
variable_editor_model::data (const QModelIndex& idx, int role) const
{
  if (! m_d->m_validity)
    {
      if (idx.isValid ())
        {
          if (role == Qt::DisplayRole)
            return QVariant (QString ("Variable %d not found")
                             .arg (QString::fromStdString (m_d->m_name)));
        }

      return QVariant (QString ("x"));
    }

  if (idx.isValid ())
    return m_d->data (idx, role);

  // Invalid.
  return QVariant ();
}

bool
variable_editor_model::setData (const QModelIndex& idx, const QVariant& v,
                                int role)
{
  if (role != Qt::EditRole || v.type () != QVariant::String
      || ! idx.isValid ())
    return false;

  // Initially, set value to whatever the user entered.

  int r = idx.row ();
  int c = idx.column ();

  QString vstr = v.toString ();

  m_d->set (r, c, impl::cell (vstr, "", "", false, sub_none));

  // Evaluate the string that the user entered.  If that fails, we
  // will restore previous value.

  octave_link::post_event<variable_editor_model,
                          std::string, int, int, std::string>
    (this, &variable_editor_model::set_data_oct,
     m_d->m_name, r, c, vstr.toStdString ());

  // This is success so far...

  return true;
}

Qt::ItemFlags
variable_editor_model::flags (const QModelIndex& idx) const
{
  if (m_d->m_validity)
    {
      if (requires_sub_editor (idx))
        {
          if (editor_type (idx) != sub_string)
            return QAbstractTableModel::flags (idx);
        }

      return QAbstractTableModel::flags (idx) | Qt::ItemIsEditable;
    }

  return Qt::NoItemFlags;
}

bool
variable_editor_model::insertRows (int row, int count, const QModelIndex&)
{
  // FIXME: cells?

  octave_link::post_event <variable_editor_model, std::string, std::string>
    (this, &variable_editor_model::eval_oct, m_d->m_name,
     QString ("%1 = [ %1(1:%2,:) ; zeros(%3, columns(%1)) ; %1(%2+%3:end,:) ]")
     .arg (QString::fromStdString (m_d->m_name))
     .arg (row)
     .arg (count)
     .toStdString ());

  return true;
}

bool
variable_editor_model::removeRows (int row, int count, const QModelIndex&)
{
  if (row + count > m_d->rows ())
    {
      qDebug () << "Tried to remove too many rows " << m_d->rows () << " "
                << count << " (" << row << ")";
      return false;
    }

  octave_link::post_event <variable_editor_model, std::string, std::string>
    (this, &variable_editor_model::eval_oct, m_d->m_name,
     QString ("%1(%2:%3, :) = []")
     .arg (QString::fromStdString (m_d->m_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, std::string, std::string>
    (this, &variable_editor_model::eval_oct, m_d->m_name,
     QString ("%1 = [ %1(:,1:%2) ; zeros(rows(%1), %3) %1(:,%2+%3:end) ]")
     .arg (QString::fromStdString (m_d->m_name))
     .arg (col)
     .arg (count)
     .toStdString ());

  return true;
}

bool
variable_editor_model::removeColumns (int col, int count, const QModelIndex&)
{
  if (col + count > m_d->columns ())
    {
      qDebug () << "Tried to remove too many cols " << m_d->columns () << " "
                << count << " (" << col << ")";
      return false;
    }

  octave_link::post_event <variable_editor_model, std::string, std::string>
    (this, &variable_editor_model::eval_oct, m_d->m_name,
     QString ("%1(:, %2:%3) = []")
     .arg (QString::fromStdString (m_d->m_name))
     .arg (col)
     .arg (col + count)
     .toStdString ());

  return true;
}

void
variable_editor_model::update_data_cache (void)
{
  octave_link::post_event
    (this, &variable_editor_model::init_from_oct, m_d->m_name);
}

bool
variable_editor_model::requires_sub_editor (const QModelIndex& idx) const
{
  return m_d->requires_sub_editor (idx);
}

bool variable_editor_model::editor_type_matrix (const QModelIndex& idx) const
{
  return m_d->sub_editor_type (idx) == sub_matrix;
}

bool variable_editor_model::editor_type_string (const QModelIndex& idx) const
{
  return m_d->sub_editor_type (idx) == sub_string;
}

QString
variable_editor_model::subscript_expression (const QModelIndex& idx) const
{
  return (QString (m_d->m_value.iscell () ? "{%1, %2}" : "(%1, %2)")
          .arg (idx.row () + 1)
          .arg (idx.column () + 1));
}

// Private slots.

void
variable_editor_model::user_error (const QString& title, const QString& msg)
{
  QMessageBox::critical (nullptr, title, msg);
}

void
variable_editor_model::update_data (const octave_value& val)
{
  if (val.is_undefined ())
    {
      QString msg = (QString ("variable '%1' is invalid or undefined")
                     .arg (QString::fromStdString (m_d->m_name)));

      emit data_error_signal (msg);

      return;
    }

  if (! type_is_editable (val))
    return;

  // Add or remove rows and columns when the size changes, but always
  // invalidate the entire m_table cache because we don't know which
  // elements of val have changed.

  int old_rows = m_d->rows ();
  int old_cols = m_d->columns ();

  int new_rows = 0;
  int new_cols = 0;

  get_rows_and_columns (val, new_rows, new_cols);

  m_d->reset (val);

  if (new_rows < old_rows)
    {
      beginRemoveRows (QModelIndex (), new_rows, old_rows-1);
      endRemoveRows ();
    }
  else if (new_rows > old_rows)
    {
      beginInsertRows (QModelIndex (), old_rows, new_rows-1);
      endInsertRows ();
    }

  if (new_cols < old_cols)
    {
      beginRemoveColumns (QModelIndex (), new_cols, old_cols-1);
      endRemoveColumns ();
    }
  else if (new_cols > old_cols)
    {
      beginInsertColumns (QModelIndex (), old_cols, new_cols-1);
      endInsertColumns ();
    }

  display_valid ();

  emit dataChanged (QAbstractTableModel::index (0, 0),
                    QAbstractTableModel::index (new_rows-1, new_cols-1));

  emit resize_columns_signal ();
}

// Private.

// val has to be copied!

void
variable_editor_model::set_data_oct (const std::string& name,
                                     const int& row, const int& col,
                                     const std::string& rhs)
{
  // INTERPRETER THREAD

  std::string expr;

  try
    {
      int parse_status = 0;

      std::ostringstream os;
      os << name << "(" << row+1 << "," << col+1 << ") = " << rhs;

      expr = os.str ();

      octave::eval_string (expr, true, parse_status);

      octave_value val = retrieve_variable (name);

      emit update_data_signal (val);
    }
  catch (octave::execution_exception&)
    {
      evaluation_error (expr);

      // This will ultimately cause the data in the cell to be reset
      // from the cached octave_value object.

      emit clear_data_cell_signal (row, col);
    }
}

void
variable_editor_model::init_from_oct (const std::string& name)
{
  // INTERPRETER THREAD

  try
    {
      octave_value val = retrieve_variable (name);

      m_d->m_validity = true;

      emit update_data_signal (val);
    }
  catch (octave::execution_exception&)
    {
      QString msg = (QString ("variable '%1' is invalid or undefined")
                     .arg (QString::fromStdString (name)));

      emit data_error_signal (msg);
    }
}

void
variable_editor_model::eval_oct (const std::string& name, const std::string& x)
{
  // INTERPRETER THREAD

  try
    {
      int parse_status = 0;

      octave::eval_string (x, true, parse_status);

      init_from_oct (name);
    }
  catch  (octave::execution_exception&)
    {
      evaluation_error (x);
    }
}

// If the variable exists, load it into the data model.  If it doesn't
// exist, flag the data model as referring to a nonexistent variable.
// This allows the variable to be opened before it is created.

// This function should only be called within other functions that
// execute in the interpreter thread.  It should also be called in a
// try-catch block that catches execution exceptions.

octave_value
variable_editor_model::retrieve_variable (const std::string& x)
{
  // INTERPRETER THREAD

  std::string name = x;

  if (x.back () == ')' || x.back () == '}')
    name = x.substr (0, x.find (x.back () == ')' ? "(" : "{"));

  if (symbol_exist (name, "var") > 0)
    {
      int parse_status = 0;

      return octave::eval_string (x, true, parse_status);
    }

  return octave_value ();
}

sub_editor_types variable_editor_model::editor_type (const QModelIndex& idx) const
{
  return m_d->sub_editor_type (idx);
}

void
variable_editor_model::invalidate (void)
{
  beginResetModel ();

  m_d->invalidate ();

  endResetModel ();
}

void
variable_editor_model::data_error (const QString& msg)
{
  invalidate ();

  m_d->m_label->setTextFormat (Qt::PlainText);

  m_d->m_label->setText (msg);

  dynamic_cast<QWidget *> (m_parent)->setVisible (false);
}

void
variable_editor_model::display_valid (void)
{
  m_d->m_label->setTextFormat (Qt::PlainText);

  m_d->m_label->setText (m_d->m_validtext);

  dynamic_cast<QWidget *> (m_parent)->setVisible (true);
}

void
variable_editor_model::clear_data_cell (int r, int c)
{
  m_d->clear (r, c);
}

bool
variable_editor_model::type_is_editable (const octave_value& val,
                                         bool display_error) const
{
  if ((val.isnumeric () || val.islogical () || val.iscell ())
      && val.ndims () == 2)
    return true;

  if (display_error)
    {
      QString tname = QString::fromStdString (val.type_name ());

      dim_vector dv = val.dims ();
      QString dimstr = QString::fromStdString (dv.str ());

      emit data_error_signal (QString ("unable to edit [%1] '%2' objects")
                              .arg (dimstr)
                              .arg (tname));
    }

  return false;
}

void
variable_editor_model::evaluation_error (const std::string& expr) const
{
  emit user_error_signal ("Evaluation failed",
                          QString ("failed to evaluate expression: '%1'")
                          .arg (QString::fromStdString (expr)));
}