view libgui/graphics/Table.cc @ 30564:796f54d4ddbf stable

update Octave Project Developers copyright for the new year In files that have the "Octave Project Developers" copyright notice, update for 2021. In all .txi and .texi files except gpl.txi and gpl.texi in the doc/liboctave and doc/interpreter directories, change the copyright to "Octave Project Developers", the same as used for other source files. Update copyright notices for 2022 (not done since 2019). For gpl.txi and gpl.texi, change the copyright notice to be "Free Software Foundation, Inc." and leave the date at 2007 only because this file only contains the text of the GPL, not anything created by the Octave Project Developers. Add Paul Thomas to contributors.in.
author John W. Eaton <jwe@octave.org>
date Tue, 28 Dec 2021 18:22:40 -0500
parents 97a3e7ba640d
children
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2016-2022 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// This file is part of Octave.
//
// Octave is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Octave is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Octave; see the file COPYING.  If not, see
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <QCheckBox>
#include <QComboBox>
#include <QEvent>
#include <QFrame>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QModelIndexList>
#include <QMouseEvent>
#include <QString>
#include <QStringList>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QTimer>

#include "Container.h"
#include "ContextMenu.h"
#include "Table.h"
#include "QtHandlesUtils.h"

#include "octave-qobject.h"

#include "graphics.h"
#include "interpreter.h"
#include "oct-map.h"
#include "oct-stream.h"
#include "oct-string.h"
#include "oct-strstrm.h"

namespace octave
{

  static const int AUTO_WIDTH = 75;

#define AUTO_HEIGHT (tp.get_fontsize () * 2 - 1)

  static QSize realQSizeForTable (QTableWidget *t)
  {
    int w = t->verticalHeader ()->width () + 4;
    for (int i = 0; i < t->columnCount (); i++)
      w += t->columnWidth (i);
    int h = t->horizontalHeader ()->height () + 4;
    for (int i = 0; i < t->rowCount (); i++)
      h += t->rowHeight (i);
    return QSize (w, h);
  }

#define FORMATNUMBER(type)                                                    \
  static QString formatNumber (type d,                                        \
                               char format = 'f',                             \
                               int precision = 4)                             \
  {                                                                           \
    type ten = 10;                                                            \
    if (format == 'n')                                                        \
      {                                                                       \
        if (d == floor (d))                                                   \
          return QString::number (d, 'g', precision);                         \
        else if (d <= pow (ten, precision - 1)                                \
                 && d > pow (ten, 1 - precision))                             \
          return QString::number (d, 'f', precision);                         \
        else                                                                  \
          return QString::number (d, 'e', precision);                         \
      }                                                                       \
    else if (format == 'F')                                                   \
      {                                                                       \
        int exponent = floor (log10 (d) / 3) * 3;                             \
        d *= pow (ten, -exponent);                                            \
        return QString::number (d, 'f', precision) + "e" +                    \
          (exponent < 0 ? "-" : "+") +                                        \
          QString ("%1").arg (abs (exponent), 3, 10, QChar ('0'));            \
      }                                                                       \
    else if (format == 'E')                                                   \
      {                                                                       \
        int exponent = floor (log10 (d) / 3) * 3;                             \
        d *=  pow (ten, -exponent);                                           \
        return QString::number (d,                                            \
                                'f',                                          \
                                precision - floor (log10 (d)) - 1) +          \
               "e" + (exponent < 0 ? "-" : "+") +                             \
               QString ("%1").arg (abs (exponent), 3, 10, QChar ('0'));       \
      }                                                                       \
    else                                                                      \
      return QString::number (d, format, precision);                          \
  }

  FORMATNUMBER(double)
  FORMATNUMBER(float)

#undef FORMATNUMBER

  static QString formatComplex (Complex c, char format = 'f', int precision = 4)
  {
    return formatNumber (c.real (), format, precision) + " + "
           + formatNumber (c.imag (), format, precision) + "i";
  }

#define FORMAT_VALUE_EXCEPT_RAT(f,l)                      \
  if (format == "numeric" || format == "short")           \
    text = formatNumber (value, 'n', f);                  \
  else if (format == "short f" || format == "shortf")     \
    text = formatNumber (value, 'f', f);                  \
  else if (format == "short e" || format == "shorte")     \
    text = formatNumber (value, 'e', f);                  \
  else if (format == "short eng" || format == "shorteng") \
    text = formatNumber (value, 'F', f);                  \
  else if (format == "short g" || format == "shortg")     \
    text = formatNumber (value, 'g', f + 1);              \
  else if (format == "long")                              \
    text = formatNumber (value, 'n', l);                  \
  else if (format == "long f" || format == "longf")       \
    text = formatNumber (value, 'f', l);                  \
  else if (format == "long e" || format == "longe")       \
    text = formatNumber (value, 'e', l);                  \
  else if (format == "long eng" || format == "longeng")   \
    text = formatNumber (value, 'E', l);                  \
  else if (format == "long g" || format == "longg")       \
    text = formatNumber (value, 'g', l + 1);              \
  else if (format == "bank")                              \
    text = QString::number (value, 'f', 2);               \
  else if (format == "+")                                 \
    if (value > 0)                                        \
      text = Utils::fromStdString ("+");                  \
    else if (value < 0)                                   \
      text = Utils::fromStdString ("-");                  \
    else                                                  \
      text = Utils::fromStdString ("");

#define FORMAT_VALUE(f,l)                                               \
  FORMAT_VALUE_EXCEPT_RAT(f,l)                                          \
  else if (format == "rat")                                             \
    text = Utils::fromStdString (rational_approx (double (value), 0));  \
  else                                                                  \
    {                                                                   \
      text = formatNumber (value, 'n', f);                              \
      flag = Qt::AlignLeft ;                                            \
    }

#define FORMAT_UINT_VALUE()                    \
  text = QString::number (value);              \
  if (format == "char"  || format == "popup")  \
    flag = Qt::AlignLeft;                      \
  else if (format == "+")                      \
    {                                          \
      if (value > 0)                           \
        text = Utils::fromStdString ("+");     \
      else                                     \
        text = Utils::fromStdString ("");      \
    }

#define FORMAT_INT_VALUE()                     \
  text = QString::number (value);              \
  if (format == "char" || format == "popup")   \
    flag = Qt::AlignLeft ;                     \
  else if (format == "+")                      \
    {                                          \
      if (value > 0)                           \
        text = Utils::fromStdString ("+");     \
      else if (value < 0)                      \
        text = Utils::fromStdString ("-");     \
      else                                     \
        text = Utils::fromStdString ("");      \
    }

