Mercurial > octave
view libgui/graphics/Figure.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 | d4d83344d653 |
children | ca7d58406f82 c6d54dd31a7e |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 2011-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 <QAction> #include <QActionEvent> #include <QApplication> #include <QClipboard> #include <QEvent> #include <QFileDialog> #include <QFileInfo> #include <QFrame> #include <QImage> #include <QMainWindow> #include <QMenu> #include <QMenuBar> #include <QMessageBox> #include <QtDebug> #include <QTimer> #include <QToolBar> #if defined (HAVE_QSCREEN_DEVICEPIXELRATIO) # include <QWindow> # include <QScreen> #endif #include "Canvas.h" #include "Container.h" #include "Figure.h" #include "FigureWindow.h" #include "QtHandlesUtils.h" #include "gui-preferences-global.h" #include "qt-interpreter-events.h" #include "file-ops.h" #include "unwind-prot.h" #include "utils.h" #include "version.h" #include "builtin-defun-decls.h" #include "interpreter.h" namespace octave { DECLARE_GENERICEVENTNOTIFY_SENDER(MenuBar, QMenuBar); static QRect boundingBoxToRect (const Matrix& bb) { QRect r; if (bb.numel () == 4) { r = QRect (octave::math::round (bb(0)), octave::math::round (bb(1)), octave::math::round (bb(2)), octave::math::round (bb(3))); if (! r.isValid ()) r = QRect (); } return r; } static QImage pointer_to_qimage (const Matrix& cdata) { QImage retval (cdata.rows (), cdata.columns (), QImage::Format_ARGB32); QColor tmp ("White"); QColor black ("Black"); QColor white ("White"); for (octave_idx_type ii = 0; ii < cdata.rows (); ii++) for (octave_idx_type jj = 0; jj < cdata.columns (); jj++) { if (cdata(ii, jj) == 1.0) tmp = black; else if (cdata(ii, jj) == 2.0) tmp = white; else tmp.setAlpha (0); retval.setPixel (jj, ii, tmp.rgba ()); } return retval; } Figure * Figure::create (octave::base_qobject& oct_qobj, octave::interpreter& interp, const graphics_object& go) { return new Figure (oct_qobj, interp, go, new FigureWindow ()); } Figure::Figure (octave::base_qobject& oct_qobj, octave::interpreter& interp, const graphics_object& go, FigureWindow *win) : Object (oct_qobj, interp, go, win), m_blockUpdates (false), m_figureToolBar (nullptr), m_menuBar (nullptr), m_innerRect (), m_outerRect (), m_previousHeight (0), m_resizable (true) { m_container = new Container (win, oct_qobj, interp); win->setCentralWidget (m_container); connect (m_container, QOverload<const octave::fcn_callback&>::of (&Container::interpreter_event), this, QOverload<const octave::fcn_callback&>::of (&Figure::interpreter_event)); connect (m_container, QOverload<const octave::meth_callback&>::of (&Container::interpreter_event), this, QOverload<const octave::meth_callback&>::of (&Figure::interpreter_event)); figure::properties& fp = properties<figure> (); // Adjust figure position m_innerRect = boundingBoxToRect (fp.get_boundingbox (true)); m_outerRect = boundingBoxToRect (fp.get_boundingbox (false)); set_geometry (m_innerRect); // Menubar m_menuBar = new MenuBar (win); win->setMenuBar (m_menuBar); m_menuBar->addReceiver (this); m_menuBar->setStyleSheet (m_menuBar->styleSheet () + global_menubar_style); // Status bar m_statusBar = win->statusBar (); m_statusBar->setVisible (false); if (fp.toolbar_is ("figure") || (fp.toolbar_is ("auto") && fp.menubar_is ("figure"))) showFigureStatusBar (true); // Enable mouse tracking unconditionally enableMouseTracking (); // When this constructor gets called all properties are already // set, even non default. We force "update" here to get things right. // Figure title update (figure::properties::ID_NUMBERTITLE); // Decide what keyboard events we listen to m_container->canvas (m_handle)->setEventMask (0); update (figure::properties::ID_KEYPRESSFCN); update (figure::properties::ID_KEYRELEASEFCN); // modal style update (figure::properties::ID_WINDOWSTYLE); // Handle resizing constraints update (figure::properties::ID_RESIZE); // Custom pointer data update (figure::properties::ID_POINTERSHAPECDATA); // Visibility update (figure::properties::ID_VISIBLE); connect (this, &Figure::asyncUpdate, this, &Figure::updateContainer); // Register for the signal that indicates when a window has moved // to a different screen connect (win, &FigureWindow::figureWindowShown, this, &Figure::figureWindowShown); win->addReceiver (this); m_container->addReceiver (this); } Figure::~Figure (void) { } QString Figure::fileName (void) { gh_manager& gh_mgr = m_interpreter.get_gh_manager (); octave::autolock guard (gh_mgr.graphics_lock ()); const figure::properties& fp = properties<figure> (); std::string name = fp.get_filename (); return QString::fromStdString (name); } void Figure::setFileName (const QString& name) { gh_manager& gh_mgr = m_interpreter.get_gh_manager (); octave::autolock guard (gh_mgr.graphics_lock ()); figure::properties& fp = properties<figure> (); fp.set_filename (name.toStdString ()); } MouseMode Figure::mouseMode (void) { gh_manager& gh_mgr = m_interpreter.get_gh_manager (); octave::autolock guard (gh_mgr.graphics_lock ()); const figure::properties& fp = properties<figure> (); std::string mode = fp.get___mouse_mode__ (); if (mode == "zoom") { octave_scalar_map zm = fp.get___zoom_mode__ ().scalar_map_value (); std::string direction = zm.getfield ("Direction").string_value (); mode += ' ' + direction; } if (mode == "rotate") return RotateMode; else if (mode == "zoom in") return ZoomInMode; else if (mode == "zoom out") return ZoomOutMode; else if (mode == "pan") return PanMode; else if (mode == "text") return TextMode; return NoMode; } void Figure::set_geometry (QRect r) { QMainWindow *win = qWidget<QMainWindow> (); if (! m_resizable) { win->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred); win->setFixedSize (QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); } // Unlock window if it is maximized or full-screen int state = win->windowState (); if (state == Qt::WindowFullScreen || state == Qt::WindowMaximized) win->setWindowState (Qt::WindowNoState); win->setGeometry (r); if (! m_resizable) { win->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); win->setFixedSize(win->size ()); } } Container * Figure::innerContainer (void) { return m_container; } void Figure::redraw (void) { Canvas *canvas = m_container->canvas (m_handle); if (canvas) canvas->redraw (); for (auto *qobj : qWidget<QWidget> ()->findChildren<QObject *> ()) { if (qobj->objectName () == "UIPanel" || qobj->objectName () == "UIButtonGroup" || qobj->objectName () == "UIControl" || qobj->objectName () == "UITable") { Object *obj = Object::fromQObject (qobj); if (obj) obj->slotRedraw (); } } } void Figure::show (void) { QWidget *win = qWidget<QWidget> (); win->activateWindow (); win->raise (); } void Figure::print (const QString& file_cmd, const QString& term) { Canvas *canvas = m_container->canvas (m_handle); if (canvas) canvas->print (file_cmd, term); } uint8NDArray Figure::slotGetPixels (void) { uint8NDArray retval; Canvas *canvas = m_container->canvas (m_handle); if (canvas) { gh_manager& gh_mgr = m_interpreter.get_gh_manager (); gh_mgr.process_events (); octave::autolock guard (gh_mgr.graphics_lock ()); retval = canvas->getPixels (); } return retval; } void Figure::beingDeleted (void) { Canvas *canvas = m_container->canvas (m_handle.value (), false); if (canvas) canvas->blockRedraw (true); m_container->removeReceiver (this); qWidget<FigureWindow> ()->removeReceiver (this); } void Figure::update (int pId) { if (m_blockUpdates) return; figure::properties& fp = properties<figure> (); if (fp.is___printing__ ()) return; QMainWindow *win = qWidget<QMainWindow> (); // If the window doesn't exist, there's nothing we can do. if (! win) return; m_blockUpdates = true; switch (pId) { case figure::properties::ID_POSITION: { m_innerRect = boundingBoxToRect (fp.get_boundingbox (true)); int toffset = 0; int boffset = 0; for (auto *tb : win->findChildren<QToolBar *> ()) if (! tb->isHidden ()) toffset += tb->sizeHint ().height (); if (! m_menuBar->isHidden ()) toffset += m_menuBar->sizeHint ().height (); if (! m_statusBar->isHidden ()) boffset += m_statusBar->sizeHint ().height (); set_geometry (m_innerRect.adjusted (0, -toffset, 0, boffset)); } break; case figure::properties::ID_NAME: case figure::properties::ID_NUMBERTITLE: win->setWindowTitle (Utils::fromStdString (fp.get_title ())); break; case figure::properties::ID_VISIBLE: if (fp.is_visible ()) { QTimer::singleShot (0, win, &QMainWindow::show); if (! fp.is___gl_window__ ()) { gh_manager& gh_mgr = m_interpreter.get_gh_manager (); octave::autolock guard (gh_mgr.graphics_lock ()); fp.set ("__gl_window__", "on"); } } else win->hide (); break; case figure::properties::ID_RESIZE: if (fp.is_resize ()) { win->setSizePolicy (QSizePolicy::Preferred, QSizePolicy::Preferred); win->setFixedSize (QSize( QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)); m_resizable = true; } else { win->setSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed); win->setFixedSize(win->size ()); m_resizable = false; } break; case figure::properties::ID_MENUBAR: case figure::properties::ID_TOOLBAR: if (fp.toolbar_is ("none")) showFigureStatusBar (false); else if (fp.toolbar_is ("figure")) showFigureStatusBar (true); else // "auto" showFigureStatusBar (fp.menubar_is ("figure")); break; case figure::properties::ID_KEYPRESSFCN: if (fp.get_keypressfcn ().isempty ()) m_container->canvas (m_handle)->clearEventMask (Canvas::KeyPress); else m_container->canvas (m_handle)->addEventMask (Canvas::KeyPress); // Signal the change to uipanels as well for (auto *qobj : qWidget<QWidget> ()->findChildren<QObject *> ()) { if (qobj->objectName () == "UIPanel") { Object *obj = Object::fromQObject (qobj); if (obj) { if (fp.get_keypressfcn ().isempty ()) obj->innerContainer ()->canvas (m_handle)-> clearEventMask (Canvas::KeyPress); else obj->innerContainer ()->canvas (m_handle)-> addEventMask (Canvas::KeyPress); } } } break; case figure::properties::ID_KEYRELEASEFCN: if (fp.get_keyreleasefcn ().isempty ()) m_container->canvas (m_handle)->clearEventMask (Canvas::KeyRelease); else m_container->canvas (m_handle)->addEventMask (Canvas::KeyRelease); break; // Signal the change to uipanels as well for (auto *qobj : qWidget<QWidget> ()->findChildren<QObject *> ()) { if (qobj->objectName () == "UIPanel") { Object *obj = Object::fromQObject (qobj); if (obj) { if (fp.get_keypressfcn ().isempty ()) obj->innerContainer ()->canvas (m_handle)-> clearEventMask (Canvas::KeyRelease); else obj->innerContainer ()->canvas (m_handle)-> addEventMask (Canvas::KeyRelease); } } } break; case figure::properties::ID_WINDOWSTYLE: if (fp.windowstyle_is ("modal")) { bool is_visible = win->isVisible (); // if window is already visible, need to hide and reshow it in order to // make it use the modal settings if (is_visible) win->setVisible (false); win->setWindowModality (Qt::ApplicationModal); win->setVisible (is_visible); } else win->setWindowModality (Qt::NonModal); break; case figure::properties::ID_POINTERSHAPECDATA: m_pointer_cdata = pointer_to_qimage (fp.get_pointershapecdata ().matrix_value ()); if (fp.get_pointer () != "custom") break; OCTAVE_FALLTHROUGH; case figure::properties::ID_POINTER: case figure::properties::ID_POINTERSHAPEHOTSPOT: case figure::properties::ID___MOUSE_MODE__: case figure::properties::ID___ZOOM_MODE__: m_container->canvas (m_handle)->setCursor (mouseMode (), fp.get_pointer (), m_pointer_cdata, fp.get_pointershapehotspot () .matrix_value()); break; default: break; } m_blockUpdates = false; } void Figure::showFigureStatusBar (bool visible) { if (m_statusBar && (! m_statusBar->isHidden ()) != visible) { int dy = m_statusBar->sizeHint ().height (); QRect r = qWidget<QWidget> ()->geometry (); if (! visible) r.adjust (0, 0, 0, -dy); else r.adjust (0, 0, 0, dy); m_blockUpdates = true; set_geometry (r); m_statusBar->setVisible (visible); m_blockUpdates = false; updateBoundingBox (false); } } void Figure::updateFigureHeight (int dh) { gh_manager& gh_mgr = m_interpreter.get_gh_manager (); octave::autolock guard (gh_mgr.graphics_lock ()); graphics_object go = object (); if (go.valid_object () && dh != 0) { QRect r = qWidget<QWidget> ()->geometry (); r.adjust (0, dh, 0, 0); m_blockUpdates = true; set_geometry (r); m_blockUpdates = false; updateBoundingBox (false); } } void Figure::updateStatusBar (ColumnVector pt) { if (! m_statusBar->isHidden ()) m_statusBar->showMessage (QString ("(%1, %2)") .arg (pt(0), 0, 'g', 5) .arg (pt(1), 0, 'g', 5)); } void Figure::do_connections (const QObject *receiver, const QObject * /* emitter */) { Object::do_connections (receiver); Object::do_connections (receiver, m_container->canvas (m_handle)); } QWidget * Figure::menu (void) { return qWidget<QMainWindow> ()->menuBar (); } void Figure::updateBoundingBox (bool internal, int flags) { QWidget *win = qWidget<QWidget> (); Matrix bb (1, 4); std::string prop; if (internal) { prop = "position"; QRect r = m_innerRect; if (flags & UpdateBoundingBoxPosition) r.moveTopLeft (win->mapToGlobal (m_container->pos ())); if (flags & UpdateBoundingBoxSize) r.setSize (m_container->size ()); if (r.isValid () && r != m_innerRect) { m_innerRect = r; bb(0) = r.x (); bb(1) = r.y (); bb(2) = r.width (); bb(3) = r.height (); } else return; } else { prop = "outerposition"; QRect r = m_outerRect; if (flags & UpdateBoundingBoxPosition) r.moveTopLeft (win->pos ()); if (flags & UpdateBoundingBoxSize) r.setSize (win->frameGeometry ().size ()); if (r.isValid () && r != m_outerRect) { m_outerRect = r; bb(0) = r.x (); bb(1) = r.y (); bb(2) = r.width (); bb(3) = r.height (); } else return; } figure::properties& fp = properties<figure> (); emit gh_set_event (m_handle, prop, fp.bbox2position (bb), false, prop == "position"); } bool Figure::eventNotifyBefore (QObject *obj, QEvent *xevent) { if (! m_blockUpdates) { // Clicking the toolbar or the menubar makes this figure current if (xevent->type () == QEvent::MouseButtonPress) { figure::properties& fp = properties<figure> (); gh_manager& gh_mgr = m_interpreter.get_gh_manager (); graphics_object root = gh_mgr.get_object (0); if (fp.get_handlevisibility () == "on") root.set ("currentfigure", fp.get___myhandle__ ().as_octave_value ()); } if (obj == m_container) { // Do nothing... } else if (obj == m_menuBar) { switch (xevent->type ()) { case QEvent::ActionAdded: case QEvent::ActionChanged: case QEvent::ActionRemoved: m_previousHeight = m_menuBar->sizeHint ().height (); default: break; } } else { switch (xevent->type ()) { case QEvent::Close: xevent->ignore (); emit gh_callback_event (m_handle, "closerequestfcn"); return true; default: break; } } } return false; } void Figure::eventNotifyAfter (QObject *watched, QEvent *xevent) { if (! m_blockUpdates) { if (watched == m_container) { gh_manager& gh_mgr = m_interpreter.get_gh_manager (); switch (xevent->type ()) { case QEvent::Resize: updateBoundingBox (true, UpdateBoundingBoxSize); break; case QEvent::ChildAdded: if (dynamic_cast<QChildEvent *> (xevent)->child ()->isWidgetType()) { octave::autolock guard (gh_mgr.graphics_lock ()); update (figure::properties::ID_TOOLBAR); enableMouseTracking (); } break; case QEvent::ChildRemoved: if (dynamic_cast<QChildEvent *> (xevent)->child ()->isWidgetType()) { octave::autolock guard (gh_mgr.graphics_lock ()); update (figure::properties::ID_TOOLBAR); } break; default: break; } } else if (watched == m_menuBar) { switch (xevent->type ()) { case QEvent::ActionAdded: case QEvent::ActionChanged: case QEvent::ActionRemoved: // The menubar may have been resized if no action is visible { QAction *a = dynamic_cast<QActionEvent *> (xevent)->action (); int currentHeight = m_menuBar->sizeHint ().height (); if (currentHeight != m_previousHeight && ! a->isSeparator ()) updateFigureHeight (m_previousHeight - currentHeight); } break; default: break; } } else { switch (xevent->type ()) { case QEvent::Move: updateBoundingBox (false, UpdateBoundingBoxPosition); updateBoundingBox (true, UpdateBoundingBoxPosition); break; case QEvent::Resize: updateBoundingBox (false, UpdateBoundingBoxSize); break; default: break; } } } } void Figure::addCustomToolBar (QToolBar *bar, bool visible, bool isdefault) { QMainWindow *win = qWidget<QMainWindow> (); if (isdefault) m_figureToolBar = bar; if (! visible) win->addToolBar (bar); else { QSize sz = bar->sizeHint (); QRect r = win->geometry (); r.adjust (0, -sz.height (), 0, 0); m_blockUpdates = true; set_geometry (r); win->addToolBarBreak (); win->addToolBar (bar); m_blockUpdates = false; updateBoundingBox (false); } } void Figure::showCustomToolBar (QToolBar *bar, bool visible) { QMainWindow *win = qWidget<QMainWindow> (); if ((! bar->isHidden ()) != visible) { QSize sz = bar->sizeHint (); QRect r = win->geometry (); if (visible) r.adjust (0, -sz.height (), 0, 0); else r.adjust (0, sz.height (), 0, 0); m_blockUpdates = true; set_geometry (r); bar->setVisible (visible); m_blockUpdates = false; updateBoundingBox (false); } } void Figure::updateContainer (void) { redraw (); } void Figure::figureWindowShown () { #if defined (HAVE_QSCREEN_DEVICEPIXELRATIO) QWindow *window = qWidget<QMainWindow> ()->windowHandle (); QScreen *screen = window->screen (); gh_manager& gh_mgr = m_interpreter.get_gh_manager (); octave::autolock guard (gh_mgr.graphics_lock ()); figure::properties& fp = properties<figure> (); fp.set___device_pixel_ratio__ (screen->devicePixelRatio ()); connect (window, &QWindow::screenChanged, this, &Figure::screenChanged); #endif } void Figure::screenChanged (QScreen *screen) { #if defined (HAVE_QSCREEN_DEVICEPIXELRATIO) gh_manager& gh_mgr = m_interpreter.get_gh_manager (); octave::autolock guard (gh_mgr.graphics_lock ()); figure::properties& fp = properties<figure> (); double old_dpr = fp.get___device_pixel_ratio__ (); double new_dpr = screen->devicePixelRatio (); if (old_dpr != new_dpr) { fp.set___device_pixel_ratio__ (new_dpr); // For some obscure reason, changing the __device_pixel_ratio__ property // from the GUI thread does not necessarily trigger a redraw. Force it. redraw (); } #else octave_unused_parameter (screen); #endif } void Figure::enableMouseTracking (void) { // Enable mouse tracking on every widgets m_container->setMouseTracking (true); m_container->canvas (m_handle)->qWidget ()->setMouseTracking (true); for (auto *w : m_container->findChildren<QWidget *> ()) w->setMouseTracking (true); } }