exporting patch: # HG changeset patch # User Daniel J Sebald # Date 1525331524 18000 # Thu May 03 02:12:04 2018 -0500 # Node ID 1d439c7d3000e6f926adc99a3b42c77959279054 # Parent 224779b7c0947f8daaf1d96a4c6e29c071e13b76 Use default title bar to preserve GUI float decorations (bug #53276) * QUnixTerminalImpl.cpp (QUnixTerminalImpl::setBackgroundColor): Use cascading style sheets to set the background color of the TerminalView. * documentation-dock-widget.cc (constructor): Use setWindowTitle() for default title bar instead of set_title(). * files-dock-widget.cc (constructor): Ditto. * history-dock-widget.cc (history_dock_widget::construct): Ditto. * file-editor.cc (file_editor::construct): Ditto. * terminal-dock-widget.cc (constructor): Ditto. * workspace-view.cc (constructor): Ditto. * main-window.cc (main_window::focus_changed): Simplify this routine by eliminating the indefinite loop search through all previous focus objects. Instead, just look for the currently focused object in all the octave dock widgets and if not found don't change the highlight. Also drop the special handling for the editor. (main_window::read_settings): Move settings_changed() ahead of layout function set_window_layout(). (main_window::set_window_layout): Remove reparent step make_widget(false). * octave-dock-widget.cc (label_dock_widget::label_dock_widget): Initialize m_default_float_button and m_default_close_button to nullptr then check for the existence of these buttons in the default title bar. Do not call setTitleBarWidget(m_title_widget). (label_dock_widget::set_title): Use m_title_widget for pointer instead of titleBarWidget(). Call setTitleBarWidget(m_title_widget) to activate use of custom title bar. (qdockwidget_css): Added. New local routine that constructs the cascading style sheet for the QDockWidget used in several places. (octave_dock_widget::octave_dock_widget): Initialize m_recent_float_geom, m_recent_dock_area, m_recent_dock_geom, m_waiting_for_mouse_button_release. Remove m_floating. Connect the topLevelChanged() signal to the toplevel_change() slot. Do not restrict features floatable and closeable. If the m_default_float_button was found, disconnect its default slots and connect it instead to slot make_window(). Enable queuing of make_window() via the queue_make_window() signal. Enable queuing of make_widget() via the queue_make_widget() signal. Connect the m_dock_action triggered() signal to make_window() slot rather than change_floating(). Set the style sheet (icons and tooltips) for the dockable widget and disable the main widget's tooltip. Set all dock widget features. (octave_dock_widget::make_window): Change the input to be bool (unused) so that the function can be used as a slot. Prevent follow-up calls by setting m_waiting_for_mouse_button_release to 'false'. Set focus to the predecessor in tab list. Set floating off prior to making it a toplevel window. Do not perform a second reparent with a custom title bar. Get geometry from m_recent_float_geom rather than settings. Drop use of m_floating. Make the (un)dock icon adjustment more elaborate by using style sheets via the set_style() function. Disconnect the existing behavior and connect the "make widget" behavior into the appropriate button/action. Call raise(), show(), activateWindow() and setFocus() to bring the new window to the front of the GUI desktop. (octave_dock_widget::make_widget): Drop the dock/not-dock logic. When adding widget back to parent window set the area to m_recent_dock_area. Make the (un)dock icon adjustment more elaborate by using style sheets via set_style() function. Disconnect the existing behavior and connect the "make window" behavior into the appropriate button/action. (octave_dock_widget::default_dock): Added. Simply call base setFloating() with argument "false". (octave_dock_widget::event): Reimplement the virtual low-level function to check if a conversion from widget to window took place as a result of a double-click or if a button release took place after a window drag, and if so set m_waiting_for_mouse_button_release to 'false' and emit a queued make_window(). (octave_dock_widget::handle_settings): Get the geometry and docked layout information, stored in m_recent_float_geom, m_recent_dock_area and m_recent_dock_geom. (octave_dock_widget::save_settings): Get saved geometry info from m_recent_float_geom, m_recent_dock_area and m_recent_dock_geom. Use isFloating() rather than m_floating for "Floating" state. (octave_dock_widget::change_floating): Removed. (octave_dock_widget::store_geometry): Added. Reuasable routine to save geometry and dock info in the appropriate variables based on isFloating() state. (octave_dock_widget::moveEvent): Added. Reimplemented virtual routine to store the geometry whenever the widget is moved. (octave_dock_widget::resizeEvent): Added. Reimplemented virtual routine to store the geometry whenever the widget is resized. (octave_dock_widget::toplevel_change): Added. Puts a fallback behavior in place in case the transition to a toplevel window fails on some OS. Also, handles drag-to-float by setting m_waiting_for_mouse_button_release to 'true' if toplevel input is true. If toplevel input is false, set m_waiting_for_mouse_button_release to 'false'. If need to reparent, emit queue_make_widget() to avoid button-click/double-click corruption. (octave_dock_widget::set_style): Use isFloating() instead of m_floating. Condition the style changes to custom title bar widgets on titleBarWidget() and use CSS style sheets to match the default title bar color scheme to that of the custom title bar. * octave-dock-widget.h (label_dock_widget:QDockWidget): Add pointers m_default_float_button and m_default_close_button. (label_dock_widget::set_title): Add note about selecting use of default or custom title bar via this routine. (octave_dock_widget:label_dock_widget): Add variables m_recent_float_geom, m_recent_dock_area, m_recent_dock_geom for avoiding using the file settings as an intermediate storage space. Add boolean variable m_waiting_for_mouse_button_release for implementing a single delayed make_window() if needed. (octave_dock_widget::make_window): Change to a public slot with an unused bool input variable. (octave_dock_widget::make_widget): Change to a public slot. (octave_dock_widget::queue_make_window): Added. New signal to queue make_window() and avoid crash when calling make_widget() directly. (octave_dock_widget::queue_make_widget): Added. New signal to queue make_widget() and avoid mouse click corruption when calling make_widget() directly. (octave_dock_widget::event): Added. Reimplement low-level event() routine, check for double-click float and for drag-and-drop float. (octave_dock_widget::moveEvent): Added. Declartion of reimplemented virtual routine. (octave_dock_widget::resizeEvent): Ditto. (octave_dock_widget::default_dock): Added. A simple routine that calls the default setFloating(false). (octave_dock_widget::change_floating): Removed. (octave_dock_widget::toplevel_change): Added. React to topLevelChanged() signal and install the default button action in case toplevel transition fails. Check if (re)parent action needed. (octave_dock_widget::store_geometry): Added. An in-memory copy of the geometry. * variable-editor.cc (variable_dock_widget::variable_dock_widget): Initialize three new state variables m_waiting_for_mouse_move, m_waiting_for_mouse_button_release and m_waiting_for_activation_change intending to identify exactly when a float-by-mouse-drag occurs in order to circumvent Qt bug QTBUG-44813. Use pointer m_title_widget instead of titleBarWidget() and construct the layout unconditionally. (variable_dock_widget::toplevel_change): Remove the default input from setFocus(). If toplevel is true, set m_waiting_for_mouse_move in case a mouse-drag occurs. If toplevel is not true, clear the state variables m_waiting_for_mouse_move, etc. (variable_dock_widget::change_fullscreen): Fix a bug in which geometry was not properly restored during the fullscreen-to-dock transition by moving the copy m_prev_geom after the variable_dock_widget is floated rather than before. (variable_dock_widget::handle_focus_change): Condition the label adjustment on the presence of custom titleBarWidget(). (variable_dock_widget::event): (variable_editor::variable_editor): Use setWindowTitle() for default title bar instead of set_title(). Added to address QTBUG-44813 by using a very simple state machine to identify float-by-mouse-drag from mouse move and button press events. Then emit queue_unfloat_float() to redock and undock the QDockWidget directly which doesn't have the bug. (variable_dock_widget::unfloat_float): Short slot that does setFloating(true) followed by setFloating(false). This code is conditioned on the yet-to-be- fully determined Qt bug range, and need a NULL-function slot routine. (variable_editor::variable_editor): Initialize new variables m_focus_widget and m_focus_widget_vdw to nullptr. Use setWindowTitle() rather than set_title(). (variable_editor::focusInEvent): Make the focusing rules more complex and use the variables m_focus_widget and m_focus_widget_vdw for logic. (variable_editor::focusOutEvent): Removed. (variable_editor::edit_variable): Make the connection between signal queue_unfloat_float() and slot unfloat_float() pre-processor conditioned on the range for Qt bug QTBUG-44813. Condition the connection between the model's update_lable_signal() and label's setText() slot on the presence of custom titleBarWidget(). (variable_editor::variable_destroyed): If the variable_dock_widget that is destroyed matches m_focus_widget_vdw then clear both that variable and m_focus_widget. Make use of the focusInEvent() routine to set the focus to the next logical variable_dock_widget. (variable_editor::variable_focused): Make the association between the currently-focused object and the variable_dock_widget on which it resides. * variable-editor.h (variable_dock_widget:label_dock_widget): Define QTBUG_44813_FIX_VERSION as an indefinite version in the future when Qt developers fix QTBUG-44813. (variable_dock_widget::queue_unfloat_float): Added. New signal. (variable_dock_widget::unfloat_float): Added. New protected slot. (variable_dock_widget::event): Added. Conditioned on QTBUG-44813 range, the overrided virtual function that will identify float-by-mouse-drag scenario. Requires new private Boolean variables m_waiting_for_mouse_move, m_waiting_for_mouse_button_release and m_waiting_for_activation_change. (variable_editor:octave_dock_widget): Change m_variable_focus_widget to m_focus_widget and add variable_dock_widget pointer m_focus_widget_vdw. (variable_editor::focusOutEvent): Removed. diff --git a/libgui/qterminal/libqterminal/unix/QUnixTerminalImpl.cpp b/libgui/qterminal/libqterminal/unix/QUnixTerminalImpl.cpp --- a/libgui/qterminal/libqterminal/unix/QUnixTerminalImpl.cpp +++ b/libgui/qterminal/libqterminal/unix/QUnixTerminalImpl.cpp @@ -188,6 +188,11 @@ void QUnixTerminalImpl::setBackgroundCol m_terminalView->setColorTable(cols); + QString css = QString ("TerminalView {\n" + " background: %1;\n" + "}\n").arg (color.name ()); + setStyleSheet (css); + } void QUnixTerminalImpl::setForegroundColor (const QColor& color) { diff --git a/libgui/src/documentation-dock-widget.cc b/libgui/src/documentation-dock-widget.cc --- a/libgui/src/documentation-dock-widget.cc +++ b/libgui/src/documentation-dock-widget.cc @@ -36,7 +36,7 @@ namespace octave { setObjectName ("DocumentationDockWidget"); setWindowIcon (QIcon (":/actions/icons/logo.png")); - set_title (tr ("Documentation")); + setWindowTitle (tr ("Documentation")); setStatusTip (tr ("See the documentation for help.")); m_docs = new documentation (this); diff --git a/libgui/src/files-dock-widget.cc b/libgui/src/files-dock-widget.cc --- a/libgui/src/files-dock-widget.cc +++ b/libgui/src/files-dock-widget.cc @@ -70,7 +70,7 @@ namespace octave { setObjectName ("FilesDockWidget"); setWindowIcon (QIcon (":/actions/icons/logo.png")); - set_title (tr ("File Browser")); + setWindowTitle (tr ("File Browser")); setToolTip (tr ("Browse your files")); m_sig_mapper = nullptr; diff --git a/libgui/src/history-dock-widget.cc b/libgui/src/history-dock-widget.cc --- a/libgui/src/history-dock-widget.cc +++ b/libgui/src/history-dock-widget.cc @@ -294,7 +294,7 @@ namespace octave m_filter_checkbox = new QCheckBox (); setWindowIcon (QIcon (":/actions/icons/logo.png")); - set_title (tr ("Command History")); + setWindowTitle (tr ("Command History")); setWidget (new QWidget ()); m_filter_widget = new QWidget (this); diff --git a/libgui/src/m-editor/file-editor.cc b/libgui/src/m-editor/file-editor.cc --- a/libgui/src/m-editor/file-editor.cc +++ b/libgui/src/m-editor/file-editor.cc @@ -2039,7 +2039,7 @@ namespace octave resize (500, 400); setWindowIcon (QIcon (":/actions/icons/logo.png")); - set_title (tr ("Editor")); + setWindowTitle (tr ("Editor")); check_actions (); } diff --git a/libgui/src/main-window.cc b/libgui/src/main-window.cc --- a/libgui/src/main-window.cc +++ b/libgui/src/main-window.cc @@ -289,41 +289,22 @@ namespace octave return; octave_dock_widget *dock = nullptr; - QWidget *w_new = new_widget; // get a copy of new focus widget - QWidget *start = w_new; // Save it as start of our search - int count = 0; // fallback to prevent endless loop - - QList w_list = dock_widget_list (); - - while (w_new && w_new != m_main_tool_bar && count < 100) + + if (new_widget != m_main_tool_bar) { // Go through all dock widgets and check whether the current widget // widget with focus is a child of one of it + QList w_list = dock_widget_list (); foreach (octave_dock_widget *w, w_list) { - if (w->isAncestorOf (w_new)) + if (w == new_widget || w->isAncestorOf (new_widget)) dock = w; } - - if (dock) - break; - - // If not yet found (in case w_new is not a childs of its dock widget), - // test next widget in the focus chain - w_new = qobject_cast (w_new->previousInFocusChain ()); - - // Measures preventing an endless loop - if (w_new == start) - break; // We have arrived where we began ==> exit loop - count++; // Limited number of trials } - // editor needs extra handling - octave_dock_widget *edit_dock_widget - = static_cast (m_editor_window); // if new dock has focus, emit signal and store active focus // except editor changes to a dialog (dock=0) - if ((dock || m_active_dock != edit_dock_widget) && (dock != m_active_dock)) + if (dock && (dock != m_active_dock)) { // signal to all dock widgets for updating the style emit active_dock_changed (m_active_dock, dock); @@ -332,6 +313,10 @@ namespace octave if (tabbed.contains (m_active_dock)) dock->set_predecessor_widget (m_active_dock); + // editor needs extra handling + octave_dock_widget *edit_dock_widget + = static_cast (m_editor_window); + if (edit_dock_widget == dock) emit editor_focus_changed (true); else if (edit_dock_widget == m_active_dock) @@ -1212,6 +1197,8 @@ namespace octave return; } + emit settings_changed (settings); + set_window_layout (settings); // restore the list of the last directories @@ -1221,7 +1208,6 @@ namespace octave { m_current_directory_combo_box->addItem (curr_dirs.at (i)); } - emit settings_changed (settings); } void main_window::init_terminal_size (void) @@ -1262,9 +1248,6 @@ namespace octave } else // not floating { - if (! widget->parent ()) // should not be floating but is - widget->make_widget (false); // no docking, just reparent - widget->make_widget (); widget->setVisible (visible); // not floating -> show } diff --git a/libgui/src/octave-dock-widget.cc b/libgui/src/octave-dock-widget.cc --- a/libgui/src/octave-dock-widget.cc +++ b/libgui/src/octave-dock-widget.cc @@ -39,11 +39,29 @@ along with Octave; see the file COPYING. namespace octave { label_dock_widget::label_dock_widget (QWidget *p) - : QDockWidget (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 buttonlist = findChildren (); + 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 (0); + m_default_close_button = buttonlist.at (1); + } + } + // the custom (extra) title bar of the widget m_title_widget = new QWidget (); @@ -75,7 +93,6 @@ namespace octave h_layout->setContentsMargins (5,2,2,2); m_title_widget->setLayout (h_layout); - setTitleBarWidget (m_title_widget); // copy & paste handling connect (p, SIGNAL (copyClipboard_signal ()), @@ -93,21 +110,63 @@ namespace octave label_dock_widget::set_title (const QString& title) { QHBoxLayout *h_layout - = static_cast (titleBarWidget ()->layout ()); - QLabel *label = new QLabel (title, titleBarWidget ()); - label->setStyleSheet ("background: transparent;"); + = static_cast (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_background) + { + return QString ("QDockWidget::close-button, QDockWidget::float-button\n" + "{\n" + " border: none;\n" + " icon-size: %1px;\n" + "}\n" + "\n" + "QDockWidget\n" + "{\n" + " border: none;\n" + " titlebar-close-icon: url(%2);\n" + " titlebar-normal-icon: url(%3);\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" + " %6\n" + " padding-left: 1px;\n" + "}\n" + ).arg (icon_size).arg (close_icon).arg (float_icon) + .arg (close_tooltip).arg (float_tooltip) + .arg (titlebar_background); + } + octave_dock_widget::octave_dock_widget (QWidget *p) - : label_dock_widget (p) + : label_dock_widget (p), m_recent_float_geom (), + m_recent_dock_area (Qt::NoDockWidgetArea), m_recent_dock_geom (), + m_waiting_for_mouse_button_release (false) { m_parent = static_cast (p); // store main window - m_floating = false; m_predecessor_widget = nullptr; + connect (this, SIGNAL (topLevelChanged (bool)), + this, SLOT (toplevel_change (bool))); connect (this, SIGNAL (visibilityChanged (bool)), this, SLOT (handle_visibility_changed (bool))); @@ -119,21 +178,41 @@ namespace octave this, SLOT (handle_active_dock_changed (octave_dock_widget*, octave_dock_widget*))); - setFeatures (QDockWidget::DockWidgetMovable); // not floatable or closeable + if (m_default_float_button != nullptr) + { + disconnect (m_default_float_button, 0, 0, 0); + connect (m_default_float_button, SIGNAL (clicked (bool)), + this, SLOT (make_window (bool))); + } + connect (this, SIGNAL (queue_make_window ()), + this, SLOT (make_window ()), Qt::QueuedConnection); + connect (this, SIGNAL (queue_make_widget ()), + this, SLOT (make_widget ()), Qt::QueuedConnection); connect (m_dock_action, SIGNAL (triggered (bool)), - this, SLOT (change_floating (bool))); + this, SLOT (make_window (bool))); connect (m_close_action, SIGNAL (triggered (bool)), this, SLOT (change_visibility (bool))); m_close_action->setToolTip (tr ("Hide widget")); + setStyleSheet (qdockwidget_css (QString (":/actions/icons/widget-close.png"), + QString ("Close widget"), + QString (":/actions/icons/widget-undock.png"), + QString ("Undock widget"), + m_icon_size, + QString (""))); + if (widget ()) + widget ()->setToolTip (QString ("")); + m_icon_color = ""; m_title_3d = 50; installEventFilter (this); setFocusPolicy (Qt::StrongFocus); + + setFeatures (QDockWidget::AllDockWidgetFeatures); } // connect signal visibility changed to related slot (called from main-window) @@ -147,82 +226,97 @@ namespace octave // make the widget floating void - octave_dock_widget::make_window (void) + octave_dock_widget::make_window (bool) { - // the widget has to be reparented (parent = 0) + // prevent follow-up calls by clearing state variable + m_waiting_for_mouse_button_release = false; - QSettings *settings = resource_manager::get_settings (); + set_focus_predecessor (); // set focus previously active widget if tabbed - // save the docking area and geometry for later redocking - settings->setValue ("DockWidgets/" + objectName () + "_dock_area", - m_parent->dockWidgetArea (this)); - settings->setValue ("DockWidgets/" + objectName (), saveGeometry ()); - settings->sync (); - - // remove parent and adjust the (un)dock icon - setTitleBarWidget (0); - setParent (0, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint | - Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint); - setTitleBarWidget (m_title_widget); + // 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); +// Remove after thorough testing 3/20/18 m_parent->removeDockWidget (this); setParent (0, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint); -#if defined (Q_OS_UNIX) - m_title_widget->setToolTip ( - tr ("Use + for moving the window")); -#endif + // restore the last geometry when floating + QRect geom = m_recent_float_geom.isNull () ? QRect (50,100,480,480) + : m_recent_float_geom; + setGeometry (geom); - m_dock_action->setIcon (QIcon (":/actions/icons/widget-dock" - + m_icon_color + ".png")); - m_dock_action->setToolTip (tr ("Dock widget")); + // adjust the (un)dock icon + if (titleBarWidget ()) + { + m_dock_action->setIcon (QIcon (":/actions/icons/widget-dock" + + m_icon_color + ".png")); + m_dock_action->setToolTip (tr ("Dock widget")); + disconnect (m_dock_action, 0, this, 0); + connect (m_dock_action, SIGNAL (triggered (bool)), + this, SLOT (make_widget (bool))); + } + else + { + set_style (isActiveWindow ()); + disconnect (m_default_float_button, 0, this, 0); + connect (m_default_float_button, SIGNAL (clicked (bool)), + this, SLOT (make_widget (bool))); + } - // restore the last geometry when floating - setGeometry (settings->value ("DockWidgets/" + objectName () - + "_floating_geometry", - QRect (50,100,480,480)).toRect ()); - - m_floating = true; - - set_focus_predecessor (); // set focus previously active widget if tabbed + raise (); + show (); + activateWindow (); + setFocus (); } // dock the widget void - octave_dock_widget::make_widget (bool dock) + octave_dock_widget::make_widget (bool) { // Since floating widget has no parent, we have to read it - QSettings *settings = resource_manager::get_settings (); - // save last floating geometry if widget really was floating - if (m_floating) - settings->setValue ("DockWidgets/" + objectName () + "_floating_geometry", - geometry ()); - settings->sync (); - - if (dock) - { - settings->setValue ("MainWindow/windowState", m_parent->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_parent, Qt::Window); - m_parent->addDockWidget (Qt::TopDockWidgetArea, this); - // recover old window states, hide and re-show new added widget - m_parent->restoreState (settings->value ("MainWindow/windowState").toByteArray ()); - setFloating (false); - focus (); - QApplication::setActiveWindow (this); - m_title_widget->setToolTip (""); - } - else // only reparent, no docking - setParent (m_parent); + settings->setValue ("MainWindow/windowState", m_parent->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_parent, Qt::Window); + m_parent->addDockWidget (m_recent_dock_area, this); + // recover old window states, hide and re-show new added widget + m_parent->restoreState (settings->value ("MainWindow/windowState").toByteArray ()); + setFloating (false); + show (); + QApplication::setActiveWindow (this); + focus (); // adjust the (un)dock icon - m_dock_action->setIcon (QIcon (":/actions/icons/widget-undock" - + m_icon_color + ".png")); - m_dock_action->setToolTip (tr ("Undock widget")); + if (titleBarWidget ()) + { +#if defined (Q_OS_UNIX) + m_title_widget->setToolTip (""); +#endif + m_dock_action->setIcon (QIcon (":/actions/icons/widget-undock" + + m_icon_color + ".png")); + m_dock_action->setToolTip (tr ("Undock widget")); + disconnect (m_dock_action, 0, this, 0); + connect (m_dock_action, SIGNAL (triggered (bool)), + this, SLOT (make_window (bool))); + } + else + { + set_style (isActiveWindow ()); + disconnect (m_default_float_button, 0, this, 0); + connect (m_default_float_button, SIGNAL (clicked (bool)), + this, SLOT (make_window (bool))); + } + } - m_floating = false; + // dock the widget + void + octave_dock_widget::default_dock (bool) + { + setFloating (false); } // set the widget which previously had focus when tabified @@ -250,6 +344,26 @@ namespace octave 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 (); + } + return retval; + } + + return QDockWidget::event (event); + } + void octave_dock_widget::handle_settings (const QSettings *settings) { @@ -278,7 +392,7 @@ namespace octave if (! m_custom_style) { - bcol = QWidget::palette ().color (m_title_widget->backgroundRole()); + bcol = QWidget::palette ().color (m_title_widget->backgroundRole ()); bcola = bcol; } @@ -295,6 +409,24 @@ namespace octave else m_icon_color_active = ""; + m_recent_float_geom = settings->value ("DockWidgets/" + objectName () + + "_floating_geometry", + QRect (50,100,480,480)).toRect (); + +#if 0 + m_recent_dock_area = settings->value ("DockWidgets/" + objectName () + + "_dock_area", Qt::NoDockWidgetArea).toInt (); +#else + m_recent_dock_area = Qt::NoDockWidgetArea; +#endif + +#if 0 + m_recent_dock_geom = settings->value ("DockWidgets/" + objectName (), + QByteArray ()).toRect (); +#else + m_recent_dock_geom = QByteArray (); +#endif + notice_settings (settings); // call individual handler set_style (false); @@ -329,13 +461,17 @@ namespace octave settings->beginGroup ("DockWidgets"); - if (m_floating) // widget is floating (windows), save actual floating geometry - settings->setValue (name+"_floating_geometry", geometry ()); - else // not floating save docked (normal) geometry - settings->setValue (name, saveGeometry ()); + // conditional needed? + if (! m_recent_float_geom.isNull ()) + settings->setValue (name + "_floating_geometry", m_recent_float_geom); + if (! m_recent_dock_geom.isEmpty ()) + { + settings->setValue (name + "_dock_area", m_recent_dock_area); + settings->setValue (name, m_recent_dock_geom); + } settings->setValue (name+"Visible", isVisible ()); // store visibility - settings->setValue (name+"Floating", m_floating); // store visibility + settings->setValue (name+"Floating", isFloating ()); // store floating settings->setValue (name+"_minimized", isMinimized ()); // store minimized settings->endGroup (); @@ -353,19 +489,37 @@ namespace octave return QDockWidget::eventFilter (obj,e); } - // slot for (un)dock action void - octave_dock_widget::change_floating (bool) + octave_dock_widget::store_geometry (void) { - if (m_floating) - make_widget (); + if (isFloating ()) + { + if (! parent ()) + m_recent_float_geom = geometry (); + } else { - make_window (); - focus (); + m_recent_dock_area = m_parent->dockWidgetArea (this); + m_recent_dock_geom = saveGeometry (); } } + 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) @@ -375,6 +529,47 @@ namespace octave } 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; @@ -383,10 +578,19 @@ namespace octave QString icon_col = m_icon_color; - if (m_floating) - dock_icon = "widget-dock"; + QString close_tooltip = "Close widget"; + QString dock_tooltip; + + if (isFloating ()) + { + dock_icon = "widget-dock"; + dock_tooltip = "Dock widget"; + } else - dock_icon = "widget-undock"; + { + dock_icon = "widget-undock"; + dock_tooltip = "Undock widget"; + } #if defined (Q_OS_MAC) QString alignment = "center"; @@ -425,26 +629,38 @@ namespace octave QString 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);"). + " 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 ()); - css = background + QString (" color: %1 ;").arg (fg_col.name ()); + css = background + QString (" color: %1;").arg (fg_col.name ()); } else { css = QString (""); } - m_title_widget->setStyleSheet (css); - css_button = QString ("QToolButton {background: transparent; border: 0px;}"); - m_dock_button->setStyleSheet (css_button); - m_close_button->setStyleSheet (css_button); - m_dock_action->setIcon (QIcon (":/actions/icons/" + dock_icon + icon_col + - ".png")); - m_close_action->setIcon (QIcon (":/actions/icons/widget-close" + icon_col + - ".png")); + QString full_dock_icon = ":/actions/icons/" + dock_icon + icon_col + ".png"; + QString full_close_icon = ":/actions/icons/widget-close" + icon_col + ".png"; + if (titleBarWidget ()) + { + titleBarWidget ()->setStyleSheet (css); + css_button = QString ("QToolButton {background: transparent; border: 0px;}"); + m_dock_button->setStyleSheet (css_button); + m_close_button->setStyleSheet (css_button); + m_dock_action->setIcon (QIcon (full_dock_icon)); + m_close_action->setIcon (QIcon (full_close_icon)); + } + else + { + setStyleSheet (qdockwidget_css (full_close_icon, + close_tooltip, + full_dock_icon, + dock_tooltip, + m_icon_size, + css)); + } } // set focus to previously active widget in tabbed widget stack diff --git a/libgui/src/octave-dock-widget.h b/libgui/src/octave-dock-widget.h --- a/libgui/src/octave-dock-widget.h +++ b/libgui/src/octave-dock-widget.h @@ -43,6 +43,8 @@ namespace octave label_dock_widget (QWidget *p = nullptr); + // set_title() uses the custom title bar while setWindowTitle() uses + // the default title bar (with style sheets) void set_title (const QString&); protected slots: @@ -66,6 +68,9 @@ namespace octave QToolButton *m_close_button; QAction *m_dock_action; QAction *m_close_action; + + QAbstractButton *m_default_float_button; + QAbstractButton *m_default_close_button; }; class octave_dock_widget : public label_dock_widget @@ -80,8 +85,6 @@ namespace octave virtual void connect_visibility_changed (void); - void make_window (void); - void make_widget (bool dock=true); void set_predecessor_widget (octave_dock_widget *prev_widget); signals: @@ -91,12 +94,18 @@ namespace octave void active_changed (bool active); + void queue_make_window (void); + + void queue_make_widget (void); + protected: virtual void closeEvent (QCloseEvent *e); QWidget * focusWidget (void); + bool event (QEvent *event); + public slots: virtual void focus (void) @@ -125,6 +134,16 @@ namespace octave void save_settings (void); + void moveEvent (QMoveEvent *event); + + void resizeEvent (QResizeEvent *event); + + void make_window (bool not_used = false); + + void make_widget (bool not_used = false); + + void default_dock (bool not_used = false); + protected slots: //! Slot to steer changing visibility from outside. @@ -141,19 +160,19 @@ namespace octave private slots: - void change_floating (bool); void change_visibility (bool); + void toplevel_change (bool); private: void set_style (bool active); void set_focus_predecessor (void); + void store_geometry (void); //! Stores the parent, since we are reparenting to 0. QMainWindow *m_parent; - bool m_floating; bool m_custom_style; int m_title_3d; QColor m_bg_color; @@ -163,6 +182,10 @@ namespace octave QString m_icon_color; QString m_icon_color_active; octave_dock_widget *m_predecessor_widget; + QRect m_recent_float_geom; + Qt::DockWidgetArea m_recent_dock_area; + QByteArray m_recent_dock_geom; + bool m_waiting_for_mouse_button_release; }; } diff --git a/libgui/src/terminal-dock-widget.cc b/libgui/src/terminal-dock-widget.cc --- a/libgui/src/terminal-dock-widget.cc +++ b/libgui/src/terminal-dock-widget.cc @@ -45,7 +45,7 @@ namespace octave setObjectName ("TerminalDockWidget"); setWindowIcon (QIcon (":/actions/icons/logo.png")); - set_title (tr ("Command Window")); + setWindowTitle (tr ("Command Window")); setWidget (m_terminal); setFocusProxy (m_terminal); diff --git a/libgui/src/variable-editor.cc b/libgui/src/variable-editor.cc --- a/libgui/src/variable-editor.cc +++ b/libgui/src/variable-editor.cc @@ -86,6 +86,12 @@ namespace octave variable_dock_widget::variable_dock_widget (QWidget *p) : label_dock_widget (p) +// 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) + , m_waiting_for_activation_change (false) +#endif { setFocusPolicy (Qt::StrongFocus); setAttribute (Qt::WA_DeleteOnClose); @@ -109,28 +115,25 @@ namespace octave m_prev_floating = false; m_prev_geom = QRect (0, 0, 0, 0); - QHBoxLayout *h_layout = findChild (); - if (h_layout != nullptr && titleBarWidget () != nullptr) - { - m_fullscreen_action = new QAction - (resource_manager::icon ("view-fullscreen", false), "", this); - m_fullscreen_action->setToolTip (tr (DOCKED_FULLSCREEN_BUTTON_TOOLTIP)); - QToolButton *fullscreen_button = new QToolButton (titleBarWidget ()); - 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); + QHBoxLayout *h_layout = m_title_widget->findChild (); + m_fullscreen_action = new QAction + (resource_manager::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, SIGNAL (triggered ()), - this, SLOT (change_fullscreen ())); + connect (m_fullscreen_action, SIGNAL (triggered ()), + this, SLOT (change_fullscreen ())); - int index = -1; - QToolButton *first = titleBarWidget ()->findChild (); - if (first != nullptr) - index = h_layout->indexOf (first); - h_layout->insertWidget (index, fullscreen_button); - } + int index = -1; + QToolButton *first = m_title_widget->findChild (); + if (first != nullptr) + index = h_layout->indexOf (first); + h_layout->insertWidget (index, fullscreen_button); #endif // Custom title bars cause loss of decorations, add a frame @@ -176,15 +179,27 @@ namespace octave m_dock_action->setIcon (QIcon (":/actions/icons/widget-dock.png")); m_dock_action->setToolTip (tr ("Dock widget")); - activateWindow(); - setFocus (Qt::OtherFocusReason); + 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 (Qt::OtherFocusReason); + 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; + m_waiting_for_activation_change = false; +#endif } } @@ -195,8 +210,6 @@ namespace octave if (! m_full_screen) { m_prev_floating = isFloating (); - m_prev_geom = geometry (); - m_fullscreen_action->setIcon (resource_manager::icon ("view-restore", false)); if (m_prev_floating) m_fullscreen_action->setToolTip (tr ("Restore geometry")); @@ -205,6 +218,7 @@ namespace octave m_fullscreen_action->setToolTip (tr ("Redock")); setFloating (true); } + m_prev_geom = geometry (); // showFullscreen() and setWindowState() only work for QWindow objects. QScreen *pscreen = QGuiApplication::primaryScreen (); @@ -244,23 +258,35 @@ namespace octave { octave_unused_parameter (now); - // The is a proxied test + // This is a proxied test if (hasFocus ()) { - QLabel *label = titleBarWidget ()->findChild (); - if (label != nullptr) + if (old == this) + return; + + if (titleBarWidget () != nullptr) { - label->setBackgroundRole (QPalette::Highlight); - label->setAutoFillBackground (true); + QLabel *label = titleBarWidget ()->findChild (); + 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()) { - QLabel *label = titleBarWidget ()->findChild (); - if (label != NULL) - label->setBackgroundRole (QPalette::NoRole); + if (titleBarWidget () != nullptr) + { + QLabel *label = titleBarWidget ()->findChild (); + if (label != nullptr) + { + label->setBackgroundRole (QPalette::NoRole); + label->setStyleSheet (";"); + } + } } } @@ -270,6 +296,54 @@ namespace octave 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; + 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; + m_waiting_for_activation_change = true; + } + if (event->type () == QEvent::ActivationChange && m_waiting_for_activation_change) + { + m_waiting_for_activation_change = 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) + { + setFloating (false); + setFloating (true); + } + +#else + + void + variable_dock_widget::unfloat_float (void) + {} + +#endif // Variable editor stack @@ -990,10 +1064,11 @@ namespace octave m_table_colors (), m_current_focus_vname (""), m_hovered_focus_vname (""), - m_variable_focus_widget (nullptr) + m_focus_widget (nullptr), + m_focus_widget_vdw (nullptr) { setObjectName ("VariableEditor"); - set_title (tr ("Variable Editor")); + setWindowTitle (tr ("Variable Editor")); setStatusTip (tr ("Edit variables.")); setWindowIcon (QIcon (":/actions/icons/logo.png")); setAttribute (Qt::WA_AlwaysShowToolTips); @@ -1039,19 +1114,33 @@ namespace octave octave_dock_widget::focusInEvent (ev); // set focus to the current variable or most recent if still valid - QWidget *fw = m_main->focusWidget (); - if (fw != nullptr) - fw->setFocus (); - else if (m_main->isAncestorOf (m_variable_focus_widget)) - m_variable_focus_widget->setFocus (); - } - - void variable_editor::focusOutEvent (QFocusEvent *ev) - { - // focusWidget() appears lost in transition to/from main window - m_variable_focus_widget = m_main->focusWidget (); - - octave_dock_widget::focusOutEvent (ev); + 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 (); + if (any_qdw != nullptr) + { + activateWindow (); + any_qdw->setFocus (); + } + else + setFocus(); + } + } } // Add an action to a menu or the widget itself. @@ -1119,6 +1208,11 @@ namespace octave this, SLOT (variable_destroyed (QObject *))); connect (page, SIGNAL (variable_focused_signal (const QString&)), this, SLOT (variable_focused (const QString&))); +// 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); +#endif variable_editor_stack *stack = new variable_editor_stack (page); stack->setObjectName (name); @@ -1191,10 +1285,13 @@ namespace octave // to always supply a QLabl (initially empty) and then simply update its // contents. page->set_title (name); - QLabel *existing_ql = page->titleBarWidget ()->findChild (); - connect (model, SIGNAL (update_label_signal (const QString&)), - existing_ql, SLOT (setText (const QString&))); - existing_ql->setMargin (2); + if (page->titleBarWidget () != nullptr) + { + QLabel *existing_ql = page->titleBarWidget ()->findChild (); + connect (model, SIGNAL (update_label_signal (const QString&)), + existing_ql, SLOT (setText (const QString&))); + existing_ql->setMargin (2); + } model->update_data (val); @@ -1355,17 +1452,49 @@ namespace octave } void - variable_editor::variable_destroyed (QObject *) + 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 no variable pages remain, deactivate the tool bar. QList vdwlist = findChildren (); 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 vdwlist = findChildren (); + 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 diff --git a/libgui/src/variable-editor.h b/libgui/src/variable-editor.h --- a/libgui/src/variable-editor.h +++ b/libgui/src/variable-editor.h @@ -93,6 +93,30 @@ namespace octave QRect m_prev_geom; #endif + +// See Octave bug #53807 and https://bugreports.qt.io/browse/QTBUG-44813 +#define QTBUG_44813_FIX_VERSION 0x999999 + signals: + + void queue_unfloat_float (void); + + protected slots: + + void unfloat_float (void); + +#if (QT_VERSION >= 0x050302) && (QT_VERSION <= QTBUG_44813_FIX_VERSION) + protected: + + bool event (QEvent *event); + + private: + + bool m_waiting_for_mouse_move; + + bool m_waiting_for_mouse_button_release; + + bool m_waiting_for_activation_change; +#endif }; class variable_editor_stack : public QStackedWidget @@ -338,8 +362,6 @@ namespace octave void focusInEvent (QFocusEvent *ev); - void focusOutEvent (QFocusEvent *ev); - private: QAction * add_action (QMenu *menu, const QIcon& icon, const QString& text, @@ -380,7 +402,9 @@ namespace octave QString m_hovered_focus_vname; - QWidget *m_variable_focus_widget; + QWidget *m_focus_widget; + + variable_dock_widget *m_focus_widget_vdw; }; } diff --git a/libgui/src/workspace-view.cc b/libgui/src/workspace-view.cc --- a/libgui/src/workspace-view.cc +++ b/libgui/src/workspace-view.cc @@ -52,7 +52,7 @@ namespace octave { setObjectName ("WorkspaceView"); setWindowIcon (QIcon (":/actions/icons/logo.png")); - set_title (tr ("Workspace")); + setWindowTitle (tr ("Workspace")); setStatusTip (tr ("View the variables in the active workspace.")); m_filter = new QComboBox (this);