  static std::pair<Qt::AlignmentFlag, QString>
  qStringValueFor (octave_value val, std::string format = "")
  {
    Qt::AlignmentFlag flag = Qt::AlignRight;
    QString text;
    if (val.isempty ())
      {
        text = "";
        flag = Qt::AlignLeft;
      }
    else if (val.is_string ())
      {
        text = octave::Utils::fromStdString (val.string_value ());
        flag = Qt::AlignLeft;
      }
    else if (val.iscomplex ())
      {
        // Matlab has multiple complex types, we only have double.
        Complex c = val.complex_value ();
        if (format == "short")
          text = formatComplex (c, 'f', 4);
        else if (format == "short e" || format == "shorte")
          text = formatComplex (c, 'e', 4);
        else if (format == "short eng" || format == "shorteng")
          text = formatComplex (c, 'E', 4);
        else if (format == "short g" || format == "shortg")
          text = formatComplex (c, 'g', 5);
        else if (format == "long")
          text = formatComplex (c, 'f', 15);
        else if (format == "long e" || format == "longe")
          text = formatComplex (c, 'e', 15);
        else if (format == "long eng" || format == "longeng")
          text = formatComplex (c, 'E', 15);
        else if (format == "long g" || format == "longg")
          text = formatComplex (c, 'g', 16);
        else if (format == "bank")
          text = QString::number (c.real (), 'f', 2);
        else if (format == "+")
          {
            if (c.real () > 0)
              text = Utils::fromStdString ("+");
            else if (c.real () < 0)
              text = Utils::fromStdString ("-");
            else
              text = Utils::fromStdString ("");
          }
        else if (format == "rat")
          text = Utils::fromStdString (rational_approx (c.real (), 0)) + " + "
                 + Utils::fromStdString (rational_approx (c.imag (), 0)) + "i";
        else if (format == "numeric")
          text = QString::number (c.real (), 'g', 5) + " + "
                 + QString::number (c.imag (), 'g', 5) + "i";
        else
          {
            text = QString::number (c.real (), 'g', 5) + " + "
                   + QString::number (c.imag (), 'g', 5) + "i";
            flag = Qt::AlignLeft;
          }
      }
    else if (val.is_double_type () )
      {
        double value = val.double_value ();
        FORMAT_VALUE(4, 15)
      }
    else if (val.is_single_type ())
      {
        float value = val.float_value ();
        FORMAT_VALUE(4, 7)
      }
    else if (val.is_int8_type ())
      {
        short int value = val.short_value ();
        FORMAT_INT_VALUE()
      }
    else if (val.is_uint8_type ())
      {
        unsigned short int value = val.ushort_value ();
        FORMAT_UINT_VALUE()
      }
    else if (val.is_int16_type ())
      {
        int value = val.int_value ();
        FORMAT_INT_VALUE()
      }
    else if (val.is_uint16_type ())
      {
        unsigned int value = val.uint_value ();
        FORMAT_UINT_VALUE()
      }
    else if (val.is_int32_type ())
      {
        long int value = val.long_value ();
        FORMAT_INT_VALUE()
      }
    else if (val.is_uint32_type ())
      {
        unsigned long int value = val.ulong_value ();
        FORMAT_UINT_VALUE()
      }
    else if (val.is_int64_type ())
      {
        int64_t value = val.int64_value ();
        FORMAT_INT_VALUE()
      }
    else if (val.is_uint64_type ())
      {
        uint64_t value = val.uint64_value ();
        FORMAT_UINT_VALUE()
      }
    else if (val.islogical ())
      {
        bool b = val.bool_value ();
        if (format == "char" || format == "popup" || format == "")
          {
            text = Utils::fromStdString (b ? "true" : "false");
            flag = Qt::AlignLeft;
          }
        else if (format == "+")
          {
            text = Utils::fromStdString (b ? "+" : "");
            flag = Qt::AlignLeft;
          }
        else
          text = Utils::fromStdString (b ? "1" : "0");
      }
    else
      {
        std::stringstream warn_string;
        warn_string << "Unknown conversion for datatype " << val.class_name ()
                    << " to " << format
                    << " for value " << val.string_value (true);
        warning ("%s", warn_string.str ().c_str ());

        text = Utils::fromStdString (val.string_value (true));
      }

    return std::make_pair (flag, text);
  }

#undef FORMAT_VALUE
#undef FORMAT_VALUE_EXCEPT_RAT
#undef FORMAT_UINT_VALUE
#undef FORMAT_INT_VALUE

  static QTableWidgetItem * itemFor (octave_value val, std::string format = "",
                                     bool enabled = false)
  {
    QTableWidgetItem *retval = new QTableWidgetItem ();
    std::pair<Qt::AlignmentFlag, QString> flag_and_text =
      qStringValueFor (val, format);
    retval->setTextAlignment (flag_and_text.first);
    retval->setText (flag_and_text.second);

    if (enabled)
      retval->setFlags (retval->flags () | Qt::ItemIsEditable);
    else
      retval->setFlags (retval->flags () & ~Qt::ItemIsEditable);

    return retval;
  }

