Mercurial > octave
view libgui/src/variable-editor.cc @ 31210:9ad55d2e1bbf stable
Make sure we don't pass short 8.3 path to latex on Windows (bug #62779).
* latex-text-renderer.cc (latex_renderer::write_tex_file): On Windows, use
canonicalized path of temporary directory.
author | Pantxo Diribarne <pantxo.diribarne@gmail.com> |
---|---|
date | Sun, 28 Aug 2022 22:44:49 +0200 |
parents | 796f54d4ddbf |
children | 04601f6c47f4 |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 2013-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/>. // //////////////////////////////////////////////////////////////////////// #ifdef HAVE_CONFIG_H # include "config.h" #endif #include <algorithm> #include <limits> #include <QApplication> #include <QClipboard> #include <QFileDialog> #include <QHeaderView> #include <QLabel> #include <QMdiArea> #include <QMenu> #include <QPalette> #include <QScreen> #include <QScrollBar> #include <QStackedWidget> #include <QTabWidget> #include <QTableView> #include <QTextEdit> #include <QToolBar> #include <QToolButton> #include <QVBoxLayout> #include "builtin-defun-decls.h" #include "dw-main-window.h" #include "gui-preferences-cs.h" #include "gui-preferences-global.h" #include "gui-preferences-sc.h" #include "gui-preferences-ve.h" #include "octave-qobject.h" #include "octave-qtutils.h" #include "ovl.h" #include "qt-utils.h" #include "shortcut-manager.h" #include "variable-editor-model.h" #include "variable-editor.h" namespace octave { // Code reuse functions static QSignalMapper * make_plot_mapper (QMenu *menu) { QList<QString> list; list << "plot" << "bar" << "stem" << "stairs" << "area" << "pie" << "hist"; QSignalMapper *plot_mapper = new QSignalMapper (menu); for (int i = 0; i < list.size(); ++i) plot_mapper->setMapping (menu->addAction (list.at (i), plot_mapper, SLOT (map ())), list.at (i)); return plot_mapper; } // Variable dock widget variable_dock_widget::variable_dock_widget (QWidget *p, base_qobject& oct_qobj) : label_dock_widget (p, oct_qobj) // See Octave bug #53807 and https://bugreports.qt.io/browse/QTBUG-44813 #if (QT_VERSION >= 0x050302) && (QT_VERSION <= QTBUG_44813_FIX_VERSION) , m_waiting_for_mouse_move (false) , m_waiting_for_mouse_button_release (false) #endif { setFocusPolicy (Qt::StrongFocus); setAttribute (Qt::WA_DeleteOnClose); connect (m_dock_action, &QAction::triggered, this, &variable_dock_widget::change_floating); connect (m_close_action, &QAction::triggered, this, &variable_dock_widget::change_existence); connect (this, &variable_dock_widget::topLevelChanged, this, &variable_dock_widget::toplevel_change); #define DOCKED_FULLSCREEN_BUTTON_TOOLTIP "Fullscreen undock" #define UNDOCKED_FULLSCREEN_BUTTON_TOOLTIP "Fullscreen" // Add a fullscreen button m_fullscreen_action = nullptr; m_full_screen = false; m_prev_floating = false; m_prev_geom = QRect (0, 0, 0, 0); QHBoxLayout *h_layout = m_title_widget->findChild<QHBoxLayout *> (); resource_manager& rmgr = m_octave_qobj.get_resource_manager (); m_fullscreen_action = new QAction (rmgr.icon ("view-fullscreen", false), "", this); m_fullscreen_action->setToolTip (tr (DOCKED_FULLSCREEN_BUTTON_TOOLTIP)); QToolButton *fullscreen_button = new QToolButton (m_title_widget); fullscreen_button->setDefaultAction (m_fullscreen_action); fullscreen_button->setFocusPolicy (Qt::NoFocus); fullscreen_button->setIconSize (QSize (m_icon_size, m_icon_size)); QString css_button = QString ("QToolButton {background: transparent; border: 0px;}"); fullscreen_button->setStyleSheet (css_button); connect (m_fullscreen_action, &QAction::triggered, this, &variable_dock_widget::change_fullscreen); int index = -1; QToolButton *first = m_title_widget->findChild<QToolButton *> (); if (first != nullptr) index = h_layout->indexOf (first); h_layout->insertWidget (index, fullscreen_button); // Custom title bars cause loss of decorations, add a frame m_frame = new QFrame (this); m_frame->setFrameStyle (QFrame::Box | QFrame::Sunken); m_frame->setAttribute (Qt::WA_TransparentForMouseEvents); } // slot for (un)dock action void variable_dock_widget::change_floating (bool) { if (isFloating ()) { if (m_full_screen) { setGeometry (m_prev_geom); resource_manager& rmgr = m_octave_qobj.get_resource_manager (); m_fullscreen_action->setIcon (rmgr.icon ("view-fullscreen", false)); m_full_screen = false; } m_fullscreen_action->setToolTip (tr (DOCKED_FULLSCREEN_BUTTON_TOOLTIP)); } else m_fullscreen_action->setToolTip (tr (UNDOCKED_FULLSCREEN_BUTTON_TOOLTIP)); setFloating (! isFloating ()); } // slot for hiding the widget void variable_dock_widget::change_existence (bool) { close (); } void variable_dock_widget::toplevel_change (bool toplevel) { if (toplevel) { m_dock_action->setIcon (QIcon (":/actions/icons/widget-dock.png")); m_dock_action->setToolTip (tr ("Dock widget")); setWindowFlags (Qt::Window); setWindowTitle (tr ("Variable Editor: ") + objectName ()); show (); activateWindow (); setFocus (); // See Octave bug #53807 and https://bugreports.qt.io/browse/QTBUG-44813 #if (QT_VERSION >= 0x050302) && (QT_VERSION <= QTBUG_44813_FIX_VERSION) m_waiting_for_mouse_move = true; #endif } else { m_dock_action->setIcon (QIcon (":/actions/icons/widget-undock.png")); m_dock_action->setToolTip (tr ("Undock widget")); setFocus (); // See Octave bug #53807 and https://bugreports.qt.io/browse/QTBUG-44813 #if (QT_VERSION >= 0x050302) && (QT_VERSION <= QTBUG_44813_FIX_VERSION) m_waiting_for_mouse_move = false; m_waiting_for_mouse_button_release = false; #endif } } void variable_dock_widget::change_fullscreen (void) { resource_manager& rmgr = m_octave_qobj.get_resource_manager (); if (! m_full_screen) { m_prev_floating = isFloating (); m_fullscreen_action->setIcon (rmgr.icon ("view-restore", false)); if (m_prev_floating) m_fullscreen_action->setToolTip (tr ("Restore geometry")); else { m_fullscreen_action->setToolTip (tr ("Redock")); setFloating (true); } m_prev_geom = geometry (); // showFullscreen() and setWindowState() only work for QWindow objects. QScreen *pscreen = QGuiApplication::primaryScreen (); QRect rect (0, 0, 0, 0); rect = pscreen->availableGeometry (); setGeometry (rect); m_full_screen = true; } else { m_fullscreen_action->setIcon (rmgr.icon ("view-fullscreen", false)); setGeometry (m_prev_geom); if (m_prev_floating) m_fullscreen_action->setToolTip (tr (UNDOCKED_FULLSCREEN_BUTTON_TOOLTIP)); else { setFloating (false); m_fullscreen_action->setToolTip (tr (DOCKED_FULLSCREEN_BUTTON_TOOLTIP)); } m_full_screen = false; } #undef DOCKED_FULLSCREEN_BUTTON_TOOLTIP #undef UNDOCKED_FULLSCREEN_BUTTON_TOOLTIP } void variable_dock_widget::closeEvent (QCloseEvent *e) { QDockWidget::closeEvent (e); } void variable_dock_widget::handle_focus_change (QWidget *old, QWidget *now) { octave_unused_parameter (now); // This is a proxied test if (hasFocus ()) { if (old == this) return; if (titleBarWidget () != nullptr) { QLabel *label = titleBarWidget ()->findChild<QLabel *> (); if (label != nullptr) { label->setBackgroundRole (QPalette::Highlight); label->setStyleSheet ("background-color: palette(highlight); color: palette(highlightedText);"); } } emit variable_focused_signal (objectName ()); } else if (old == focusWidget()) { if (titleBarWidget () != nullptr) { QLabel *label = titleBarWidget ()->findChild<QLabel *> (); if (label != nullptr) { label->setBackgroundRole (QPalette::NoRole); label->setStyleSheet (";"); } } } } void variable_dock_widget::resizeEvent (QResizeEvent *) { if (m_frame) m_frame->resize (size ()); } // See Octave bug #53807 and https://bugreports.qt.io/browse/QTBUG-44813 #if (QT_VERSION >= 0x050302) && (QT_VERSION <= QTBUG_44813_FIX_VERSION) bool variable_dock_widget::event (QEvent *event) { // low-level check of whether docked-widget became a window via // via drag-and-drop if (event->type () == QEvent::MouseButtonPress) { m_waiting_for_mouse_move = false; m_waiting_for_mouse_button_release = false; } if (event->type () == QEvent::MouseMove && m_waiting_for_mouse_move) { m_waiting_for_mouse_move = false; m_waiting_for_mouse_button_release = true; } if (event->type () == QEvent::MouseButtonRelease && m_waiting_for_mouse_button_release) { m_waiting_for_mouse_button_release = false; bool retval = QDockWidget::event (event); if (isFloating ()) emit queue_unfloat_float (); return retval; } return QDockWidget::event (event); } void variable_dock_widget::unfloat_float (void) { hide (); setFloating (false); // Avoid a Ubunty Unity issue by queuing this rather than direct. emit queue_float (); m_waiting_for_mouse_move = false; m_waiting_for_mouse_button_release = false; } void variable_dock_widget::refloat (void) { setFloating (true); m_waiting_for_mouse_move = false; m_waiting_for_mouse_button_release = false; show (); activateWindow (); setFocus (); } #else void variable_dock_widget::unfloat_float (void) { } void variable_dock_widget::refloat (void) { } #endif // Variable editor stack variable_editor_stack::variable_editor_stack (QWidget *p, base_qobject& oct_qobj) : QStackedWidget (p), m_octave_qobj (oct_qobj), m_edit_view (new variable_editor_view (this, m_octave_qobj)) { setFocusPolicy (Qt::StrongFocus); m_disp_view = make_disp_view (this); addWidget (m_edit_view); addWidget (m_disp_view); } QTextEdit * variable_editor_stack::make_disp_view (QWidget *parent) { QTextEdit *viewer = new QTextEdit (parent); viewer->setLineWrapMode (QTextEdit::NoWrap); viewer->setReadOnly (true); return viewer; } void variable_editor_stack::set_editable (bool editable) { // The QTableView is for editable data models // and the QTextEdit is for non-editable models. if (editable) { if (m_edit_view != nullptr) { setCurrentWidget (m_edit_view); setFocusProxy (m_edit_view); m_edit_view->setFocusPolicy (Qt::StrongFocus); } if (m_disp_view != nullptr) m_disp_view->setFocusPolicy (Qt::NoFocus); } else { if (m_disp_view != nullptr) { setCurrentWidget (m_disp_view); setFocusProxy (m_disp_view); QAbstractTableModel *model = findChild<QAbstractTableModel *> (); if (model != nullptr) m_disp_view->setPlainText (model->data (QModelIndex ()).toString ()); else m_disp_view->setPlainText (""); } if (m_edit_view != nullptr) m_edit_view->setFocusPolicy (Qt::NoFocus); } } void variable_editor_stack::levelUp (void) { if (! hasFocus ()) return; QString name = objectName (); // FIXME: Is there a better way? if (name.endsWith (')') || name.endsWith ('}')) { name.remove ( QRegExp ("[({][^({]*[)}]$)") ); emit edit_variable_signal (name, octave_value ()); } } // Slot for saving a variable into a file void variable_editor_stack::save (const QString& format) { if (! hasFocus ()) return; // Check whether a format for saving the variable is given QString format_string; if (! format.isEmpty ()) { format_string = "-" + format; do_save (format_string, format_string); return; } // No format given, test save default options emit interpreter_event ([=] (interpreter& interp) { // INTERPRETER THREAD octave_value_list argout = Fsave_default_options (interp, octave_value_list (), 1); QString save_opts = QString::fromStdString (argout(0).string_value ()); connect (this, &variable_editor_stack::do_save_signal, this, &variable_editor_stack::do_save); emit (do_save_signal (format_string, save_opts)); }); } // Perform saving the variable after desired format is determined void variable_editor_stack::do_save (const QString& format, const QString& save_opts) { QString file_ext = "txt"; for (int i = 0; i < ve_save_formats_ext.length ()/2; i++) { if (save_opts.contains (ve_save_formats_ext.at (2*i), Qt::CaseInsensitive)) { file_ext = ve_save_formats_ext.at (2*i + 1); break; } } // FIXME: Remove, if for all common KDE versions (bug #54607) is resolved. int opts = 0; // No options by default. resource_manager& rmgr = m_octave_qobj.get_resource_manager (); gui_settings *settings = rmgr.get_settings (); if (! settings->value (global_use_native_dialogs).toBool ()) opts = QFileDialog::DontUseNativeDialog; QString name = objectName (); QString file = QFileDialog::getSaveFileName (this, tr ("Save Variable %1 As").arg (name), QString ("./%1.%2").arg (name).arg (file_ext), 0, 0, QFileDialog::Option (opts)); if (file.isEmpty ()) return; // No file selected: Just return // Let the interpreter thread do the saving emit interpreter_event ([=] (interpreter& interp) { // INTERPRETER THREAD octave_value_list ovl; std::list<octave_value> str_list = {octave_value (file.toStdString ()), octave_value (name.toStdString ())}; if (! format.isEmpty ()) str_list.push_front (octave_value (format.toStdString ())); Fsave (interp, octave_value_list (str_list)); }); } // Custom editable variable table view variable_editor_view::variable_editor_view (QWidget *p, base_qobject& oct_qobj) : QTableView (p), m_octave_qobj (oct_qobj), m_var_model (nullptr) { setWordWrap (false); setContextMenuPolicy (Qt::CustomContextMenu); setSelectionMode (QAbstractItemView::ContiguousSelection); horizontalHeader ()->setContextMenuPolicy (Qt::CustomContextMenu); verticalHeader ()->setContextMenuPolicy (Qt::CustomContextMenu); setHorizontalScrollMode (QAbstractItemView::ScrollPerPixel); setVerticalScrollMode (QAbstractItemView::ScrollPerPixel); verticalHeader ()->setSectionResizeMode (QHeaderView::Interactive); } void variable_editor_view::setModel (QAbstractItemModel *model) { QTableView::setModel (model); horizontalHeader ()->setSectionResizeMode (QHeaderView::Interactive); m_var_model = parent ()->findChild<variable_editor_model *> (); if (m_var_model != nullptr && m_var_model->column_width () > 0) { // col_width is in characters. The font should be a fixed-width // font, so any character will do. If not, you lose! QFontMetrics fm (font ()); int w = (m_var_model->column_width () * qt_fontmetrics_horizontal_advance (fm, '0')); horizontalHeader ()->setDefaultSectionSize (w); } } QList<int> variable_editor_view::range_selected (void) { QItemSelectionModel *sel = selectionModel (); // Return early if nothing selected. if (! sel->hasSelection ()) return QList<int> (); QList<QModelIndex> indices = sel->selectedIndexes (); // FIXME: Shouldn't this be keyed to octave_idx_type? 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 ()); } QVector<int> vect; vect << from_row + 1 << to_row + 1 << from_col + 1 << to_col + 1; QList<int> range = QList<int>::fromVector(vect); return range; } void variable_editor_view::selected_command_requested (const QString& cmd) { if (! hasFocus ()) return; QList<int> range = range_selected (); if (range.isEmpty ()) { // Nothing selected, apply print command to all data range << 1 << m_var_model->data_rows () << 1 << m_var_model->data_columns (); } int s1 = m_var_model->data_rows (); int s2 = m_var_model->data_columns (); if (s1 < range.at (0) || s2 < range.at (2)) return; // Selected range does not contain data s1 = std::min (s1, range.at (1)); s2 = std::min (s2, range.at (3)); // Variable with desired range as string QString variable = QString ("%1(%2:%3,%4:%5)") .arg (objectName ()) .arg (range.at (0)).arg (s1) .arg (range.at (2)).arg (s2); // Desired command as string QString command; if (cmd == "create") command = QString ("unnamed = %1;").arg (variable); else command = QString ("figure (); %1 (%2); title ('%2');") .arg (cmd).arg (variable); emit command_signal (command); } void variable_editor_view::add_edit_actions (QMenu *menu, const QString& qualifier_string) { resource_manager& rmgr = m_octave_qobj.get_resource_manager (); menu->addAction (rmgr.icon ("edit-cut"), tr ("Cut") + qualifier_string, this, &variable_editor_view::cutClipboard); menu->addAction (rmgr.icon ("edit-copy"), tr ("Copy") + qualifier_string, this, &variable_editor_view::copyClipboard); menu->addAction (rmgr.icon ("edit-paste"), tr ("Paste"), this, &variable_editor_view::pasteClipboard); menu->addSeparator (); menu->addAction (rmgr.icon ("edit-delete"), tr ("Clear") + qualifier_string, this, &variable_editor_view::clearContent); menu->addAction (rmgr.icon ("edit-delete"), tr ("Delete") + qualifier_string, this, &variable_editor_view::delete_selected); menu->addAction (rmgr.icon ("document-new"), tr ("Variable from Selection"), this, &variable_editor_view::createVariable); } void variable_editor_view::createContextMenu (const QPoint& qpos) { QModelIndex index = indexAt (qpos); if (index.isValid ()) { QMenu *menu = new QMenu (this); add_edit_actions (menu, tr ("")); // FIXME: addAction for sort? // FIXME: Add icon for transpose. menu->addAction (tr ("Transpose"), this, &variable_editor_view::transposeContent); QItemSelectionModel *sel = selectionModel (); QList<QModelIndex> indices = sel->selectedIndexes (); if (! indices.isEmpty ()) { menu->addSeparator (); QSignalMapper *plot_mapper = make_plot_mapper (menu); connect (plot_mapper, SIGNAL (mapped (const QString&)), this, SLOT (selected_command_requested (const QString&))); } menu->exec (mapToGlobal (qpos)); } } void variable_editor_view::createColumnMenu (const QPoint& pt) { int index = horizontalHeader ()->logicalIndexAt (pt); if (index < 0 || index > model ()->columnCount ()) return; QList<int> coords = range_selected (); bool nothingSelected = coords.isEmpty (); bool whole_columns_selected = (nothingSelected ? false : (coords[0] == 1 && coords[1] == 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) { selectColumn (index); column_selection_count = 1; } QString column_string = column_selection_count > 1 ? tr (" columns") : tr (" column"); QMenu *menu = new QMenu (this); add_edit_actions (menu, column_string); menu->addSeparator (); QSignalMapper *plot_mapper = make_plot_mapper (menu); connect (plot_mapper, SIGNAL (mapped (const QString&)), this, SLOT (selected_command_requested (const QString&))); QPoint menupos = pt; menupos.setY (horizontalHeader ()->height ()); menu->exec (mapToGlobal (menupos)); } void variable_editor_view::createRowMenu (const QPoint& pt) { int index = verticalHeader ()->logicalIndexAt (pt); if (index < 0 || index > model ()->columnCount ()) return; QList<int> coords = range_selected (); bool nothingSelected = coords.isEmpty (); bool whole_rows_selected = (nothingSelected ? false : (coords[2] == 1 && coords[3] == 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) { selectRow (index); rowselection_count = 1; } QString row_string = rowselection_count > 1 ? tr (" rows") : tr (" row"); QMenu *menu = new QMenu (this); add_edit_actions (menu, row_string); menu->addSeparator (); QSignalMapper *plot_mapper = make_plot_mapper (menu); connect (plot_mapper, SIGNAL (mapped (const QString&)), this, SLOT (selected_command_requested (const QString&))); QPoint menupos = pt; menupos.setX (verticalHeader ()->width ()); // FIXME: What was the intent here? // setY (verticalHeader ()->sectionPosition (index+1) + // verticalHeader ()->sectionSize (index)); menu->exec (mapToGlobal (menupos)); } void variable_editor_view::createVariable (void) { // FIXME: Create unnamed1..n if exist ('unnamed', 'var') is true. selected_command_requested ("create"); } void variable_editor_view::transposeContent (void) { if (! hasFocus ()) return; emit command_signal (QString ("%1 = %1';").arg (objectName ())); } void variable_editor_view::delete_selected (void) { if (! hasFocus ()) return; QAbstractItemModel *mod = model (); QList<int> coords = range_selected (); if (coords.isEmpty ()) return; bool whole_columns_selected = coords[0] == 1 && coords[1] == mod->rowCount (); bool whole_rows_selected = coords[2] == 1 && coords[3] == mod->columnCount (); // Must be deleting whole columns or whole rows, and not the whole thing. if (whole_columns_selected == whole_rows_selected) return; if (whole_rows_selected) mod->removeRows (coords[0], coords[1] - coords[0]); if (whole_columns_selected) mod->removeColumns (coords[2], coords[3] - coords[2]); } void variable_editor_view::clearContent (void) { if (! hasFocus ()) return; if (m_var_model == nullptr) return; QItemSelectionModel *sel = selectionModel (); QList<QModelIndex> indices = sel->selectedIndexes (); // FIXME: Use [] for empty cells? for (const auto& idx : indices) m_var_model->clear_content (idx); } void variable_editor_view::cutClipboard (void) { copyClipboard (); clearContent (); } void variable_editor_view::copyClipboard (void) { if (! hasFocus ()) return; QItemSelectionModel *sel = selectionModel (); QList<QModelIndex> indices = sel->selectedIndexes (); std::sort (indices.begin (), indices.end ()); if (indices.isEmpty ()) return; // Convert selected items into TSV format and copy that. // Spreadsheet tools should understand that. QAbstractItemModel *mod = model (); QModelIndex previous = indices.first (); QString copy = mod->data (previous).toString (); indices.removeFirst (); for (auto idx : indices) { copy.push_back (previous.row () != idx.row () ? '\n' : '\t'); copy.append (mod->data (idx).toString ()); previous = idx; } QClipboard *clipboard = QApplication::clipboard (); clipboard->setText (copy); } void variable_editor_view::pasteClipboard (void) { if (! hasFocus ()) return; QAbstractItemModel *mod = model (); QItemSelectionModel *sel = selectionModel (); QList<QModelIndex> indices = sel->selectedIndexes (); QClipboard *clipboard = QApplication::clipboard (); QString text = clipboard->text (); QPoint start, end; QPoint tabsize = QPoint (mod->rowCount (), mod->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; mod->setData (mod->index (rownum + start.x (), colnum + start.y ()), QVariant (col)); colnum++; } colnum = 0; rownum++; } } void variable_editor_view::handle_horizontal_scroll_action (int action) { if (action == QAbstractSlider::SliderSingleStepAdd || action == QAbstractSlider::SliderPageStepAdd || action == QAbstractSlider::SliderToMaximum || action == QAbstractSlider::SliderMove) { if (m_var_model != nullptr) { QScrollBar *sb = horizontalScrollBar (); if (sb && sb->value () == sb->maximum ()) { int new_cols = m_var_model->display_columns () + 16; m_var_model->maybe_resize_columns (new_cols); } } } } void variable_editor_view::handle_vertical_scroll_action (int action) { if (action == QAbstractSlider::SliderSingleStepAdd || action == QAbstractSlider::SliderPageStepAdd || action == QAbstractSlider::SliderToMaximum || action == QAbstractSlider::SliderMove) { if (m_var_model != nullptr) { QScrollBar *sb = verticalScrollBar (); if (sb && sb->value () == sb->maximum ()) { int new_rows = m_var_model->display_rows () + 16; m_var_model->maybe_resize_rows (new_rows); } } } } // Gadgets for focus restoration HoverToolButton::HoverToolButton (QWidget *parent) : QToolButton (parent) { installEventFilter (this); } bool HoverToolButton::eventFilter (QObject *obj, QEvent *ev) { if (ev->type () == QEvent::HoverEnter) emit hovered_signal (); else if (ev->type () == QEvent::MouseButtonPress) emit popup_shown_signal (); return QToolButton::eventFilter (obj, ev); } ReturnFocusToolButton::ReturnFocusToolButton (QWidget *parent) : HoverToolButton (parent) { installEventFilter (this); } bool ReturnFocusToolButton::eventFilter (QObject *obj, QEvent *ev) { if (ev->type () == QEvent::MouseButtonRelease && isDown ()) { emit about_to_activate (); setDown (false); QAction *action = defaultAction (); if (action != nullptr) action->activate (QAction::Trigger); return true; } return HoverToolButton::eventFilter (obj, ev); } ReturnFocusMenu::ReturnFocusMenu (QWidget *parent) : QMenu (parent) { installEventFilter (this); } bool ReturnFocusMenu::eventFilter (QObject *obj, QEvent *ev) { if (ev->type () == QEvent::MouseButtonRelease && underMouse ()) { emit about_to_activate (); } return QMenu::eventFilter (obj, ev); } // Variable editor. variable_editor::variable_editor (QWidget *p, base_qobject& oct_qobj) : octave_dock_widget ("VariableEditor", p, oct_qobj), m_main (new dw_main_window (oct_qobj)), m_tool_bar (new QToolBar (m_main)), m_default_width (30), m_default_height (100), m_add_font_height (0), m_use_terminal_font (true), m_alternate_rows (true), m_stylesheet (""), m_font (), m_sel_font (), m_table_colors (), m_current_focus_vname (""), m_hovered_focus_vname (""), m_plot_mapper (nullptr), m_focus_widget (nullptr), m_focus_widget_vdw (nullptr) { set_title (tr ("Variable Editor")); setStatusTip (tr ("Edit variables.")); setWindowIcon (QIcon (":/actions/icons/logo.png")); setAttribute (Qt::WA_AlwaysShowToolTips); m_main->setParent (this); // See Octave bug #53409 and https://bugreports.qt.io/browse/QTBUG-55357 #if (QT_VERSION < 0x050601) || (QT_VERSION >= 0x050701) m_main->setDockOptions (QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::VerticalTabs); #else m_main->setDockNestingEnabled (true); #endif // Tool Bar. construct_tool_bar (); m_main->addToolBar (m_tool_bar); // Colors. for (int i = 0; i < ve_colors_count; i++) m_table_colors.append (QColor (Qt::white)); // Use an MDI area that is shrunk to nothing as the central widget. // Future feature might be to switch to MDI mode in which the dock // area is shrunk to nothing and the widgets live in the MDI window. QMdiArea *central_mdiarea = new QMdiArea (m_main); central_mdiarea->setMinimumSize (QSize (0, 0)); central_mdiarea->setMaximumSize (QSize (0, 0)); central_mdiarea->resize (QSize (0, 0)); m_main->setCentralWidget (central_mdiarea); setWidget (m_main); if (! p) make_window (); } void variable_editor::focusInEvent (QFocusEvent *ev) { octave_dock_widget::focusInEvent (ev); // set focus to the current variable or most recent if still valid if (m_focus_widget != nullptr) { // Activating a floating window causes problems. if (! m_focus_widget_vdw->isFloating ()) activateWindow (); m_focus_widget->setFocus (); } else { QWidget *fw = m_main->focusWidget (); if (fw != nullptr) { activateWindow (); fw->setFocus (); } else { QDockWidget *any_qdw = m_main->findChild<QDockWidget *> (); if (any_qdw != nullptr) { activateWindow (); any_qdw->setFocus (); } else setFocus(); } } } variable_editor::~variable_editor (void) { // FIXME: Maybe toolbar actions could be handled with signals and // slots so that deleting the toolbar here would disconnect all // toolbar actions and any other slots that might try to access the // toolbar would work properly (I'm looking at you, // handle_focus_change). delete m_tool_bar; m_tool_bar = nullptr; } void variable_editor::edit_variable (const QString& name, const octave_value& val) { resource_manager& rmgr = m_octave_qobj.get_resource_manager (); if (m_stylesheet.isEmpty ()) { gui_settings *settings = rmgr.get_settings (); notice_settings (settings); } QDockWidget *existing_qdw = m_main->findChild<QDockWidget *> (name); if (existing_qdw != NULL) { // Already open. // Put current focused variable out of focus if (m_main->focusWidget () != nullptr) { QFocusEvent event (QEvent::FocusOut, Qt::OtherFocusReason); QApplication::sendEvent (m_main->focusWidget (), &event); } // Put existing variable in focus and raise m_main->parentWidget ()->show (); existing_qdw->show (); existing_qdw->raise (); existing_qdw->activateWindow (); tab_to_front (); existing_qdw->setFocus (); return; } variable_dock_widget *page = new variable_dock_widget (this, m_octave_qobj); page->setObjectName (name); m_main->addDockWidget (Qt::LeftDockWidgetArea, page); // The old-style signal/slot connection appears to be needed here to // prevent a crash when closing a variable_dock_widget object. connect (qApp, SIGNAL (focusChanged (QWidget *, QWidget *)), page, SLOT (handle_focus_change (QWidget *, QWidget *))); connect (this, &variable_editor::visibilityChanged, page, &variable_dock_widget::setVisible); // Notify the variable editor for page actions. connect (page, &variable_dock_widget::destroyed, this, &variable_editor::variable_destroyed); connect (page, &variable_dock_widget::variable_focused_signal, this, &variable_editor::variable_focused); // See Octave bug #53807 and https://bugreports.qt.io/browse/QTBUG-44813 #if (QT_VERSION >= 0x050302) && (QT_VERSION <= QTBUG_44813_FIX_VERSION) connect (page, SIGNAL (queue_unfloat_float ()), page, SLOT (unfloat_float ()), Qt::QueuedConnection); connect (page, SIGNAL (queue_float ()), page, SLOT (refloat ()), Qt::QueuedConnection); #endif variable_editor_stack *stack = new variable_editor_stack (page, m_octave_qobj); stack->setObjectName (name); page->setWidget (stack); page->setFocusProxy (stack); // Any interpreter_event signal from a variable_editor_stack object is // handled the same as for the parent variable_editor object. connect (stack, QOverload<const fcn_callback&>::of (&variable_editor_stack::interpreter_event), this, QOverload<const fcn_callback&>::of (&variable_editor::interpreter_event)); connect (stack, QOverload<const meth_callback&>::of (&variable_editor_stack::interpreter_event), this, QOverload<const meth_callback&>::of (&variable_editor::interpreter_event)); connect (stack, &variable_editor_stack::edit_variable_signal, this, &variable_editor::edit_variable); connect (this, &variable_editor::level_up_signal, stack, &variable_editor_stack::levelUp); connect (this, &variable_editor::save_signal, stack, [=] () { stack->save (); }); variable_editor_view *edit_view = stack->edit_view (); edit_view->setObjectName (name); edit_view->setFont (m_font); edit_view->setStyleSheet (m_stylesheet); edit_view->setAlternatingRowColors (m_alternate_rows); edit_view->verticalHeader ()->setDefaultSectionSize (m_default_height + m_add_font_height); connect (m_plot_mapper, SIGNAL (mapped (const QString&)), edit_view, SLOT (selected_command_requested (const QString&))); connect (m_save_mapper, SIGNAL (mapped (const QString&)), stack, SLOT (save (const QString&))); connect (edit_view, &variable_editor_view::command_signal, this, &variable_editor::command_signal); connect (this, &variable_editor::delete_selected_signal, edit_view, &variable_editor_view::delete_selected); connect (this, &variable_editor::clear_content_signal, edit_view, &variable_editor_view::clearContent); connect (this, &variable_editor::copy_clipboard_signal, edit_view, &variable_editor_view::copyClipboard); connect (this, &variable_editor::paste_clipboard_signal, edit_view, &variable_editor_view::pasteClipboard); connect (edit_view->horizontalHeader (), &QHeaderView::customContextMenuRequested, edit_view, &variable_editor_view::createColumnMenu); connect (edit_view->verticalHeader (), &QHeaderView::customContextMenuRequested, edit_view, &variable_editor_view::createRowMenu); connect (edit_view, &variable_editor_view::customContextMenuRequested, edit_view, &variable_editor_view::createContextMenu); connect (edit_view->horizontalScrollBar (), &QScrollBar::actionTriggered, edit_view, &variable_editor_view::handle_horizontal_scroll_action); connect (edit_view->verticalScrollBar (), &QScrollBar::actionTriggered, edit_view, &variable_editor_view::handle_vertical_scroll_action); variable_editor_model *model = new variable_editor_model (name, val, stack); connect (model, &variable_editor_model::edit_variable_signal, this, &variable_editor::edit_variable); connect (model, &variable_editor_model::dataChanged, this, &variable_editor::callUpdate); connect (this, &variable_editor::refresh_signal, model, &variable_editor_model::update_data_cache); connect (model, &variable_editor_model::set_editable_signal, stack, &variable_editor_stack::set_editable); edit_view->setModel (model); connect (edit_view, &variable_editor_view::doubleClicked, model, &variable_editor_model::double_click); // Any interpreter_event signal from a variable_editor_model object is // handled the same as for the parent variable_editor object. connect (model, QOverload<const fcn_callback&>::of (&variable_editor_model::interpreter_event), this, QOverload<const fcn_callback&>::of (&variable_editor::interpreter_event)); connect (model, QOverload<const meth_callback&>::of (&variable_editor_model::interpreter_event), this, QOverload<const meth_callback&>::of (&variable_editor::interpreter_event)); // Must supply a title for a QLabel to be created. Calling set_title() // more than once will add more QLabels. Could change octave_dock_widget // to always supply a QLabel (initially empty) and then simply update its // contents. page->set_title (name); if (page->titleBarWidget () != nullptr) { QLabel *existing_ql = page->titleBarWidget ()->findChild<QLabel *> (); // FIXME: What was the intent here? update_label_signal does // not seem to exist now. connect (model, SIGNAL (description_changed (const QString&)), existing_ql, SLOT (setText (const QString&))); existing_ql->setMargin (2); } model->update_data (val); if (m_tool_bar) { QList<QTableView *> viewlist = findChildren<QTableView *> (); if (viewlist.size () == 1 && m_tool_bar) m_tool_bar->setEnabled (true); } show (); page->show (); page->raise (); page->activateWindow (); tab_to_front (); page->setFocus (); } void variable_editor::tab_to_front (void) { QWidget *parent = parentWidget (); if (parent) { QList<QTabBar *> barlist = parent->findChildren<QTabBar *> (); QVariant this_value (reinterpret_cast<quintptr> (this)); for (auto *tbar : barlist) { for (int i = 0; i < tbar->count (); i++) { if (tbar->tabData (i) == this_value) { tbar->setCurrentIndex (i); return; } } } } } void variable_editor::refresh (void) { emit refresh_signal (); } void variable_editor::callUpdate (const QModelIndex&, const QModelIndex&) { emit updated (); } void variable_editor::notice_settings (const gui_settings *settings) { m_main->notice_settings (settings); // update settings in parent main win m_default_width = settings->value (ve_column_width).toInt (); m_default_height = settings->value (ve_row_height).toInt (); m_alternate_rows = settings->value (ve_alternate_rows).toBool (); m_use_terminal_font = settings->value (ve_use_terminal_font).toBool (); QString font_name; int font_size; QString default_font = settings->value (global_mono_font).toString (); if (m_use_terminal_font) { font_name = settings->value (cs_font.key, default_font).toString (); font_size = settings->value (cs_font_size).toInt (); } else { font_name = settings->value (ve_font_name.key, default_font).toString (); font_size = settings->value (ve_font_size).toInt (); } m_font = QFont (font_name, font_size); QFontMetrics fm (m_font); m_add_font_height = fm.height (); int mode = settings->value (ve_color_mode).toInt (); for (int i = 0; i < ve_colors_count; i++) { QColor setting_color = settings->color_value (ve_colors[i], mode); m_table_colors.replace (i, setting_color); } update_colors (); // Icon size in the toolbar. if (m_tool_bar) { int size_idx = settings->value (global_icon_size).toInt (); size_idx = (size_idx > 0) - (size_idx < 0) + 1; // Make valid index from 0 to 2 QStyle *st = style (); int icon_size = st->pixelMetric (global_icon_sizes[size_idx]); m_tool_bar->setIconSize (QSize (icon_size, icon_size)); } // Shortcuts (same as file editor) shortcut_manager& scmgr = m_octave_qobj.get_shortcut_manager (); scmgr.set_shortcut (m_save_action, sc_edit_file_save); } void variable_editor::closeEvent (QCloseEvent *e) { emit finished (); octave_dock_widget::closeEvent (e); } void variable_editor::variable_destroyed (QObject *obj) { // Invalidate the focus-restoring widget pointer if currently active. if (m_focus_widget_vdw == obj) { m_focus_widget = nullptr; m_focus_widget_vdw = nullptr; } if (m_tool_bar) { // If no variable pages remain, deactivate the tool bar. QList<variable_dock_widget *> vdwlist = findChildren<variable_dock_widget *> (); if (vdwlist.isEmpty ()) m_tool_bar->setEnabled (false); } QFocusEvent ev (QEvent::FocusIn); focusInEvent (&ev); } void variable_editor::variable_focused (const QString& name) { m_current_focus_vname = name; // focusWidget() appears lost in transition to/from main window // so keep a record of the widget. QWidget *current = QApplication::focusWidget (); m_focus_widget = nullptr; m_focus_widget_vdw = nullptr; if (current != nullptr) { QList<variable_dock_widget *> vdwlist = findChildren<variable_dock_widget *> (); for (int i = 0; i < vdwlist.size (); i++) { variable_dock_widget *vdw = vdwlist.at (i); if (vdw->isAncestorOf (current)) { m_focus_widget = current; m_focus_widget_vdw = vdw; break; } } } } void variable_editor::record_hovered_focus_variable (void) { m_hovered_focus_vname = m_current_focus_vname; } void variable_editor::restore_hovered_focus_variable (void) { variable_dock_widget *tofocus = findChild<variable_dock_widget *> (m_hovered_focus_vname); if (tofocus != nullptr) { // Note that this may be platform and window system dependent. // On a particular Linux system, activateWindow() alone didn't // immediately set the active window and there was a race // between the window focus and action signal. Setting the // active window via the QApplication route did work. QApplication::setActiveWindow(tofocus->window()); tofocus->activateWindow (); tofocus->setFocus (Qt::OtherFocusReason); } } void variable_editor::save (void) { emit save_signal (); } void variable_editor::cutClipboard (void) { copyClipboard (); emit clear_content_signal (); } void variable_editor::copyClipboard (void) { emit copy_clipboard_signal (); } void variable_editor::pasteClipboard (void) { emit paste_clipboard_signal (); emit updated (); } void variable_editor::levelUp (void) { emit level_up_signal (); } // Also updates the font. void variable_editor::update_colors (void) { m_stylesheet = ""; if (m_table_colors.length () > 0) m_stylesheet += "QTableView::item{ color: " + m_table_colors[0].name () +" }"; if (m_table_colors.length () > 1) m_stylesheet += "QTableView::item{ background-color: " + m_table_colors[1].name () +" }"; if (m_table_colors.length () > 2) m_stylesheet += "QTableView::item{ selection-color: " + m_table_colors[2].name () +" }"; if (m_table_colors.length () > 3) 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 () +" }"; } QList<QTableView *> viewlist = findChildren<QTableView *> (); for (int i = 0; i < viewlist.size (); i++) { QTableView *view = viewlist.at (i); if (! view) continue; view->setAlternatingRowColors (m_alternate_rows); view->setStyleSheet (m_stylesheet); view->setFont (m_font); } } QAction * variable_editor::add_tool_bar_button (const QIcon& icon, const QString& text, const QObject *receiver, const char *member) { QAction *action = new QAction (icon, text, this); connect(action, SIGNAL (triggered ()), receiver, member); QToolButton *button = new ReturnFocusToolButton (m_tool_bar); button->setDefaultAction (action); button->setText (text); button->setToolTip (text); button->setIcon (icon); m_tool_bar->addWidget (button); return action; } void variable_editor::construct_tool_bar (void) { m_tool_bar->setAllowedAreas (Qt::TopToolBarArea); m_tool_bar->setObjectName ("VariableEditorToolBar"); m_tool_bar->setWindowTitle (tr ("Variable Editor Toolbar")); resource_manager& rmgr = m_octave_qobj.get_resource_manager (); m_save_action = add_tool_bar_button (rmgr.icon ("document-save"), tr ("Save"), this, SLOT (save ())); addAction (m_save_action); m_save_action->setShortcutContext (Qt::WidgetWithChildrenShortcut); m_save_action->setStatusTip(tr("Save variable to a file")); QAction *action = new QAction (rmgr.icon ("document-save-as"), tr ("Save in format ..."), m_tool_bar); QToolButton *save_tool_button = new HoverToolButton (m_tool_bar); save_tool_button->setDefaultAction (action); save_tool_button->setText (tr ("Save in format ...")); save_tool_button->setToolTip (tr("Save variable to a file in different format")); save_tool_button->setIcon (rmgr.icon ("document-save-as")); save_tool_button->setPopupMode (QToolButton::InstantPopup); QMenu *save_menu = new ReturnFocusMenu (save_tool_button); save_menu->setTitle (tr ("Save in format ...")); save_menu->setSeparatorsCollapsible (false); m_save_mapper = new QSignalMapper (save_menu); for (int i = 0; i < ve_save_formats.length (); i++) m_save_mapper->setMapping (save_menu->addAction (ve_save_formats.at (i), m_save_mapper, SLOT (map ())), ve_save_formats.at (i)); save_tool_button->setMenu (save_menu); m_tool_bar->addWidget (save_tool_button); m_tool_bar->addSeparator (); action = add_tool_bar_button (rmgr.icon ("edit-cut"), tr ("Cut"), this, SLOT (cutClipboard ())); action->setStatusTip(tr("Cut data to clipboard")); action = add_tool_bar_button (rmgr.icon ("edit-copy"), tr ("Copy"), this, SLOT (copyClipboard ())); action->setStatusTip(tr("Copy data to clipboard")); action = add_tool_bar_button (rmgr.icon ("edit-paste"), tr ("Paste"), this, SLOT (pasteClipboard ())); action->setStatusTip(tr("Paste clipboard into variable data")); m_tool_bar->addSeparator (); // FIXME: Add a print item? // QAction *print_action; /icons/fileprint.png // m_tool_bar->addSeparator (); action = new QAction (rmgr.icon ("plot-xy-curve"), tr ("Plot"), m_tool_bar); action->setToolTip (tr ("Plot Selected Data")); QToolButton *plot_tool_button = new HoverToolButton (m_tool_bar); plot_tool_button->setDefaultAction (action); plot_tool_button->setText (tr ("Plot")); plot_tool_button->setToolTip (tr ("Plot selected data")); plot_tool_button->setIcon (rmgr.icon ("plot-xy-curve")); plot_tool_button->setPopupMode (QToolButton::InstantPopup); QMenu *plot_menu = new ReturnFocusMenu (plot_tool_button); plot_menu->setTitle (tr ("Plot")); plot_menu->setSeparatorsCollapsible (false); m_plot_mapper = make_plot_mapper (plot_menu); plot_tool_button->setMenu (plot_menu); m_tool_bar->addWidget (plot_tool_button); m_tool_bar->addSeparator (); action = add_tool_bar_button (rmgr.icon ("go-up"), tr ("Up"), this, SLOT (levelUp ())); action->setStatusTip(tr("Go one level up in variable hierarchy")); // The QToolButton mouse-clicks change active window, so connect all // HoverToolButton and ReturnFocusToolButton objects to the mechanism // that restores active window and focus before acting. QList<HoverToolButton *> hbuttonlist = m_tool_bar->findChildren<HoverToolButton *> ("" , Qt::FindDirectChildrenOnly ); for (int i = 0; i < hbuttonlist.size (); i++) { connect (hbuttonlist.at (i), &HoverToolButton::hovered_signal, this, &variable_editor::record_hovered_focus_variable); connect (hbuttonlist.at (i), &HoverToolButton::popup_shown_signal, this, &variable_editor::restore_hovered_focus_variable); } QList<ReturnFocusToolButton *> rfbuttonlist = m_tool_bar->findChildren<ReturnFocusToolButton *> ("" , Qt::FindDirectChildrenOnly ); for (int i = 0; i < rfbuttonlist.size (); i++) { connect (rfbuttonlist.at (i), &ReturnFocusToolButton::about_to_activate, this, &variable_editor::restore_hovered_focus_variable); } // Same for QMenu QList<ReturnFocusMenu *> menulist = m_tool_bar->findChildren<ReturnFocusMenu *> (); for (int i = 0; i < menulist.size (); i++) { connect (menulist.at (i), &ReturnFocusMenu::about_to_activate, this, &variable_editor::restore_hovered_focus_variable); } m_tool_bar->setAttribute(Qt::WA_ShowWithoutActivating); m_tool_bar->setFocusPolicy (Qt::NoFocus); // Disabled when no tab is present. m_tool_bar->setEnabled (false); } }