Mercurial > octave
view libgui/src/octave-dock-widget.cc @ 32632:2e484f9f1f18 stable
maint: update Octave Project Developers copyright for the new year
author | John W. Eaton <jwe@octave.org> |
---|---|
date | Fri, 22 Dec 2023 12:08:17 -0500 |
parents | 6ff63f154144 |
children | 4b601ca024d5 |
line wrap: on
line source
//////////////////////////////////////////////////////////////////////// // // Copyright (C) 2012-2024 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 <QApplication> #include <QHBoxLayout> #include <QLabel> #include <QScreen> #include <QStyle> #include <QToolBar> #include <QMenuBar> #include <QWindow> #include "gui-preferences-dw.h" #include "gui-preferences-global.h" #include "gui-preferences-mw.h" #include "gui-preferences-sc.h" #include "gui-settings.h" #include "gui-utils.h" #include "main-window.h" #include "octave-dock-widget.h" OCTAVE_BEGIN_NAMESPACE(octave) label_dock_widget::label_dock_widget (QWidget *p) : QDockWidget (p), m_default_float_button (nullptr), m_default_close_button (nullptr) { QStyle *st = style (); m_icon_size = 0.75*st->pixelMetric (QStyle::PM_SmallIconSize); // keep track of the original buttons on the default title bar, // the button further left is considered "float" QList<QAbstractButton *> buttonlist = findChildren<QAbstractButton *> (); if (buttonlist.size () == 2) { if (buttonlist.at (0)->x () < buttonlist.at (1)->x ()) { m_default_float_button = buttonlist.at (0); m_default_close_button = buttonlist.at (1); } else { m_default_float_button = buttonlist.at (1); m_default_close_button = buttonlist.at (0); } } gui_settings settings; // the custom (extra) title bar of the widget m_title_widget = new QWidget (); m_dock_action = new QAction (settings.icon ("widget-undock", true), "", this); m_dock_action->setToolTip (tr ("Undock Widget")); m_dock_button = new QToolButton (m_title_widget); m_dock_button->setDefaultAction (m_dock_action); m_dock_button->setFocusPolicy (Qt::NoFocus); m_dock_button->setIconSize (QSize (m_icon_size, m_icon_size)); m_close_action = new QAction (settings.icon ("widget-close", true), "", this); m_close_action->setToolTip (tr ("Close Widget")); m_close_button = new QToolButton (m_title_widget); m_close_button->setDefaultAction (m_close_action); m_close_button->setFocusPolicy (Qt::NoFocus); m_close_button->setIconSize (QSize (m_icon_size, m_icon_size)); QString css_button = QString ("QToolButton {background: transparent; border: 0px;}"); m_dock_button->setStyleSheet (css_button); m_close_button->setStyleSheet (css_button); QHBoxLayout *h_layout = new QHBoxLayout (); h_layout->addStretch (100); h_layout->addWidget (m_dock_button); h_layout->addWidget (m_close_button); h_layout->setSpacing (10); h_layout->setContentsMargins (5, 2, 2, 2); m_title_widget->setLayout (h_layout); if (p && (p->objectName () == gui_obj_name_main_window)) { // Only connect the when a parent (main window) is given // copy & paste handling connect (p, SIGNAL (copyClipboard_signal ()), this, SLOT (copyClipboard ())); connect (p, SIGNAL (pasteClipboard_signal ()), this, SLOT (pasteClipboard ())); connect (p, SIGNAL (selectAll_signal ()), this, SLOT (selectAll ())); // undo handling connect (p, SIGNAL (undo_signal ()), this, SLOT (do_undo ())); } } // set the title in the dockwidgets title bar void label_dock_widget::set_title (const QString& title) { QHBoxLayout *h_layout = static_cast<QHBoxLayout *> (m_title_widget->layout ()); QLabel *label = new QLabel (title, m_title_widget); label->setStyleSheet ("background-color: transparent;"); h_layout->insertWidget (0, label); setTitleBarWidget (m_title_widget); setWindowTitle (title); } static QString qdockwidget_css (const QString& close_icon, const QString& close_tooltip, const QString& float_icon, const QString& float_tooltip, int icon_size, const QString& titlebar_foreground, const QString& titlebar_background) { return QString ("QDockWidget\n" "{\n" "%6" " border: none;\n" " titlebar-close-icon: url(%1);\n" " titlebar-normal-icon: url(%2);\n" "}\n" "\n" "QDockWidget::close-button, QDockWidget::float-button\n" "{\n" " border: none;\n" " icon-size: %3px;\n" "}\n" "\n" "QAbstractButton#qt_dockwidget_closebutton\n" "{\n" " qproperty-toolTip: \"%4\";\n" "}\n" "\n" "QAbstractButton#qt_dockwidget_floatbutton\n" "{\n" " qproperty-toolTip: \"%5\";\n" "}\n" "\n" "QDockWidget::title {\n" " text-align: left;\n" "%7" " padding-left: 1px;\n" "}\n" "\n" "QDockWidget::close-button\n" "{\n" " right: %8px;\n" " top: 3px;\n" "}\n" "\n" "QDockWidget::float-button\n" "{\n" " right: %9px;\n" " top: 3px;\n" "}\n" ).arg (close_icon).arg (float_icon).arg (icon_size) .arg (close_tooltip).arg (float_tooltip) .arg (titlebar_foreground).arg (titlebar_background) .arg ((icon_size*2)/3).arg((icon_size*7)/3); } octave_dock_widget::octave_dock_widget (const QString& obj_name, QWidget *p) : label_dock_widget (p), m_main_window (nullptr), m_adopted (false), m_custom_style (false), m_focus_follows_mouse (false), m_recent_float_geom (), m_recent_dock_geom (), m_waiting_for_mouse_button_release (false) { setObjectName (obj_name); // FIXME: Can we avoid the cast here? m_main_window = dynamic_cast<main_window *> (p); m_predecessor_widget = nullptr; connect (this, &octave_dock_widget::topLevelChanged, this, &octave_dock_widget::toplevel_change); connect (this, &octave_dock_widget::visibilityChanged, this, &octave_dock_widget::handle_visibility); if (m_default_float_button != nullptr) { disconnect (m_default_float_button, 0, 0, 0); connect (m_default_float_button, &QAbstractButton::clicked, this, &octave_dock_widget::make_window); } connect (this, &octave_dock_widget::queue_make_window, this, &octave_dock_widget::make_window, Qt::QueuedConnection); connect (this, &octave_dock_widget::queue_make_widget, this, [=] () { make_widget (); }, Qt::QueuedConnection); gui_settings settings; settings.set_shortcut (m_dock_action, sc_dock_widget_dock); m_dock_action->setShortcutContext (Qt::WidgetWithChildrenShortcut); addAction (m_dock_action); connect (m_dock_action, &QAction::triggered, this, &octave_dock_widget::make_window); settings.set_shortcut (m_close_action, sc_dock_widget_close); m_close_action->setShortcutContext (Qt::WidgetWithChildrenShortcut); addAction (m_close_action); connect (m_close_action, &QAction::triggered, this, &octave_dock_widget::change_visibility); m_close_action->setToolTip (tr ("Hide Widget")); setStyleSheet (qdockwidget_css (global_icon_paths.at (ICON_THEME_OCTAVE) + "widget-close.png", QString ("Close Widget"), global_icon_paths.at (ICON_THEME_OCTAVE) + "widget-undock.png", QString ("Undock Widget"), m_icon_size, "", "")); if (widget ()) widget ()->setToolTip (""); m_icon_color = ""; m_title_3d = 50; installEventFilter (this); setFocusPolicy (Qt::StrongFocus); setFeatures (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); handle_settings (); } void octave_dock_widget::init_window_menu_entry () { emit active_changed (isVisible ()); // emit once for init of window menu } // make the widget floating void octave_dock_widget::make_window (bool widget_was_dragged) { bool vis = isVisible (); // prevent follow-up calls by clearing state variable m_waiting_for_mouse_button_release = false; set_focus_predecessor (); // set focus previously active widget if tabbed // Before unparenting, get current geometry for restoring if dragged QRect geom = geometry (); // the widget has to be reparented (parent = 0), preferably // from a non-toplevel widget otherwise may not have full // decorations, e.g., no taskbar icon and always in front if (isFloating ()) setFloating (false); if (m_main_window) { // Before making it a separate (no more parent) floating widget, // remove the dock widget from the main window. This ensures // that tabbed widgets keep their focus when it is re-docked // later m_main_window->removeDockWidget (this); } setParent (0, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint | Qt::Window); // restore the last geometry when floating only if we have not dragged // the window outside the main window if (! widget_was_dragged) geom = m_recent_float_geom.isNull () ? QRect (50,100,480,480) : m_recent_float_geom; setGeometry (geom); // adjust the (un)dock action disconnect (m_dock_action, 0, this, 0); connect (m_dock_action, &QAction::triggered, this, &octave_dock_widget::make_widget); // adjust the (un)dock icon if (titleBarWidget ()) { gui_settings settings; m_dock_action->setIcon (settings.icon ("widget-dock" + m_icon_color, true)); m_dock_action->setToolTip (tr ("Dock Widget")); } else { disconnect (m_default_float_button, 0, this, 0); connect (m_default_float_button, &QAbstractButton::clicked, this, &octave_dock_widget::make_widget); } raise (); activateWindow (); if (vis) { show (); setFocus (); set_style (true); } emit topLevelChanged (true); // Be sure signal is emitted } // dock the widget void octave_dock_widget::make_widget (bool) { gui_settings settings; bool vis = isVisible (); // Since floating widget has no parent, we have to read it if (m_main_window) { settings.setValue (mw_state.settings_key (), m_main_window->saveState ()); // Stay window, otherwise will bounce back to window by default // because there is no layout information for this widget in the // saved settings. setParent (m_main_window, Qt::Window); m_main_window->addDockWidget (Qt::BottomDockWidgetArea, this); m_adopted = false; // recover old window states, hide and re-show new added widget m_main_window->restoreState (settings.value (mw_state.settings_key ()).toByteArray ()); setFloating (false); // restore size using setGeometry instead of restoreGeometry // following this post: // https://forum.qt.io/topic/79326/qdockwidget-restoregeometry-not-working-correctly-when-qmainwindow-is-maximized/5 setGeometry (m_recent_dock_geom); } // adjust the (un)dock icon disconnect (m_dock_action, 0, this, 0); connect (m_dock_action, &QAction::triggered, this, &octave_dock_widget::make_window); if (titleBarWidget ()) { m_dock_action->setIcon (settings.icon ("widget-undock" + m_icon_color, true)); m_dock_action->setToolTip (tr ("Undock Widget")); } else { disconnect (m_default_float_button, 0, this, 0); connect (m_default_float_button, &QAbstractButton::clicked, this, &octave_dock_widget::make_window); } raise (); activateWindow (); if (vis) { show (); setFocus (); set_style (true); } } // dock the widget void octave_dock_widget::default_dock (bool) { setFloating (false); } // set the widget which previously had focus when tabified void octave_dock_widget::set_predecessor_widget (octave_dock_widget *prev_widget) { m_predecessor_widget = prev_widget; } void octave_dock_widget::set_main_window (main_window *mw) { m_main_window = mw; if (m_main_window) { connect (m_main_window, &main_window::copyClipboard_signal, this, &octave_dock_widget::copyClipboard); connect (m_main_window, &main_window::pasteClipboard_signal, this, &octave_dock_widget::pasteClipboard); connect (m_main_window, &main_window::selectAll_signal, this, &octave_dock_widget::selectAll); connect (m_main_window, &main_window::undo_signal, this, &octave_dock_widget::do_undo); } } // close event void octave_dock_widget::closeEvent (QCloseEvent *e) { emit active_changed (false); set_focus_predecessor (); save_settings (); QDockWidget::closeEvent (e); } // get focus widget QWidget * octave_dock_widget::focusWidget () { QWidget *w = QApplication::focusWidget (); if (w && w->focusProxy ()) w = w->focusProxy (); return w; } bool octave_dock_widget::event (QEvent *event) { // low-level check of whether docked-widget became a window via // double-click or via drag-and-drop if ( (event->type () == QEvent::MouseButtonDblClick && ! isFloating ()) || (event->type () == QEvent::ActivationChange && m_waiting_for_mouse_button_release)) { bool retval = QDockWidget::event (event); if (isFloating () && parent () != 0) { m_waiting_for_mouse_button_release = false; emit queue_make_window (event->type () != QEvent::MouseButtonDblClick); } return retval; } return QDockWidget::event (event); } void octave_dock_widget::handle_settings () { gui_settings settings; m_focus_follows_mouse = settings.bool_value (dw_focus_follows_mouse); m_custom_style = settings.bool_value (dw_title_custom_style); m_title_3d = settings.int_value (dw_title_3d); m_fg_color = settings.color_value (dw_title_fg_color); m_fg_color_active = settings.color_value (dw_title_fg_color_active); m_bg_color = settings.color_value (dw_title_bg_color); m_bg_color_active = settings.color_value (dw_title_bg_color_active); QColor bcol (m_bg_color); QColor bcola (m_bg_color_active); if (! m_custom_style) { bcol = QWidget::palette ().color (m_title_widget->backgroundRole ()); bcola = bcol; } int r, g, b; bcol.getRgb (&r, &g, &b); if (r+g+b < 400) m_icon_color = "-light"; else m_icon_color = ""; bcola.getRgb (&r, &g, &b); if (r+g+b < 400) m_icon_color_active = "-light"; else m_icon_color_active = ""; int x, y, w, h; QGuiApplication::primaryScreen ()->availableGeometry ().getRect (&x, &y, &w, &h); QRect default_floating_size = QRect (x+16, y+32, w/3, h/2); QRect default_dock_size; if (m_main_window) { // We have a main window, dock size depends on size of main window m_main_window->geometry ().getRect (&x, &y, &w, &h); default_dock_size = QRect (x+16, y+32, w/3, h/3); } else { // No main window, default dock size should never be used default_dock_size = QRect (0, 0, w/10, h/10); } m_recent_float_geom = settings.value (dw_float_geometry.settings_key ().arg (objectName ()), default_floating_size).toRect (); adjust_to_screen (m_recent_float_geom, default_floating_size); // The following is required for ensure smooth transition from old // saveGeomety to new QRect setting (see comment for restoring size // of docked widgets) QVariant dock_geom = settings.value (dw_dock_geometry.settings_key ().arg (objectName ()), default_dock_size); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (dock_geom.canConvert (QMetaType (QMetaType::QRect))) #else if (dock_geom.canConvert (QMetaType::QRect)) #endif m_recent_dock_geom = dock_geom.toRect (); else m_recent_dock_geom = dw_dock_geometry.def ().toRect (); notice_settings (); // call individual handler set_style (false); // Compacter design QToolBar *toolbar = findChild <QToolBar *> (); if (toolbar) toolbar->setStyleSheet (toolbar->styleSheet () + global_toolbar_style); QMenuBar *menubar = findChild <QMenuBar *> (); if (menubar) menubar->setStyleSheet (menubar->styleSheet () + global_menubar_style); } void octave_dock_widget::handle_active_dock_changed (octave_dock_widget *w_old, octave_dock_widget *w_new) { if (m_custom_style && this == w_old) { set_style (false); update (); } if (m_custom_style && this == w_new) { set_style (true); update (); } } void octave_dock_widget::save_settings () { gui_settings settings; // save state of this dock-widget QString name = objectName (); store_geometry (); // conditional needed? if (! m_recent_float_geom.isNull ()) settings.setValue (dw_float_geometry.settings_key ().arg (name), m_recent_float_geom); if (! m_recent_dock_geom.isEmpty ()) settings.setValue (dw_dock_geometry.settings_key ().arg (name), m_recent_dock_geom); settings.setValue (dw_is_visible.settings_key ().arg (name), isVisible ()); // store visibility settings.setValue (dw_is_floating.settings_key ().arg (name), isFloating ()); // store floating settings.setValue (dw_is_minimized.settings_key ().arg (name), isMinimized ()); // store minimized settings.sync (); } bool octave_dock_widget::eventFilter (QObject *obj, QEvent *e) { // Ignore double clicks into window decoration elements if (e->type () == QEvent::NonClientAreaMouseButtonDblClick) { e->ignore (); return true; } // Detect mouse enter events if "focus follows mouse" is desired // for widgets docked to the main window (non floating) and activate // the widget currently under the mouse if (m_focus_follows_mouse && ! isFloating () && (e->type () == QEvent::Enter)) setFocus (); return QDockWidget::eventFilter (obj, e); } void octave_dock_widget::store_geometry () { if (isFloating ()) { if (! parent ()) m_recent_float_geom = geometry (); } else { m_recent_dock_geom = geometry (); } } void octave_dock_widget::moveEvent (QMoveEvent *event) { store_geometry (); QDockWidget::moveEvent (event); } void octave_dock_widget::resizeEvent (QResizeEvent *event) { store_geometry (); QDockWidget::resizeEvent (event); } // slot for hiding the widget void octave_dock_widget::change_visibility (bool) { setVisible (false); emit active_changed (false); } void octave_dock_widget::activate () { if (! isVisible ()) setVisible (true); setFocus (); activateWindow (); raise (); } void octave_dock_widget::handle_visibility (bool visible) { if (visible) { emit active_changed (true); if (! isFloating ()) setFocus (); } } void octave_dock_widget::toplevel_change (bool toplevel) { QObject *dockobj; const char *docksig; if (titleBarWidget ()) { dockobj = m_dock_action; docksig = SIGNAL (triggered (bool)); } else { dockobj = m_default_float_button; docksig = SIGNAL (clicked (bool)); } if (toplevel) { // This is a fallback in case the attempt to create a floated // top-level window fails and the QDockWidget remains a child // of the QMainWindow. connect (dockobj, docksig, this, SLOT (default_dock (bool))); // Could be dragging window, so must wait until there is a // change in focus. if (parent () != 0) m_waiting_for_mouse_button_release = true; } else { // If a drag-and-drop within the QMainWindow occurred, want to remain a widget. m_waiting_for_mouse_button_release = false; // Making into a widget immediately will mangle the double-click // status and cause problems on followup button clicks. if (parent () == 0) emit queue_make_widget (); } } void octave_dock_widget::set_style (bool active) { QString css_foreground; QString css_background; QString css_button; QString dock_icon; QString icon_col = m_icon_color; QString close_tooltip = "Close Widget"; QString dock_tooltip; if (isFloating ()) { dock_icon = "widget-dock"; dock_tooltip = "Dock Widget"; } else { dock_icon = "widget-undock"; dock_tooltip = "Undock Widget"; } #if defined (Q_OS_MAC) QString alignment = "center"; #else QString alignment = "center left"; #endif if (m_custom_style) { QColor bg_col, fg_col; if (active) { bg_col = m_bg_color_active; fg_col = m_fg_color_active; icon_col = m_icon_color_active; } else { bg_col = m_bg_color; fg_col = m_fg_color; icon_col = m_icon_color; } QColor bg_col_top, bg_col_bottom; if (m_title_3d > 0) { bg_col_top = bg_col.lighter (100 + m_title_3d); bg_col_bottom = bg_col.darker (100 + m_title_3d); } else { bg_col_top = bg_col.darker (100 - m_title_3d); bg_col_bottom = bg_col.lighter (100 - m_title_3d); } css_foreground = QString (" color: %1;\n").arg (fg_col.name ()); css_background = QString (" background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1," " stop: 0 %1, stop: 0.60 %2, stop: 0.95 %2 stop: 1.0 %3);\n"). arg (bg_col_top.name ()). arg (bg_col.name ()). arg (bg_col_bottom.name ()); } else { css_foreground = ""; css_background = ""; } QString full_dock_icon = dock_icon + icon_col; QString full_close_icon = "widget-close" + icon_col; if (titleBarWidget ()) { gui_settings settings; titleBarWidget ()->setStyleSheet (css_foreground + css_background); css_button = QString ("QToolButton {background: transparent; border: 0px;}"); m_dock_button->setStyleSheet (css_button); m_close_button->setStyleSheet (css_button); m_dock_action->setIcon (settings.icon (full_dock_icon, true)); m_close_action->setIcon (settings.icon (full_close_icon, true)); } else { setStyleSheet (qdockwidget_css (global_icon_paths.at (ICON_THEME_OCTAVE) + full_close_icon + ".png", close_tooltip, global_icon_paths.at (ICON_THEME_OCTAVE) + full_dock_icon + ".png", dock_tooltip, m_icon_size, css_foreground, css_background)); } } // set focus to previously active widget in tabbed widget stack void octave_dock_widget::set_focus_predecessor () { // only != 0 if widget was tabbed if (m_predecessor_widget && m_predecessor_widget->isVisible ()) m_predecessor_widget->setFocus (); m_predecessor_widget = nullptr; } OCTAVE_END_NAMESPACE(octave)