  static octave_value
  attempt_type_conversion (const octave_value& ov,
                           const octave_value& old_value)
  {
    octave_value retval;

    // Define a macro to help with the conversion of strings to integers
    // FIXME: these will happily integer overflow in the (u)int64 case
    // - this probably doesn't matter.
#define SCANF_AND_CONVERT(name,ctype,format)           \
  else if (old_value.is_ ## name ## _type ())          \
    {                                                  \
      ctype val;                                       \
      int n;                                           \
      const std::string cxx_str = ov.string_value ();  \
      const char *c_str = cxx_str.c_str ();            \
      int error = sscanf (c_str, format, &val, &n);    \
      if (error != 1 || c_str[n])                      \
        {                                              \
          val = 0;                                     \
        }                                              \
      retval = octave_value ( octave_ ## name (val));  \
    }

    if (old_value.is_string ())
      retval = ov;
    SCANF_AND_CONVERT(int8, int64_t, "%" PRId64 " %n")
    SCANF_AND_CONVERT(uint8, uint64_t, "%" PRIu64 " %n")
    SCANF_AND_CONVERT(int16, int64_t, "%" PRId64 " %n")
    SCANF_AND_CONVERT(uint16, uint64_t, "%" PRIu64 " %n")
    SCANF_AND_CONVERT(int32, int64_t, "%" PRId64 " %n")
    SCANF_AND_CONVERT(uint32, uint64_t, "%" PRIu64 " %n")
    SCANF_AND_CONVERT(int64, int64_t, "%" PRId64 " %n")
    SCANF_AND_CONVERT(uint64, uint64_t, "%" PRIu64 " %n")

#undef SCANF_AND_CONVERT

    else if (old_value.isnumeric () && ! old_value.isinteger ())
      {
        // Basically need to do str2double
        Complex complex = octave::string::str2double (ov.string_value ());
        if (old_value.is_single_type ())
          retval = octave_value (FloatComplex (complex));
        else
          retval = octave_value (complex);
      }
    else if (old_value.islogical ())
      {
        // Right: Matlab basically needs this to be true or false, we should
        // accept 1 too.
        if (ov.string_value ()  == "true" || ov.string_value () == "1")
          retval = octave_value (true);
        else
          retval = octave_value (false);
      }
    else
      retval = octave_value (octave::string::str2double (ov.string_value ()));
    return retval;
  }

  QWidget *
  Table::checkBoxForLogical (octave_value val, bool enabled = false)
  {
    QWidget *retval = new QWidget (m_tableWidget);
    QCheckBox *checkBox = new QCheckBox ();
    QHBoxLayout *layout = new QHBoxLayout (retval);
    layout->addWidget (checkBox);
    layout->setAlignment (Qt::AlignCenter);
    layout->setContentsMargins (0, 0, 0, 0);
    retval->setLayout (layout);

    if ((val.islogical () || val.is_bool_scalar ()) && val.bool_value ())
      checkBox->setCheckState (Qt::Checked);
    else
      checkBox->setCheckState (Qt::Unchecked);

    checkBox->setAttribute (Qt::WA_TransparentForMouseEvents, true);
    checkBox->setFocusPolicy (Qt::NoFocus);
    checkBox->setProperty ("Enabled", QVariant (enabled));

    return retval;
  }

  Table *
  Table::create (octave::base_qobject& oct_qobj, octave::interpreter& interp,
                 const graphics_object& go)
  {
    Object *parent = parentObject (interp, go);

    if (parent)
      {
        Container *container = parent->innerContainer ();

        if (container)
          return new Table (oct_qobj, interp, go, new QTableWidget (container));
      }

    return 0;
  }

  Table::Table (octave::base_qobject& oct_qobj, octave::interpreter& interp,
                const graphics_object& go, QTableWidget *tableWidget)
    : Object (oct_qobj, interp, go, tableWidget), m_tableWidget (tableWidget),
      m_curData (), m_blockUpdates (false)
  {
    qObject ()->setObjectName ("UItable");
    uitable::properties& tp = properties<uitable> ();

    m_curData = octave_value (tp.get_data ());
    Matrix bb = tp.get_boundingbox (false);
    m_tableWidget->setObjectName ("UITable");
    m_tableWidget->setAutoFillBackground (true);
    m_tableWidget->setGeometry (octave::math::round (bb(0)),
                                octave::math::round (bb(1)),
                                octave::math::round (bb(2)),
                                octave::math::round (bb(3)));
    m_tableWidget->setFont (Utils::computeFont<uitable> (tp)) ;
    m_tableWidget->setSelectionBehavior (QAbstractItemView::SelectItems);
    updatePalette ();
    m_keyPressHandlerDefined = ! tp.get_keypressfcn ().isempty ();
    m_keyReleaseHandlerDefined = ! tp.get_keyreleasefcn ().isempty ();
    updateData ();
    updateRowname ();
    updateColumnname ();
    updateColumnwidth ();
    updateEnable ();  // Also does rearrangeableColumns
    m_tableWidget->setToolTip (Utils::fromStdString (tp.get_tooltipstring ()));
    m_tableWidget->setVisible (tp.is_visible ());
    updateExtent ();
    m_tableWidget->installEventFilter (this);


    connect (m_tableWidget, &QTableWidget::itemChanged,
             this, &Table::itemChanged);
    connect (m_tableWidget, &QTableWidget::cellClicked,
             this, &Table::cellClicked);
    connect (m_tableWidget, &QTableWidget::itemSelectionChanged,
             this, &Table::itemSelectionChanged);
  }

  Table::~Table (void) { }

  void
  Table::itemSelectionChanged ()
  {
    if (! (properties<uitable> ().get_cellselectioncallback ().isempty ()))
      {
        QModelIndexList modelIndexList =
          m_tableWidget->selectionModel ()->selectedIndexes ();
        int length = modelIndexList.size ();
        Matrix indices = Matrix (length, 2);
        for (int i = 0; i < length; i++)
          {
            indices(i, 0) = modelIndexList.value (i).row () + 1;
            indices(i, 1) = modelIndexList.value (i).column () + 1;
          }
        octave_scalar_map eventData;
        eventData.setfield ("Indices", indices);
        octave_value cellSelectionCallbackEventObject (eventData);
        emit gh_callback_event (m_handle, "cellselectioncallback",
                                cellSelectionCallbackEventObject);
      }
  }

  void
  Table::cellClicked (int row, int col)
  {
    QCheckBox *checkBox = nullptr;
    QWidget *widget
      = qobject_cast<QWidget *> (m_tableWidget->cellWidget (row, col));
    if (widget && ! widget->children ().isEmpty ())
      {
        QHBoxLayout *layout
          = qobject_cast<QHBoxLayout *> (widget->children ().first ());

        if (layout && layout->count () > 0)
          checkBox = qobject_cast<QCheckBox *> (layout->itemAt (0)-> widget ());
      }

    if (checkBox && checkBox->property ("Enabled").toBool ())
      checkBoxClicked (row, col, checkBox);
  }

  void
  Table::sendCellEditCallback (int row,
                               int col,
                               octave_value old_value,
                               octave_value new_value,
                               octave_value edit_data,
                               octave_value error)
  {

    if (!(properties<uitable> ().get_celleditcallback ().isempty ()))
      {
        Matrix indices = Matrix (1, 2);
        indices(0, 0) = row + 1;
        indices(0, 1) = col + 1;

        octave_scalar_map eventData;
        eventData.setfield ("Indices", indices);
        eventData.setfield ("PreviousData", old_value);
        eventData.setfield ("NewData", new_value);
        eventData.setfield ("EditData", edit_data);
        eventData.setfield ("Error", error);

        octave_value cellEditCallbackEventObject (eventData);

        emit gh_callback_event (m_handle, "celleditcallback",
                                cellEditCallbackEventObject);
      }
    else if (error.string_value ().length () > 0)
      warning ("%s", error.string_value ().c_str ());
  }

  void
  Table::comboBoxCurrentIndexChanged (const QString& value)
  {
    if (m_blockUpdates)
      return;

    m_blockUpdates = true;

    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());

    octave_value data = octave_value (m_curData);
    bool ok = false;

    QComboBox *comboBox = qobject_cast<QComboBox *> (sender ());
    int row = comboBox->property ("row").toInt ();
    int col = comboBox->property ("col").toInt ();

    octave_value edit_data = octave_value (Utils::toStdString (value));

    if (row < data.rows () && col < data.columns ())
      {
        if (data.iscell ())
          {
            Cell cell = data.cell_value ();
            octave_value old_data = cell(row, col);
            if (cell(row, col).is_string ())
              {
                cell(row, col) = edit_data;
                if (edit_data.string_value () != old_data.string_value ())
                  {
                    m_curData = octave_value (cell);
                    emit gh_set_event (m_handle, "data", octave_value (cell),
                                       false);
                  }

                octave_value error = octave_value ("");
                sendCellEditCallback (row, col,
                                      old_data,
                                      edit_data,
                                      edit_data,
                                      error);
                ok = true;
              }
            else
              {
                cell(row, col) = attempt_type_conversion (edit_data, old_data);

                // Inform the QTableWidget of our change
                updateData (row, col, cell(row, col),
                            columnformat (col), columneditable (col));

                m_curData = octave_value (cell);
                emit gh_set_event (m_handle, "data", octave_value (cell),
                                   false);

                octave_value error = octave_value ("");
                sendCellEditCallback (row,
                                      col,
                                      old_data,
                                      cell(row, col),
                                      edit_data,
                                      error);
                ok = true;
              }
          }
        else
          {
            octave_value old_data = data.is_matrix_type ()
                                    ? data.fast_elem_extract (row + col * data.rows ())
                                    : octave_value ();
            data.fast_elem_insert (row + col * data.rows (),
                                   attempt_type_conversion (edit_data, old_data));

            // Inform the QTableWidget of our change
            updateData (row,
                        col,
                        data.fast_elem_extract (row + col * data.rows ()),
                        columnformat (col),
                        columneditable (col));

            m_curData = octave_value (data);
            emit gh_set_event (m_handle, "data", data, false);

            octave_value error = octave_value ("");
            sendCellEditCallback (row,
                                  col,
                                  old_data,
                                  data.fast_elem_extract (row + col * data.rows ()),
                                  edit_data,
                                  error);
            ok = true;
          }
      }
    else
      {
        // Reset the QTableWidgetItem
        updateData (row, col, octave_value (""), columnformat (col),
                    columneditable (col));

        octave_value error =
          octave_value ("Table data is not editable at this location.");
        sendCellEditCallback (row,
                              col,
                              octave_value (),
                              octave_value (),
                              edit_data,
                              error);
      }

    if (! ok)
      {
        comboBox->setCurrentIndex (-1);
        comboBox->setEditable (true);
        comboBox->setEditText (comboBox->property ("original_value").toString ());
        comboBox->lineEdit ()->setReadOnly (true);
      }
    m_blockUpdates = false;
  }

  void
  Table::checkBoxClicked (int row, int col, QCheckBox *checkBox)
  {
    if (m_blockUpdates)
      return;
    m_blockUpdates = true;

    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());

    bool new_value = ! checkBox->isChecked ();

    octave_value data = octave_value (m_curData);
    if (data.islogical ())
      {
        // EASY WE JUST CONVERT
        boolMatrix matrix = data.bool_matrix_value ();
        if (row < matrix.rows () && col < matrix.columns ())
          {
            bool old_value = matrix(row, col);
            matrix(row, col) = new_value;
            checkBox->setChecked (new_value);
            if (new_value != old_value)
              {
                m_curData = octave_value (matrix);
                emit gh_set_event (m_handle, "data", octave_value (matrix),
                                   false);
              }

            sendCellEditCallback (row, col,
                                  octave_value (old_value),
                                  octave_value (new_value),
                                  octave_value (new_value),
                                  octave_value (""));

          }
        else
          {
            sendCellEditCallback (row,
                                  col,
                                  octave_value (),
                                  octave_value (),
                                  octave_value (new_value),
                                  octave_value ("Table data is not editable at this location."));
          }
      }
    else if (data.iscell ())
      {
        Cell cell = data.cell_value ();
        if (row < cell.rows () && col < cell.columns ())
          {
            if (cell(row, col).islogical ())
              {
                bool old_value = cell(row, col).bool_value ();
                cell(row, col) = octave_value (new_value);
                checkBox->setChecked (new_value);
                if (new_value != old_value)
                  {
                    m_curData = octave_value (cell);
                    emit gh_set_event (m_handle, "data", octave_value (cell),
                                       false);
                  }

                sendCellEditCallback (row,
                                      col,
                                      octave_value (old_value),
                                      octave_value (new_value),
                                      octave_value (new_value),
                                      octave_value (""));
              }
            else
              {
                sendCellEditCallback (row,
                                      col,
                                      cell(row, col),
                                      octave_value (),
                                      octave_value (new_value),
                                      octave_value ("Cannot convert logical edit to other type."));
              }
          }
        else
          {
            sendCellEditCallback (row,
                                  col,
                                  cell(row, col),
                                  octave_value (),
                                  octave_value (new_value),
                                  octave_value ("Table data is not editable at this location."));
          }
      }
    else if (data.is_matrix_type ())
      {
        if (row < data.rows () && col < data.columns ())
          {
            sendCellEditCallback (row,
                                  col,
                                  data.fast_elem_extract (row + col * data.rows ()),
                                  octave_value (),
                                  octave_value (new_value),
                                  octave_value ("Cannot convert logical edit to other type."));
          }
        else
          {
            sendCellEditCallback (row,
                                  col,
                                  data.fast_elem_extract (row + col * data.rows ()),
                                  octave_value (),
                                  octave_value (new_value),
                                  octave_value ("Table data is not editable at this location."));
          }
      }
    m_blockUpdates = false;
  }


