From ef893e0d9434be5aa26cae1688ebbe232e5f5ad0 Mon Sep 17 00:00:00 2001 From: Ulrich Haberl Date: Thu, 6 Sep 2018 11:35:02 +0200 Subject: [PATCH] Changes for 3ds Max 2020 - Merging all 3ds Max specific changes from Qt 5.6.3 into Qt 5.11.2 - Adding raiseWidget() function to the QMainWindow (exposing the function with the same name from the layout). - Adding UI-Automation WIN32/Qt hierarchy mix - Adding back versioning information to libEGL.dll and libGLESv2.dll --- qmake/generators/win32/winmakefile.cpp | 12 +- src/3rdparty/angle/src/common/version.h | 2 +- src/3rdparty/angle/src/libEGL/libEGL.rc | 103 ++ src/3rdparty/angle/src/libGLESv2/libGLESv2.rc | 103 ++ src/angle/src/libEGL/libEGL.pro | 2 + src/angle/src/libGLESv2/libGLESv2.pro | 2 + .../platforms/windows/qwindowscontext.cpp | 5 + src/plugins/platforms/windows/qwindowsdrag.h | 2 + .../platforms/windows/qwindowswindow.cpp | 32 +- .../windows/uiautomation/qwindowsuiautils.cpp | 20 + src/widgets/accessible/qaccessiblewidget.cpp | 32 +- src/widgets/accessible/rangecontrols.cpp | 167 +- src/widgets/accessible/rangecontrols_p.h | 8 + src/widgets/kernel/qapplication.cpp | 33 +- src/widgets/kernel/qwidget.cpp | 215 ++- src/widgets/kernel/qwidget_p.h | 13 + src/widgets/kernel/qwidgetwindow.cpp | 50 +- src/widgets/styles/qcommonstyle.cpp | 71 +- src/widgets/styles/qstyle.h | 10 + src/widgets/styles/qstylesheetstyle.cpp | 84 +- src/widgets/widgets/qdockarealayout.cpp | 1572 ++++++++++++++++- src/widgets/widgets/qdockarealayout_p.h | 74 +- src/widgets/widgets/qdockwidget.cpp | 51 +- src/widgets/widgets/qfocusframe.cpp | 6 +- src/widgets/widgets/qmainwindow.cpp | 34 + src/widgets/widgets/qmainwindow.h | 3 + src/widgets/widgets/qmainwindowlayout.cpp | 9 +- src/widgets/widgets/qmainwindowlayout_p.h | 75 +- src/widgets/widgets/qmenu.cpp | 4 + src/widgets/widgets/qtabbar.cpp | 265 ++- src/widgets/widgets/qtabbar_p.h | 48 + .../kernel/qsizepolicy/tst_qsizepolicy.cpp | 12 +- 32 files changed, 3009 insertions(+), 110 deletions(-) create mode 100644 src/3rdparty/angle/src/libEGL/libEGL.rc create mode 100644 src/3rdparty/angle/src/libGLESv2/libGLESv2.rc diff --git a/qmake/generators/win32/winmakefile.cpp b/qmake/generators/win32/winmakefile.cpp index bca27b7044a..a4026c2a3ec 100644 --- a/qmake/generators/win32/winmakefile.cpp +++ b/qmake/generators/win32/winmakefile.cpp @@ -264,8 +264,18 @@ void Win32MakefileGenerator::processRcFileVar() QTextStream ts(&rcString, QFile::WriteOnly); QStringList vers = project->first("VERSION").toQString().split(".", QString::SkipEmptyParts); + //--------------------------------------------------------------------- + // Autodesk 3ds Max Addition: + // Qt defaults to "0" here, what we use for 3ds Max Release, but change + // that to "1" for update 1, "2" for update 2. "3" for update 3, etc. + // This is needed to allow the patch scripts to replace the dlls with + // the newer version during the update installation. + // "5.11.1.0" -> "5.11.1.*" + //--------------------------------------------------------------------- for (int i = vers.size(); i < 4; i++) - vers += "0"; + { + vers += "0"; //for 3ds Max + } QString versionString = vers.join('.'); QStringList rcIcons; diff --git a/src/3rdparty/angle/src/common/version.h b/src/3rdparty/angle/src/common/version.h index e7ffa7cab34..808bbbb8376 100644 --- a/src/3rdparty/angle/src/common/version.h +++ b/src/3rdparty/angle/src/common/version.h @@ -7,7 +7,7 @@ #ifndef COMMON_VERSION_H_ #define COMMON_VERSION_H_ -#include "id/commit.h" +#include "../id/commit.h" #define ANGLE_MAJOR_VERSION 2 #define ANGLE_MINOR_VERSION 1 diff --git a/src/3rdparty/angle/src/libEGL/libEGL.rc b/src/3rdparty/angle/src/libEGL/libEGL.rc new file mode 100644 index 00000000000..65e0aa50c8f --- /dev/null +++ b/src/3rdparty/angle/src/libEGL/libEGL.rc @@ -0,0 +1,103 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#include "../common/version.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "#include ""../common/version.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ANGLE_MAJOR_VERSION,ANGLE_MINOR_VERSION,0,0 + PRODUCTVERSION ANGLE_MAJOR_VERSION,ANGLE_MINOR_VERSION,0,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "ANGLE libEGL Dynamic Link Library" + VALUE "FileVersion", ANGLE_VERSION_STRING + VALUE "InternalName", "libEGL" + VALUE "LegalCopyright", "Copyright (C) 2011 Google Inc." + VALUE "OriginalFilename", "libEGL.dll" + VALUE "PrivateBuild", ANGLE_VERSION_STRING + VALUE "ProductName", "ANGLE libEGL Dynamic Link Library" + VALUE "ProductVersion", ANGLE_VERSION_STRING + VALUE "Comments", "Build Date: " ANGLE_COMMIT_DATE + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/src/3rdparty/angle/src/libGLESv2/libGLESv2.rc b/src/3rdparty/angle/src/libGLESv2/libGLESv2.rc new file mode 100644 index 00000000000..76cd05566ee --- /dev/null +++ b/src/3rdparty/angle/src/libGLESv2/libGLESv2.rc @@ -0,0 +1,103 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#include "../common/version.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "#include ""../common/version.h""\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ANGLE_MAJOR_VERSION,ANGLE_MINOR_VERSION,0,0 + PRODUCTVERSION ANGLE_MAJOR_VERSION,ANGLE_MINOR_VERSION,0,0 + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "ANGLE libGLESv2 Dynamic Link Library" + VALUE "FileVersion", ANGLE_VERSION_STRING + VALUE "InternalName", "libGLESv2" + VALUE "LegalCopyright", "Copyright (C) 2011 Google Inc." + VALUE "OriginalFilename", "libGLESv2.dll" + VALUE "PrivateBuild", ANGLE_VERSION_STRING + VALUE "ProductName", "ANGLE libGLESv2 Dynamic Link Library" + VALUE "ProductVersion", ANGLE_VERSION_STRING + VALUE "Comments", "Build Date: " ANGLE_COMMIT_DATE + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/src/angle/src/libEGL/libEGL.pro b/src/angle/src/libEGL/libEGL.pro index 9e9c639002a..333919a2d3a 100644 --- a/src/angle/src/libEGL/libEGL.pro +++ b/src/angle/src/libEGL/libEGL.pro @@ -24,3 +24,5 @@ egl_headers.files = \ $$ANGLE_DIR/include/EGL/eglplatform.h egl_headers.path = $$[QT_INSTALL_HEADERS]/QtANGLE/EGL INSTALLS += egl_headers + +RC_FILE = $$ANGLE_DIR/src/libEGL/libEGL.rc \ No newline at end of file diff --git a/src/angle/src/libGLESv2/libGLESv2.pro b/src/angle/src/libGLESv2/libGLESv2.pro index b84314dd71f..9745061954b 100644 --- a/src/angle/src/libGLESv2/libGLESv2.pro +++ b/src/angle/src/libGLESv2/libGLESv2.pro @@ -10,3 +10,5 @@ TARGET = $$qtLibraryTarget($${LIBGLESV2_NAME}) } else { DEFINES += DllMain=DllMain_ANGLE # prevent symbol from conflicting with the user's DllMain } + +RC_FILE = $$ANGLE_DIR/src/libGLESv2/libGLESv2.rc \ No newline at end of file diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 1da39a05165..bdabbf46826 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -676,6 +676,11 @@ static inline bool findPlatformWindowHelper(const POINT &screenPoint, unsigned c if (!(cwexFlags & CWP_SKIPTRANSPARENT) && (GetWindowLongPtr(child, GWL_EXSTYLE) & WS_EX_TRANSPARENT)) { const HWND nonTransparentChild = ChildWindowFromPointEx(*hwnd, point, cwexFlags | CWP_SKIPTRANSPARENT); + + if ( !nonTransparentChild || nonTransparentChild == *hwnd ) { + return false; + } + if (QWindowsWindow *nonTransparentWindow = context->findPlatformWindow(nonTransparentChild)) { *result = nonTransparentWindow; *hwnd = nonTransparentChild; diff --git a/src/plugins/platforms/windows/qwindowsdrag.h b/src/plugins/platforms/windows/qwindowsdrag.h index d934679488b..31a1b090d87 100644 --- a/src/plugins/platforms/windows/qwindowsdrag.h +++ b/src/plugins/platforms/windows/qwindowsdrag.h @@ -54,6 +54,8 @@ QT_BEGIN_NAMESPACE class QPlatformScreen; class QWindowsDropMimeData : public QWindowsInternalMimeData { + Q_OBJECT + Q_PROPERTY( IDataObject* IDataObject READ retrieveDataObject ) public: QWindowsDropMimeData() {} IDataObject *retrieveDataObject() const override; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index ca87f1b6a43..306fd830ae3 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -2214,7 +2214,37 @@ void QWindowsWindow::requestActivateWindow() } } } - SetForegroundWindow(m_data.hwnd); + + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: When 3ds Max starts up we have a lot + // of focus stealing issues of 3ds Max on other applications foreground + // windows. This is caused by the call to SetForegroundWindow() + // whenever a Qt window gets focused / activated. + // To prevent this we make the SetForegroundWindow() call conditional + // on a flag that 3ds Max can set when it is launching. + // Note that QWindow::requestActivate() offers a possiblity via the + // flag 'Qt::WindowDoesNotAcceptFocus' to suppress calls to + // QWindowsWindow::requestActivateWindow() for that window, but using + // the flag would disable both calls to SetForegroundWindow() and + // SetFocus(), which is not useful in the case of 3ds Max, since it + // can execute scripts on start up that popup Qt windows which rely + // on the proper focus setting. + //------------------------------------------------------------------ + bool doSetForegroundWnd = true; + if ( qApp ) + { + auto prop = qApp->property( "_3dsmax_disableSetForegroundWnd" ); + if ( prop.isValid() && prop.toBool() == true ) + { + doSetForegroundWnd = false; + } + } + + if ( doSetForegroundWnd ) + { + SetForegroundWindow(m_data.hwnd); + } + SetFocus(m_data.hwnd); if (attached) AttachThreadInput(foregroundThread, currentThread, FALSE); diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp index 294eed77019..81d45c0d6fb 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp @@ -71,6 +71,26 @@ QWindow *windowForAccessible(const QAccessibleInterface *accessible) // Usually it will be NULL, as Qt5 by default uses alien widgets with no native windows. HWND hwndForAccessible(const QAccessibleInterface *accessible) { + //------------------------------------------------------------------------- + // Autodesk 3ds Max Addition + //------------------------------------------------------------------------- + // Some QWidgets in 3ds Max are hosting native legacy HWNDs showing various + // UI elements. Since those are just shallow wrappers around the embedded + // HWNDs, we directly jump to the embedded HWND here. + // The 3ds Max team has patched the QWinHost class of the QtWinMigrate + // project also to use this property accordingly. + //------------------------------------------------------------------------- + if (auto obj = accessible->object()) { + auto prop = obj->property("_3dsmax_hosted_hwnd"); + if (prop.isValid()) { + if (auto nativeChildHWND = reinterpret_cast(qvariant_cast(prop))) { + if (IsWindow(nativeChildHWND)) { + return nativeChildHWND; + } + } + } + } + if (QWindow *window = accessible->window()) { if (!accessible->parent() || (accessible->parent()->window() != window)) { return QWindowsBaseWindow::handleOf(window); diff --git a/src/widgets/accessible/qaccessiblewidget.cpp b/src/widgets/accessible/qaccessiblewidget.cpp index c96d213e7be..7246d133dd0 100644 --- a/src/widgets/accessible/qaccessiblewidget.cpp +++ b/src/widgets/accessible/qaccessiblewidget.cpp @@ -99,11 +99,19 @@ static QString buddyString(const QWidget *widget) } #endif -#if QT_CONFIG(groupbox) - QGroupBox *groupbox = qobject_cast(parent); - if (groupbox) - return groupbox->title(); -#endif + +//---------------------------------------------------------------------------- +// Autodesk 3ds Max change: We don't want that behavior to happen, cause this +// additional buddy-string thing is interfering with our automation scripts. +// We often have a bunch of widgets inside a groupbox that do not have an +// explicit accessible name set, so this would lead to a ton of spinners that +// share the same name. +// #if QT_CONFIG(groupbox) +// QGroupBox *groupbox = qobject_cast(parent); +// if (groupbox) +// return groupbox->title(); +// #endif +//---------------------------------------------------------------------------- return QString(); } @@ -434,10 +442,24 @@ QString QAccessibleWidget::text(QAccessible::Text t) const str = qt_setWindowTitle_helperHelper(widget()->windowTitle(), widget()); } else { str = qt_accStripAmp(buddyString(widget())); + //---------------------------------------------------------------- + // Autodesk 3ds Max change: If no specific string has been set and + // no buddy eigther, we will fall back to use the objectname - or + // even the class name for fixing that. + //---------------------------------------------------------------- + if ( !widget()->inherits("QGroupBox") && !widget()->inherits("QMenu") ) + { + if (str.isEmpty()) + str = widget()->objectName(); + if (str.isEmpty()) + str = widget()->metaObject()->className(); + } } break; case QAccessible::Description: str = widget()->accessibleDescription(); + if (str.isEmpty()) + str = widget()->objectName(); #ifndef QT_NO_TOOLTIP if (str.isEmpty()) str = widget()->toolTip(); diff --git a/src/widgets/accessible/rangecontrols.cpp b/src/widgets/accessible/rangecontrols.cpp index b5b8608418b..514d1e4c3f0 100644 --- a/src/widgets/accessible/rangecontrols.cpp +++ b/src/widgets/accessible/rangecontrols.cpp @@ -68,14 +68,120 @@ QT_BEGIN_NAMESPACE #ifndef QT_NO_ACCESSIBILITY #if QT_CONFIG(spinbox) + +class QAccessibleSpinBoxButton : public QAccessibleInterface +{ + public: + + QAccessibleSpinBoxButton( QAccessibleInterface* parent, QAbstractSpinBox* spinBox, QStyle::SubControl which ) + : m_Parent( parent ), + m_SpinBox( spinBox ), + m_Which( which ) + {} + + ~QAccessibleSpinBoxButton() { m_SpinBox = nullptr; } + + // check for valid pointers + bool isValid() const override { return m_SpinBox != nullptr; } + QObject *object() const override { return nullptr; } + + // relations + QAccessibleInterface *childAt(int, int) const override { return nullptr; } + + // navigation, hierarchy + QAccessibleInterface* parent() const override { return m_Parent; } + QAccessibleInterface* child(int) const override { return nullptr; } + int childCount() const override { return 0; } + int indexOfChild(const QAccessibleInterface*) const override { return -1; } + + // properties and state + QString text(QAccessible::Text t) const override + { + if (t == QAccessible::Text::Name) + { + switch (m_Which) + { + case QStyle::SC_SpinBoxUp: + return "Up"; + case QStyle::SC_SpinBoxDown: + return "Down"; + } + } + return ""; + } + void setText(QAccessible::Text, const QString &) override + { + Q_ASSERT(0); + } + + QRect rect() const override + { + if ( m_SpinBox ) + { + QStyleOptionSpinBox opt; + opt.initFrom( m_SpinBox ); + QRect rect = m_SpinBox->style()->subControlRect( QStyle::CC_SpinBox, &opt, m_Which, m_SpinBox ); + rect.translate(m_SpinBox->mapToGlobal(QPoint(0, 0))); + return rect; + } + return QRect(); + } + + QAccessible::Role role() const override { return QAccessible::Button; } + + QAccessible::State state() const override + { + QAccessible::State state; + if ( m_SpinBox ) + { + if (m_SpinBox->testAttribute(Qt::WA_WState_Visible) == false) + { + state.invisible = true; + } + if (!m_SpinBox->isEnabled()) + { + state.disabled = true; + } + else + { + QStyleOptionSpinBox opt; + opt.initFrom( m_SpinBox ); + if (( m_Which == QStyle::SC_SpinBoxUp && !opt.stepEnabled.testFlag( QAbstractSpinBox::StepUpEnabled ) ) || + ( m_Which == QStyle::SC_SpinBoxDown && !opt.stepEnabled.testFlag( QAbstractSpinBox::StepDownEnabled ) ) ) + { + state.disabled = true; + } + } + } + return state; + } + + private: + QAccessibleInterface* m_Parent; + QAbstractSpinBox* m_SpinBox; + QStyle::SubControl m_Which; +}; + + QAccessibleAbstractSpinBox::QAccessibleAbstractSpinBox(QWidget *w) -: QAccessibleWidget(w, QAccessible::SpinBox), lineEdit(nullptr) +: QAccessibleWidget(w, QAccessible::SpinBox), + lineEdit(nullptr), buttonUp( nullptr ), buttonDown( nullptr ) { Q_ASSERT(abstractSpinBox()); } QAccessibleAbstractSpinBox::~QAccessibleAbstractSpinBox() { + if ( buttonUp ) + { + QAccessible::deleteAccessibleInterface( QAccessible::uniqueId( buttonUp ) ); + buttonUp = nullptr; + } + if ( buttonDown ) + { + QAccessible::deleteAccessibleInterface( QAccessible::uniqueId( buttonDown ) ); + buttonDown = nullptr; + } delete lineEdit; } @@ -99,6 +205,36 @@ QAccessibleInterface *QAccessibleAbstractSpinBox::lineEditIface() const #endif } +QAccessibleInterface *QAccessibleAbstractSpinBox::buttonUpIface() const +{ + if ( !buttonUp ) + { + QAbstractSpinBox* sp = abstractSpinBox(); + if ( sp->buttonSymbols() != QAbstractSpinBox::NoButtons ) + { + QAccessibleSpinBoxButton* up = new QAccessibleSpinBoxButton( const_cast(this), sp, QStyle::SC_SpinBoxUp ); + QAccessible::registerAccessibleInterface( up ); + buttonUp = up; + } + } + return buttonUp; +} + +QAccessibleInterface *QAccessibleAbstractSpinBox::buttonDownIface() const +{ + if ( !buttonDown ) + { + QAbstractSpinBox* sp = abstractSpinBox(); + if ( sp->buttonSymbols() != QAbstractSpinBox::NoButtons ) + { + QAccessibleSpinBoxButton* down = new QAccessibleSpinBoxButton( const_cast(this), sp, QStyle::SC_SpinBoxDown ); + QAccessible::registerAccessibleInterface( down ); + buttonDown = down; + } + } + return buttonDown; +} + QString QAccessibleAbstractSpinBox::text(QAccessible::Text t) const { if (t == QAccessible::Value) @@ -106,6 +242,35 @@ QString QAccessibleAbstractSpinBox::text(QAccessible::Text t) const return QAccessibleWidget::text(t); } +int QAccessibleAbstractSpinBox::childCount() const +{ + QAbstractSpinBox* sp = abstractSpinBox(); + if ( sp->buttonSymbols() == QAbstractSpinBox::NoButtons ) return 1; + return 3; +} + +QAccessibleInterface* QAccessibleAbstractSpinBox::child(int idx) const +{ + if (idx == 0) return QAccessible::queryAccessibleInterface( abstractSpinBox()->lineEdit() ); + if (idx == 1) return buttonUpIface(); + if (idx == 2) return buttonDownIface(); + return nullptr; +} + +int QAccessibleAbstractSpinBox::indexOfChild(const QAccessibleInterface *child) const +{ + if ( child ) + { + if ( child->object() == abstractSpinBox()->lineEdit() ) + { + return 0; + } + if ( buttonUp == child ) return 1; + if ( buttonDown == child ) return 2; + } + return -1; +} + void *QAccessibleAbstractSpinBox::interface_cast(QAccessible::InterfaceType t) { if (t == QAccessible::ValueInterface) diff --git a/src/widgets/accessible/rangecontrols_p.h b/src/widgets/accessible/rangecontrols_p.h index 1eada8e456c..029b5062cdb 100644 --- a/src/widgets/accessible/rangecontrols_p.h +++ b/src/widgets/accessible/rangecontrols_p.h @@ -66,6 +66,7 @@ class QSpinBox; class QDoubleSpinBox; class QDial; class QAccessibleLineEdit; +class QAccessibleSpinBoxButton; #if QT_CONFIG(spinbox) class QAccessibleAbstractSpinBox: @@ -79,6 +80,9 @@ class QAccessibleAbstractSpinBox: virtual ~QAccessibleAbstractSpinBox(); QString text(QAccessible::Text t) const override; + int childCount() const override; + QAccessibleInterface* child(int idx) const override; + int indexOfChild(const QAccessibleInterface *child) const override; void *interface_cast(QAccessible::InterfaceType t) override; // QAccessibleValueInterface @@ -117,8 +121,12 @@ class QAccessibleAbstractSpinBox: protected: QAbstractSpinBox *abstractSpinBox() const; QAccessibleInterface *lineEditIface() const; + QAccessibleInterface *buttonUpIface() const; + QAccessibleInterface *buttonDownIface() const; private: mutable QAccessibleLineEdit *lineEdit; + mutable QAccessibleSpinBoxButton* buttonUp; + mutable QAccessibleSpinBoxButton* buttonDown; }; class QAccessibleSpinBox : public QAccessibleAbstractSpinBox diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index ba315d4338e..c5e5ea09a19 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -1774,7 +1774,15 @@ void QApplicationPrivate::setFocusWidget(QWidget *focus, Qt::FocusReason reason) else if (focus && reason == Qt::ShortcutFocusReason) { focus->window()->setAttribute(Qt::WA_KeyboardFocusChange); } - QWidget *prev = focus_widget; + + //------------------------------------------------------------------ + // Autodesk 3ds Max change: In 3ds Max we discovered crashes in Qt + // when during the focus change widgets that are affected by the change + // get deleted. To avoid a crash we keep track of the widgets with + // a QPointer and skip the code if the pointer is already a nullptr. + //------------------------------------------------------------------ + QPointer prev = focus_widget; + QPointer newFocus = focus; focus_widget = focus; if(focus_widget) @@ -1791,17 +1799,15 @@ void QApplicationPrivate::setFocusWidget(QWidget *focus, Qt::FocusReason reason) } #endif QFocusEvent out(QEvent::FocusOut, reason); - QPointer that = prev; QApplication::sendEvent(prev, &out); - if (that) - QApplication::sendEvent(that->style(), &out); + if (prev) + QApplication::sendEvent(prev->style(), &out); } - if(focus && QApplicationPrivate::focus_widget == focus) { + if(newFocus && QApplicationPrivate::focus_widget == newFocus) { QFocusEvent in(QEvent::FocusIn, reason); - QPointer that = focus; - QApplication::sendEvent(focus, &in); - if (that) - QApplication::sendEvent(that->style(), &in); + QApplication::sendEvent(newFocus, &in); + if (newFocus) + QApplication::sendEvent(newFocus->style(), &in); } emit qApp->focusChanged(prev, focus_widget); } @@ -3157,13 +3163,19 @@ bool QApplication::notify(QObject *receiver, QEvent *e) case QEvent::MouseMove: { QWidget* w = static_cast(receiver); + QPointer pw = w; QMouseEvent* mouse = static_cast(e); QPoint relpos = mouse->pos(); if (e->spontaneous()) { - if (e->type() != QEvent::MouseMove) + if (e->type() != QEvent::MouseMove) { QApplicationPrivate::giveFocusAccordingToFocusPolicy(w, e, relpos); + if ( pw.isNull() ){ + // receiver was deleted during the focus-event-handling + break; + } + } // ### Qt 5 These dynamic tool tips should be an OPT-IN feature. Some platforms // like OS X (probably others too), can optimize their views by not @@ -3185,7 +3197,6 @@ bool QApplication::notify(QObject *receiver, QEvent *e) bool eventAccepted = mouse->isAccepted(); - QPointer pw = w; while (w) { QMouseEvent me(mouse->type(), relpos, mouse->windowPos(), mouse->globalPos(), mouse->button(), mouse->buttons(), mouse->modifiers(), mouse->source()); diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 1e249dc1919..38b9bce705d 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -1822,6 +1822,7 @@ void QWidgetPrivate::createExtra() extra->nativeChildrenForced = 0; extra->inRenderWithPainter = 0; extra->hasWindowContainer = false; + extra->hasNativeChildren = false; extra->hasMask = 0; createSysExtra(); #ifdef QWIDGET_EXTRA_DEBUG @@ -4247,6 +4248,7 @@ QPoint QWidget::mapTo(const QWidget * parent, const QPoint & pos) const while (w != parent) { Q_ASSERT_X(w, "QWidget::mapTo(const QWidget *parent, const QPoint &pos)", "parent must be in parent hierarchy"); + if( !w ){ break; } p = w->mapToParent(p); w = w->parentWidget(); } @@ -4271,7 +4273,7 @@ QPoint QWidget::mapFrom(const QWidget * parent, const QPoint & pos) const while (w != parent) { Q_ASSERT_X(w, "QWidget::mapFrom(const QWidget *parent, const QPoint &pos)", "parent must be in parent hierarchy"); - + if( !w ){ break; } p = w->mapFromParent(p); w = w->parentWidget(); } @@ -6497,7 +6499,14 @@ void QWidget::setFocus(Qt::FocusReason reason) if (!isEnabled()) return; - QWidget *f = d_func()->deepestFocusProxy(); + //------------------------------------------------------------------ + // Autodesk 3ds Max change: In 3ds Max we discovered crashes in Qt + // when during the focus change widgets that are affected by the change + // get deleted. To avoid a crash we keep track of the widgets with + // a QPointer and quit the function if a tracked widget got deleted + // during the focus change. + //------------------------------------------------------------------ + QPointer f = d_func()->deepestFocusProxy(); if (!f) f = this; @@ -6508,6 +6517,8 @@ void QWidget::setFocus(Qt::FocusReason reason) ) return; + QPointer thisPtr = this; + #if QT_CONFIG(graphicsview) QWidget *previousProxyFocus = 0; if (QWExtra *topData = window()->d_func()->extra) { @@ -6533,7 +6544,7 @@ void QWidget::setFocus(Qt::FocusReason reason) } #endif - if (f->isActiveWindow()) { + if (f && f->isActiveWindow()) { QWidget *prev = QApplicationPrivate::focus_widget; if (prev) { if (reason != Qt::PopupFocusReason && reason != Qt::MenuBarFocusReason @@ -6550,6 +6561,16 @@ void QWidget::setFocus(Qt::FocusReason reason) f->d_func()->updateFocusChild(); QApplicationPrivate::setFocusWidget(f, reason); + + //------------------------------------------------------------------ + // Autodesk 3ds Max change: Tracked widgets has been deleted + // during the focus change. + //------------------------------------------------------------------ + if ( !thisPtr || !f ) + { + return; + } + #ifndef QT_NO_ACCESSIBILITY # ifdef Q_OS_WIN // The negation of the condition in setFocus_sys @@ -6583,15 +6604,14 @@ void QWidget::setFocus(Qt::FocusReason reason) #endif // Send event to self QFocusEvent event(QEvent::FocusIn, reason); - QPointer that = f; QApplication::sendEvent(f, &event); - if (that) - QApplication::sendEvent(that->style(), &event); + if (f) + QApplication::sendEvent(f->style(), &event); } } } #endif - } else { + } else if ( f ) { f->d_func()->updateFocusChild(); } } @@ -7132,6 +7152,90 @@ QSize QWidget::frameSize() const return data->crect.size(); } +bool QWidgetPrivate::propagateRaisedLoweredToChildren( QWidget* widget, bool raised ) +{ + if ( !widget ) { return false; } + bool keep_flag = false; + foreach( QObject* child_object, widget->children() ) + { + if ( auto child_widget = qobject_cast( child_object ) ) + { + if ( QWidgetPrivate* child_private = child_widget->d_func() ) + { + if ( child_widget->testAttribute( Qt::WA_NativeWindow ) ) + { + if ( auto window = child_widget->windowHandle() ) + { + if ( window->parent() ) + { + if ( raised ) + { + window->raise(); + } + else + { + window->lower(); + } + } + } + keep_flag = true; + } + else if ( child_private->extra && child_private->extra->hasNativeChildren ) + { + bool keep_child_flag = propagateRaisedLoweredToChildren( child_widget, raised ); + if ( !keep_child_flag ) + { + child_private->extra->hasNativeChildren = false; + } + else + { + keep_flag = true; + } + } + } + } + } + return keep_flag; +} + +bool QWidgetPrivate::propagateMoveToChildren( QWidget* widget, QPoint offset ) +{ + if ( !widget ) { return false; } + bool keep_flag = false; + + foreach( QObject* child_object, widget->children() ) + { + if ( auto child_widget = qobject_cast( child_object ) ) + { + if ( QWidgetPrivate* child_private = child_widget->d_func() ) + { + if ( child_widget->testAttribute( Qt::WA_NativeWindow ) ) + { + if ( auto window = child_widget->windowHandle() ) + { + auto p = child_widget->pos() + offset; + window->setGeometry( QRect( p, child_widget->size() ) ); + } + keep_flag = true; + } + else if ( child_private->extra && child_private->extra->hasNativeChildren ) + { + bool keep_child_flag = propagateMoveToChildren( child_widget, child_widget->pos() + offset ); + if ( !keep_child_flag ) + { + child_private->extra->hasNativeChildren = false; + } + else + { + keep_flag = true; + } + } + } + } + } + return keep_flag; +} + /*! \fn void QWidget::move(int x, int y) \overload @@ -7158,8 +7262,20 @@ void QWidget::move(const QPoint &p) setAttribute(Qt::WA_PendingMoveEvent); } - if (d->extra && d->extra->hasWindowContainer) - QWindowContainer::parentWasMoved(this); + if ( d->extra ) + { + if ( d->extra->hasWindowContainer ) + { + QWindowContainer::parentWasMoved( this ); + } + if ( d->extra->hasNativeChildren ) + { + if ( testAttribute( Qt::WA_NativeWindow ) || ( d->propagateMoveToChildren( this, mapTo( nativeParentWidget(), QPoint() ) ) == false ) ) + { + d->extra->hasNativeChildren = false; + } + } + } } // move() was invoked with Qt::WA_WState_Created not set (frame geometry @@ -7230,8 +7346,20 @@ void QWidget::setGeometry(const QRect &r) setAttribute(Qt::WA_PendingResizeEvent); } - if (d->extra && d->extra->hasWindowContainer) - QWindowContainer::parentWasMoved(this); + if ( d->extra ) + { + if ( d->extra->hasWindowContainer ) + { + QWindowContainer::parentWasMoved( this ); + } + if ( d->extra->hasNativeChildren ) + { + if ( testAttribute( Qt::WA_NativeWindow ) || ( d->propagateMoveToChildren( this, mapTo( nativeParentWidget(), QPoint() ) ) == false ) ) + { + d->extra->hasNativeChildren = false; + } + } + } } void QWidgetPrivate::setGeometry_sys(int x, int y, int w, int h, bool isMove) @@ -10782,8 +10910,35 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) } #endif - if (d->extra && d->extra->hasWindowContainer) - QWindowContainer::parentWasChanged(this); + if ( d->extra ) + { + if ( d->extra->hasWindowContainer ) + { + QWindowContainer::parentWasChanged( this ); + } + + // we re-flag the non-native parent chain. + if ( d->extra->hasNativeChildren || testAttribute(Qt::WA_NativeWindow) ) + { + auto pw = parentWidget(); + while ( pw ) + { + if ( pw->testAttribute( Qt::WA_NativeWindow ) ) + { + break; + } + if ( auto pw_e = pw->d_func() ? pw->d_func()->extra : nullptr ) + { + if ( pw_e->hasNativeChildren ) + { + break; + } + pw_e->hasNativeChildren = true; + } + pw = pw->parentWidget(); + } + } + } } void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f) @@ -11877,8 +12032,22 @@ void QWidget::raise() if (testAttribute(Qt::WA_WState_Created)) d->raise_sys(); - if (d->extra && d->extra->hasWindowContainer) - QWindowContainer::parentWasRaised(this); + if (d->extra ) + { + if ( d->extra->hasWindowContainer ) + { + QWindowContainer::parentWasRaised(this); + } + if ( d->extra->hasNativeChildren ) + { + if ( testAttribute( Qt::WA_NativeWindow ) || ( d->propagateRaisedLoweredToChildren( this, true ) == false ) ) + { + d->extra->hasNativeChildren = false; + } + } + } + + QEvent e(QEvent::ZOrderChange); QApplication::sendEvent(this, &e); @@ -11927,8 +12096,20 @@ void QWidget::lower() if (testAttribute(Qt::WA_WState_Created)) d->lower_sys(); - if (d->extra && d->extra->hasWindowContainer) - QWindowContainer::parentWasLowered(this); + if ( d->extra ) + { + if ( d->extra->hasWindowContainer ) + { + QWindowContainer::parentWasLowered(this); + } + if ( d->extra->hasNativeChildren ) + { + if ( testAttribute( Qt::WA_NativeWindow ) || ( d->propagateRaisedLoweredToChildren( this, false ) == false ) ) + { + d->extra->hasNativeChildren = false; + } + } + } QEvent e(QEvent::ZOrderChange); QApplication::sendEvent(this, &e); diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h index e8b550b1cd3..251bf6e53d3 100644 --- a/src/widgets/kernel/qwidget_p.h +++ b/src/widgets/kernel/qwidget_p.h @@ -265,6 +265,7 @@ struct QWExtra { uint inRenderWithPainter : 1; uint hasMask : 1; uint hasWindowContainer : 1; + uint hasNativeChildren : 1; // Autodesk 3ds Max Addition // *************************** Platform specific values (bit fields first) ********** #if 0 /* Used to be included in Qt4 for Q_WS_WIN */ // <----------------------------------------------------------- WIN @@ -604,6 +605,18 @@ class Q_WIDGETS_EXPORT QWidgetPrivate : public QObjectPrivate return extra ? extra->nativeChildrenForced : false; } + //------------------------------------------------------------------------- + // Autodesk 3ds Max Addition: Since we have a lot of legacy code that rely + // on a fixed HWND parent hierarchy, we sometimes are forced to use a mixed + // native / non-native QWidget parent chain. In these cases the QWidget + // movement has to be propagated to the child chain, to ensure the correct + // positioning of the underlying native QWindow, even if the direct parent + // QWindow was not moved. + //------------------------------------------------------------------------- + static bool propagateRaisedLoweredToChildren( QWidget*, bool raised ); + static bool propagateMoveToChildren( QWidget* widget, QPoint offset ); + //------------------------------------------------------------------------- + inline QRect effectiveRectFor(const QRect &rect) const { #if QT_CONFIG(graphicseffect) diff --git a/src/widgets/kernel/qwidgetwindow.cpp b/src/widgets/kernel/qwidgetwindow.cpp index 1f3057b008d..4536212fbfe 100644 --- a/src/widgets/kernel/qwidgetwindow.cpp +++ b/src/widgets/kernel/qwidgetwindow.cpp @@ -237,7 +237,30 @@ bool QWidgetWindow::event(QEvent *event) switch (event->type()) { case QEvent::Close: handleCloseEvent(static_cast(event)); - QWindow::event(event); + //------------------------------------------------------------------------- + // Autodesk 3ds Max change: This newly introduced code line causes a lot of + // troubles on 3ds Max side, so we comment it out again. + // 1. Dock widgets that have the native tool window frame are not dockable + // anymore when you close and reopen a floating dock widget via the OS + // titlebar close button or sysmenu. This is due to the fact that + // setFrameStrutEventsEnabled() is not called on the newly created + // platform window when the dock widget gets shown again. Without framestrut + // enabled the nonclientarea events on Qt side won't work and the dock widget + // drag is not started. + // 2. It also plays not well with a mixed qt/win32 window hierarchy, cause + // with this change the complete native window hierarchy gets now deleted + // before the widget hierarchy, even hwnds that get hosted in a QWinHost are + // now gone before the widgets client code is actually reached. This caused + // crashes in 3ds Max where the Qt widget still tried to work on a hosted + // native win32 child window. + // 3. Another issue is, that widgets that do not have the Qt::WA_DeleteOnClose + // flag set, cannot be shown again when they get closed via the native OS + // titlebar. This seems to be due to the fact that QWidgetPrivate::show_sys() + // is doing nothing cause the Qt::WA_OutsideWSRange flag is set. It gets set + // by QWidget::setVisible() during the layout->active() call, cause + // mw->setMaximumSize(totalMaximumSize()) is setting a total max height of 0. + //------------------------------------------------------------------------- + //QWindow::event(event); return true; case QEvent::Enter: @@ -718,6 +741,31 @@ bool QWidgetWindow::updatePos() bool changed = false; if (m_widget->testAttribute(Qt::WA_OutsideWSRange)) return changed; + //------------------------------------------------------------------------- + // Autodesk 3ds Max Addition: Since we have a lot of legacy code that rely + // on a fixed HWND parent hierarchy, we sometimes are forced to use a mixed + // native / non-native QWidget parent chain. In these cases the QWindow and + // the QWidget positions may not always match - depending on the in-between + // non-native parent chain - like in scroll-areas, e.g. + //------------------------------------------------------------------------- + if ( m_widget->testAttribute( Qt::WA_NativeWindow ) ) + { + QWidget* np = m_widget->nativeParentWidget(); + QWidget* p = m_widget->parentWidget(); + if ( np && p && ( np != p ) ) + { + QPoint top_left_calculated = p->mapTo( np, m_widget->data->crect.topLeft() ); + QPoint top_left = geometry().topLeft(); + if( top_left_calculated != top_left ) + { + changed = true; + //m_widget->data->crect.moveTopLeft( p->mapFrom( np, geometry().topLeft() ) ); + } + updateMargins(); + return changed; + } + } + //------------------------------------------------------------------------- if (m_widget->data->crect.topLeft() != geometry().topLeft()) { changed = true; m_widget->data->crect.moveTopLeft(geometry().topLeft()); diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index 3b9186e61a1..e5635bb6a17 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -2858,22 +2858,69 @@ QRect QCommonStyle::subElementRect(SubElement sr, const QStyleOption *opt, r = visualRect(opt->direction, opt->rect, r); } break; + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Tabs menu button + // Adds a sub element rectangle for the new 3ds Max tabs menu button. + // The button is placed by default on the right side of the tab scroll buttons. + //------------------------------------------------------------------ + case SE_TabBarTabsMenuButton: { + const bool vertical = opt->rect.width() < opt->rect.height(); + const Qt::LayoutDirection ld = widget ? widget->layoutDirection() : Qt::LeftToRight; + const int tabsMenuBtnWidth = qMax( proxy()->pixelMetric( QStyle::PM_TabBarTabsMenuButtonWidth, 0, widget ), QApplication::globalStrut().width() ); + + r = vertical ? QRect( 0, opt->rect.height() - tabsMenuBtnWidth, opt->rect.width(), tabsMenuBtnWidth ) + : QStyle::visualRect( ld, opt->rect, QRect( opt->rect.width() - tabsMenuBtnWidth, 0, tabsMenuBtnWidth, opt->rect.height() ) ); + break; + } case SE_TabBarScrollLeftButton: { const bool vertical = opt->rect.width() < opt->rect.height(); - const Qt::LayoutDirection ld = widget->layoutDirection(); - const int buttonWidth = qMax(pixelMetric(QStyle::PM_TabBarScrollButtonWidth, 0, widget), QApplication::globalStrut().width()); - const int buttonOverlap = pixelMetric(QStyle::PM_TabBar_ScrollButtonOverlap, 0, widget); + const Qt::LayoutDirection ld = widget ? widget->layoutDirection() : Qt::LeftToRight; + const int buttonWidth = qMax( proxy()->pixelMetric(QStyle::PM_TabBarScrollButtonWidth, 0, widget), QApplication::globalStrut().width()); + const int buttonOverlap = proxy()->pixelMetric(QStyle::PM_TabBar_ScrollButtonOverlap, 0, widget); + + int buttonArea = 2 * buttonWidth - buttonOverlap; + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Tabs menu button + // Check the 3ds Max property for the tab scroll options if the + // tabs menu button is shown. + //------------------------------------------------------------------ + if ( widget ) + { + auto prop = widget->property( "_3dsmax_tab_scroll_options" ); + if ( prop.isValid() && prop.toInt() > 1 ) + { + const int tabsMenuBtnWidth = qMax( proxy()->pixelMetric( QStyle::PM_TabBarTabsMenuButtonWidth, 0, widget ), QApplication::globalStrut().width() ); + buttonArea += tabsMenuBtnWidth - buttonOverlap; + } + } - r = vertical ? QRect(0, opt->rect.height() - (buttonWidth * 2) + buttonOverlap, opt->rect.width(), buttonWidth) - : QStyle::visualRect(ld, opt->rect, QRect(opt->rect.width() - (buttonWidth * 2) + buttonOverlap, 0, buttonWidth, opt->rect.height())); + r = vertical ? QRect( 0, opt->rect.height() - buttonArea, opt->rect.width(), buttonWidth ) + : QStyle::visualRect( ld, opt->rect, QRect(opt->rect.width() - buttonArea, 0, buttonWidth, opt->rect.height()) ); break; } case SE_TabBarScrollRightButton: { const bool vertical = opt->rect.width() < opt->rect.height(); - const Qt::LayoutDirection ld = widget->layoutDirection(); - const int buttonWidth = qMax(pixelMetric(QStyle::PM_TabBarScrollButtonWidth, 0, widget), QApplication::globalStrut().width()); + const Qt::LayoutDirection ld = widget ? widget->layoutDirection() : Qt::LeftToRight; + const int buttonWidth = qMax( proxy()->pixelMetric(QStyle::PM_TabBarScrollButtonWidth, 0, widget), QApplication::globalStrut().width()); + const int buttonOverlap = proxy()->pixelMetric( QStyle::PM_TabBar_ScrollButtonOverlap, 0, widget ); + + int buttonArea = buttonWidth; + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Tabs menu button + // Check the 3ds Max property for the tab scroll options if the + // tabs menu button is shown. + //------------------------------------------------------------------ + if ( widget ) + { + auto prop = widget->property( "_3dsmax_tab_scroll_options" ); + if ( prop.isValid() && prop.toInt() > 1 ) + { + const int tabsMenuBtnWidth = qMax( proxy()->pixelMetric( QStyle::PM_TabBarTabsMenuButtonWidth, 0, widget ), QApplication::globalStrut().width() ); + buttonArea += tabsMenuBtnWidth - buttonOverlap; + } + } - r = vertical ? QRect(0, opt->rect.height() - buttonWidth, opt->rect.width(), buttonWidth) - : QStyle::visualRect(ld, opt->rect, QRect(opt->rect.width() - buttonWidth, 0, buttonWidth, opt->rect.height())); + r = vertical ? QRect( 0, opt->rect.height() - buttonArea, opt->rect.width(), buttonWidth ) + : QStyle::visualRect( ld, opt->rect, QRect(opt->rect.width() - buttonArea, 0, buttonWidth, opt->rect.height()) ); break; } #endif case SE_TreeViewDisclosureItem: @@ -4683,6 +4730,12 @@ int QCommonStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const QWid case PM_TabBarScrollButtonWidth: ret = int(QStyleHelper::dpiScaled(16.)); break; + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Tabs menu button + //------------------------------------------------------------------ + case PM_TabBarTabsMenuButtonWidth: + ret = proxy()->pixelMetric( PM_TabBarScrollButtonWidth, opt, widget ); + break; case PM_LayoutLeftMargin: case PM_LayoutTopMargin: case PM_LayoutRightMargin: diff --git a/src/widgets/styles/qstyle.h b/src/widgets/styles/qstyle.h index cef569d514c..182de4b020b 100644 --- a/src/widgets/styles/qstyle.h +++ b/src/widgets/styles/qstyle.h @@ -356,6 +356,11 @@ class Q_WIDGETS_EXPORT QStyle : public QObject SE_TabBarScrollRightButton, SE_TabBarTearIndicatorRight, + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Tabs menu button + //------------------------------------------------------------------ + SE_TabBarTabsMenuButton, + // do not add any values below/greater than this SE_CustomBase = 0xf0000000 }; @@ -574,6 +579,11 @@ class Q_WIDGETS_EXPORT QStyle : public QObject PM_TitleBarButtonIconSize, PM_TitleBarButtonSize, + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Tabs menu button + //------------------------------------------------------------------ + PM_TabBarTabsMenuButtonWidth, + // do not add any values below/greater than this PM_CustomBase = 0xf0000000 }; diff --git a/src/widgets/styles/qstylesheetstyle.cpp b/src/widgets/styles/qstylesheetstyle.cpp index c046ac52f90..6924947c447 100644 --- a/src/widgets/styles/qstylesheetstyle.cpp +++ b/src/widgets/styles/qstylesheetstyle.cpp @@ -111,6 +111,8 @@ #include "qdrawutil.h" #include +#include +#include #if QT_CONFIG(toolbar) #include #endif @@ -2990,29 +2992,63 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC #if QT_CONFIG(spinbox) case CC_SpinBox: if (const QStyleOptionSpinBox *spin = qstyleoption_cast(opt)) { - QStyleOptionSpinBox spinOpt(*spin); - rule.configurePalette(&spinOpt.palette, QPalette::ButtonText, QPalette::Button); - rule.configurePalette(&spinOpt.palette, QPalette::Text, QPalette::Base); - spinOpt.rect = rule.borderRect(opt->rect); + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Since 3ds Max uses a derived version + // of QStyleOptionSpinBox to accomplish the use of the animation key + // brackets, we need to clone this option in a special manner, the + // previous "spinOpt = QStyleOptionSpinBox( *spin );" doesn't work. + // To allow that, we invoke a method called "cloneStyleOption" on + // the baseStyle(), if available - or we just fall back into the old + // behavior. + //------------------------------------------------------------------ + std::unique_ptr spinOpt; + if ( opt->version == 2 ) + { + if ( auto b = baseStyle() ) + { + QStyleOption* clone = nullptr; + if ( QMetaObject::invokeMethod( b, "cloneStyleOption", Qt::DirectConnection, + Q_RETURN_ARG( QStyleOption*, clone ), + Q_ARG( const QStyleOption*, opt ) ) ) + { + if ( auto clone_cast = qstyleoption_cast( clone ) ) + { + spinOpt.reset( clone_cast ); + } + else + { + delete clone; + } + } + } + } + if ( !spinOpt ) + { + spinOpt.reset( new QStyleOptionSpinBox( *spin ) ); + } + + rule.configurePalette(&spinOpt->palette, QPalette::ButtonText, QPalette::Button); + rule.configurePalette(&spinOpt->palette, QPalette::Text, QPalette::Base); + spinOpt->rect = rule.borderRect(opt->rect); bool customUp = true, customDown = true; QRenderRule upRule = renderRule(w, opt, PseudoElement_SpinBoxUpButton); QRenderRule downRule = renderRule(w, opt, PseudoElement_SpinBoxDownButton); bool upRuleMatch = upRule.hasGeometry() || upRule.hasPosition(); bool downRuleMatch = downRule.hasGeometry() || downRule.hasPosition(); if (rule.hasNativeBorder() && !upRuleMatch && !downRuleMatch) { - rule.drawBackgroundImage(p, spinOpt.rect); + rule.drawBackgroundImage(p, spinOpt->rect); customUp = (opt->subControls & QStyle::SC_SpinBoxUp) && (hasStyleRule(w, PseudoElement_SpinBoxUpButton) || hasStyleRule(w, PseudoElement_UpArrow)); if (customUp) - spinOpt.subControls &= ~QStyle::SC_SpinBoxUp; + spinOpt->subControls &= ~QStyle::SC_SpinBoxUp; customDown = (opt->subControls & QStyle::SC_SpinBoxDown) && (hasStyleRule(w, PseudoElement_SpinBoxDownButton) || hasStyleRule(w, PseudoElement_DownArrow)); if (customDown) - spinOpt.subControls &= ~QStyle::SC_SpinBoxDown; + spinOpt->subControls &= ~QStyle::SC_SpinBoxDown; if (rule.baseStyleCanDraw()) { - baseStyle()->drawComplexControl(cc, &spinOpt, p, w); + baseStyle()->drawComplexControl(cc, spinOpt.get(), p, w); } else { - QWindowsStyle::drawComplexControl(cc, &spinOpt, p, w); + QWindowsStyle::drawComplexControl(cc, spinOpt.get(), p, w); } if (!customUp && !customDown) return; @@ -3029,8 +3065,8 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC r = positionRect(w, subRule, subRule2, PseudoElement_SpinBoxUpArrow, r, opt->direction); subRule2.drawRule(p, r); } else { - spinOpt.subControls = QStyle::SC_SpinBoxUp; - QWindowsStyle::drawComplexControl(cc, &spinOpt, p, w); + spinOpt->subControls = QStyle::SC_SpinBoxUp; + QWindowsStyle::drawComplexControl(cc, spinOpt.get(), p, w); } } @@ -3043,8 +3079,8 @@ void QStyleSheetStyle::drawComplexControl(ComplexControl cc, const QStyleOptionC r = positionRect(w, subRule, subRule2, PseudoElement_SpinBoxDownArrow, r, opt->direction); subRule2.drawRule(p, r); } else { - spinOpt.subControls = QStyle::SC_SpinBoxDown; - QWindowsStyle::drawComplexControl(cc, &spinOpt, p, w); + spinOpt->subControls = QStyle::SC_SpinBoxDown; + QWindowsStyle::drawComplexControl(cc, spinOpt.get(), p, w); } } return; @@ -4939,20 +4975,34 @@ QSize QStyleSheetStyle::sizeFromContents(ContentsType ct, const QStyleOption *op switch (ct) { #if QT_CONFIG(spinbox) case CT_SpinBox: // ### hopelessly broken QAbstractSpinBox (part 1) - if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast(opt)) { + if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast(opt)) + { + //------------------------------------------------------------------ + // The QStyleSheetStyle internally creates a default stylesheet for + // QAbstractSpinBox having a "native border" setup, so we need to + // check here if we have an actual rule - or just falling through to + // the defaults. + //------------------------------------------------------------------ + bool has_rule = rule.hasGeometry() || rule.hasBox() || rule.hasOutline() || + ( rule.hasBorder() && !rule.hasNativeBorder() ); + // Add some space for the up/down buttons QRenderRule subRule = renderRule(w, opt, PseudoElement_SpinBoxUpButton); if (subRule.hasDrawable()) { QRect r = positionRect(w, rule, subRule, PseudoElement_SpinBoxUpButton, opt->rect, opt->direction); sz += QSize(r.width(), 0); + has_rule = true; } else { QSize defaultUpSize = defaultSize(w, subRule.size(), spinbox->rect, PseudoElement_SpinBoxUpButton); sz += QSize(defaultUpSize.width(), 0); } - if (rule.hasBox() || rule.hasBorder() || !rule.hasNativeBorder()) - sz = rule.boxSize(sz); - return sz; + if ( has_rule ) + { + if ( rule.hasBox() || rule.hasBorder() || !rule.hasNativeBorder() ) + sz = rule.boxSize( sz ); + return sz; + } } break; #endif // QT_CONFIG(spinbox) diff --git a/src/widgets/widgets/qdockarealayout.cpp b/src/widgets/widgets/qdockarealayout.cpp index 75289e9d1ff..217d953fc91 100644 --- a/src/widgets/widgets/qdockarealayout.cpp +++ b/src/widgets/widgets/qdockarealayout.cpp @@ -160,6 +160,60 @@ QSize QDockAreaLayoutItem::maximumSize() const return QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); } +namespace +{ +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method returns if the extended docking resize behavior is enabled +// for a main window or not. +//------------------------------------------------------------------------- +bool doExtendedDockWidgetResize( QMainWindow* mainWindow ) +{ + if ( mainWindow ) + { + auto prop = mainWindow->property( "_3dsmax_disable_extended_docking_resize" ); + return (!prop.isValid() || prop.toBool() == false); + } + return false; +} + + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Retain dock widget sizes +// This method returns if the mechanism for retaining the frame sizes +// of the dock widgets when docking/undocking a neighboring panels is +// enabled for a main window or not. +//------------------------------------------------------------------------- +bool retainDockWidgetSizes( QMainWindow* mainWindow ) +{ + if ( mainWindow ) + { + auto prop = mainWindow->property( "_3dsmax_disable_retain_dockwidget_sizes" ); + return (!prop.isValid() || prop.toBool() == false); + } + return false; +} + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method wraps the layout items default implementation of hasFixedSize(). +// For 3dsmax it will always return false for the items fixed size. +// The default implementation is blocking the UI when a widget has a fixed +// size constraint for the width or height. Also there is no separator added +// although the widget could be resized on one side. +//------------------------------------------------------------------------- +bool hasLayoutItemFixedSize( QMainWindow* mainWindow, const QDockAreaLayoutItem& item, Qt::Orientation o ) +{ + // extended 3dsmax dock widget resize behavior + if ( doExtendedDockWidgetResize( mainWindow ) ) + { + return false; + } + + return item.hasFixedSize( o ); +} +} + bool QDockAreaLayoutItem::hasFixedSize(Qt::Orientation o) const { return perp(o, minimumSize()) == perp(o, maximumSize()); @@ -431,7 +485,7 @@ QSize QDockAreaLayoutInfo::sizeHint() const #endif { if (previous && !gap && !(previous->flags & QDockAreaLayoutItem::GapItem) - && !previous->hasFixedSize(o)) { + && !hasLayoutItemFixedSize( mainWindow, *previous, o )) { a += *sep; } a += gap ? item.size : pick(o, size_hint); @@ -572,7 +626,7 @@ void QDockAreaLayoutInfo::fitItems() if (!(previous->flags & QDockAreaLayoutItem::GapItem)) { QLayoutStruct &ls = layout_struct_list[j++]; ls.init(); - ls.minimumSize = ls.maximumSize = ls.sizeHint = previous->hasFixedSize(o) ? 0 : *sep; + ls.minimumSize = ls.maximumSize = ls.sizeHint = hasLayoutItemFixedSize( mainWindow, *previous, o ) ? 0 : *sep; ls.empty = false; } } @@ -615,6 +669,50 @@ void QDockAreaLayoutInfo::fitItems() item.flags &= ~QDockAreaLayoutItem::KeepSize; previous = &item; } + + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: Extended docking resize behavior + // The automatic layout method fitItems applies the new free space on all expansive layout + // items in the layout info list. We only want to apply it on the first expansive item + // so that the others keep their size. + // The definition of the first expansive item depends on where the separator was moved, it + // can be in front of the layout info container or behind it. When the resize was in front, + // the fitItemsExpandMode will be set to ExpandFirst and the first expansive item will get + // the free space, otherwise in case of ExpandLast the last expansive one. + //------------------------------------------------------------------------- + if ( fitItemsExpandMode != ExpandAll ) + { + Qt::Orientation dockAreaOrientation = dockPos == QInternal::LeftDock || dockPos == QInternal::RightDock + ? Qt::Horizontal + : Qt::Vertical; + + // Only apply the 'expansive' change to layout info structs that have the same orientation + // as our root main docking area. + if ( o == dockAreaOrientation ) + { + bool firstExpansiveItemFound = false; + bool doReverse = (fitItemsExpandMode == ExpandLast); + + for ( int i = doReverse ? (j - 1) : 0; i >= 0 && i < j; doReverse ? --i : ++i ) + { + QLayoutStruct* ls = &layout_struct_list[i]; + + // only allow the first expansive item, with no fixed size, to auto resize + if ( !firstExpansiveItemFound && ls->expansive && (ls->minimumSize != ls->maximumSize) ) + { + firstExpansiveItemFound = true; + } + // and try to keep the size for the rest of the items in the container + else + { + ls->expansive = false; + ls->stretch = 0; + } + } + } + } + //------------------------------------------------------------------------- + layout_struct_list.resize(j); // If there is more space than the widgets can take (due to maximum size constraints), @@ -644,12 +742,26 @@ void QDockAreaLayoutInfo::fitItems() if (item.subinfo != 0) { item.subinfo->rect = itemRect(i); + + // Autodesk 3ds Max addition: + // Inherit the expand flag to the nested layout info structs + // if they have not already their own mode set. + if ( item.subinfo->fitItemsExpandMode == ExpandAll ) + { + item.subinfo->fitItemsExpandMode = fitItemsExpandMode; + } + item.subinfo->fitItems(); } prev_gap = gap; first = false; } + + // Autodesk 3ds Max addition: + // Reset the expand mode after the layout again, it will be newly set + // depending on the position of the separator that the user moves. + fitItemsExpandMode = ExpandAll; } static QInternal::DockPosition dockPosHelper(const QRect &rect, const QPoint &_pos, @@ -941,7 +1053,284 @@ static int separatorMoveHelper(QVector &list, int index, int delt return delta; } -int QDockAreaLayoutInfo::separatorMove(int index, int delta) + +namespace +{ +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// Small helper function that calculates the amount of what a dock layout +// container changes for a given shrink delta on one separator side, +// the grow delta on the other side and the resize direction 'deltaSign', +// with a value of the either -1 or 1. +//------------------------------------------------------------------------- +int calcDeltaContainerChanged( int deltaShrink, int deltaGrow, int deltaSign ) +{ + int deltaContainerChanged = 0; + if ( deltaGrow != deltaShrink ) + { + if ( deltaShrink == 0 ) + deltaContainerChanged = deltaSign * deltaGrow; + else if ( deltaGrow == 0 ) + deltaContainerChanged = deltaSign * deltaShrink; + else if ( deltaGrow > deltaShrink ) + deltaContainerChanged = deltaSign * (deltaGrow - deltaShrink); + else + deltaContainerChanged = deltaSign * (deltaShrink - deltaGrow); + } + return deltaContainerChanged; +} + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method works similar to the default layout implementation of +// separatorMoveHelper(). The difference is, that it grows and shrinks +// the layout items on both sides without taking in account the size limits +// of the opposite side. +// With the default Qt algorithm it would block in some cases, when size +// constraint items are involved, the resizing. +//------------------------------------------------------------------------- +int separatorMoveHelperTwoSided( QVector &list, int index, int delta, int sep, + SeparatorMoveInfo& smi ) +{ + // adjust sizes + int pos = -1; + for ( int i = 0; i < list.size(); ++i ) { + const QLayoutStruct &ls = list.at( i ); + if ( !ls.empty ) { + pos = ls.pos; + break; + } + } + if ( pos == -1 ) + return 0; + + int deltaSign = (delta > 0) ? 1 : -1; + int deltaShrink = 0; + int deltaGrow = 0; + int deltaReturn = 0; + + + if ( delta > 0 ) { + + int d = 0; + for ( int i = index + 1; d < delta && i < list.count(); ++i ) + d += shrink( list[i], delta - d ); + deltaShrink = d; + + d = 0; + for ( int i = index; d < delta && i >= 0; --i ) + d += grow( list[i], delta - d ); + deltaGrow = d; + + deltaReturn = d; + + } + else if ( delta < 0 ) { + + int d = 0; + for ( int i = index; d < -delta && i >= 0; --i ) + d += shrink( list[i], -delta - d ); + deltaShrink = d; + + d = 0; + for ( int i = index + 1; d < -delta && i < list.count(); ++i ) + d += grow( list[i], -delta - d ); + deltaGrow = d; + + deltaReturn = -d; + } + + // set the return values + smi.deltaContainerChangedReturn = calcDeltaContainerChanged( deltaShrink, deltaGrow, deltaSign ); + smi.containerShrinkedReturn = deltaShrink > deltaGrow; + // max delta that has been processed + int deltaUsed = deltaSign * qMax( deltaShrink, deltaGrow ); + smi.deltaNotMovedReturn = (delta - deltaUsed); + + + // adjust positions + bool first = true; + for ( int i = 0; i < list.size(); ++i ) { + QLayoutStruct &ls = list[i]; + if ( ls.empty ) { + ls.pos = pos + (first ? 0 : sep); + continue; + } + if ( !first ) + pos += sep; + ls.pos = pos; + pos += ls.size; + first = false; + } + + return deltaReturn; +} + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method works similar to the default layout implementation of +// separatorMoveHelper(). The difference is, that it just grows or shrinks +// one side of the layout items moved by the according separator. +// +// The extended resize behavior will work like this: +// +// A drag move separator will now just do a single sided resizing of one +// layout item and keep the size of the layout item on the other side of +// the separator. The space that it needs for growing or shrinking will be +// taken from the center docking area. +// +// A shift+drag move separator will do the common Qt two sided resizing +// where on both sides of the separator one item will grow and the other +// one shrink. When the dragging is done in direction of the center docking +// area and all items in that direction has been already shrunk to their +// minimum size, then dragging doesn't get stuck as used to be, instead it +// will continue and move the shrunken items into the center docking area. +//------------------------------------------------------------------------- +int separatorMoveHelperSingleSided( QVector &list, int index, int delta, int sep, + QInternal::DockPosition dockPos, SeparatorMoveInfo& smi ) +{ + if ( delta == 0 ) + { + return 0; + } + + bool isCenterSeparatorMove = smi.isCenterSeparatorMove; + bool doGrow = smi.doGrow; + + // adjust sizes + int pos = -1; + for ( int i = 0; i < list.size(); ++i ) + { + const QLayoutStruct &ls = list.at( i ); + if ( !ls.empty ) + { + pos = ls.pos; + break; + } + } + if ( pos == -1 ) + { + return 0; + } + + + if ( delta > 0 ) + { + int d = 0; + // shrink items after separator + if ( !doGrow ) + { + for ( int i = isCenterSeparatorMove ? index : index + 1; d < delta && i < list.count(); ++i ) + { + d += shrink( list[i], delta - d ); + } + } + // grow items before separator + else + { + int growlimit = 0; + int listEnd = (dockPos == QInternal::LeftDock || dockPos == QInternal::TopDock) ? index : list.count() - 1; + for ( int i = 0; i <= listEnd; ++i ) + { + const QLayoutStruct &ls = list.at( i ); + if ( ls.empty ) + { + continue; + } + + if ( ls.maximumSize == QLAYOUTSIZE_MAX ) + { + growlimit = QLAYOUTSIZE_MAX; + break; + } + growlimit += ls.maximumSize - ls.size; + } + if ( delta > growlimit ) + { + delta = growlimit; + } + + d = 0; + for ( int i = index; d < delta && i >= 0; --i ) + { + d += grow( list[i], delta - d ); + } + + } + + delta = d; + } + else if ( delta < 0 ) + { + int d = 0; + // shrink items before separator + if ( !doGrow ) + { + for ( int i = index; d < -delta && i >= 0; --i ) + { + d += shrink( list[i], -delta - d ); + } + } + // grow items after separator + else + { + int growlimit = 0; + int indexStart = (dockPos == QInternal::RightDock || dockPos == QInternal::BottomDock) ? (isCenterSeparatorMove ? index : index + 1) : 0; + for ( int i = indexStart; i < list.count(); ++i ) + { + const QLayoutStruct &ls = list.at( i ); + if ( ls.empty ) + { + continue; + } + if ( ls.maximumSize == QLAYOUTSIZE_MAX ) + { + growlimit = QLAYOUTSIZE_MAX; + break; + } + growlimit += ls.maximumSize - ls.size; + } + if ( -delta > growlimit ) + { + delta = -growlimit; + } + + d = 0; + for ( int i = isCenterSeparatorMove ? index : index + 1; d < -delta && i < list.count(); ++i ) + { + d += grow( list[i], -delta - d ); + } + } + + delta = -d; + } + + + // adjust positions + bool first = true; + for ( int i = 0; i < list.size(); ++i ) + { + QLayoutStruct &ls = list[i]; + if ( ls.empty ) + { + ls.pos = pos + (first ? 0 : sep); + continue; + } + if ( !first ) + { + pos += sep; + } + ls.pos = pos; + pos += ls.size; + first = false; + } + + return delta; +} + +} // end anonymous namespace + +int QDockAreaLayoutInfo::separatorMove(int index, int delta, SeparatorMoveInfo* smi, bool doFitSubInfoItems ) { #if QT_CONFIG(tabbar) Q_ASSERT(!tabbed); @@ -955,7 +1344,7 @@ int QDockAreaLayoutInfo::separatorMove(int index, int delta) if (item.skip()) { ls.empty = true; } else { - const int separatorSpace = item.hasFixedSize(o) ? 0 : *sep; + const int separatorSpace = hasLayoutItemFixedSize( mainWindow, item, o ) ? 0 : *sep; ls.empty = false; ls.pos = item.pos; ls.size = item.size + separatorSpace; @@ -966,19 +1355,40 @@ int QDockAreaLayoutInfo::separatorMove(int index, int delta) } //the separator space has been added to the size, so we pass 0 as a parameter - delta = separatorMoveHelper(list, index, delta, 0 /*separator*/); + if ( smi ) + { + if ( smi->doTwoSidedMove ) + { + delta = separatorMoveHelperTwoSided( list, index, delta, 0, *smi ); + } + else + { + delta = separatorMoveHelperSingleSided( list, index, delta, 0, dockPos, *smi ); + } + } + else + { + delta = separatorMoveHelper( list, index, delta, 0 /*separator*/ ); + } + // apply layout data for (int i = 0; i < list.size(); ++i) { QDockAreaLayoutItem &item = item_list[i]; if (item.skip()) continue; QLayoutStruct &ls = list[i]; - const int separatorSpace = item.hasFixedSize(o) ? 0 : *sep; + const int separatorSpace = hasLayoutItemFixedSize( mainWindow, item, o ) ? 0 : *sep; item.size = ls.size - separatorSpace; item.pos = ls.pos; if (item.subinfo != 0) { + // Check if separator index is before or behind the sub info. + item.subinfo->fitItemsExpandMode = (i >= (index + 1)) ? ExpandFirst : ExpandLast; + item.subinfo->rect = itemRect(i); - item.subinfo->fitItems(); + if (doFitSubInfoItems) + { + item.subinfo->fitItems(); + } } } @@ -1172,6 +1582,14 @@ bool QDockAreaLayoutInfo::insertGap(const QList &path, QLayoutItem *dockWid item.widgetItem = 0; item.placeHolderItem = 0; + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: Retain dock widget sizes + // Copy over the old rect to the new inserted subinfo so that size methods + // work properly on it. Otherwise e.g. calling get tabContentRect() + // afterwards returns an empty rect. + new_info->rect = r; + //------------------------------------------------------------------------- + QDockAreaLayoutItem new_item = widgetItem == 0 ? QDockAreaLayoutItem(subinfo) @@ -1423,7 +1841,7 @@ QList QDockAreaLayoutInfo::findSeparator(const QPoint &_pos) const if (!sepRect.isNull() && *sep == 1) sepRect.adjust(-2, -2, 2, 2); //we also make sure we don't find a separator that's not there - if (sepRect.contains(_pos) && !item.hasFixedSize(o)) { + if (sepRect.contains(_pos) && !hasLayoutItemFixedSize( mainWindow, item, o ) ) { return QList() << i; } @@ -1655,7 +2073,7 @@ void QDockAreaLayoutInfo::paintSeparators(QPainter *p, QWidget *widget, if (next == -1) break; QRect r = separatorRect(i); - if (clip.contains(r) && !item.hasFixedSize(o)) + if (clip.contains(r) && !hasLayoutItemFixedSize( mainWindow, item, o ) ) paintSep(p, widget, r, o, r.contains(mouse)); } } @@ -2915,8 +3333,135 @@ void QDockAreaLayout::setGrid(QVector *ver_struct_list, } } + +namespace +{ + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Retain dock widget sizes +// Helper function that updates the rectangles of the given +// QDockAreaLayoutInfo structure to the actual content sizes. +// In that way already undocked/hidden or newly docked/shown widgets are +// respected by the subinfo's rectangle and when fitLayout() is +// called afterwards, it will maintain the dock frame sizes and will not +// distribute the freed space to all other widgets in the container, +// which would imply a size changes of all contained dock widgets. +//------------------------------------------------------------------------- +QSize updateDockAreaSubInfoRects( QDockAreaLayoutInfo* info, int sep ) +{ + if ( !info ) + { + return QSize( 0, 0 ); + } + + QSize infoSize; + if ( info->tabbed ) + { + infoSize = info->tabContentRect().size(); + if ( !infoSize.isEmpty() ) + { + QSize minSize = info->minimumSize(); + QSize maxSize = info->maximumSize(); + infoSize = infoSize.boundedTo( maxSize ).expandedTo( minSize ); + } + } + else + { + Qt::Orientation o = info->o; + const QDockAreaLayoutItem* previous = nullptr; + int a = 0, b = 0; + + for ( int i = 0; i < info->item_list.count(); ++i ) + { + QDockAreaLayoutItem& item = info->item_list[i]; + if ( item.skip() ) + { + continue; + } + + bool gap = item.flags & QDockAreaLayoutItem::GapItem; + + if ( previous && !gap && !(previous->flags & QDockAreaLayoutItem::GapItem) ) + { + a += sep; + } + + if ( gap ) + { + a += item.size; + } + else if ( item.widgetItem != nullptr ) + { + int s = item.size; + if ( s == -1 ) // not valid yet, pick the sizeHint + { + s = pick( o, item.widgetItem->sizeHint() ); + } + + a += s; + } + else if ( item.subinfo != nullptr ) + { + QSize s = updateDockAreaSubInfoRects( item.subinfo, sep ); + + if ( pick( o, s ) <= 0 ) // still zero no valid rect + { + // stick to old item size for this subinfo + rpick( o, s ) = item.size; + item.subinfo->rect.setSize( s ); + } + else + item.size = pick( o, s ); + + a += pick( o, s ); + b = qMax( b, perp( o, s ) ); + } + + previous = &item; + } + + rpick( o, infoSize ) = a; + rperp( o, infoSize ) = b; + } + + // If one of the directions is not valid we stick to the old rectangle extends. + if ( infoSize.width() <= 0 ) + infoSize.setWidth( info->rect.size().width() ); + + if ( infoSize.height() <= 0 ) + infoSize.setHeight( info->rect.size().height() ); + + // Update subinfo rectangle with the new calculated size. + info->rect.setSize( infoSize ); + + return infoSize; +} + +} // end anonymous namespace + + void QDockAreaLayout::fitLayout() { + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: Retain dock widget sizes + // We try to retain the dock frame sizes when widgets are docked/undocked + // and fitLayout() is called afterwards. + // This is done by re-calculating the QDockAreaLayoutInfo subinfo rectangles + // on the actual content sizes. In that way when a dock widget e.g. gets + // undocked or hidden the subinfo rectangles won't include its size anymore. + // With the default Qt behavior the rectangles will still stay the same + // and fitLayout() will distribute the freed space to all other widgets + // in the container which means that they'll change their size, which is + // not intended for 3dsmax. + //------------------------------------------------------------------------- + if ( retainDockWidgetSizes( mainWindow ) ) + { + updateDockAreaSubInfoRects( &docks[QInternal::LeftDock], sep ); + updateDockAreaSubInfoRects( &docks[QInternal::RightDock], sep ); + updateDockAreaSubInfoRects( &docks[QInternal::TopDock], sep ); + updateDockAreaSubInfoRects( &docks[QInternal::BottomDock], sep ); + } + QVector ver_struct_list(3); QVector hor_struct_list(3); getGrid(&ver_struct_list, &hor_struct_list); @@ -3102,18 +3647,51 @@ bool QDockAreaLayout::restoreDockWidget(QDockWidget *dockWidget) return true; } +//------------------------------------------------------------------------- +// Autodesk 3ds Max Change: Adds an additional toFront parameter, that +// makes it possible to add a dock widget to the front of the dock area +// container so that the widget can appear close to the main windows center area. +//------------------------------------------------------------------------- void QDockAreaLayout::addDockWidget(QInternal::DockPosition pos, QDockWidget *dockWidget, - Qt::Orientation orientation) + Qt::Orientation orientation, bool toFront) { QLayoutItem *dockWidgetItem = new QDockWidgetItem(dockWidget); QDockAreaLayoutInfo &info = docks[pos]; if (orientation == info.o || info.item_list.count() <= 1) { + + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: Retain dock widget sizes + // Correct the pos and the size when we've just one layout item in the + // container. + // This is necessary since the orientation might be swapped by the code + // below this fix and in that case the old values won't be correct anymore, + // since pos/size were meant for the opposite orientation. + if ( orientation != info.o && info.item_list.count() == 1 ) + { + // do it like in insertGab() + QDockAreaLayoutItem &item = info.item_list[0]; + QDockAreaLayoutInfo *subinfo = item.subinfo; + QLayoutItem *widgetItem = item.widgetItem; + QPlaceHolderItem *placeHolderItem = item.placeHolderItem; + QRect r = subinfo == 0 ? widgetItem ? dockedGeometry( widgetItem->widget() ) : placeHolderItem->topLevelRect : subinfo->rect; + + item.size = pick( orientation, r.size() ); + item.pos = pick( orientation, r.topLeft() ); + } + //------------------------------------------------------------------------- + // empty dock areas, or dock areas containing exactly one widget can have their orientation // switched. info.o = orientation; QDockAreaLayoutItem new_item(dockWidgetItem); - info.item_list.append(new_item); + + // Autodesk 3ds Max 'toFront' change, please see comment above. + if ( toFront ) + info.item_list.insert( 0, new_item ); + else + info.item_list.append( new_item ); + #if QT_CONFIG(tabbar) if (info.tabbed && !new_item.skip()) { info.updateTabBar(); @@ -3128,7 +3706,13 @@ void QDockAreaLayout::addDockWidget(QInternal::DockPosition pos, QDockWidget *do #endif QDockAreaLayoutInfo new_info(&sep, pos, orientation, tbshape, mainWindow); new_info.item_list.append(QDockAreaLayoutItem(new QDockAreaLayoutInfo(info))); - new_info.item_list.append(QDockAreaLayoutItem(dockWidgetItem)); + + // Autodesk 3ds Max 'toFront' change, please see comment above. + if ( toFront ) + new_info.item_list.insert( 0, QDockAreaLayoutItem( dockWidgetItem ) ); + else + new_info.item_list.append( QDockAreaLayoutItem( dockWidgetItem ) ); + info = new_info; } @@ -3262,12 +3846,968 @@ QRegion QDockAreaLayout::separatorRegion() const return result; } -int QDockAreaLayout::separatorMove(const QList &separator, const QPoint &origin, - const QPoint &dest) + +namespace { - int delta = 0; - int index = separator.last(); +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// Used when a center separator on the main dock grid is resized, for +// determining if there is a proper inner nested separator position close +// to the center area which be used for growing or shrinking the layout items. +//------------------------------------------------------------------------- +QList findClosestInnerSeparator( QDockAreaLayoutInfo* info, Qt::Orientation o ) +{ + if ( !info || info->item_list.isEmpty() || info->tabbed ) + { + return QList(); + } + + bool doReverse = (info->dockPos == QInternal::LeftDock || info->dockPos == QInternal::TopDock); + + for ( int i = doReverse ? (info->item_list.size() - 1) : 0; + i >= 0 && i < info->item_list.size(); doReverse ? --i : ++i ) + { + QDockAreaLayoutItem& item = info->item_list[i]; + if ( item.skip() ) + { + continue; + } + + if ( item.widgetItem && info->o == o ) + { + return QList( { i } ); + } + else if ( item.subinfo && !info->tabbed ) + { + QList result = findClosestInnerSeparator( item.subinfo, o ); + if ( !result.isEmpty() ) + { + result.prepend( i ); + return result; + } + else + { + if ( info->o == o ) + { + result.prepend( i ); + return result; + } + } + } + } + + return QList(); +} + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method does a nested single sided separator move, where the layout +// items are just either growing or shrinking. +// It traverses down the separator path and starts growing / shrinking +// from the inside to the outside. So first the actual resized item grows +// or shrinks and then the moved delta is applied recursively on the parent. +// Before traversing down, the move delta is clipped to the min / max of +// what would be possible for the current container, in that way the inner +// most separator move starts with a valid delta, which won't violate any +// size constraints of outer container. +// This method is also use by the shift move separator algorithm in +// separatorShiftMoveRecursive(), where it grows or shrinks layout items +// when a sliding separator has reached the current container extends and +// promotes the rest of it unused moving delta further to the next container. +//------------------------------------------------------------------------- +int separatorMoveRecursive( QDockAreaLayoutInfo* info, Qt::Orientation dockAreaOrientation, + const QList &path, int delta, SeparatorMoveInfo& smi, int& deltaNotMovedReturn, + bool doFitSubInfoItems, bool shiftPressed = false ) +{ + if ( delta == 0 || !info || path.isEmpty() ) + return 0; + +#ifndef QT_NO_TABBAR + Q_ASSERT( !info->tabbed ); +#endif + + int returnDelta = 0; + int index = path.first(); + if ( index >= 0 && index < info->item_list.count() ) + { + int deltaMinMaxLoss = 0; + QDockAreaLayoutItem& li = info->item_list[ index ]; + + // clip the move delta to the min / max of what is actually possible for this container. + if ( !shiftPressed ) // in shift mode we don't need to pre clip the delta the the outer layout info has been already resized and the incoming delta is in bounds + { + deltaMinMaxLoss = delta; + // growing check + if ( ((delta > 0) && (info->dockPos == QInternal::LeftDock || info->dockPos == QInternal::TopDock)) || + ((delta < 0) && (info->dockPos == QInternal::RightDock || info->dockPos == QInternal::BottomDock)) ) + { + int maximumSize = pick( dockAreaOrientation, info->maximumSize() ); + int growlimit = maximumSize - pick( dockAreaOrientation, info->size() ); + if ( (delta > 0) && (delta > growlimit) ) + delta = growlimit; + else if ( (delta < 0) && (-delta > growlimit) ) + delta = -growlimit; + } + + // shrinking check + if ( ((delta < 0) && (info->dockPos == QInternal::LeftDock || info->dockPos == QInternal::TopDock)) || + ((delta > 0) && (info->dockPos == QInternal::RightDock || info->dockPos == QInternal::BottomDock)) ) + { + int minimumSize = pick( dockAreaOrientation, info->minimumSize() ); + int shrinklimit = pick( dockAreaOrientation, info->size() ) - minimumSize; + if ( (delta > 0) && (delta > shrinklimit) ) + delta = shrinklimit; + else if ( (delta < 0) && (-delta > shrinklimit) ) + delta = -shrinklimit; + } + + deltaMinMaxLoss -= delta; + } + + + // Traverse down the path first to the inner item that was resized by the separator. + int deltaNotMovedNestedChild = 0; + if ( path.count() > 1 && li.subinfo != nullptr ) + { + returnDelta = separatorMoveRecursive( li.subinfo, dockAreaOrientation, path.mid( 1 ), delta, smi, deltaNotMovedNestedChild, doFitSubInfoItems, shiftPressed ); + } + + // add the delta that we lost due to the min/max constraint to the child containers + // hang over, so that we can apply it later after our container was resized to the + // container in front of us. + deltaNotMovedNestedChild += deltaMinMaxLoss; + + if ( info->o == dockAreaOrientation ) + { + int recDelta = (path.count() == 1) ? delta // apply full delta on the actual resized inner item + : returnDelta; // apply whats left over from recursion + + if ( recDelta != 0 ) + { + bool isCenterSeparatorMoveOld = smi.isCenterSeparatorMove; + + if ( path.count() > 1 ) + { + smi.isCenterSeparatorMove = true; + } + + if ( !shiftPressed ) + { + smi.doGrow = ( ((recDelta > 0) && (info->dockPos == QInternal::LeftDock || info->dockPos == QInternal::TopDock)) || + ((recDelta < 0) && (info->dockPos == QInternal::RightDock || info->dockPos == QInternal::BottomDock)) ); + } + + int deltaMoved = info->separatorMove( index, recDelta, &smi, false ); + deltaNotMovedReturn = recDelta - deltaMoved; + returnDelta = deltaMoved; + + smi.isCenterSeparatorMove = isCenterSeparatorMoveOld; + } + + + // If there are delta move left overs from recursion that couldn't be applied to the + // layout items moved by the separator, we try to resize the items in front / behind + // of the indexed item in this container. + if ( deltaNotMovedNestedChild != 0 ) + { + if ( info->dockPos == QInternal::LeftDock || info->dockPos == QInternal::TopDock ) + { + index = info->prev( index ); + } + + if ( index >= 0 && index < info->item_list.count() ) + { + SeparatorMoveInfo smi; + smi.doGrow = ( ((deltaNotMovedNestedChild > 0) && (info->dockPos == QInternal::LeftDock || info->dockPos == QInternal::TopDock)) || + ((deltaNotMovedNestedChild < 0) && (info->dockPos == QInternal::RightDock || info->dockPos == QInternal::BottomDock)) ); + + int deltaMoved = info->separatorMove( index, deltaNotMovedNestedChild, &smi, false ); + deltaNotMovedReturn += (deltaNotMovedNestedChild - deltaMoved); + returnDelta += deltaMoved; + } + else + { + deltaNotMovedReturn = deltaNotMovedNestedChild; // pass up to parent + } + } + } + else // skip container with opposite orientation + { + deltaNotMovedReturn = deltaNotMovedNestedChild; // pass up to parent + } + } + + return returnDelta; +} + + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method returns the limit to what the layout items ahead the +// specified index can be shrunk. The definition of 'ahead' depends on +// the resize direction. So e.g. a resize to the right returns the shrink +// limit of the items on the right side of the specified indexed item. +// This method is used for determining the limits on the main grid. +//------------------------------------------------------------------------- +int calcShrinkLimitAhead( QDockAreaLayout* layout, int firstIndex, int delta ) +{ + if ( !layout ) + { + return 0; + } + + // get the main grids layout structs + QVector list; + if ( firstIndex == QInternal::LeftDock || firstIndex == QInternal::RightDock ) + layout->getGrid( 0, &list ); + else + layout->getGrid( &list, 0 ); + + + int shrinkLimit = 0; + int index = 1; // start in the middle of the 3 areas + + if ( delta > 0 ) + { + for ( int i = index; i < list.count(); ++i ) + { + if ( list[i].empty ) + { + continue; + } + shrinkLimit += (list[i].size - list[i].minimumSize); + } + } + else if ( delta < 0 ) + { + for ( int i = index; i >= 0; --i ) + { + if ( list[i].empty ) + { + continue; + } + + shrinkLimit += (list[i].size - list[i].minimumSize); + } + } + + return shrinkLimit; +} + + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method returns the limit to what the layout items ahead the +// specified index can be shrunk. The definition of 'ahead' depends on +// the resize direction. So e.g. a resize to the right returns the shrink +// limit of the items on the right side of the specified indexed item. +//------------------------------------------------------------------------- +int calcShrinkLimitAhead( QDockAreaLayoutInfo* info, Qt::Orientation dockAreaOrientation, int index, int delta, bool includeSeparatorIndex = false ) +{ + if ( !info ) + { + return 0; + } + + int shrinkLimit = 0; + if ( delta > 0 ) + { + for ( int i = index + 1; i < info->item_list.count(); ++i ) + { + if ( info->item_list[i].skip() ) + { + continue; + } + int min = pick( dockAreaOrientation, info->item_list[i].minimumSize() ); + shrinkLimit += (info->item_list[i].size - min); + } + } + else if ( delta < 0 ) + { + for ( int i = index; i >= 0; --i ) + { + if ( info->item_list[i].skip() || + (!includeSeparatorIndex && i == index && info->item_list[i].subinfo) ) + { + continue; + } + + int min = pick( dockAreaOrientation, info->item_list[i].minimumSize() ); + shrinkLimit += (info->item_list[i].size - min); + } + } + + return shrinkLimit; +} + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method returns the limit to what extent the layout items behind the +// specified index can grow. The definition of 'behind' depends on +// the resize direction. So e.g. a resize to the right returns the grow +// limit of the items on the left side of the specified indexed item. +//------------------------------------------------------------------------- +int calcGrowLimitBehind( QDockAreaLayoutInfo* info, Qt::Orientation dockAreaOrientation, int index, int delta, bool includeSeparatorIndex = false ) +{ + if ( !info ) + { + return 0; + } + + int growLimit = 0; + if ( delta > 0 ) + { + for ( int i = index; i >= 0; --i ) + { + if ( info->item_list[i].skip() || + (!includeSeparatorIndex && i == index && info->item_list[i].subinfo) ) + { + continue; + } + + int max = pick( dockAreaOrientation, info->item_list[i].maximumSize() ); + growLimit += (max - info->item_list[i].size); + } + } + else if ( delta < 0 ) + { + for ( int i = index + 1; i < info->item_list.count(); ++i ) + { + if ( info->item_list[i].skip() ) + { + continue; + } + int max = pick( dockAreaOrientation, info->item_list[i].maximumSize() ); + growLimit += (max - info->item_list[i].size); + } + } + + return growLimit; +} + + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method is helper that gets called by separatorShiftMoveRecursive(). +// It is used for the resizing of the nested dock layout container, when +// separatorShiftMoveRecursive() steps out of recursion. The resize logic +// is applied to every level of the separator index path, except the +// the innermost one where the logic of doInnermostContainerResize() is used. +// When separatorShiftMoveRecursive() steps out of recursion, this method +// first resizes the container at the current index path, depending if the +// innermost resize has changed its container size, and then layout items +// ahead & behind the indexed container depending on what's left over from +// the last recursion step plus the amount of the container change. +// The method also does a pre-calculation step for determining if a +// resulting dock layout container change would violate any size +// constraints and adapts the applied delta. +//------------------------------------------------------------------------- +void doNestedContainerResize( QDockAreaLayoutInfo* info, int index, int recDelta, + int deltaNotMovedNestedChild, + bool childContainerShrinked, + Qt::Orientation dockAreaOrientation, + int shrinkLimitParent, + int growLimitParent, + bool& containerShrinkedReturn, + int& deltaContainerChangedReturn, + int& deltaNotMovedReturn ) +{ + if ( !info ) + { + return; + } + + // determine resize direction + int dir = (recDelta < 0) ? -1 : 1; + if ( recDelta == 0 ) + { + dir = (deltaNotMovedNestedChild < 0) ? -1 : 1; + } + + // First we do a single sided grow/shrink on the actual index of the current path step. + SeparatorMoveInfo smi; + smi.isCenterSeparatorMove = true; // don't do the index+1 thing in separatorMove() + smi.doGrow = !childContainerShrinked; + + // We don't need to do a constraint check for the index where we step out of recursion, + // cause the inner resize has already checked the constraints. + int deltaMoved = info->separatorMove( index, recDelta, &smi, false ); + int deltaMovedAbs = qAbs( deltaMoved ); + + // Do a pre-calculation step if we hit any parent size constraints + // when we would apply whats left over from the inner resizing. + deltaNotMovedReturn = deltaNotMovedNestedChild; + { + int d = deltaNotMovedNestedChild; + int ds = d; + int dg = d; + + // Add the delta that the indexed container shrunk or grew to the move delta + // of layout items ahead or behind it. + if ( childContainerShrinked ) + { + dg += deltaMoved; + } + else + { + ds += deltaMoved; + } + + // Move what is locally possible + // What are the size limits of the items around the indexed container item? + int shrinkLimit = calcShrinkLimitAhead( info, dockAreaOrientation, index, dir ); + int growLimit = calcGrowLimitBehind( info, dockAreaOrientation, index, dir ); + // Do shrink and grow of what's locally allowed + int dShrink = qMin( shrinkLimit, (dir > 0) ? ds : -ds ); + int dGrow = qMin( growLimit, (dir > 0) ? dg : -dg ); + + // Check the local possible deltas against the outer size constraints + if ( dShrink > dGrow ) // shrink, clip against min + { + int shrunk = dShrink - dGrow; + // add what has already grown or shrunk in the center + if ( childContainerShrinked ) + { + shrunk += deltaMovedAbs; + } + else + { + shrunk -= deltaMovedAbs; + } + + if ( shrunk > shrinkLimitParent ) + { + // limit the delta to was is actually used / can be processed by the separatorMove + d = qMax( dShrink, dGrow ); + d -= (shrunk - shrinkLimitParent); + + if ( !childContainerShrinked ) + { + d -= deltaMovedAbs; + } + + if ( d < 0 ) + { + d = 0; + } + + d *= dir; + } + } + else if ( dShrink < dGrow )// grow clip against max + { + int grown = dGrow - dShrink; + // add what has already grown or shrunk in the center + if ( childContainerShrinked ) + { + grown -= deltaMovedAbs; + } + else + { + grown += deltaMovedAbs; + } + + if ( grown > growLimitParent ) + { + // limit the delta to was is actually used / can be processed by the separatorMove + d = qMax( dShrink, dGrow ); + d -= (grown - growLimitParent); + + if ( childContainerShrinked ) + { + d -= deltaMovedAbs; + } + + if ( d < 0 ) + { + d = 0; + } + + d *= dir; + } + } + else if ( dShrink == 0 && dGrow == 0 ) + { + d = 0; + } + + deltaNotMovedNestedChild = d; + } + deltaNotMovedReturn -= deltaNotMovedNestedChild; + // end of pre-calculation step + + int dShrink = 0; + int dGrow = 0; + + int idxShrink = (dir < 0) ? info->prev( index ) : info->next( index ); + if ( idxShrink >= 0 && idxShrink < info->item_list.count() ) + { + int s = deltaNotMovedNestedChild; + if ( !childContainerShrinked ) + { + s += deltaMoved; + } + + smi.doGrow = false; + dShrink = qAbs( info->separatorMove( idxShrink, s, &smi, false ) ); + } + + int idxGrow = (dir < 0) ? info->next( index ) : info->prev( index ); + if ( idxGrow >= 0 && idxGrow < info->item_list.count() ) + { + int g = deltaNotMovedNestedChild; + if ( childContainerShrinked ) + { + g += deltaMoved; + } + + smi.doGrow = true; + dGrow = qAbs( info->separatorMove( idxGrow, g, &smi, false ) ); + } + + if ( childContainerShrinked ) + { + dShrink += deltaMovedAbs; + } + else + { + dGrow += deltaMovedAbs; + } + + // determine return values + containerShrinkedReturn = (dShrink > dGrow); + deltaContainerChangedReturn = calcDeltaContainerChanged( dShrink, dGrow, dir ); + + // max delta that has been processed + int deltaUsed = dir * qMax( dShrink, dGrow ); + deltaNotMovedReturn += (recDelta + deltaNotMovedNestedChild) - deltaUsed; +} + + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method is helper that gets called by separatorShiftMoveRecursive(). +// It is used for the resizing of the layout items of the innermost dock +// layout container, at the end of the separator index path. +// The method resized the layout items similar to the default layout +// implementation of separatorMoveHelper(), with the difference that it +// grows and shrinks the layout items on both sides without taking in +// account the size limits of the opposite side. +// The method also does a pre-calculation step for determining if a +// resulting dock layout container change would violate any size +// constraints and adapts the applied delta. +//------------------------------------------------------------------------- +void doInnermostContainerResize( QDockAreaLayoutInfo* info, int index, + int delta, + Qt::Orientation dockAreaOrientation, + int shrinkLimitParent, + int growLimitParent, + bool& containerShrinkedReturn, + int& deltaContainerChangedReturn, + int& deltaNotMovedReturn ) +{ + if ( !info ) + { + return; + } + + // Determine resize direction. + int dir = (delta < 0) ? -1 : 1; + + // Do a layout pre-calculation step. + deltaNotMovedReturn = delta; + { + // Move what is locally possible + int gl = calcGrowLimitBehind( info, info->o, index, dir, true ); + int sl = calcShrinkLimitAhead( info, info->o, index, dir, true ); + int deltaShrink = qMin( sl, (dir > 0) ? delta : -delta ); + int deltaGrow = qMin( gl, (dir > 0) ? delta : -delta ); + + if ( deltaShrink > deltaGrow ) // shrink, clip against min + { + int shrunk = deltaShrink - deltaGrow; + if ( shrunk > shrinkLimitParent ) + { + // Limit the delta to was is actually used / can be processed by the separatorMove + delta = qMax( deltaShrink, deltaGrow ); + delta -= (shrunk - shrinkLimitParent); + if ( delta < 0 ) + { + delta = 0; + } + delta *= dir; + } + } + else if ( deltaShrink < deltaGrow )// grow clip against max + { + int grown = deltaGrow - deltaShrink; + if ( grown > growLimitParent ) + { + // Limit the delta to was is actually used / can be processed by the separatorMove + delta = qMax( deltaShrink, deltaGrow ); + delta -= (grown - growLimitParent); + if ( delta < 0 ) + { + delta = 0; + } + delta *= dir; + } + } + } + deltaNotMovedReturn -= delta; + // End of pre-calculation step + + // Do the actual separator move. + SeparatorMoveInfo smi; + smi.doTwoSidedMove = true; + int deltaMoved = info->separatorMove( index, delta, &smi, false ); + + if ( smi.deltaContainerChangedReturn != 0 ) // that's what the container has shrunk/grown + { + containerShrinkedReturn = smi.containerShrinkedReturn; + deltaContainerChangedReturn = smi.deltaContainerChangedReturn; + deltaNotMovedReturn += smi.deltaNotMovedReturn; + } + else + { + // container size hasn't been changed + deltaContainerChangedReturn = 0; + deltaNotMovedReturn += delta - deltaMoved; + } + +} + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// This method does a nested two sided separator move, where the layout +// items are growing on one side and at the same time shrinking on the +// other side. +// It is used when the users shift drags a separator. +// It traverses down the separator path and starts the separator sliding +// from the inside to the outside. +// For the layout item resizing it uses two methods: +// doInnermostContainerResize() which applies to the layout items of the +// innermost dock layout container, at the end of the separator index path, +// and doNestedContainerResize() which is used for the layout container +// inbetween when separatorShiftMoveRecursive() steps out of recursion. +// +// The base logic is that inner layout items can freely grow/shrink +// according to the incoming move delta and without violating any size +// constraints. When the layout container has changed its size due to +// an unequal grow and shrink, which might be caused by hitting the size +// limits, this container change delta and the unused move delta is +// promoted up to the next parent container. +// The parent container first resizes the container at the current index +// path according to the child's change, and then layout items ahead and +// behind the indexed container depending on what's left over from the last +// recursion step plus the amount of the container change. +// This layout item adaption is applied every recursion step backwards. +// When finally there is a container change delta left over, this is +// applied to the main dock grid. +//------------------------------------------------------------------------- +int separatorShiftMoveRecursive( QDockAreaLayoutInfo* info, + Qt::Orientation dockAreaOrientation, + const QList& path, + int delta, + int shrinkLimitParent, + int growLimitParent, + int& deltaNotMovedReturn, + bool& containerShrinkedReturn ) +{ + if ( delta == 0 || !info || path.isEmpty() ) + { + return 0; + } + +#ifndef QT_NO_TABBAR + Q_ASSERT( !info->tabbed ); +#endif + + int returnDelta = 0; + int index = path.first(); + if ( index >= 0 && index < info->item_list.count() ) + { + QDockAreaLayoutItem& li = info->item_list[index]; + + // Traverse down the path first to the inner item that was resized by the separator. + int deltaNotMovedNestedChild = 0; + bool childContainerShrinked = false; + if ( path.count() > 1 && li.subinfo != nullptr ) + { + // Calculate the limits that the nested container, we step into next, + // is allowed to shrink/grow. + + // Limits of the current container + int size = pick( dockAreaOrientation, info->size() ); + int minimumSize = pick( dockAreaOrientation, info->minimumSize() ); + int shrinklim = size - minimumSize; + + int maximumSize = pick( dockAreaOrientation, info->maximumSize() ); + int growlim = maximumSize - size; + + // Look what space we have available ahead/behind of the container we step into. + int shrinkLimitAhead = 0; + int growLimitBehind = 0; + + if ( info->o == dockAreaOrientation ) + { + shrinkLimitAhead = calcShrinkLimitAhead( info, dockAreaOrientation, index, delta ); + growLimitBehind = calcGrowLimitBehind( info, dockAreaOrientation, index, delta ); + } + + // Clip the nested shrinking/growing against what's possible in the parent container. + int shrinkLimitChild = qMin( growLimitBehind + shrinkLimitParent, shrinklim ); + int growLimitChild = qMin( shrinkLimitAhead + growLimitParent, growlim ); + + // Step further down into the nested container. + returnDelta = separatorShiftMoveRecursive( li.subinfo, dockAreaOrientation, path.mid( 1 ), delta, + shrinkLimitChild, growLimitChild, + deltaNotMovedNestedChild, + childContainerShrinked ); + } + + // From here we step out of recursion again + + if ( info->o == dockAreaOrientation ) + { + int recDelta = (path.count() == 1) ? delta // apply full delta on the actual resized inner item + : returnDelta; // apply the delta of the container change when we step out of recursion + + if ( recDelta != 0 || deltaNotMovedNestedChild != 0 ) + { + int deltaContainerChanged = 0; + + if ( path.count() == 1 ) + { + doInnermostContainerResize( info, index, recDelta, dockAreaOrientation, + shrinkLimitParent, growLimitParent, + containerShrinkedReturn, deltaContainerChanged, deltaNotMovedReturn ); + } + else + { + doNestedContainerResize( info, index, recDelta, deltaNotMovedNestedChild, + childContainerShrinked, dockAreaOrientation, + shrinkLimitParent, growLimitParent, + containerShrinkedReturn, deltaContainerChanged, deltaNotMovedReturn ); + } + + return deltaContainerChanged; + } + } + else // skip container with opposite orientation + { + deltaNotMovedReturn = deltaNotMovedNestedChild; + containerShrinkedReturn = childContainerShrinked; + } + } + + return returnDelta; +} + + +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// +// A drag move separator will just do a single sided resizing of one +// layout item and keep the size of the layout item on the other side of +// the separator. The space that it needs for growing or shrinking will be +// taken from the center docking area. +// +// A shift+drag move separator will do the common Qt two sided resizing +// where on both sides of the separator one item will grow and the other +// one shrink. When the dragging is done in direction of the center docking +// area and all items in that direction has been already shrunk to their +// minimum size, then dragging doesn't get stuck as used to be, instead it +// will continue and move the shrunken items into the center docking area. +//------------------------------------------------------------------------- +int separatorMoveExt( QDockAreaLayout* layout, Qt::Orientation dockAreaOrientation, QList path, int delta ) +{ + if ( !layout || path.isEmpty() ) + { + return 0; + } + + int firstIndex = path.first(); + + if ( firstIndex < 0 || firstIndex >= 4 ) // index in range of the 4 dock area sides + return 0; + + bool shiftPressed = qt_mainwindow_layout( layout->mainWindow )->shiftMoveSeparator; + + SeparatorMoveInfo smi; + + int centerSeparatorMoveDelta = 0; + bool isCenterSeparatorMove = (path.count() == 1); + bool skipNestedSeparatorMove = false; + + if ( path.count() == 1 ) // resize on a center separator + { + // Find first separator in the dock container with the same orientation as the master dock area. + QList< int > result = findClosestInnerSeparator( &layout->docks[firstIndex], dockAreaOrientation ); + if ( !result.isEmpty() ) + { + result.prepend( firstIndex ); + smi.isCenterSeparatorMove = true; + path = result; + } + // No inner sep in the same orientation found, that means there is no sub info + // with the same orientation as the master dock area. + // In that case just use standard behavior on the center separator. + else + { + // skip to dock root, no need for nested calculation + skipNestedSeparatorMove = true; + centerSeparatorMoveDelta = delta; + } + } + + if ( !skipNestedSeparatorMove ) + { + // Do a shift move which resizes both sides of the separator (shrink & grow). + if ( shiftPressed ) + { + // Calculate the size limits for the first container we step into. + int shrinklimit = QLAYOUTSIZE_MAX; + int growlimit = QLAYOUTSIZE_MAX; + + if ( ((delta > 0) && (firstIndex == QInternal::LeftDock || firstIndex == QInternal::TopDock)) || + ((delta < 0) && (firstIndex == QInternal::RightDock || firstIndex == QInternal::BottomDock)) ) + { + shrinklimit = 0; // nested container not allowed to get smaller for the given direction + growlimit = calcShrinkLimitAhead( layout, firstIndex, delta ); + } + else + { + shrinklimit = calcShrinkLimitAhead( layout, firstIndex, delta ); + growlimit = 0; // nested container not allowed to grow for the given direction + } + + int deltaNotMoved = 0; bool containerShrinked = false; // not from interest at this level we only care about deltaContainerChanged + centerSeparatorMoveDelta = separatorShiftMoveRecursive( &layout->docks[firstIndex], dockAreaOrientation, path.mid( 1 ), delta, + shrinklimit, growlimit, deltaNotMoved, containerShrinked ); + + } + // Do a single sided separator move (shrink or grow). + else + { + QVector list; + if ( firstIndex == QInternal::LeftDock || firstIndex == QInternal::RightDock ) + layout->getGrid( 0, &list ); + else + layout->getGrid( &list, 0 ); + + int sep_index = firstIndex == QInternal::LeftDock || firstIndex == QInternal::TopDock + ? 0 : 1; + + // Lets see first what delta is actually possible on the main grid. + int deltaPossible = separatorMoveHelper( list, sep_index, delta, layout->sep ); + int deltaSqueeze = 0; + if ( delta != deltaPossible ) + { + // Squeeze only if we don't resize the center area separator, + // cause we need some widgets in front of us that we can actually squeeze. + if ( !isCenterSeparatorMove ) + { + deltaSqueeze = delta - deltaPossible; + } + delta = deltaPossible; + } + + // Do the single sided separator move. + int deltaNotMoved = 0; + centerSeparatorMoveDelta = separatorMoveRecursive( &layout->docks[firstIndex], dockAreaOrientation, path.mid( 1 ), delta, smi, deltaNotMoved, false ); + + // Try to squeeze the rest in front of the separator together, this equals to a shift move separator. + if ( deltaSqueeze != 0 ) + { + bool containerShrinked = false; // not from interest at this level we only care about deltaContainerChanged + // shrink / grow limits are zero since we are already at the min border for this resize + separatorShiftMoveRecursive( &layout->docks[firstIndex], dockAreaOrientation, path.mid( 1 ), deltaSqueeze, + 0, 0, deltaNotMoved, containerShrinked ); + } + } + } + + // Resize the docking main grid with the delta that is left over from the internal separator move operations. + if ( centerSeparatorMoveDelta != 0 ) + { + QVector list; + + if ( firstIndex == QInternal::LeftDock || firstIndex == QInternal::RightDock ) + layout->getGrid( 0, &list ); + else + layout->getGrid( &list, 0 ); + + int sep_index = firstIndex == QInternal::LeftDock || firstIndex == QInternal::TopDock + ? 0 : 1; + + // Calculate layout on the main grids layout data. + separatorMoveHelper( list, sep_index, centerSeparatorMoveDelta, layout->sep ); + + if ( firstIndex == QInternal::LeftDock || firstIndex == QInternal::RightDock ) + layout->setGrid( 0, &list ); + else + layout->setGrid( &list, 0 ); + } + // Just do a fit items on the affected docking area. + else + { + // Does a repositioning and fit on all nested layout sub infos and items + // according to the sizes we calculated before. + layout->docks[firstIndex].fitItems(); + } + + // master apply, applies also down to the nested sub infos + layout->apply( false ); + + return delta; +} + +} // end anonymous namespace + +int QDockAreaLayout::separatorMove(const QList &separator, const QPoint &origin, + const QPoint &dest) +{ + int delta = 0; + int index = separator.last(); + + // extended 3dsmax dock widget resize behavior + if ( doExtendedDockWidgetResize( mainWindow ) ) + { + QDockAreaLayoutInfo* info = this->info( separator ); + if ( info ) + { + // determine general orientation of the docking area + int firstIndex = separator.first(); + Qt::Orientation dockAreaOrientation = firstIndex == QInternal::LeftDock || firstIndex == QInternal::RightDock + ? Qt::Horizontal + : Qt::Vertical; + + if ( separator.count() == 1 ) // resize on a center separator + delta = pick( dockAreaOrientation, dest - origin ); + else // resize on a dock area internal separator + delta = pick( info->o, dest - origin ); + + if ( delta != 0 ) + { + if ( info->o == dockAreaOrientation || separator.count() == 1 ) + { + delta = separatorMoveExt( this, dockAreaOrientation, separator, delta ); + } + else // not in the direction of the docking area so no extended behavior + { + delta = info->separatorMove( index, delta ); + info->apply( false ); + } + } + } + + return delta; + } + + + // default dock widget resize behavior if (separator.count() > 1) { QDockAreaLayoutInfo *info = this->info(separator); delta = pick(info->o, dest - origin); diff --git a/src/widgets/widgets/qdockarealayout_p.h b/src/widgets/widgets/qdockarealayout_p.h index 49bd1571797..8138eda0a80 100644 --- a/src/widgets/widgets/qdockarealayout_p.h +++ b/src/widgets/widgets/qdockarealayout_p.h @@ -123,6 +123,48 @@ class Q_AUTOTEST_EXPORT QPlaceHolderItem QRect topLevelRect; }; +//------------------------------------------------------------------------- +// Autodesk 3ds Max addition: Extended docking resize behavior +// Helper class that contains a collection of parameters that is used +// for the docking layout calculation during the separator move. +// +// A drag move separator will now just do a single sided resizing of one +// layout item and keep the size of the layout item on the other side of +// the separator. The space that it needs for growing or shrinking will be +// taken from the center docking area. +// +// A shift+drag move separator will do the common Qt two sided resizing +// where on both sides of the separator one item will grow and the other +// one shrink. When the dragging is done in direction of the center docking +// area and all items in that direction has been already shrunk to their +// minimum size, then dragging doesn't get stuck as used to be, instead it +// will continue and move the shrunken items into the center docking area. +//------------------------------------------------------------------------- +class SeparatorMoveInfo +{ +public: + // True when one of the center dock area separators is moved. + bool isCenterSeparatorMove = false; + + // Indicates whether we should do a grow or shrink. + bool doGrow = false; + + // Indicates if we should do a single sided or two sided separator resizing. + bool doTwoSidedMove = false; + + // Returns the amount the layout container has changed. + int deltaContainerChangedReturn = 0; + + // Returns if the layout container was shrunk or grown. + bool containerShrinkedReturn = false; + + // Returns the delta of the separator move that wasn't processed, + // due to some min/max constraints. + int deltaNotMovedReturn = 0; + +}; +//------------------------------------------------------------------------- + class Q_AUTOTEST_EXPORT QDockAreaLayoutInfo { public: @@ -181,7 +223,13 @@ class Q_AUTOTEST_EXPORT QDockAreaLayoutInfo void paintSeparators(QPainter *p, QWidget *widget, const QRegion &clip, const QPoint &mouse) const; QRegion separatorRegion() const; - int separatorMove(int index, int delta); + + //------------------------------------------------------------------------- + // Autodesk 3ds Max Change: Extended docking resize behavior + // Added additional parameters necessary for the extended separator move + // behavior. + //------------------------------------------------------------------------- + int separatorMove(int index, int delta, SeparatorMoveInfo* smi = nullptr, bool doFitSubInfoItems = true); int separatorMove(const QList &separator, const QPoint &origin, const QPoint &dest); QLayoutItem *itemAt(int *x, int index) const; @@ -220,6 +268,23 @@ class Q_AUTOTEST_EXPORT QDockAreaLayoutInfo int tabIndexToListIndex(int) const; void moveTab(int from, int to); #endif // QT_CONFIG(tabbar) + + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: Extended docking resize behavior + // Adds an additional flag for the indication if on info->fitItems() the + // Qt default layout item distribution should happen, which means that all + // expansive items will get a part of the available space, or if the + // first/last expansive item in the layout list should be preferred for + // receiving the free space. + //------------------------------------------------------------------------- + enum FitItemsExpandMode + { + ExpandAll = 0, // Qt default, distributes the available space to all expandable items. + ExpandFirst = 1, // Prefers the first expansive item to get the available space. + ExpandLast = 2 // Prefers the last expansive item to get the available space. + }; + FitItemsExpandMode fitItemsExpandMode = ExpandAll; + //------------------------------------------------------------------------- }; class Q_AUTOTEST_EXPORT QDockAreaLayout @@ -271,7 +336,12 @@ class Q_AUTOTEST_EXPORT QDockAreaLayout QSize sizeHint() const; QSize minimumSize() const; - void addDockWidget(QInternal::DockPosition pos, QDockWidget *dockWidget, Qt::Orientation orientation); + //------------------------------------------------------------------------- + // Autodesk 3ds Max Change: Adds an additional toFront parameter, that + // makes it possible to add a dock widget to the front of the dock area + // container so that the widget can appear close to the main windows center area. + //------------------------------------------------------------------------- + void addDockWidget(QInternal::DockPosition pos, QDockWidget *dockWidget, Qt::Orientation orientation, bool toFront); bool restoreDockWidget(QDockWidget *dockWidget); void splitDockWidget(QDockWidget *after, QDockWidget *dockWidget, Qt::Orientation orientation); diff --git a/src/widgets/widgets/qdockwidget.cpp b/src/widgets/widgets/qdockwidget.cpp index 84cb78a4746..a93d8fe4e71 100644 --- a/src/widgets/widgets/qdockwidget.cpp +++ b/src/widgets/widgets/qdockwidget.cpp @@ -285,7 +285,20 @@ bool QDockWidgetLayout::wmSupportsNativeWindowDeco() */ bool QDockWidgetLayout::nativeWindowDeco(bool floating) const { - return wmSupportsNativeWindowDeco() && floating && item_list.at(QDockWidgetLayout::TitleBar) == 0; + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: 3ds Max uses in the docked version a custom + // titlebar widget and in floating mode the native window frame. + // In floating mode the custom titlebar widget gets hidden so we don't + // do the custom titlebar widget check here to determine if the window + // should have native window decoration. + //------------------------------------------------------------------------- + if ( floating && parentWidget() && parentWidget()->property( "_3dsmax_hide_titlebar_on_floating" ).toBool() ) + { + return wmSupportsNativeWindowDeco(); + } + //------------------------------------------------------------------------- + + return wmSupportsNativeWindowDeco() && floating && item_list.at( QDockWidgetLayout::TitleBar ) == 0; } @@ -725,6 +738,22 @@ void QDockWidgetPrivate::updateButtons() bool nativeDeco = dwLayout->nativeWindowDeco(); bool hideButtons = nativeDeco || customTitleBar; + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: 3ds Max uses in the docked version a custom + // titlebar widget and in floating mode the native window frame. + // Adding / removing the custom titlebar widget during the topLevelChanged + // signal causes a crash, so we just show / hide the bar like it is also + // done with the system window buttons. + //------------------------------------------------------------------------- + if ( q->property( "_3dsmax_hide_titlebar_on_floating" ).toBool() ) + { + if ( auto custTitleBar = dwLayout->widgetForRole( QDockWidgetLayout::TitleBar ) ) + { + custTitleBar->setVisible( !nativeDeco ); + } + } + //------------------------------------------------------------------------- + bool canClose = hasFeature(this, QDockWidget::DockWidgetClosable); bool canFloat = hasFeature(this, QDockWidget::DockWidgetFloatable); @@ -1147,6 +1176,26 @@ void QDockWidgetPrivate::setWindowState(bool floating, bool unplug, const QRect flags |= Qt::FramelessWindowHint; } + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: 3ds Max uses in the docked version a custom + // titlebar widget and in floating mode the native window frame. + // For the floating native window frame we allow 3ds Max to change the + // window flags so that it is possible to show up a standard OS frame with + // the minimize / maximize window feature and the other fancy OS frame stuff. + // (e.g. knock the frame on screen border) + // The Qt default floating window type is always just a tool window, which + // has none of the required features. + //------------------------------------------------------------------------- + if ( floating ) + { + auto prop = q_ptr->property( "_3dsmax_floating_window_flags" ); + if ( prop.isValid() ) + { + flags = (Qt::WindowFlags) prop.toUInt(); + } + } + //------------------------------------------------------------------------- + if (unplug) flags |= Qt::X11BypassWindowManagerHint; diff --git a/src/widgets/widgets/qfocusframe.cpp b/src/widgets/widgets/qfocusframe.cpp index 8b8f4db86e8..75197c0109a 100644 --- a/src/widgets/widgets/qfocusframe.cpp +++ b/src/widgets/widgets/qfocusframe.cpp @@ -69,7 +69,7 @@ void QFocusFramePrivate::update() Q_Q(QFocusFrame); q->setParent(frameParent); updateSize(); - if (q->parentWidget()->rect().intersects(q->geometry())) { + if (q->parentWidget() && q->parentWidget()->rect().intersects(q->geometry())) { if (showFrameAboveWidget) q->raise(); else @@ -89,7 +89,7 @@ void QFocusFramePrivate::updateSize() int vmargin = q->style()->pixelMetric(QStyle::PM_FocusFrameVMargin), hmargin = q->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); QPoint pos(widget->x(), widget->y()); - if (q->parentWidget() != widget->parentWidget()) + if (q->parentWidget() && widget->parentWidget() && q->parentWidget() != widget->parentWidget()) pos = widget->parentWidget()->mapTo(q->parentWidget(), pos); QRect geom(pos.x()-hmargin, pos.y()-vmargin, widget->width()+(hmargin*2), widget->height()+(vmargin*2)); @@ -201,7 +201,7 @@ QFocusFrame::setWidget(QWidget *widget) p = p->parentWidget(); }while (p); } - if (widget && !widget->isWindow() && widget->parentWidget()->windowType() != Qt::SubWindow) { + if (widget && !widget->isWindow() && (widget->parentWidget() && widget->parentWidget()->windowType() != Qt::SubWindow)) { d->widget = widget; d->widget->installEventFilter(this); QWidget *p = widget->parentWidget(); diff --git a/src/widgets/widgets/qmainwindow.cpp b/src/widgets/widgets/qmainwindow.cpp index 2014bdabf3c..ef8559c8ff1 100644 --- a/src/widgets/widgets/qmainwindow.cpp +++ b/src/widgets/widgets/qmainwindow.cpp @@ -1105,6 +1105,20 @@ bool QMainWindow::restoreDockWidget(QDockWidget *dockwidget) return d_func()->layout->restoreDockWidget(dockwidget); } +//------------------------------------------------------------------------- +// Autodesk 3ds Max Addition +/*! + Raises \a dockwidget, if that \a dockWidget is tabbed together with other + dockwidgets, so that after calling this function the \a dockWidget is the + current one of the tabbed ones. +*/ +//------------------------------------------------------------------------- +void QMainWindow::raiseDockWidget(QDockWidget *dockwidget) +{ + d_func()->layout->activate(); // make sure the tabs are in place. + d_func()->layout->raise(dockwidget); +} + /*! Adds \a dockwidget into the given \a area in the direction specified by the \a orientation. @@ -1119,6 +1133,26 @@ void QMainWindow::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget d_func()->layout->addDockWidget(area, dockwidget, orientation); } +//------------------------------------------------------------------------- +// Autodesk 3ds Max Addition +/*! + Adds \a dockwidget into the given \a area in the direction + specified by the \a orientation. + By setting \a toFront to true, the \a dockwidget is placed at the front + of the docking area container instead of appending it to the end, which + corresponds to the default behavior. +*/ +//------------------------------------------------------------------------- +void QMainWindow::addDockWidget( Qt::DockWidgetArea area, QDockWidget *dockwidget, + Qt::Orientation orientation, bool toFront ) +{ + if ( !checkDockWidgetArea( area, "QMainWindow::addDockWidget" ) ) + return; + + d_func()->layout->addDockWidget( area, dockwidget, orientation, toFront ); +} + + /*! \fn void QMainWindow::splitDockWidget(QDockWidget *first, QDockWidget *second, Qt::Orientation orientation) diff --git a/src/widgets/widgets/qmainwindow.h b/src/widgets/widgets/qmainwindow.h index 8f2a1924461..e729f0e0763 100644 --- a/src/widgets/widgets/qmainwindow.h +++ b/src/widgets/widgets/qmainwindow.h @@ -165,12 +165,15 @@ class Q_WIDGETS_EXPORT QMainWindow : public QWidget void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget); void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget, Qt::Orientation orientation); + void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget, + Qt::Orientation orientation, bool toFront); void splitDockWidget(QDockWidget *after, QDockWidget *dockwidget, Qt::Orientation orientation); void tabifyDockWidget(QDockWidget *first, QDockWidget *second); QList tabifiedDockWidgets(QDockWidget *dockwidget) const; void removeDockWidget(QDockWidget *dockwidget); bool restoreDockWidget(QDockWidget *dockwidget); + Q_INVOKABLE void raiseDockWidget(QDockWidget *dockwidget); Qt::DockWidgetArea dockWidgetArea(QDockWidget *dockwidget) const; diff --git a/src/widgets/widgets/qmainwindowlayout.cpp b/src/widgets/widgets/qmainwindowlayout.cpp index 43c22910f9a..f70cc7a9058 100644 --- a/src/widgets/widgets/qmainwindowlayout.cpp +++ b/src/widgets/widgets/qmainwindowlayout.cpp @@ -1480,9 +1480,14 @@ Qt::DockWidgetArea QMainWindowLayout::corner(Qt::Corner corner) const return layoutState.dockAreaLayout.corners[corner]; } +//------------------------------------------------------------------------- +// Autodesk 3ds Max Change: Adds an additional toFront parameter, that +// makes it possible to add a dock widget to the front of the dock area +// container so that the widget can appear close to the main windows center area. +//------------------------------------------------------------------------- void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget, - Qt::Orientation orientation) + Qt::Orientation orientation, bool toFront) { addChildWidget(dockwidget); @@ -1491,7 +1496,7 @@ void QMainWindowLayout::addDockWidget(Qt::DockWidgetArea area, if (!movingSeparator.isEmpty()) endSeparatorMove(movingSeparatorPos); - layoutState.dockAreaLayout.addDockWidget(toDockPos(area), dockwidget, orientation); + layoutState.dockAreaLayout.addDockWidget(toDockPos(area), dockwidget, orientation, toFront); emit dockwidget->dockLocationChanged(area); invalidate(); } diff --git a/src/widgets/widgets/qmainwindowlayout_p.h b/src/widgets/widgets/qmainwindowlayout_p.h index 4ccfb1786e5..9f081d060f4 100644 --- a/src/widgets/widgets/qmainwindowlayout_p.h +++ b/src/widgets/widgets/qmainwindowlayout_p.h @@ -104,6 +104,7 @@ class QMainWindowLayoutSeparatorHelper QList movingSeparator; QPoint movingSeparatorOrigin, movingSeparatorPos; QBasicTimer separatorMoveTimer; + bool shiftMoveSeparator = false; // Autodesk 3ds Max addition: Separator moved while shift pressed bool startSeparatorMove(const QPoint &pos); bool separatorMove(const QPoint &pos); @@ -216,7 +217,10 @@ bool QMainWindowLayoutSeparatorHelper::windowEvent(QEvent *event) adjustCursor(QPoint(0, 0)); return true; case QEvent::ShortcutOverride: // when a menu pops up - adjustCursor(QPoint(0, 0)); + if ( movingSeparator.isEmpty() ) // ADSK: Don't revert the cursor if we move a separator and a key is pressed. + { + adjustCursor( QPoint( 0, 0 ) ); + } break; #endif // QT_CONFIG(cursor) @@ -317,6 +321,67 @@ bool QMainWindowLayoutSeparatorHelper::separatorMove(const QPoint &pos) if (movingSeparator.isEmpty()) return false; movingSeparatorPos = pos; + + //------------------------------------------------------------------------- + // Autodesk 3ds Max addition: Extended docking resize behavior + // A drag move separator will now just do a single sided resizing of one + // layout item and keep the size of the layout item on the other side of + // the separator. The space that it needs for growing/shrinking will be + // taken from the center docking area. + // + // A shift+drag move separator will do the common Qt two sided resizing + // where on both sides of the separator one item will grow and the other + // one shrink. When the dragging is done in direction of the center docking + // area and all items in that direction has been already shrunk to their + // minimum size, then dragging doesn't get stuck as used to be, instead it + // will continue and move the shrunken items into the center docking area. + //------------------------------------------------------------------------- + auto prop = layout()->layoutState.mainWindow->property( "_3dsmax_disable_extended_docking_resize" ); + if ( !prop.isValid() || prop.toBool() == false ) + { + if ( movingSeparator.count() > 1 ) + { + int firstIndex = movingSeparator.first(); + Qt::Orientation o = firstIndex == QInternal::LeftDock || firstIndex == QInternal::RightDock + ? Qt::Horizontal + : Qt::Vertical; + + QDockAreaLayoutInfo* info = layout()->dockAreaLayoutInfo()->info( movingSeparator ); + + // Extended resizing only if the affected layout info shares the same orientation + // as the master dock area. + if ( info && info->o == o ) + { + // If the shift state changes, we start with a new 'fresh' move again, cause shift + // toggling between single-/two-sided separator resize during the move won't work. + Qt::KeyboardModifiers keyMods = QGuiApplication::queryKeyboardModifiers(); + if ( (keyMods == Qt::ShiftModifier) != shiftMoveSeparator ) + { + shiftMoveSeparator = !shiftMoveSeparator; + + // As move origin update we take the moved separators center. + QPoint sepCenter = layout()->dockAreaLayoutInfo()->separatorRect( movingSeparator ).center(); + + if ( o == Qt::Horizontal ) + { + movingSeparatorOrigin = QPoint( sepCenter.x(), movingSeparatorOrigin.y() ); + } + else + { + movingSeparatorOrigin = QPoint( movingSeparatorOrigin.x(), sepCenter.y() ); + } + + // Update the saved layout state, from which the separator move calculation + // starts on the timer event, to the current layoutState. This will be our new + // starting point for the calculation. + layout()->savedState.clear(); + layout()->savedState = layout()->layoutState; + } + } + } + } + //------------------------------------------------------------------------- + separatorMoveTimer.start(0, window()); return true; } @@ -327,6 +392,7 @@ bool QMainWindowLayoutSeparatorHelper::endSeparatorMove(const QPoint &) return false; movingSeparator.clear(); layout()->savedState.clear(); + shiftMoveSeparator = false; return true; } @@ -488,9 +554,14 @@ class Q_AUTOTEST_EXPORT QMainWindowLayout #if QT_CONFIG(dockwidget) void setCorner(Qt::Corner corner, Qt::DockWidgetArea area); Qt::DockWidgetArea corner(Qt::Corner corner) const; + //------------------------------------------------------------------------- + // Autodesk 3ds Max Change: Adds an additional toFront parameter, that + // makes it possible to add a dock widget to the front of the dock area + // container so that the widget can appear close to the main windows center area. + //------------------------------------------------------------------------- void addDockWidget(Qt::DockWidgetArea area, QDockWidget *dockwidget, - Qt::Orientation orientation); + Qt::Orientation orientation, bool toFront = false); void splitDockWidget(QDockWidget *after, QDockWidget *dockwidget, Qt::Orientation orientation); diff --git a/src/widgets/widgets/qmenu.cpp b/src/widgets/widgets/qmenu.cpp index 363647aee0d..307bab48b88 100644 --- a/src/widgets/widgets/qmenu.cpp +++ b/src/widgets/widgets/qmenu.cpp @@ -3649,6 +3649,10 @@ void QMenu::internalDelayedPopup() } d->activeMenu->popup(subMenuPos); + + if (!d->activeMenu) + return; + d->sloppyState.setSubMenuPopup(actionRect, d->currentAction, d->activeMenu); #if !defined(Q_OS_DARWIN) diff --git a/src/widgets/widgets/qtabbar.cpp b/src/widgets/widgets/qtabbar.cpp index 8af70d0f9cf..6f6205cd14e 100644 --- a/src/widgets/widgets/qtabbar.cpp +++ b/src/widgets/widgets/qtabbar.cpp @@ -57,6 +57,7 @@ #include "qwhatsthis.h" #endif #include "private/qtextengine_p.h" +#include "qmenu.h" // Adsk 3ds Max #ifndef QT_NO_ACCESSIBILITY #include "qaccessible.h" #endif @@ -100,6 +101,48 @@ inline static bool verticalTabs(QTabBar::Shape shape) || shape == QTabBar::TriangularEast; } +namespace +{ +//------------------------------------------------------------------ +// Autodesk 3ds Max addition: Tabs menu button +// Refactors some Qt hit test code out, that is used in several places. +//------------------------------------------------------------------ +inline bool isMousePosInCornerButtons( QTabBarPrivate* d, const QPoint& pos ) +{ + return (!d->leftB->isHidden() && d->leftB->geometry().contains( pos )) + || (!d->rightB->isHidden() && d->rightB->geometry().contains( pos )) + || (!d->tabsMenuBtn->isHidden() && d->tabsMenuBtn->geometry().contains( pos )); +} + +//------------------------------------------------------------------ +// Autodesk 3ds Max addition: Tabs menu button +// The extra width is defined by the width of the 3 tool buttons +// left scroll / right scroll / tabs menu button and their distance +// to each other. This can vary depending on their visibility. +//------------------------------------------------------------------ +int extraButtonWidth( const QTabBarPrivate* d, const QTabBar* q ) +{ + const int btnWidth = qMax( q->style()->pixelMetric( QStyle::PM_TabBarScrollButtonWidth, 0, q ), + QApplication::globalStrut().width() ); + const int btnOverlap = q->style()->pixelMetric( QStyle::PM_TabBar_ScrollButtonOverlap, 0, q ); + + int extra = 0; + if ( d->tabScrollBtnOptions.testFlag( QTabBarPrivate::TabScrollBtnsShown ) ) + { + extra = 2 * btnWidth - btnOverlap; + } + + if ( d->tabScrollBtnOptions.testFlag( QTabBarPrivate::TabMenuBtnShown ) ) + { + const int tabsMenuBtnWidth = qMax( q->style()->pixelMetric( QStyle::PM_TabBarTabsMenuButtonWidth, 0, q ), QApplication::globalStrut().width() ); + extra += tabsMenuBtnWidth - (d->tabScrollBtnOptions.testFlag( QTabBarPrivate::TabScrollBtnsShown ) ? btnOverlap : 0); + } + + return extra; +} + +} + void QTabBarPrivate::updateMacBorderMetrics() { #if defined(Q_OS_OSX) @@ -406,9 +449,14 @@ void QTabBarPrivate::init() #endif q->setFocusPolicy(Qt::TabFocus); + // Adsk 3ds Max: Adds the new tabs menu button. + tabsMenuBtn = new TabsMenuBtn( q ); + tabsMenuBtn->hide(); + #ifndef QT_NO_ACCESSIBILITY leftB->setAccessibleName(QTabBar::tr("Scroll Left")); rightB->setAccessibleName(QTabBar::tr("Scroll Right")); + tabsMenuBtn->setAccessibleName( QTabBar::tr( "Tabs Menu Button" ) ); #endif q->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); elideMode = Qt::TextElideMode(q->style()->styleHint(QStyle::SH_TabBar_ElideMode, 0, q)); @@ -550,6 +598,10 @@ void QTabBarPrivate::layoutTabs() QRect scrollButtonRightRect = q->style()->subElementRect(QStyle::SE_TabBarScrollRightButton, &opt, q); int scrollButtonWidth = q->style()->pixelMetric(QStyle::PM_TabBarScrollButtonWidth, &opt, q); + // Adsk 3ds Max + QRect tabsMenuButtonRect = q->style()->subElementRect( QStyle::SE_TabBarTabsMenuButton, &opt, q ); + + // Normally SE_TabBarScrollLeftButton should have the same width as PM_TabBarScrollButtonWidth. // But if that is not the case, we set the actual button width to PM_TabBarScrollButtonWidth, and // use the extra space from SE_TabBarScrollLeftButton as margins towards the tabs. @@ -573,17 +625,29 @@ void QTabBarPrivate::layoutTabs() rightB->setArrowType(Qt::RightArrow); } - leftB->setGeometry(scrollButtonLeftRect); - leftB->setEnabled(false); - leftB->show(); + if ( tabScrollBtnOptions.testFlag( TabScrollBtnsShown ) ) + { + leftB->setGeometry( scrollButtonLeftRect ); + leftB->setEnabled( false ); + leftB->show(); + + rightB->setGeometry( scrollButtonRightRect ); + rightB->setEnabled( last - scrollOffset > scrollRect.x() + scrollRect.width() ); + rightB->show(); + } + + // Adsk 3ds Max: Update the new tabs menu button. + if ( tabScrollBtnOptions.testFlag( TabMenuBtnShown ) ) + { + tabsMenuBtn->setGeometry( tabsMenuButtonRect ); + tabsMenuBtn->show(); + } - rightB->setGeometry(scrollButtonRightRect); - rightB->setEnabled(last - scrollOffset > scrollRect.x() + scrollRect.width()); - rightB->show(); } else { scrollOffset = 0; rightB->hide(); leftB->hide(); + tabsMenuBtn->hide(); // Adsk 3ds Max } layoutWidgets(); @@ -606,6 +670,15 @@ QRect QTabBarPrivate::normalizedScrollRect(int index) QRect scrollButtonRightRect = q->style()->subElementRect(QStyle::SE_TabBarScrollRightButton, &opt, q); QRect tearLeftRect = q->style()->subElementRect(QStyle::SE_TabBarTearIndicatorLeft, &opt, q); QRect tearRightRect = q->style()->subElementRect(QStyle::SE_TabBarTearIndicatorRight, &opt, q); + + // Adsk 3ds Max + // If only the tabs menu button is shown, we use this button as reference + // for scroll rect calculation. + if ( tabScrollBtnOptions == TabMenuBtnShown ) + { + QRect tabsMenuButtonRect = q->style()->subElementRect( QStyle::SE_TabBarTabsMenuButton, &opt, q ); + scrollButtonLeftRect = scrollButtonRightRect = tabsMenuButtonRect; + } if (verticalTabs(shape)) { int topEdge, bottomEdge; @@ -677,7 +750,7 @@ int QTabBarPrivate::hoveredTabIndex() const void QTabBarPrivate::makeVisible(int index) { Q_Q(QTabBar); - if (!validIndex(index) || leftB->isHidden()) + if (!validIndex(index) || (leftB->isHidden() && tabsMenuBtn->isHidden())) return; const QRect tabRect = tabList.at(index).rect; @@ -982,6 +1055,7 @@ int QTabBar::insertTab(int index, const QIcon& icon, const QString &text) ++d->tabList[i].lastTab; } + d->tabsMenuBtn->tabOrderChanged(); // Adsk 3ds Max tabInserted(index); d->autoHideTabs(); return index; @@ -1074,6 +1148,7 @@ void QTabBar::removeTab(int index) } update(d->hoverRect); } + d->tabsMenuBtn->tabOrderChanged(); // Adsk 3ds Max tabRemoved(index); } } @@ -1436,9 +1511,9 @@ QSize QTabBar::minimumSizeHint() const return r.size().expandedTo(QApplication::globalStrut()); } if (verticalTabs(d->shape)) - return QSize(sizeHint().width(), d->rightB->sizeHint().height() * 2 + 75); + return QSize( sizeHint().width(), extraButtonWidth( d, this ) + 75 ); else - return QSize(d->rightB->sizeHint().width() * 2 + 75, sizeHint().height()); + return QSize( extraButtonWidth( d, this ) + 75, sizeHint().height() ); } // Compute the most-elided possible text, for minimumSizeHint @@ -1596,7 +1671,24 @@ bool QTabBar::event(QEvent *event) if (event->type() == QEvent::HoverMove || event->type() == QEvent::HoverEnter) { QHoverEvent *he = static_cast(event); - if (!d->hoverRect.contains(he->pos())) { + + //------------------------------------------------------------------ + // Autodesk 3ds Max modification: Tabs menu button + // No hover on the tabs when we are on the left/right/tabmenu buttons. + //------------------------------------------------------------------ + if ( isMousePosInCornerButtons( d, he->pos() ) ) + { + // Clear old tab hover rect. + if ( d->hoverRect.isValid() ) + { + QRect oldHoverRect = d->hoverRect; + d->hoverIndex = -1; + d->hoverRect = QRect(); + update( oldHoverRect ); + } + } + else if ( !d->hoverRect.contains( he->pos() ) ) + { QRect oldHoverRect = d->hoverRect; bool cursorOverTabs = false; for (int i = 0; i < d->tabList.count(); ++i) { @@ -1660,9 +1752,7 @@ bool QTabBar::event(QEvent *event) #endif } else if (event->type() == QEvent::MouseButtonDblClick) { // ### fixme Qt 6: move to mouseDoubleClickEvent(), here for BC reasons. const QPoint pos = static_cast(event)->pos(); - const bool isEventInCornerButtons = (!d->leftB->isHidden() && d->leftB->geometry().contains(pos)) - || (!d->rightB->isHidden() && d->rightB->geometry().contains(pos)); - if (!isEventInCornerButtons) + if ( !isMousePosInCornerButtons( d, pos ) ) emit tabBarDoubleClicked(tabAt(pos)); } else if (event->type() == QEvent::Move) { d->updateMacBorderMetrics(); @@ -1688,6 +1778,26 @@ bool QTabBar::event(QEvent *event) event->ignore(); #endif } + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Tabs menu button + // Sync dynamic 3ds Max property for the tab scroll options with + // the internal flag. + //------------------------------------------------------------------ + else if ( event->type() == QEvent::DynamicPropertyChange ) + { + auto pe = static_cast< QDynamicPropertyChangeEvent* >( event ); + if ( pe->propertyName() == "_3dsmax_tab_scroll_options" ) + { + auto prop = property( "_3dsmax_tab_scroll_options" ); + if ( prop.isValid()) + { + d->tabScrollBtnOptions = (QTabBarPrivate::TabScrollOptions)prop.toInt(); + d->refresh(); + } + } + } + + return QWidget::event(event); } @@ -1793,13 +1903,13 @@ void QTabBar::paintEvent(QPaintEvent *) } // Only draw the tear indicator if necessary. Most of the time we don't need too. - if (d->leftB->isVisible() && cutLeft >= 0) { + if ( (d->leftB->isVisible() || d->tabsMenuBtn->isVisible()) && cutLeft >= 0) { cutTabLeft.rect = rect(); cutTabLeft.rect = style()->subElementRect(QStyle::SE_TabBarTearIndicatorLeft, &cutTabLeft, this); p.drawPrimitive(QStyle::PE_IndicatorTabTearLeft, cutTabLeft); } - if (d->rightB->isVisible() && cutRight >= 0) { + if ( (d->rightB->isVisible() || d->tabsMenuBtn->isVisible()) && cutRight >= 0) { cutTabRight.rect = rect(); cutTabRight.rect = style()->subElementRect(QStyle::SE_TabBarTearIndicatorRight, &cutTabRight, this); p.drawPrimitive(QStyle::PE_IndicatorTabTearRight, cutTabRight); @@ -1902,6 +2012,9 @@ void QTabBar::moveTab(int from, int to) d->layoutWidgets(start); update(); + + d->tabsMenuBtn->tabOrderChanged(); // Adsk 3ds Max + emit tabMoved(from, to); if (previousIndex != d->currentIndex) emit currentChanged(d->currentIndex); @@ -1942,9 +2055,8 @@ void QTabBar::mousePressEvent(QMouseEvent *event) Q_D(QTabBar); const QPoint pos = event->pos(); - const bool isEventInCornerButtons = (!d->leftB->isHidden() && d->leftB->geometry().contains(pos)) - || (!d->rightB->isHidden() && d->rightB->geometry().contains(pos)); - if (!isEventInCornerButtons) { + + if ( !isMousePosInCornerButtons( d, pos ) ) { const int index = d->indexAtPos(pos); emit tabBarClicked(index); } @@ -2101,6 +2213,9 @@ void QTabBarPrivate::setupMovableTab() leftB->raise(); if (rightB) rightB->raise(); + if ( tabsMenuBtn ) // Adsk 3ds Max + tabsMenuBtn->raise(); + movingTab->setVisible(true); } @@ -2692,6 +2807,120 @@ void QTabBarPrivate::Tab::TabBarAnimation::updateState(QAbstractAnimation::State } #endif + +//------------------------------------------------------------------ +// Autodesk 3ds Max addition: Tabs menu button +// Adds a new tool button to the tab left / right scroll buttons +// which opens up a quick select menu containing all tabs. +//------------------------------------------------------------------ +TabsMenuBtn::TabsMenuBtn( QTabBar* parent ) + : QToolButton( parent ), mTabBar( parent ) +{ + setObjectName( QLatin1String( "_3dsmax_tabbar_tabs_menu_button" ) ); + setPopupMode( QToolButton::InstantPopup ); + setArrowType( Qt::DownArrow ); + + mTabsMenu = new QMenu( this ); + setMenu( mTabsMenu ); + + QObject::connect( mTabsMenu, &QMenu::triggered, this, &TabsMenuBtn::tabsMenuActionTriggered ); + QObject::connect( mTabsMenu, &QMenu::aboutToShow, this, &TabsMenuBtn::tabsMenuAboutToShow ); + QObject::connect( mTabBar, &QTabBar::currentChanged, this, &TabsMenuBtn::currentTabChanged ); +} + +//------------------------------------------------------------------ +//------------------------------------------------------------------ +void TabsMenuBtn::tabsMenuActionTriggered( QAction* action ) +{ + int idx = action->data().toInt(); + + if ( idx >= 0 && idx < mTabBar->count() ) + { + mTabBar->setCurrentIndex( idx ); + } +} + +//------------------------------------------------------------------ +//------------------------------------------------------------------ +void TabsMenuBtn::tabsMenuAboutToShow() +{ + if ( mTabOrderDirty ) + { + // Update complete menu. + updateTabsMenu(); + mTabOrderDirty = false; + mCurTabDirty = false; + } + else if ( mCurTabDirty ) + { + // Update just current tab selection. + // Set a bold font for the current tab menu item. + int idx = mTabBar->currentIndex(); + + for ( auto a : mTabsMenu->actions() ) + { + QFont f = a->font(); + f.setBold( a->data().toInt() == idx ); + a->setFont( f ); + } + + mCurTabDirty = false; + } +} + +//------------------------------------------------------------------ +//------------------------------------------------------------------ +void TabsMenuBtn::updateTabsMenu() +{ + int curIdx = mTabBar->currentIndex(); + mTabsMenu->clear(); + + for ( int idx = 0; idx < mTabBar->count(); ++idx ) + { + auto tabName = mTabBar->tabText( idx ); + auto icon = mTabBar->tabIcon( idx ); + auto action = mTabsMenu->addAction( icon, tabName ); + action->setData( idx ); + // Set a bold font for the current tab menu item. + if ( idx == curIdx ) + { + QFont f = action->font(); + f.setBold( true ); + action->setFont( f ); + } + } +} + +//------------------------------------------------------------------ +//------------------------------------------------------------------ +void TabsMenuBtn::paintEvent( QPaintEvent* evt ) +{ + Q_UNUSED( evt ); + QStylePainter p( this ); + QStyleOptionToolButton opt; + initStyleOption( &opt ); + + // Remove menu flag for not showing the little corner triangle. + opt.features &= ~QStyleOptionToolButton::HasMenu; + + p.drawComplexControl( QStyle::CC_ToolButton, opt ); +} + +//------------------------------------------------------------------ +//------------------------------------------------------------------ +void TabsMenuBtn::currentTabChanged() +{ + mCurTabDirty = true; +} + +//------------------------------------------------------------------ +//------------------------------------------------------------------ +void TabsMenuBtn::tabOrderChanged() +{ + mTabOrderDirty = true; +} + + QT_END_NAMESPACE #include "moc_qtabbar.cpp" diff --git a/src/widgets/widgets/qtabbar_p.h b/src/widgets/widgets/qtabbar_p.h index 1092878f2ca..ad556bd153c 100644 --- a/src/widgets/widgets/qtabbar_p.h +++ b/src/widgets/widgets/qtabbar_p.h @@ -68,6 +68,38 @@ QT_REQUIRE_CONFIG(tabbar); QT_BEGIN_NAMESPACE + +//------------------------------------------------------------------ +// Autodesk 3ds Max addition: Tabs menu button +// Adds a new tool button to the tab left / right scroll buttons +// which opens up a quick select menu containing all tabs. +//------------------------------------------------------------------ +class TabsMenuBtn : public QToolButton +{ + Q_OBJECT + +public: + TabsMenuBtn( QTabBar* parent = nullptr ); + + void tabOrderChanged(); + void updateTabsMenu(); + +protected: + void paintEvent( QPaintEvent* evt ) Q_DECL_OVERRIDE; + +public slots: + void tabsMenuActionTriggered( QAction* action ); + void tabsMenuAboutToShow(); + void currentTabChanged(); + +private: + QMenu* mTabsMenu = nullptr; + QTabBar* mTabBar = nullptr; + bool mTabOrderDirty = true; + bool mCurTabDirty = true; +}; + + class QMovableTabWidget : public QWidget { public: @@ -103,6 +135,20 @@ class Q_WIDGETS_EXPORT QTabBarPrivate : public QWidgetPrivate bool drawBase; int scrollOffset; + //------------------------------------------------------------------ + // Autodesk 3ds Max addition: Tabs menu button + // Specifies additional flags that can be used to show / hide the + // left & right tab scroll buttons in combination with + // the new tabs menu button. + //------------------------------------------------------------------ + enum TabScrollOption { + TabScrollBtnsShown = 0x00001, + TabMenuBtnShown = 0x00002 + }; + + Q_DECLARE_FLAGS( TabScrollOptions, TabScrollOption ) + + struct Tab { inline Tab(const QIcon &ico, const QString &txt) : enabled(true) , shortcutId(0), text(txt), icon(ico), @@ -186,6 +232,7 @@ class Q_WIDGETS_EXPORT QTabBarPrivate : public QWidgetPrivate QToolButton* rightB; // right or bottom QToolButton* leftB; // left or top + TabsMenuBtn* tabsMenuBtn = nullptr; // Adsk 3ds Max: Tab switch menu button void _q_scrollTabs(); void _q_closeTab(); @@ -213,6 +260,7 @@ class Q_WIDGETS_EXPORT QTabBarPrivate : public QWidgetPrivate bool elideModeSetByUser; bool useScrollButtons; bool useScrollButtonsSetByUser; + TabScrollOptions tabScrollBtnOptions = TabMenuBtnShown; // Adsk 3ds Max bool expanding; bool closeButtonOnTabs; diff --git a/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp b/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp index eb3264be534..8c8804b284d 100644 --- a/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp +++ b/tests/auto/widgets/kernel/qsizepolicy/tst_qsizepolicy.cpp @@ -116,8 +116,16 @@ void tst_QSizePolicy::constExpr() { Q_CONSTEXPR QSizePolicy sp = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); Q_UNUSED(sp); } { Q_CONSTEXPR QSizePolicy sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding, QSizePolicy::DefaultType); Q_CONSTEXPR QSizePolicy tp = sp.transposed(); Q_UNUSED(tp); } - { Q_RELAXED_CONSTEXPR auto sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::CheckBox); - Q_RELAXED_CONSTEXPR auto tp = sp.transposed(); Q_UNUSED(tp); } + { + // QTBUG-69983: For ControlType != QSizePolicy::DefaultType, qCountTrailingZeroBits() + // is used, which MSVC 15.8.1 does not consider constexpr due to built-ins +# if defined(QT_HAS_CONSTEXPR_BUILTINS) && (!defined(Q_CC_MSVC) || _MSC_VER < 1915) + Q_RELAXED_CONSTEXPR auto sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed, QSizePolicy::CheckBox); +# else + Q_CONSTEXPR auto sp = QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding, QSizePolicy::DefaultType); +# endif + Q_RELAXED_CONSTEXPR auto tp = sp.transposed(); Q_UNUSED(tp); + } #else QSKIP("QSizePolicy cannot be constexpr with this version of the compiler."); #endif