Mercurial > octave
view libgui/graphics/Figure.cc @ 27224:9b8903d3e626
Allow figure "keypressfcn" to work with uipanel on the way (bug #56598)
* Panel.cc (Panel::Panel): Make sure the canvas listens to key events when the
parent figure has non empty "key*fcn" properties.
* Figure.cc (Figure::update): Look for uipanel children objects and update their
canvas key event mask when the "key*fcn" are changed.
* Canvas.cc (Canvas::canvasKeyPressEvent, Canvas::canvasKeyReleaseEvent):
Use the handle of the ancestor figure to post changes since the m_handle may
a handle to a uipanel.
author | Pantxo Diribarne <pantxo.diribarne@gmail.com> |
---|---|
date | Tue, 09 Jul 2019 19:41:27 +0200 |
parents | 6bc32e6a1b4f |
children | 99aa1bcb8848 |
line wrap: on
line source
/* Copyright (C) 2011-2019 Michael Goffioul 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 "file-ops.h" #include "unwind-prot.h" #include "utils.h" #include "version.h" #include "octave-qt-link.h" #include "builtin-defun-decls.h" namespace QtHandles { 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 (const graphics_object& go) { return new Figure (go, new FigureWindow ()); } Figure::Figure (const graphics_object& go, FigureWindow *win) : Object (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); win->setCentralWidget (m_container); 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); // 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, SIGNAL (asyncUpdate (void)), this, SLOT (updateContainer (void))); // Register for the signal that indicates when a window has moved // to a different screen connect (win, SIGNAL (figureWindowShown ()), this, SLOT (figureWindowShown ())); win->addReceiver (this); m_container->addReceiver (this); } Figure::~Figure (void) { } QString Figure::fileName (void) { gh_manager::auto_lock 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::auto_lock lock; figure::properties& fp = properties<figure> (); fp.set_filename (name.toStdString ()); } MouseMode Figure::mouseMode (void) { gh_manager::auto_lock 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 (); foreach (QObject *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::process_events (); gh_manager::auto_lock 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; foreach (QToolBar *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, SLOT (show ())); if (! fp.is___gl_window__ ()) { gh_manager::auto_lock 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 foreach (QObject *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 foreach (QObject *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::auto_lock 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)); } 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> (); gh_manager::post_set (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> (); graphics_object root = gh_manager::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 (); gh_manager::post_callback (m_handle, "closerequestfcn"); return true; default: break; } } } return false; } void Figure::eventNotifyAfter (QObject *watched, QEvent *xevent) { if (! m_blockUpdates) { if (watched == m_container) { switch (xevent->type ()) { case QEvent::Resize: updateBoundingBox (true, UpdateBoundingBoxSize); break; case QEvent::ChildAdded: if (dynamic_cast<QChildEvent *> (xevent)->child ()->isWidgetType()) { gh_manager::auto_lock lock; update (figure::properties::ID_TOOLBAR); enableMouseTracking (); } break; case QEvent::ChildRemoved: if (dynamic_cast<QChildEvent *> (xevent)->child ()->isWidgetType()) { gh_manager::auto_lock 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::auto_lock lock; figure::properties& fp = properties<figure> (); fp.set___device_pixel_ratio__ (screen->devicePixelRatio ()); connect (window, SIGNAL (screenChanged (QScreen*)), this, SLOT (screenChanged (QScreen*))); #endif } void Figure::screenChanged (QScreen* screen) { #if defined (HAVE_QSCREEN_DEVICEPIXELRATIO) gh_manager::auto_lock 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); foreach (QWidget *w, m_container->findChildren<QWidget*> ()) w->setMouseTracking (true); } }