  void
  Table::itemChanged (QTableWidgetItem *item)
  {
    if (m_blockUpdates)
      return;
    m_blockUpdates = true;

    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    octave::autolock guard (gh_mgr.graphics_lock ());

    octave_value data = octave_value (m_curData);

    int row = item->row ();
    int col = item->column ();
    octave_value edit_data = octave_value (Utils::toStdString (item->text ()));
    octave_value new_value;
    octave_value old_value;
    octave_value new_data;

    if (row < data.rows () && col < data.columns ())
      {
        if (data.iscell ())
          {
            old_value = data.cell_value () (row, col);
          }
        else if (data.is_matrix_type ())
          {
            old_value = data.fast_elem_extract (row + col * data.rows ());
          }

        // Now we need to coerce the new_value in to the type of the old_value
        if (old_value.is_string ())
          new_value = edit_data;
        else
          {
            new_value = attempt_type_conversion (edit_data, old_value);
            std::pair<Qt::AlignmentFlag, QString> flag_and_text =
              qStringValueFor (new_value, columnformat (col));
            item->setTextAlignment (flag_and_text.first);
            item->setText (flag_and_text.second);
          }

        if (data.iscell ())
          {
            Cell cell = data.cell_value ();
            cell(row, col) = new_value;
            new_data = octave_value (cell);
          }
        else
          {
            data.fast_elem_insert (row + col * data.rows (), new_value);
            new_data = data;
          }
        m_curData = octave_value (new_data);
        emit gh_set_event (m_handle, "data", new_data, false);

        sendCellEditCallback (row,
                              col,
                              octave_value (old_value),
                              octave_value (new_value),
                              octave_value (new_value),
                              octave_value (""));
      }
    else
      {
        item->setText ("");

        octave_value error =
          octave_value ("Table data is not editable at this location.");
        sendCellEditCallback (row,
                              col,
                              octave_value (),
                              octave_value (),
                              edit_data,
                              error);
      }

    m_blockUpdates = false;
  }

