Skip to content

Commit

Permalink
Changes for 3ds Max 2025
Browse files Browse the repository at this point in the history
MAXX-71898 - SME QT: Mat/Map browser becomes narrow when collapsing rollout group or after right-click
- Adds the missing code to our 3dsMax docking patch that sets the 'fallbackToSizeHints' to false when the user moves the docking resize handles. So that on re-layout the user applied size persists and doesn't get overwritten with the sizeHint.
- Fixes an activation issue of the QWidgetResizeHandler used by the QDockWidget when the dockwidget's pos/size gets restored from an existing layout. The old code deactivated the resize handles for the floating dockwidget.
- Increases the range of the QWidgetResizeHandler for QDockWidgets so that it now matches the styles QStyle::PM_DockWidgetFrameWidth, instead of using the hardcoded non-dpi aware range value of 4px.

MAXX-74678 - QWidget::close() deletes the entire platformwindow hierarchy
- The closing of a QWidget is now deleting the entire platformWindow hierarchy in the underlying QWindow.
This leads to crashes and missing UI, cause this behaviour will destroy all the embedded win32 UI.
The 3dsMax Qt5.15 code contained a patch for this, by skipping the destroy part in QWidgetWindow/QWindow.
This patch needs to be reapplied to be in line with the old Qt5 behaviour in 3dsMax.

MAXX-74817 - Qt6: Embedded Win32 UI is slow and flickers like crazy
- Disables the SWP_NOCOPYBITS code that has been introduced with Qt6.
Disposing the old pixel data completely on the top level window causes flickering and performance degradation,
since every native child window in the hierarchy needs to completely repaint its client area,
recieving extra WM_NCPAINT, WM_ERASEBKGND and WM_PAINT messages.
This occurs even on just window activation, z-order change, window move, where it would be enough to re-blit the old content.

 - Merging all 3ds Max specific changes from Qt 5.15.1 into Qt 6.5.3
  • Loading branch information
woelker-adsk committed Mar 21, 2024
1 parent 372eaed commit 97990d1
Show file tree
Hide file tree
Showing 31 changed files with 3,310 additions and 258 deletions.
13 changes: 12 additions & 1 deletion qmake/generators/win32/winmakefile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,8 +288,19 @@ void Win32MakefileGenerator::processRcFileVar()
QTextStream ts(&rcString, QFile::WriteOnly);

QStringList vers = project->first("VERSION").toQString().split(".", Qt::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.15.1.0" -> "5.15.1.*"
//---------------------------------------------------------------------
for (int i = vers.size(); i < 4; i++)
vers += "0";
{
vers += "0"; //for 3ds Max
}
QString versionString = vers.join('.');

QStringList rcIcons;
Expand Down
17 changes: 16 additions & 1 deletion src/corelib/kernel/qeventdispatcher_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,10 +526,22 @@ bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
return false;
}

if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
// 3ds Max extension: Emit a signal that allows 3ds Max to do it's specific message preprocessing
// before Qt translates and dispatches the message. If 3ds Max already processed the message,
// there is no need to propagate / dispatch it further by Qt.
// Note, that 3ds Max cannot use the native event filter mechanism below for doing it's message
// preprocessing, since filterNativeEvent() with the event type "windows_generic_MSG" is also used
// by the QWindowsContext::windowsProc().
// Getting called twice from two different locations without be able to do a proper distinction of
// the calling source on the 3ds Max side, can lead to endless loops causing stack overflows.
bool eventProcessed = false;
emit preProcessNativeEvent( &msg, &eventProcessed );

if ( !eventProcessed && !filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

retVal = true;
}

Expand All @@ -545,6 +557,9 @@ bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
}
} while (canWait);

// 3ds Max extension for triggering progressive rendering.
emit allEventsProcessed();

return retVal;
}

Expand Down
10 changes: 10 additions & 0 deletions src/corelib/kernel/qeventdispatcher_win_p.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ class Q_CORE_EXPORT QEventDispatcherWin32 : public QAbstractEventDispatcher

HWND internalHwnd();

Q_SIGNALS:

// 3ds Max extension for win32 message preprocessing.
// Emitted before the native event filter are called and the win32 message gets translated/dispatched.
void preProcessNativeEvent( void* message, bool* returnProcessed );

// 3ds Max extension for progressive rendering.
// Emitted after all win32 messages has been dispatched.
void allEventsProcessed();