  void
  Table::redraw (void)
  {
    update (uitable::properties::ID_POSITION);
  }

  void
  Table::update (int pId)
  {
    uitable::properties& tp = properties<uitable> ();

    switch (pId)
      {
      case uitable::properties::ID_BACKGROUNDCOLOR:
      case uitable::properties::ID_FOREGROUNDCOLOR:
        updatePalette ();
        break;

      case uitable::properties::ID_COLUMNNAME:
        updateColumnname ();
        updateColumnwidth ();
        break;

      case uitable::properties::ID_COLUMNWIDTH:
        updateColumnwidth ();
        break;

      case uitable::properties::ID_COLUMNEDITABLE:
      case uitable::properties::ID_COLUMNFORMAT:
      case uitable::properties::ID_DATA:
        m_blockUpdates = true;
        m_curData = octave_value (tp.get_data ());
        updateData ();
        updateRowname ();
        updateColumnname ();
        updateColumnwidth ();
        updateEnable ();
        m_blockUpdates = false;
        break;

      case uitable::properties::ID_ENABLE:
        updateEnable ();
        break;

      case uitable::properties::ID_KEYPRESSFCN:
        m_keyPressHandlerDefined = ! tp.get_keypressfcn ().isempty ();
        break;

      case uitable::properties::ID_KEYRELEASEFCN:
        m_keyReleaseHandlerDefined = ! tp.get_keyreleasefcn ().isempty ();
        break;

      case uitable::properties::ID_FONTNAME:
      case uitable::properties::ID_FONTSIZE:
      case uitable::properties::ID_FONTWEIGHT:
      case uitable::properties::ID_FONTANGLE:
        if (m_tableWidget)
          {
            m_tableWidget->setFont (Utils::computeFont<uitable> (tp));
            for (int row = 0; row < m_tableWidget->rowCount (); row++)
              {
                m_tableWidget->setRowHeight (row, AUTO_HEIGHT);
              }
          }
        break;

      case uitable::properties::ID_POSITION:
        {
          Matrix bb = tp.get_boundingbox (false);
          m_tableWidget->setGeometry (octave::math::round (bb(0)),
                                      octave::math::round (bb(1)),
                                      octave::math::round (bb(2)),
                                      octave::math::round (bb(3)));
          updateExtent ();
        }
        break;

      case uitable::properties::ID_REARRANGEABLECOLUMNS:
        updateRearrangeableColumns ();
        break;

      case uitable::properties::ID_ROWNAME:
        updateRowname ();
        break;

      case uitable::properties::ID_ROWSTRIPING:
        updatePalette ();
        break;

      case uitable::properties::ID_TOOLTIPSTRING:
        m_tableWidget->setToolTip (Utils::fromStdString (tp.get_tooltipstring ()));
        break;

      case base_properties::ID_VISIBLE:
        m_tableWidget->setVisible (tp.is_visible ());
        break;

      default:
        break;

      }
  }

  void
  Table::updateColumnname (void)
  {
    uitable::properties& tp = properties<uitable> ();

    // Reset the Column Count
    m_tableWidget->setColumnCount (tp.get_data ().columns ());

    octave_value columnname = tp.get_columnname ();
    QStringList l;
    bool visible = true;

    if (columnname.is_string () && columnname.string_value (false) == "numbered")
      for (int i = 0; i < m_tableWidget->columnCount (); i++)
        l << QString::number (i + 1);
    else if (columnname.is_string ())
      {
        if (m_tableWidget->columnCount () > 0)
          l << Utils::fromStdString (columnname.string_value ());
        for (int i = 1; i < m_tableWidget->columnCount (); i++)
          l << "";
      }
    else if (columnname.isempty ())
      {
        for (int i = 0; i < m_tableWidget->columnCount (); i++)
          l << "";

        visible = false;
      }
    else if (columnname.iscell ())
      {
        octave_idx_type n = columnname.numel ();
        Cell cell_value = columnname.cell_value ();

        for (octave_idx_type i = 0; i < n; i++)
          {
            octave_value v = cell_value (i);
            if (v.is_string ())
              l << Utils::fromStdString (v.string_value (true));
            else if (v.is_matrix_type ())
              {
                Matrix data = v.matrix_value ();

                /* Now Matlab does something very strange here:
                 * If data is a row or column matrix,
                 * then each datapoint is added.
                 * Otherwise, nothing is set.
                 */
                if (data.rows () > 1 && data.cols () > 1)
                  l << "";
                else
                  for (octave_idx_type j = 0; j < data.numel (); j++)
                    l << QString::number (data(j));
              }
            else if (v.isnumeric ())
              l << QString::number (v.double_value ());
            else
              l << QString::number (v.double_value ());
          }
      }
    else if (columnname.is_matrix_type ())
      {
        octave_idx_type n = columnname.numel ();
        Matrix matrix_value = columnname.matrix_value ();

        for (octave_idx_type i = 0; i < n; i++)
          l << QString::number (matrix_value(i));
      }
    else
      {
        for (int i = 0; i < m_tableWidget->columnCount (); i++)
          l << "";
        visible = false;
      }

    l.replaceInStrings ("|", "\n");

    // Now add the columns as required
    if (m_tableWidget->columnCount () < l.length ())
      {
        int oldColumnCount = m_tableWidget->columnCount ();
        m_tableWidget->setColumnCount (l.length ());
        for (int col = oldColumnCount; col < l.length (); col++)
          {
            std::string format = columnformat (col);
            bool enabled = columneditable (col);

            for (int row = 0; row < m_tableWidget->rowCount (); row++)
              updateData (row, col, octave_value (""), format, enabled);
          }
      }

    m_tableWidget->setHorizontalHeaderLabels (l);
    m_tableWidget->horizontalHeader ()->setVisible (visible);
  }

  void
  Table::updateColumnwidth (void)
  {
    uitable::properties& tp = properties<uitable> ();

    octave_value columnwidth = tp.get_columnwidth ();
    if (columnwidth.isempty ()
        || (columnwidth.is_string ()
            && columnwidth.string_value (false) == "auto"))
      for (int i = 0; i < m_tableWidget->columnCount (); i++)
        m_tableWidget->setColumnWidth (i, AUTO_WIDTH);
    else if (columnwidth.is_string ()
             && columnwidth.string_value (false) == "preferred")
      for (int i = 0; i < m_tableWidget->columnCount (); i++)
        {
          int column_size =
            (qobject_cast<QAbstractItemView *> (m_tableWidget))->sizeHintForColumn (i);
          int header_size = m_tableWidget->horizontalHeader ()->sectionSizeHint (i);

          if (column_size > header_size)
            header_size = column_size;
          m_tableWidget->setColumnWidth (i, header_size);
        }
    else if (columnwidth.iscell ())
      {
        Cell cell_value = columnwidth.cell_value ();
        int i = 0;
        for (; i < m_tableWidget->columnCount () && i < cell_value.numel (); i++)
          {
            octave_value v = cell_value (i);
            if (v.is_string ()  && v.string_value (false) == "auto")
              m_tableWidget->setColumnWidth (i, AUTO_WIDTH);
            else if (v.is_string () && v.string_value (false) == "preferred")
              {
                int column_size =
                  (qobject_cast<QAbstractItemView *> (m_tableWidget))->sizeHintForColumn (i);
                int header_size = m_tableWidget->horizontalHeader ()->sectionSizeHint (i);

                if (column_size > header_size)
                  header_size = column_size;
                m_tableWidget->setColumnWidth (i, header_size);
              }
            else
              {
                int w = int (v.double_value ());
                m_tableWidget->setColumnWidth (i, w);
              }
          }
        for (; i < m_tableWidget->columnCount (); i++)
          {
            int column_size =
              (qobject_cast<QAbstractItemView *> (m_tableWidget))->sizeHintForColumn (i);
            int header_size = m_tableWidget->horizontalHeader ()->sectionSizeHint (i);

            if (column_size > header_size)
              header_size = column_size;
            m_tableWidget->setColumnWidth (i, header_size);
          }
      }
    else if (columnwidth.is_matrix_type ())
      {
        Matrix matrix_value = columnwidth.matrix_value ();
        int i = 0;
        for (; i < m_tableWidget->columnCount () && i < matrix_value.numel (); i++)
          {
            octave_value v = matrix_value(i);
            int w = int (v.double_value ());
            m_tableWidget->setColumnWidth (i, w);
          }
        for (; i < m_tableWidget->columnCount (); i++)
          m_tableWidget->setColumnWidth (i, AUTO_WIDTH);
      }
  }

  bool inline
  Table::columneditable (int col)
  {
    uitable::properties& tp = properties<uitable> ();
    boolNDArray columneditable = tp.get_columneditable ().bool_array_value ();
    bool editable = false;

    if (! columneditable.isempty () && col < columneditable.numel ())
      editable = columneditable.xelem (col);
    else if (! columneditable.isempty () && columneditable.numel () == 1)
      editable = columneditable.xelem (0);

    return editable;
  }

  std::string inline
  Table::columnformat (int col)
  {
    uitable::properties& tp = properties<uitable> ();
    std::string format = "";
    octave_value ov_columnformat = tp.get_columnformat ();

    if (ov_columnformat.iscell ())
      {
        Cell columnformat = ov_columnformat.cell_value ();
        if (! columnformat.isempty () && col < columnformat.numel ())
          {
            octave_value format_value = columnformat.xelem (col);

            if (! format_value.isempty () && format_value.is_string ())
              format = format_value.string_value ();
            else if (! format_value.isempty () && format_value.iscell ())
              format = "popup";
          }
      }
    else if (ov_columnformat.is_string ())
      {
        format = ov_columnformat.string_value ();
      }
    return format;
  }

  void inline
  Table::updateDataColumn (int col)
  {
    octave_value data = properties<uitable> ().get_data ();

    std::string format = columnformat (col);
    bool is_editable = columneditable (col);

    for (octave_idx_type row = 0; row < data.rows (); row++)
      updateData (row,
                  col,
                  data.iscell ()
                  ? data.cell_value () (row, col)
                  : data.fast_elem_extract (row + col * data.rows ()),
                  format,
                  is_editable);
  }

  void inline
  Table::updateData (int row, int col)
  {
    octave_value data = properties<uitable> ().get_data ();
    updateData (row,
                col,
                data.iscell ()
                ? data.cell_value () (row, col)
                : data.fast_elem_extract (row + col * data.rows ()),
                columnformat (col),
                columneditable (col));
  }

  void inline
  Table::updateData (int row, int col, octave_value value,
                     std::string format = "", bool enabled = false)
  {
    if (format == "logical" || (format == "" && value.islogical ()))
      {
        if (m_tableWidget->item (row, col))
          delete m_tableWidget->item (row, col);

        m_tableWidget->setCellWidget (row, col, checkBoxForLogical (value, enabled));
        m_tableWidget->cellWidget (row, col)->setProperty ("row", QVariant (row));
        m_tableWidget->cellWidget (row, col)->setProperty ("col", QVariant (col));
      }
    else if (format == "popup" && enabled)
      {
        if (m_tableWidget->item (row, col))
          delete m_tableWidget->item (row, col);

        QString string_value = qStringValueFor (value, format).second;
        uitable::properties& tp = properties<uitable> ();
        octave_value format_value = tp.get_columnformat ().cell_value ().xelem (col);

        QComboBox *comboBox = new QComboBox ();
        comboBox->setProperty ("row", QVariant (row));
        comboBox->setProperty ("col", QVariant (col));

        int index = -1;
        for (int k = 0; k < format_value.numel (); k++)
          {
            QString popup_item
              = Utils::fromStdString (format_value.fast_elem_extract (k).string_value ());

            comboBox->addItem (popup_item);

            if (popup_item == string_value)
              index = k;
          }
        comboBox->setCurrentIndex (index);

        if (index < 0)
          {
            comboBox->setEditable (true);
            comboBox->setEditText (string_value);
            comboBox->lineEdit ()->setReadOnly (true);
          }

        comboBox->setProperty ("original_value", QVariant (string_value));

        comboBox->installEventFilter (this);
        m_tableWidget->setCellWidget (row, col, comboBox);
        connect (comboBox, SIGNAL(currentIndexChanged (const QString&)),
                 this, SLOT(comboBoxCurrentIndexChanged (const QString&)));
      }
    else
      {
        if (m_tableWidget->cellWidget (row, col))
          delete m_tableWidget->cellWidget (row, col);
        m_tableWidget->setItem (row, col, itemFor (value, format, enabled));
      }
  }

  void
  Table::updateData ()
  {
    uitable::properties& tp = properties<uitable> ();

    octave_value data = tp.get_data ();

    if (data.iscell () || data.is_matrix_type ())
      {
        m_tableWidget->setRowCount (data.rows ());
        m_tableWidget->setColumnCount (data.columns ());

        for (octave_idx_type col = 0; col < data.columns (); col++)
          updateDataColumn (col);
      }

    for (octave_idx_type row = 0; row < m_tableWidget->rowCount (); row++)
      m_tableWidget->setRowHeight (row, AUTO_HEIGHT);
  }