protected:
QEventDispatcherWin32(QEventDispatcherWin32Private &dd, QObject *parent = nullptr);
virtual void sendPostedEvents();
Expand Down
29 changes: 26 additions & 3 deletions src/gui/kernel/qwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2491,16 +2491,39 @@ bool QWindow::event(QEvent *ev)
const bool wasVisible = d->treatAsVisible();
const bool participatesInLastWindowClosed = d->participatesInLastWindowClosed();

//-------------------------------------------------------------------------
// Autodesk 3ds Max change: The out-commented code for destroying the platform
// window 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.

// The window might be deleted in the close event handler
QPointer<QWindow> deletionGuard(this);
//QPointer<QWindow> deletionGuard(this);
closeEvent(static_cast<QCloseEvent*>(ev));

if (ev->isAccepted()) {
/*if (ev->isAccepted()) {
if (deletionGuard)
destroy();
if (wasVisible && participatesInLastWindowClosed)
QGuiApplicationPrivate::instance()->maybeLastWindowClosed();
}
}*/

break;
}
Expand Down
2 changes: 2 additions & 0 deletions src/plugins/platforms/windows/qwindowsdrag.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ QT_BEGIN_NAMESPACE
class QPlatformScreen;

class QWindowsDropMimeData : public QWindowsInternalMimeData {
Q_OBJECT
Q_PROPERTY( IDataObject* IDataObject READ retrieveDataObject )
public:
QWindowsDropMimeData() = default;
IDataObject *retrieveDataObject() const override;
Expand Down
51 changes: 48 additions & 3 deletions src/plugins/platforms/windows/qwindowswindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2754,11 +2754,25 @@ void QWindowsWindow::propagateSizeHints()

bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *qWindow, const QMargins &margins)
{
//-------------------------------------------------------------------------
// Autodesk 3ds Max change: We disable here the SWP_NOCOPYBITS code that
// has been introduced for Qt6 with the following commit:
// Windows QPA: Set SWP_NOCOPYBITS during resize to avoid jitter
// https://git.autodesk.com/autodesk-forks/qtbase/commit/000f1ee3604048f693f2a9425948a37ec45b4301
//
// Disposing the old pixel data completely on the top level window causes
// flickering and performance degradation, since every native child window
// in the hierarchy needs to completely repaint its client area, recieving
// extra WM_NCPAINT, WM_ERASEBKGND and WM_PAINT messages. This occurs even
// on just window activation, z-order change, window move, where it would
// be enough to re-blit the old content.
//-------------------------------------------------------------------------

auto *windowPos = reinterpret_cast<WINDOWPOS *>(message->lParam);

// Tell Windows to discard the entire contents of the client area, as re-using
// parts of the client area would lead to jitter during resize.
windowPos->flags |= SWP_NOCOPYBITS;
// windowPos->flags |= SWP_NOCOPYBITS;

if ((windowPos->flags & SWP_NOZORDER) == 0) {
if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(qWindow)) {
Expand All @@ -2772,7 +2786,8 @@ bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *
}
if (!qWindow->isTopLevel()) // Implement hasHeightForWidth().
return false;
if (windowPos->flags & SWP_NOSIZE)
if ((windowPos->flags & (SWP_NOCOPYBITS | SWP_NOSIZE))) // Autodesk 3ds Max change: Revert to Qt5 if condition
// if (windowPos->flags & SWP_NOSIZE)
return false;
const QRect suggestedFrameGeometry(windowPos->x, windowPos->y,
windowPos->cx, windowPos->cy);
Expand Down Expand Up @@ -2956,7 +2971,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);
Expand Down
20 changes: 20 additions & 0 deletions src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,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<HWND>(qvariant_cast<void *>(prop))) {
if (IsWindow(nativeChildHWND)) {
return nativeChildHWND;
}
}
}
}

if (QWindow *window = accessible->window()) {
if (!accessible->parent() || (accessible->parent()->window() != window)) {
return QWindowsBaseWindow::handleOf(window);
Expand Down
32 changes: 27 additions & 5 deletions src/widgets/accessible/qaccessiblewidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,19 @@ static QString buddyString(const QWidget *widget)
}
#endif

#if QT_CONFIG(groupbox)
QGroupBox *groupbox = qobject_cast<QGroupBox*>(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<QGroupBox*>(parent);
// if (groupbox)
// return groupbox->title();
// #endif
//----------------------------------------------------------------------------

return QString();
}
Expand Down Expand Up @@ -374,10 +382,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();
#if QT_CONFIG(tooltip)
if (str.isEmpty())
str = widget()->toolTip();
Expand Down
Loading

0 comments on commit 97990d1

Please sign in to comment.