  void
  Table::updateEnable (void)
  {
    uitable::properties& tp = properties<uitable> ();
    bool enabled = tp.is_enable ();
    m_tableWidget->setEnabled (enabled);

    bool rearrangeableColumns = tp.is_rearrangeablecolumns ();

    // Set selection mode
    m_tableWidget->setSelectionMode (enabled
                                     ? QAbstractItemView::ExtendedSelection
                                     : QAbstractItemView::NoSelection);

    // Set rearrangeablecolumns
    m_tableWidget->horizontalHeader ()->setSectionsMovable (enabled && rearrangeableColumns);
    m_tableWidget->horizontalHeader ()->setDragEnabled (enabled && rearrangeableColumns);
    m_tableWidget->horizontalHeader ()->setDragDropMode (QAbstractItemView::InternalMove);

    // Turn off column editable
    for (int col = 0; col < m_tableWidget->columnCount (); col++)
      {
        bool editable = columneditable (col);

        for (int row = 0; row < m_tableWidget->rowCount (); row++)
          if (QTableWidgetItem *item = m_tableWidget->item (row, col))
            {
              Qt::ItemFlags flags = item->flags ();
              if (enabled && editable)
                item->setFlags (flags | Qt::ItemIsEditable);
              else
                item->setFlags (flags & ~Qt::ItemIsEditable);
            }
          else if (QWidget *widget = m_tableWidget->cellWidget (row, col))
            {
              QCheckBox *checkBox = nullptr;
              if (widget && ! widget->children ().isEmpty ())
                {
                  QHBoxLayout *layout
                    = qobject_cast<QHBoxLayout *> (widget->children ().first ());

                  if (layout && layout->count () > 0)
                    checkBox = qobject_cast<QCheckBox *> (layout->itemAt (0)-> widget ());
                }

              if (checkBox)
                widget->setProperty ("Enabled", QVariant (enabled & editable));
              else
                {
                  widget->setAttribute (Qt::WA_TransparentForMouseEvents,
                                        !(editable & enabled));

                  widget->setFocusPolicy (Qt::NoFocus);
                }
            }
      }
  }

  void
  Table::updateExtent (void)
  {
    QSize s = realQSizeForTable (m_tableWidget);
    Matrix extent = Matrix (1, 4);
    extent(0, 0) = 0;
    extent(0, 1) = 0;
    extent(0, 2) = s.width ();
    extent(0, 3) = s.height () ;
    graphics_object go = object ();
    emit gh_set_event (go.get_handle (), "extent", extent, false);
  }

  void
  Table::updatePalette (void)
  {
    uitable::properties& tp = properties<uitable> ();

    QPalette p = m_tableWidget->palette ();
    p.setColor (QPalette::Text,
                Utils::fromRgb (tp.get_foregroundcolor_rgb ()));
    p.setColor (QPalette::Base,
                Utils::fromRgb (tp.get_backgroundcolor_rgb ()));
    p.setColor (QPalette::AlternateBase,
                Utils::fromRgb (tp.get_alternatebackgroundcolor_rgb ()));
    m_tableWidget->setPalette (p);
    m_tableWidget->setAlternatingRowColors (tp.is_rowstriping ());
    // FIXME: Handle multiple alternating background colors
  }

  void
  Table::updateRowname (void)
  {
    uitable::properties& tp = properties<uitable> ();

    // Reset the row count
    m_tableWidget->setRowCount (tp.get_data ().rows ());

    octave_value rowname = tp.get_rowname ();
    QStringList l;
    bool visible = true;

    if (rowname.is_string () && rowname.string_value (false) == "numbered")
      for (int i = 0; i < m_tableWidget->rowCount (); i++)
        l << QString::number (i + 1);
    else if (rowname.is_string ())
      {
        if (m_tableWidget->rowCount () > 0)
          l << Utils::fromStdString (rowname.string_value ());
        for (int i = 1; i < m_tableWidget->rowCount (); i++)
          l << "";
      }
    else if (rowname.isempty ())
      {
        for (int i = 0; i < m_tableWidget->rowCount (); i++)
          l << "";
        visible = false;
      }
    else if (rowname.iscell ())
      {
        octave_idx_type n = rowname.numel ();
        Cell cell_value = rowname.cell_value ();

        for (octave_idx_type i = 0; i < n; i++)
          {
            octave_value v = cell_value (i);
            if (v.is_string ())
              l << Utils::fromStdString (v.string_value (true));
            else if (v.is_matrix_type ())
              {
                Matrix data = v.matrix_value ();

                /* Now Matlab does something very strange here:
                 * If data is a row or column matrix,
                 * then each datapoint is added.
                 * Otherwise, nothing is set.
                 */
                if (data.rows () > 1 && data.cols () > 1)
                  l << "";
                else
                  for (octave_idx_type j = 0; j < data.numel (); j++)
                    l << QString::number (data(j));
              }
            else if (v.isnumeric ())
              l << QString::number (v.double_value (true));
            else
              l << QString::number (v.double_value (true));
          }
      }
    else if (rowname.is_matrix_type ())
      {
        octave_idx_type n = rowname.numel ();
        Matrix matrix_value = rowname.matrix_value ();

        for (octave_idx_type i = 0; i < n; i++)
          l << QString::number (matrix_value(i));
      }
    else
      {
        for (int i = 0; i < m_tableWidget->columnCount (); i++)
          l << "";
        visible = false;
      }

    // Add dummy rows as required
    if (m_tableWidget->rowCount () < l.length ())
      {
        int oldRowCount = m_tableWidget->rowCount ();
        m_tableWidget->setRowCount (l.length ());

        for (int col = 0; col < m_tableWidget->columnCount (); col++)
          {
            std::string format = columnformat (col);
            bool enabled = columneditable (col);

            for (int row = oldRowCount; row < l.length (); row++)
              {
                m_tableWidget->setRowHeight (row, AUTO_HEIGHT);

                updateData (row, col, octave_value (""), format, enabled);
              }
          }
      }

    m_tableWidget->setVerticalHeaderLabels (l);
    m_tableWidget->verticalHeader ()->setVisible (visible);
  }

  void
  Table::updateRearrangeableColumns (void)
  {
    uitable::properties& tp = properties<uitable> ();

    bool rearrangeableColumns = tp.is_rearrangeablecolumns ();
    bool enabled = tp.is_enable ();

    m_tableWidget->horizontalHeader ()->setSectionsMovable (enabled && rearrangeableColumns);
    m_tableWidget->horizontalHeader ()->setDragEnabled (enabled && rearrangeableColumns);
    m_tableWidget->horizontalHeader ()->setDragDropMode (QAbstractItemView::InternalMove);
  }

  bool
  Table::eventFilter (QObject *watched, QEvent *xevent)
  {
    gh_manager& gh_mgr = m_interpreter.get_gh_manager ();

    //uitable::properties& tp = properties<uitable> ();
    if (qobject_cast<QTableWidget *> (watched))
      {
        switch (xevent->type ())
          {
          case QEvent::Resize:
            {
              octave::autolock guard (gh_mgr.graphics_lock ());

              graphics_object go = object ();
              if (go.valid_object ())
                {
                  const uitable::properties& tp =
                    Utils::properties<uitable> (go);
                  if (tp.fontunits_is ("normalized"))
                    m_tableWidget->setFont (Utils::computeFont<uitable> (tp));
                }
            }
            break;

          case QEvent::MouseButtonPress:
            {
              octave::autolock guard (gh_mgr.graphics_lock ());

              QMouseEvent *m = dynamic_cast<QMouseEvent *> (xevent);
              graphics_object go = object ();
              const uitable::properties& tp =
                Utils::properties<uitable> (go);
              graphics_object fig = go.get_ancestor ("figure");

              if (m->button () != Qt::LeftButton || ! tp.is_enable ())
                {
                  emit gh_set_event (fig.get_handle (), "selectiontype",
                                     Utils::figureSelectionType (m), false);
                  emit gh_set_event (fig.get_handle (), "currentpoint",
                                     Utils::figureCurrentPoint (fig, m),
                                     false);
                  emit gh_callback_event (fig.get_handle (),
                                          "windowbuttondownfcn");
                  emit gh_callback_event (m_handle, "buttondownfcn");

                  if (m->button () == Qt::RightButton)
                    ContextMenu::executeAt (m_interpreter, properties (),
                                            m->globalPos ());
                }
              else
                {
                  emit gh_set_event (fig.get_handle (), "selectiontype",
                                     octave_value ("normal"), false);
                }
            }
            break;

          case QEvent::KeyPress:
            {
              QKeyEvent *k = dynamic_cast<QKeyEvent *> (xevent);
              if (m_keyPressHandlerDefined)
                {
                  octave::autolock guard (gh_mgr.graphics_lock ());

                  octave_scalar_map keyData = Utils::makeKeyEventStruct (k);
                  graphics_object fig = object ().get_ancestor ("figure");

                  emit gh_set_event (fig.get_handle (), "currentcharacter",
                                     keyData.getfield ("Character"), false);
                  emit gh_callback_event (m_handle, "keypressfcn", keyData);
                }
              int row = m_tableWidget->currentRow ();
              int col = m_tableWidget->currentColumn ();
              switch (k->key ())
                {
                case Qt::Key_Space:
                  {
                    QCheckBox *checkBox = nullptr;

                    QWidget *widget
                      = qobject_cast<QWidget *> (m_tableWidget->cellWidget (row, col));

                    if (widget && ! widget->children ().isEmpty ())
                      {
                        QHBoxLayout *layout
                          = qobject_cast<QHBoxLayout *> (widget->children ().first ());

                        if (layout && layout->count () > 0)
                          checkBox = qobject_cast<QCheckBox *> (layout->itemAt (0)-> widget ());
                      }

                    if (checkBox && checkBox->property ("Enabled").toBool ())
                      checkBoxClicked (row, col, checkBox);

                    QComboBox *comboBox
                      = qobject_cast<QComboBox *> (m_tableWidget->cellWidget (row, col));

                    if (comboBox)
                      comboBox->showPopup ();
                  }
                  break;

                case Qt::Key_Return:
                case Qt::Key_Enter:
                  {
                    if (k->modifiers () == Qt::NoModifier)
                      {
                        if (row + 1 < m_tableWidget->rowCount ())
                          m_tableWidget->setCurrentCell (row + 1, col);
                        else
                          {
                            if (col + 1 < m_tableWidget->columnCount ())
                              m_tableWidget->setCurrentCell (0, col + 1);
                            else
                              m_tableWidget->setCurrentCell (0, 0);
                          }
                      }
                    else if (k->modifiers () == Qt::ShiftModifier)
                      {
                        if (row - 1 >= 0)
                          m_tableWidget->setCurrentCell (row - 1, col);
                        else
                          {
                            if (col - 1 >= 0)
                              m_tableWidget->setCurrentCell
                              (m_tableWidget->rowCount () - 1,
                               col - 1);
                            else
                              m_tableWidget->setCurrentCell
                              (m_tableWidget->rowCount () - 1,
                               m_tableWidget->columnCount () - 1);
                          }
                      }
                  }
                  break;

                default:
                  break;
                }
            }
            break;

          case QEvent::KeyRelease:
            {
              if (m_keyReleaseHandlerDefined)
                {
                  octave::autolock guard (gh_mgr.graphics_lock ());

                  QKeyEvent *k = dynamic_cast<QKeyEvent *> (xevent);

                  octave_scalar_map keyData = Utils::makeKeyEventStruct (k);
                  graphics_object fig = object ().get_ancestor ("figure");

                  emit gh_set_event (fig.get_handle (), "currentcharacter",
                                     keyData.getfield ("Character"), false);
                  emit gh_callback_event (m_handle, "keyreleasefcn", keyData);
                }
            }
            break;

          default:
            break;
          }
      }
    else if (qobject_cast<QComboBox *> (watched))
      {
        switch (xevent->type ())
          {
          case QEvent::MouseButtonPress:
            {
              octave::autolock guard (gh_mgr.graphics_lock ());

              QMouseEvent *m = dynamic_cast<QMouseEvent *> (xevent);
              graphics_object go = object ();
              const uitable::properties& tp = Utils::properties<uitable> (go);
              graphics_object fig = go.get_ancestor ("figure");

              if (m->button () != Qt::LeftButton || ! tp.is_enable ())
                {
                  emit gh_set_event (fig.get_handle (), "selectiontype",
                                     Utils::figureSelectionType (m), false);
                  emit gh_set_event (fig.get_handle (), "currentpoint",
                                     Utils::figureCurrentPoint (fig, m),
                                     false);
                  emit gh_callback_event (fig.get_handle (),
                                          "windowbuttondownfcn");
                  emit gh_callback_event (m_handle, "buttondownfcn");

                  if (m->button () == Qt::RightButton)
                    ContextMenu::executeAt (m_interpreter, tp, m->globalPos ());
                }
              else
                {
                  emit gh_set_event (fig.get_handle (), "selectiontype",
                                     Utils::figureSelectionType (m), false);

                  QComboBox *comboBox_0 = qobject_cast<QComboBox *> (watched);
                  for (int row = 0; row < m_tableWidget->rowCount (); row++)
                    {
                      for (int col = 0; col < m_tableWidget->columnCount (); col++)
                        {
                          QComboBox *comboBox_1
                            = qobject_cast<QComboBox *> (m_tableWidget->cellWidget (row, col));

                          if (comboBox_0 == comboBox_1)
                            m_tableWidget->setCurrentCell (row, col);
                        }
                    }
                }
            }
            break;

          default:
            break;
          }
      }
    return false;
  }

#undef AUTO_HEIGHT
}