diff --git a/src/corelib/kernel/qcore_mac_objc.mm b/src/corelib/kernel/qcore_mac_objc.mm index 266faca0ed..cf9dafb6d8 100644 --- a/src/corelib/kernel/qcore_mac_objc.mm +++ b/src/corelib/kernel/qcore_mac_objc.mm @@ -140,7 +140,8 @@ QMacAutoReleasePool::QMacAutoReleasePool() { Class trackerClass = [QMacAutoReleasePoolTracker class]; -#ifdef QT_DEBUG +// Patch: Disable this debug code because it is very slow. +#ifdef QT_DEBUG____REMOVED void *poolFrame = nullptr; if (__builtin_available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { void *frame; diff --git a/src/gui/kernel/qstylehints.cpp b/src/gui/kernel/qstylehints.cpp index 48060a2c37..fff3271ec9 100644 --- a/src/gui/kernel/qstylehints.cpp +++ b/src/gui/kernel/qstylehints.cpp @@ -374,7 +374,11 @@ bool QStyleHints::showIsMaximized() const */ bool QStyleHints::showShortcutsInContextMenus() const { - return themeableHint(QPlatformTheme::ShowShortcutsInContextMenus, QPlatformIntegration::ShowShortcutsInContextMenus).toBool(); + // Patch: Always show hotkeys in the standard context menu. + // This patch can be removed in 5.13 and later versions. + // See: https://bugreports.qt.io/browse/QTBUG-71471 + return true; + // return themeableHint(QPlatformTheme::ShowShortcutsInContextMenus, QPlatformIntegration::ShowShortcutsInContextMenus).toBool(); } /*! diff --git a/src/gui/painting/qbezier.cpp b/src/gui/painting/qbezier.cpp index 65e6063fe4..fcf19a1a63 100644 --- a/src/gui/painting/qbezier.cpp +++ b/src/gui/painting/qbezier.cpp @@ -400,6 +400,33 @@ static bool addCircle(const QBezier *b, qreal offset, QBezier *o) return true; } +// Patch: Workaround VS2019 compiler bug, see QTBUG-75280. +#ifdef Q_OS_WIN +Q_NEVER_INLINE void QBezier::split(QBezier *firstHalf, QBezier *secondHalf) const +{ + Q_ASSERT(firstHalf); + Q_ASSERT(secondHalf); + + qreal c = (x2 + x3)*.5; + firstHalf->x2 = (x1 + x2)*.5; + secondHalf->x3 = (x3 + x4)*.5; + firstHalf->x1 = x1; + secondHalf->x4 = x4; + firstHalf->x3 = (firstHalf->x2 + c)*.5; + secondHalf->x2 = (secondHalf->x3 + c)*.5; + firstHalf->x4 = secondHalf->x1 = (firstHalf->x3 + secondHalf->x2)*.5; + + c = (y2 + y3)/2; + firstHalf->y2 = (y1 + y2)*.5; + secondHalf->y3 = (y3 + y4)*.5; + firstHalf->y1 = y1; + secondHalf->y4 = y4; + firstHalf->y3 = (firstHalf->y2 + c)*.5; + secondHalf->y2 = (secondHalf->y3 + c)*.5; + firstHalf->y4 = secondHalf->y1 = (firstHalf->y3 + secondHalf->y2)*.5; +} +#endif // Q_OS_WIN + int QBezier::shifted(QBezier *curveSegments, int maxSegments, qreal offset, float threshold) const { Q_ASSERT(curveSegments); diff --git a/src/gui/painting/qbezier_p.h b/src/gui/painting/qbezier_p.h index f8a91e9ef3..50c60b2d71 100644 --- a/src/gui/painting/qbezier_p.h +++ b/src/gui/painting/qbezier_p.h @@ -222,6 +222,8 @@ inline QPointF QBezier::secondDerivedAt(qreal t) const a * y1 + b * y2 + c * y3 + d * y4); } +// Patch: Workaround VS2019 compiler bug, see QTBUG-75280. +#ifndef Q_OS_WIN inline void QBezier::split(QBezier *firstHalf, QBezier *secondHalf) const { Q_ASSERT(firstHalf); @@ -245,6 +247,7 @@ inline void QBezier::split(QBezier *firstHalf, QBezier *secondHalf) const secondHalf->y2 = (secondHalf->y3 + c)*.5; firstHalf->y4 = secondHalf->y1 = (firstHalf->y3 + secondHalf->y2)*.5; } +#endif // Q_OS_WIN inline void QBezier::parameterSplitLeft(qreal t, QBezier *left) { diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp index b70b29e54e..9519894076 100644 --- a/src/gui/painting/qpainter.cpp +++ b/src/gui/painting/qpainter.cpp @@ -6245,6 +6245,91 @@ static QPixmap generateWavyPixmap(qreal maxRadius, const QPen &pen) return pixmap; } +// Patch: Improved underline with SpellCheck style for macOS and Windows. +// Added implementation of underline drawing from Chrome. +static QPixmap generateChromeSpellcheckPixmap(qreal descent, qreal factor, const QPen &pen) { + QString key = QLatin1String("ChromeUnderline-") + % pen.color().name() + % HexString(factor) + % HexString(pen.widthF()); + + QPixmap pixmap; + if (QPixmapCache::find(key, pixmap)) { + return pixmap; + } + // https://chromium.googlesource.com/chromium/src/+/refs/heads/master/third_party/blink/renderer/core/paint/document_marker_painter.cc + +#ifdef Q_OS_MAC + + constexpr qreal kMarkerHeight = 3; + + const qreal height = kMarkerHeight * factor; + const qreal width = height * 2; + + pixmap = QPixmap(qCeil(width), qFloor(height) * 2); + pixmap.setDevicePixelRatio(qApp->devicePixelRatio()); + pixmap.fill(Qt::transparent); + { + QPainter imgPainter(&pixmap); + imgPainter.setPen(Qt::NoPen); + imgPainter.setBrush(pen.color()); + imgPainter.setRenderHints( + QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + imgPainter.drawEllipse(0, 0, qFloor(height), qFloor(height)); + } + +#else + + constexpr qreal kMarkerWidth = 4; + constexpr qreal kMarkerHeight = 2; + + const auto x1 = (kMarkerWidth * -3 / 8) * factor; + const auto y1 = (kMarkerHeight * 3 / 4) * factor; + + const auto cY = (kMarkerHeight * 1 / 4) * factor; + + const auto c1X1 = (kMarkerWidth * -1 / 8) * factor; + const auto c1X2 = (kMarkerWidth * 3 / 8) * factor; + const auto c1X3 = (kMarkerWidth * 7 / 8) * factor; + + const auto c2X1 = (kMarkerWidth * 1 / 8) * factor; + const auto c2X2 = (kMarkerWidth * 5 / 8) * factor; + const auto c2X3 = (kMarkerWidth * 9 / 8) * factor; + + QPainterPath path; + path.moveTo(x1, y1); + path.cubicTo(c1X1, y1, + c1X1, cY, + c2X1, cY); + path.cubicTo(c1X2, cY, + c1X2, y1, + c2X2, y1); + path.cubicTo(c1X3, y1, + c1X3, cY, + c2X3, cY); + + pixmap = QPixmap(kMarkerWidth * factor, kMarkerHeight * factor * 2); + pixmap.fill(Qt::transparent); + { + QPen wavePen = pen; + wavePen.setCapStyle(Qt::RoundCap); + wavePen.setJoinStyle(Qt::RoundJoin); + wavePen.setWidthF(1 * factor); + + QPainter imgPainter(&pixmap); + imgPainter.setPen(std::move(wavePen)); + imgPainter.setRenderHint(QPainter::Antialiasing); + imgPainter.translate(0, descent - (kMarkerHeight * factor)); + imgPainter.drawPath(std::move(path)); + } + +#endif + + QPixmapCache::insert(std::move(key), pixmap); + + return pixmap; +} + static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const QFontEngine *fe, QTextEngine *textEngine, QTextCharFormat::UnderlineStyle underlineStyle, QTextItem::RenderFlags flags, qreal width, @@ -6262,7 +6347,9 @@ static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const pen.setWidthF(fe->lineThickness().toReal()); pen.setCapStyle(Qt::FlatCap); - QLineF line(qFloor(pos.x()), pos.y(), qFloor(pos.x() + width), pos.y()); + // Patch: Improved underline with SpellCheck style for macOS and Windows. + // Slightly move the beginning of the underline to the right. + QLineF line(qFloor(pos.x() + 1), pos.y(), qFloor(pos.x() + width), pos.y()); bool wasCompatiblePainting = painter->renderHints() & QPainter::Qt4CompatiblePainting; @@ -6273,13 +6360,29 @@ static void drawTextItemDecoration(QPainter *painter, const QPointF &pos, const const qreal underlineOffset = fe->underlinePosition().toReal(); if (underlineStyle == QTextCharFormat::SpellCheckUnderline) { - QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme(); - if (theme) - underlineStyle = QTextCharFormat::UnderlineStyle(theme->themeHint(QPlatformTheme::SpellCheckUnderlineStyle).toInt()); - if (underlineStyle == QTextCharFormat::SpellCheckUnderline) // still not resolved - underlineStyle = QTextCharFormat::WaveUnderline; - } + const qreal fontFactor = qreal(charFormat.font().pixelSize()) / qreal(10.); + painter->save(); + painter->translate(0, pos.y() + 1); + const qreal maxHeight = fe->descent().toReal() - qreal(1); + + QColor uc = charFormat.underlineColor(); + if (uc.isValid()) + pen.setColor(uc); + const QPixmap wave = generateChromeSpellcheckPixmap(maxHeight, fontFactor, pen); + const int descent = qFloor(maxHeight); + + painter->setBrushOrigin(painter->brushOrigin().x(), 0); +#ifdef Q_OS_MAC + const auto h = wave.height() / 2; + painter->drawTiledPixmap( + QRectF(pos.x(), (descent - h) / 2., qCeil(width), h), + wave); +#else + painter->fillRect(pos.x(), 0, qCeil(width), descent, wave); +#endif + painter->restore(); + } else if (underlineStyle == QTextCharFormat::WaveUnderline) { painter->save(); painter->translate(0, pos.y() + 1); diff --git a/src/gui/text/qinputcontrol.cpp b/src/gui/text/qinputcontrol.cpp index 3381fdb673..6036f052e9 100644 --- a/src/gui/text/qinputcontrol.cpp +++ b/src/gui/text/qinputcontrol.cpp @@ -40,6 +40,10 @@ #include "qinputcontrol_p.h" #include +// Patch: Enable Ctrl+key and Ctrl+Shift+key in all locales except German. +// See https://github.com/telegramdesktop/tdesktop/pull/1185. +#include + QT_BEGIN_NAMESPACE QInputControl::QInputControl(Type type, QObject *parent) @@ -67,9 +71,16 @@ bool QInputControl::isAcceptableInput(const QKeyEvent *event) const if (c.category() == QChar::Other_Format) return true; - // QTBUG-35734: ignore Ctrl/Ctrl+Shift; accept only AltGr (Alt+Ctrl) on German keyboards - if (event->modifiers() == Qt::ControlModifier - || event->modifiers() == (Qt::ShiftModifier | Qt::ControlModifier)) { + // Patch: Enable Ctrl+key and Ctrl+Shift+key in all locales except German. + // See https://github.com/telegramdesktop/tdesktop/pull/1185. + bool skipCtrlAndCtrlShift = false; + if (QGuiApplication::inputMethod()->locale().language() == QLocale::German) { + if (event->modifiers() == Qt::ControlModifier + || event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) { + skipCtrlAndCtrlShift = true; + } + } + if (skipCtrlAndCtrlShift) { return false; } diff --git a/src/gui/text/qtextcursor.cpp b/src/gui/text/qtextcursor.cpp index c88497840f..2b08d13834 100644 --- a/src/gui/text/qtextcursor.cpp +++ b/src/gui/text/qtextcursor.cpp @@ -510,14 +510,16 @@ bool QTextCursorPrivate::movePosition(QTextCursor::MoveOperation op, QTextCursor const int len = blockIt.length() - 1; if (relativePos >= len) return false; - if (engine->atWordSeparator(relativePos)) { - ++relativePos; - while (relativePos < len && engine->atWordSeparator(relativePos)) - ++relativePos; - } else { - while (relativePos < len && !attributes[relativePos].whiteSpace && !engine->atWordSeparator(relativePos)) - ++relativePos; - } + // Patch: Improved apostrophe processing. + relativePos = engine->toEdge(relativePos, len, true); + // if (engine->atWordSeparator(relativePos)) { + // ++relativePos; + // while (relativePos < len && engine->atWordSeparator(relativePos)) + // ++relativePos; + // } else { + // while (relativePos < len && !attributes[relativePos].whiteSpace && !engine->atWordSeparator(relativePos)) + // ++relativePos; + // } newPosition = blockIt.position() + relativePos; break; } diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index a7834587b1..cabe897268 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -3028,7 +3028,8 @@ bool QTextEngine::atWordSeparator(int position) const case '&': case '^': case '*': - case '\'': + // Patch: Make the apostrophe a non-separator for words. + //case '\'': case '"': case '`': case '~': @@ -3041,6 +3042,74 @@ bool QTextEngine::atWordSeparator(int position) const return false; } +// Patch: Improved apostrophe processing. +// We should consider apostrophes as word separators when there is more than +// one apostrophe in a row, or when the apostrophe is at the beginning or end +// of the word. +int QTextEngine::toEdge(int pos, int len, bool isRightDirection) { + const auto step = isRightDirection ? 1 : -1; + const auto next = isRightDirection ? 0 : -1; + + QCharAttributes *attributes = const_cast(this->attributes()); + + const auto atApostrophe = [&](int position) { + return layoutData->string.at(position).unicode() == '\''; + }; + + const auto atSepOrApost = [&](int position) { + return atApostrophe(position) || atWordSeparator(position); + }; + + const auto inBounds = [&](int position) { + return isRightDirection + ? position < len + : position > 0; + }; + + const auto atSepOrSpace = [&](int position) { + return attributes[position].whiteSpace || atWordSeparator(position); + }; + + const auto isApostropheInWord = [&](int position) { + if (!atApostrophe(position)) { + return false; + } + auto p = position - 1; + if (p <= 0 || atSepOrSpace(p)) { + return false; + } + p = position + 1; + if (p >= len || atSepOrSpace(p)) { + return false; + } + return true; + }; + + auto counter = 0; + while (inBounds(pos) && atSepOrApost(pos + next)) { + counter++; + pos += step; + } + // If it's not the single apostrophe, then that's non-letter part of text. + if (counter > 1 || (counter == 1 && !isApostropheInWord(pos - step + next))) { + return pos; + } + + bool isPrevApostrophe = false; + while (inBounds(pos) && !atSepOrSpace(pos + next)) { + bool isNextApostrophe = atApostrophe(pos + next); + if (isPrevApostrophe && isNextApostrophe) { + break; + } + pos += step; + isPrevApostrophe = isNextApostrophe; + } + if (isPrevApostrophe) { + pos += -step; + } + return pos; +} + void QTextEngine::setPreeditArea(int position, const QString &preeditText) { if (preeditText.isEmpty()) { diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h index e9187ea605..51997ba066 100644 --- a/src/gui/text/qtextengine_p.h +++ b/src/gui/text/qtextengine_p.h @@ -622,6 +622,8 @@ private: public: bool atWordSeparator(int position) const; + // Patch: Improved apostrophe processing. + int toEdge(int pos, int len, bool isRightDirection); QString elidedText(Qt::TextElideMode mode, const QFixed &width, int flags = 0, int from = 0, int count = -1) const; diff --git a/src/gui/text/qtextlayout.cpp b/src/gui/text/qtextlayout.cpp index f3f0caa379..081c5f03c0 100644 --- a/src/gui/text/qtextlayout.cpp +++ b/src/gui/text/qtextlayout.cpp @@ -706,16 +706,22 @@ int QTextLayout::nextCursorPosition(int oldPos, CursorMode mode) const while (oldPos < len && !attributes[oldPos].graphemeBoundary) oldPos++; } else { - if (oldPos < len && d->atWordSeparator(oldPos)) { - oldPos++; - while (oldPos < len && d->atWordSeparator(oldPos)) - oldPos++; - } else { - while (oldPos < len && !attributes[oldPos].whiteSpace && !d->atWordSeparator(oldPos)) - oldPos++; - } + // Patch: Skip to the end of the current word, not to the start of the next one. while (oldPos < len && attributes[oldPos].whiteSpace) oldPos++; + // Patch: Improved apostrophe processing. + oldPos = d->toEdge(oldPos, len, true); + // if (oldPos < len && d->atWordSeparator(oldPos)) { + // oldPos++; + // while (oldPos < len && d->atWordSeparator(oldPos)) + // oldPos++; + // } else { + // while (oldPos < len && !attributes[oldPos].whiteSpace && !d->atWordSeparator(oldPos)) + // oldPos++; + // } + // Patch: Skip to the end of the current word, not to the start of the next one. + //while (oldPos < len && attributes[oldPos].whiteSpace) + // oldPos++; } return oldPos; @@ -745,14 +751,16 @@ int QTextLayout::previousCursorPosition(int oldPos, CursorMode mode) const while (oldPos > 0 && attributes[oldPos - 1].whiteSpace) oldPos--; - if (oldPos && d->atWordSeparator(oldPos-1)) { - oldPos--; - while (oldPos && d->atWordSeparator(oldPos-1)) - oldPos--; - } else { - while (oldPos > 0 && !attributes[oldPos - 1].whiteSpace && !d->atWordSeparator(oldPos-1)) - oldPos--; - } + // Patch: Improved apostrophe processing. + oldPos = d->toEdge(oldPos, len, false); + // if (oldPos && d->atWordSeparator(oldPos-1)) { + // oldPos--; + // while (oldPos && d->atWordSeparator(oldPos-1)) + // oldPos--; + // } else { + // while (oldPos > 0 && !attributes[oldPos - 1].whiteSpace && !d->atWordSeparator(oldPos-1)) + // oldPos--; + // } } return oldPos; diff --git a/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp b/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp index 9e6e5d88c7..8e951a2281 100644 --- a/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp +++ b/src/platformsupport/fontdatabases/windows/qwindowsfontdatabase.cpp @@ -1694,50 +1694,85 @@ HFONT QWindowsFontDatabase::systemFont() static const char *other_tryFonts[] = { "Arial", - "MS UI Gothic", - "Gulim", - "SimSun", - "PMingLiU", + "Yu Gothic UI", + "Meiryo UI", + "Yu Gothic", + "Meiryo", + "Malgun Gothic", + "Microsoft YaHei UI", + "Microsoft YaHei", + "Microsoft JhengHei UI", + "Microsoft JhengHei", + "Nirmala UI", + "Iskoola Pota", "Arial Unicode MS", 0 }; static const char *jp_tryFonts [] = { - "MS UI Gothic", + "Yu Gothic UI", + "Meiryo UI", + "Yu Gothic", + "Meiryo", "Arial", - "Gulim", - "SimSun", - "PMingLiU", + "Malgun Gothic", + "Microsoft YaHei UI", + "Microsoft YaHei", + "Microsoft JhengHei UI", + "Microsoft JhengHei", + "Nirmala UI", + "Iskoola Pota", "Arial Unicode MS", 0 }; static const char *ch_CN_tryFonts [] = { - "SimSun", + "Microsoft YaHei UI", + "Microsoft YaHei", "Arial", - "PMingLiU", - "Gulim", - "MS UI Gothic", + "Microsoft JhengHei UI", + "Microsoft JhengHei", + "Malgun Gothic", + "Yu Gothic UI", + "Meiryo UI", + "Yu Gothic", + "Meiryo", + "Nirmala UI", + "Iskoola Pota", "Arial Unicode MS", 0 }; static const char *ch_TW_tryFonts [] = { - "PMingLiU", + "Microsoft JhengHei UI", + "Microsoft JhengHei", "Arial", - "SimSun", - "Gulim", - "MS UI Gothic", + "Microsoft YaHei UI", + "Microsoft YaHei", + "Malgun Gothic", + "Yu Gothic UI", + "Meiryo UI", + "Yu Gothic", + "Meiryo", + "Nirmala UI", + "Iskoola Pota", "Arial Unicode MS", 0 }; static const char *kr_tryFonts[] = { - "Gulim", + "Malgun Gothic", "Arial", - "PMingLiU", - "SimSun", - "MS UI Gothic", + "Microsoft JhengHei UI", + "Microsoft JhengHei", + "Microsoft YaHei UI", + "Microsoft YaHei", + "Yu Gothic UI", + "Meiryo UI", + "Yu Gothic", + "Meiryo", + "Nirmala UI", + "Iskoola Pota", "Arial Unicode MS", 0 }; diff --git a/src/platformsupport/linuxaccessibility/constant_mappings.cpp b/src/platformsupport/linuxaccessibility/constant_mappings.cpp index fce2919e73..4a7d0f7d92 100644 --- a/src/platformsupport/linuxaccessibility/constant_mappings.cpp +++ b/src/platformsupport/linuxaccessibility/constant_mappings.cpp @@ -79,7 +79,12 @@ quint64 spiStatesFromQState(QAccessible::State state) if (state.checkStateMixed) setSpiStateBit(&spiState, ATSPI_STATE_INDETERMINATE); if (state.readOnly) +// Patch: Support build with AT-SPI version below 2.16. +#ifdef ATSPI_STATE_READ_ONLY setSpiStateBit(&spiState, ATSPI_STATE_READ_ONLY); +#else // ATSPI_STATE_READ_ONLY + unsetSpiStateBit(&spiState, ATSPI_STATE_EDITABLE); +#endif // ATSPI_STATE_READ_ONLY // if (state.HotTracked) if (state.defaultButton) setSpiStateBit(&spiState, ATSPI_STATE_IS_DEFAULT); diff --git a/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp index 81a730232c..42bab9aa4b 100644 --- a/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp +++ b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp @@ -273,6 +273,12 @@ bool QComposeInputContext::checkComposeTable() void QComposeInputContext::commitText(uint character) const { + // Patch: Crash fix when not focused widget still receives input events. + if (!m_focusObject) { + qWarning("QComposeInputContext::commitText: m_focusObject == nullptr, cannot commit text"); + return; + } + QInputMethodEvent event; event.setCommitString(QChar(character)); QCoreApplication::sendEvent(m_focusObject, &event); diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 2cf6672da9..ef25bb4541 100644 --- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -175,7 +175,8 @@ QT_USE_NAMESPACE if (reflectionDelegate) { if ([reflectionDelegate respondsToSelector:@selector(applicationShouldTerminate:)]) return [reflectionDelegate applicationShouldTerminate:sender]; - return NSTerminateNow; + // Patch: Don't terminate if reflectionDelegate does not respond to that selector, just use the default. + //return NSTerminateNow; } if ([self canQuit]) { @@ -252,7 +253,11 @@ QT_USE_NAMESPACE - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - Q_UNUSED(aNotification); + // Patch: We need to catch that notification in delegate. + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationDidFinishLaunching:)]) + [reflectionDelegate applicationDidFinishLaunching:aNotification]; + inLaunch = false; if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) { diff --git a/src/plugins/platforms/cocoa/qcocoakeymapper.mm b/src/plugins/platforms/cocoa/qcocoakeymapper.mm index 350ae4b9be..457bb3408d 100644 --- a/src/plugins/platforms/cocoa/qcocoakeymapper.mm +++ b/src/plugins/platforms/cocoa/qcocoakeymapper.mm @@ -462,7 +462,8 @@ QList QCocoaKeyMapper::possibleKeys(const QKeyEvent *event) const Qt::KeyboardModifiers neededMods = ModsTbl[i]; int key = kbItem->qtKey[i]; if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) { - ret << int(key + (keyMods & ~neededMods)); + // Patch: Fix non-english layout global shortcuts. + ret << int(key + neededMods); } } return ret; diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm index 597cfa8318..579d79734d 100644 --- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm +++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm @@ -96,12 +96,17 @@ QT_USE_NAMESPACE @interface QT_MANGLE_NAMESPACE(QNSStatusItem) : NSObject @property (nonatomic, assign) QCocoaMenu *menu; +// Patch: Create a rich os x tray icon (pixel-perfect, theme switching). +@property (nonatomic, assign) bool menuVisible; +@property (nonatomic, readonly) bool iconSelected; @property (nonatomic, assign) QIcon icon; @property (nonatomic, readonly) NSStatusItem *item; @property (nonatomic, readonly) QRectF geometry; - (instancetype)initWithSysTray:(QCocoaSystemTrayIcon *)systray; - (void)triggerSelector:(id)sender button:(Qt::MouseButton)mouseButton; - (void)doubleClickSelector:(id)sender; +- (void)setIconSelected:(bool)selected; +- (bool)hasMenu; @end @interface QT_MANGLE_NAMESPACE(QNSImageView) : NSImageView @@ -173,7 +178,10 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) // (device independent pixels). The menu height on past and // current OS X versions is 22 points. Provide some future-proofing // by deriving the icon height from the menu height. - const int padding = 4; + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + const int padding = 0; + const int menuHeight = [[NSStatusBar systemStatusBar] thickness]; const int maxImageHeight = menuHeight - padding; @@ -183,8 +191,12 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) // devicePixelRatio for the "best" screen on the system. qreal devicePixelRatio = qApp->devicePixelRatio(); const int maxPixmapHeight = maxImageHeight * devicePixelRatio; + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + const QIcon::Mode mode = m_sys->item.iconSelected ? QIcon::Selected : QIcon::Normal; + QSize selectedSize; - Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes())) { + Q_FOREACH (const QSize& size, sortByHeight(icon.availableSizes(mode))) { // Select a pixmap based on the height. We want the largest pixmap // with a height smaller or equal to maxPixmapHeight. The pixmap // may rectangular; assume it has a reasonable size. If there is @@ -200,9 +212,9 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon) // Handle SVG icons, which do not return anything for availableSizes(). if (!selectedSize.isValid()) - selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight)); + selectedSize = icon.actualSize(QSize(maxPixmapHeight, maxPixmapHeight), mode); - QPixmap pixmap = icon.pixmap(selectedSize); + QPixmap pixmap = icon.pixmap(selectedSize, mode); // Draw a low-resolution icon if there is not enough pixels for a retina // icon. This prevents showing a small icon on retina displays. @@ -301,6 +313,10 @@ QT_END_NAMESPACE { self.down = NO; + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + [self.parent setIconSelected:false]; + self.parent.menuVisible = false; + [self setNeedsDisplay:YES]; } @@ -310,6 +326,9 @@ QT_END_NAMESPACE int clickCount = [mouseEvent clickCount]; [self setNeedsDisplay:YES]; + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + [self.parent setIconSelected:((clickCount != 2) && [self.parent hasMenu])]; + if (clickCount == 2) { [self menuTrackingDone:nil]; [self.parent doubleClickSelector:self]; @@ -326,6 +345,10 @@ QT_END_NAMESPACE - (void)mouseUp:(NSEvent *)mouseEvent { Q_UNUSED(mouseEvent); + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + [self.parent setIconSelected:false]; + [self menuTrackingDone:nil]; } @@ -337,6 +360,10 @@ QT_END_NAMESPACE - (void)rightMouseUp:(NSEvent *)mouseEvent { Q_UNUSED(mouseEvent); + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + [self.parent setIconSelected:false]; + [self menuTrackingDone:nil]; } @@ -352,7 +379,8 @@ QT_END_NAMESPACE } - (void)drawRect:(NSRect)rect { - [[self.parent item] drawStatusBarBackgroundInRect:rect withHighlight:self.down]; + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + [[self.parent item] drawStatusBarBackgroundInRect:rect withHighlight:([self.parent hasMenu] && self.down)]; [super drawRect:rect]; } @end @@ -372,6 +400,10 @@ QT_END_NAMESPACE if (self) { item = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; menu = nullptr; + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + self.menuVisible = false; + systray = sys; imageCell = [[QNSImageView alloc] initWithParent:self]; [item setView: imageCell]; @@ -382,6 +414,11 @@ QT_END_NAMESPACE - (void)dealloc { [[NSStatusBar systemStatusBar] removeStatusItem:item]; [[NSNotificationCenter defaultCenter] removeObserver:imageCell]; + + // Patch: Fix crash in macOS 10.14. + // Somehow item and imageCell are retained and attempt to be drawn if left in view. + [item setView: nil]; + imageCell.parent = nil; [imageCell release]; [item release]; @@ -416,6 +453,10 @@ QT_END_NAMESPACE selector:@selector(menuTrackingDone:) name:NSMenuDidEndTrackingNotification object:m]; + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + self.menuVisible = true; + [item popUpStatusItemMenu: m]; } } @@ -427,6 +468,15 @@ QT_END_NAMESPACE emit systray->activated(QPlatformSystemTrayIcon::DoubleClick); } +- (void)setIconSelected:(bool)selected { + _iconSelected = selected; + systray->updateIcon(icon); +} + +- (bool)hasMenu { + return menu != nil; +} + - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification { Q_UNUSED(center); Q_UNUSED(notification); diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 3008a056a2..d98eade4a3 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -499,6 +499,15 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) // Select base window type. Note that the value of NSBorderlessWindowMask is 0. NSUInteger styleMask = (frameless || !resizable) ? NSWindowStyleMaskBorderless : NSWindowStyleMaskResizable; + // Patch: allow creating panels floating on all spaces in macOS. + // If you call "setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary" before + // setting the "NSWindowStyleMaskNonactivatingPanel" bit in the style mask it won't work after that. + // So we need a way to set that bit before Qt sets collection behavior the way it does. + QVariant nonactivatingPanelMask = window()->property("_td_macNonactivatingPanelMask"); + if (nonactivatingPanelMask.isValid() && nonactivatingPanelMask.toBool()) { + styleMask |= NSWindowStyleMaskNonactivatingPanel; + } + if (frameless) { // No further customizations for frameless since there are no window decorations. } else if (flags & Qt::CustomizeWindowHint) { diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm index ad751279bb..9a9d19693e 100644 --- a/src/plugins/platforms/cocoa/qnsview_keys.mm +++ b/src/plugins/platforms/cocoa/qnsview_keys.mm @@ -86,21 +86,29 @@ quint32 nativeVirtualKey = [nsevent keyCode]; QChar ch = QChar::ReplacementCharacter; - int keyCode = Qt::Key_unknown; - - // If a dead key occurs as a result of pressing a key combination then - // characters will have 0 length, but charactersIgnoringModifiers will - // have a valid character in it. This enables key combinations such as - // ALT+E to be used as a shortcut with an English keyboard even though - // pressing ALT+E will give a dead key while doing normal text input. - if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { - auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; - if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) - ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); - else if ([characters length] != 0) - ch = QChar([characters characterAtIndex:0]); - keyCode = [self convertKeyCode:ch]; - } + + // Patch: Fix Alt+.. shortcuts in OS X. See https://bugreports.qt.io/browse/QTBUG-42584 at the end. + if ([characters length] != 0) + ch = QChar([characters characterAtIndex:0]); + else if ([charactersIgnoringModifiers length] != 0 && ((modifiers & Qt::MetaModifier) || (modifiers & Qt::AltModifier))) + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + + int keyCode = [self convertKeyCode:ch]; + // int keyCode = Qt::Key_unknown; + + // // If a dead key occurs as a result of pressing a key combination then + // // characters will have 0 length, but charactersIgnoringModifiers will + // // have a valid character in it. This enables key combinations such as + // // ALT+E to be used as a shortcut with an English keyboard even though + // // pressing ALT+E will give a dead key while doing normal text input. + // if ([characters length] != 0 || [charactersIgnoringModifiers length] != 0) { + // auto ctrlOrMetaModifier = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::ControlModifier : Qt::MetaModifier; + // if (((modifiers & ctrlOrMetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) + // ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + // else if ([characters length] != 0) + // ch = QChar([characters characterAtIndex:0]); + // keyCode = [self convertKeyCode:ch]; + // } // we will send a key event unless the input method sets m_sendKeyEvent to false m_sendKeyEvent = true; @@ -196,6 +204,23 @@ [super keyUp:nsevent]; } +// Patch: Enable Ctrl+Tab and Ctrl+Shift+Tab / Ctrl+Backtab handle in-app. +- (BOOL)performKeyEquivalent:(NSEvent *)nsevent +{ + NSString *chars = [nsevent charactersIgnoringModifiers]; + + if ([nsevent type] == NSKeyDown && [chars length] > 0) { + QChar ch = [chars characterAtIndex:0]; + Qt::Key qtKey = qt_mac_cocoaKey2QtKey(ch); + if ([nsevent modifierFlags] & NSControlKeyMask + && (qtKey == Qt::Key_Tab || qtKey == Qt::Key_Backtab)) { + [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; + return YES; + } + } + return [super performKeyEquivalent:nsevent]; +} + - (void)cancelOperation:(id)sender { Q_UNUSED(sender); diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp index 9de3268fc8..8b281c95db 100644 --- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp +++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp @@ -1179,7 +1179,14 @@ void QWindowsNativeFileDialogBase::selectFile(const QString &fileName) const // Hack to prevent CLSIDs from being set as file name due to // QFileDialogPrivate::initialSelection() being QString-based. if (!isClsid(fileName)) - m_fileDialog->SetFileName((wchar_t*)fileName.utf16()); + // Patch: Fix handle of full fileName. + { + QString file = QDir::toNativeSeparators(fileName); + int lastBackSlash = file.lastIndexOf(QChar::fromLatin1('\\')); + if (lastBackSlash >= 0) + file = file.mid(lastBackSlash + 1); + m_fileDialog->SetFileName((wchar_t*)file.utf16());; + } } // Return the index of the selected filter, accounting for QFileDialog @@ -1456,7 +1463,8 @@ static QString createTemporaryItemCopy(QWindowsShellItem &qItem, QString *errorM static QUrl itemToDialogUrl(QWindowsShellItem &qItem, QString *errorMessage) { QUrl url = qItem.url(); - if (url.isLocalFile() || url.scheme().startsWith(QLatin1String("http"))) + // Patch: Make loaded 'http' resources copy. + if (url.isLocalFile()/*|| url.scheme().startsWith(QLatin1String("http"))*/) return url; const QString path = qItem.path(); if (path.isEmpty() && !qItem.isDir() && qItem.canStream()) { diff --git a/src/plugins/platforms/windows/qwindowsservices.cpp b/src/plugins/platforms/windows/qwindowsservices.cpp index 9504513a5e..811f3d62bd 100644 --- a/src/plugins/platforms/windows/qwindowsservices.cpp +++ b/src/plugins/platforms/windows/qwindowsservices.cpp @@ -125,6 +125,10 @@ static inline bool launchMail(const QUrl &url) command.prepend(doubleQuote); } } + + // Patch: Fix mail launch if no param is expected in this command. + if (command.indexOf(QStringLiteral("%1")) < 0) return false; + // Pass the url as the parameter. Should use QProcess::startDetached(), // but that cannot handle a Windows command line [yet]. command.replace(QLatin1String("%1"), url.toString(QUrl::FullyEncoded)); diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 7d511bf0d7..da3879cb56 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -1351,7 +1351,8 @@ void QWindowsWindow::destroyWindow() for (QWindow *w : tlw) { if (w->transientParent() == window()) { if (QWindowsWindow *tw = QWindowsWindow::windowsWindowOf(w)) - tw->updateTransientParent(); + // Patch: Fix possibility of add / remove taskbar icon of the window. + tw->clearTransientParent(); } } QWindowsContext *context = QWindowsContext::instance(); @@ -1579,6 +1580,19 @@ void QWindowsWindow::updateTransientParent() const // window is found, which can cause issues with modality. Loop up to top level. while (newTransientParent && (GetWindowLongPtr(newTransientParent, GWL_STYLE) & WS_CHILD) != 0) newTransientParent = GetParent(newTransientParent); + // Patch: Fix possibility of add / remove taskbar icon of the window. + if (newTransientParent && newTransientParent != oldTransientParent) + SetWindowLongPtr(m_data.hwnd, GWL_HWNDPARENT, (LONG_PTR)newTransientParent); +} + +// Patch: Fix possibility of add / remove taskbar icon of the window. +void QWindowsWindow::clearTransientParent() const +{ + if (window()->type() == Qt::Popup) + return; // QTBUG-34503, // a popup stays on top, no parent, see also WindowCreationData::fromWindow(). + // Update transient parent. + const HWND oldTransientParent = GetWindow(m_data.hwnd, GW_OWNER); + HWND newTransientParent = 0; if (newTransientParent != oldTransientParent) SetWindowLongPtr(m_data.hwnd, GWL_HWNDPARENT, LONG_PTR(newTransientParent)); diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index ce67e46df3..a60edc151f 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -353,6 +353,10 @@ private: inline void setWindowState_sys(Qt::WindowStates newState); inline void setParent_sys(const QPlatformWindow *parent); inline void updateTransientParent() const; + + // Patch: Fix possibility of add / remove taskbar icon of the window. + inline void clearTransientParent() const; + void destroyWindow(); inline bool isDropSiteEnabled() const { return m_dropTarget != 0; } void setDropSiteEnabled(bool enabled); diff --git a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp index c64a02fa0c..38198e61ba 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,16 @@ QT_BEGIN_NAMESPACE +// GTK file chooser image preview: thanks to Chromium + +// The size of the preview we display for selected image files. We set height +// larger than width because generally there is more free space vertically +// than horiztonally (setting the preview image will alway expand the width of +// the dialog, but usually not the height). The image's aspect ratio will always +// be preserved. +#define PREVIEW_WIDTH 256 +#define PREVIEW_HEIGHT 512 + class QGtk3Dialog : public QWindow { Q_OBJECT @@ -250,12 +261,21 @@ QGtk3FileDialogHelper::QGtk3FileDialogHelper() g_signal_connect(GTK_FILE_CHOOSER(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this); g_signal_connect_swapped(GTK_FILE_CHOOSER(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this); g_signal_connect_swapped(GTK_FILE_CHOOSER(d->gtkDialog()), "notify::filter", G_CALLBACK(onFilterChanged), this); + + previewWidget = gtk_image_new(); + g_signal_connect(G_OBJECT(d->gtkDialog()), "update-preview", G_CALLBACK(onUpdatePreview), this); + gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(d->gtkDialog()), previewWidget); } QGtk3FileDialogHelper::~QGtk3FileDialogHelper() { } +GtkImage *QGtk3FileDialogHelper::previewImage() const +{ + return GTK_IMAGE(previewWidget); +} + bool QGtk3FileDialogHelper::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) { _dir.clear(); @@ -390,6 +410,33 @@ void QGtk3FileDialogHelper::onFilterChanged(QGtk3FileDialogHelper *dialog) emit dialog->filterSelected(dialog->selectedNameFilter()); } +void QGtk3FileDialogHelper::onUpdatePreview(GtkDialog *gtkDialog, QGtk3FileDialogHelper *helper) +{ + gchar *filename = gtk_file_chooser_get_preview_filename(GTK_FILE_CHOOSER(gtkDialog)); + if (!filename) { + gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), false); + return; + } + + // Don't attempt to open anything which isn't a regular file. If a named pipe, + // this may hang. See https://crbug.com/534754. + QFileInfo fileinfo(filename); + if (!fileinfo.exists() || !fileinfo.isFile()) { + g_free(filename); + gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), false); + return; + } + + // This will preserve the image's aspect ratio. + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_size(filename, PREVIEW_WIDTH, PREVIEW_HEIGHT, 0); + g_free(filename); + if (pixbuf) { + gtk_image_set_from_pixbuf(helper->previewImage(), pixbuf); + g_object_unref(pixbuf); + } + gtk_file_chooser_set_preview_widget_active(GTK_FILE_CHOOSER(gtkDialog), pixbuf ? true : false); +} + static GtkFileChooserAction gtkFileChooserAction(const QSharedPointer &options) { switch (options->fileMode()) { diff --git a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h index e78a7fc6d1..8963dd7086 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h +++ b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h @@ -47,6 +47,8 @@ #include #include +typedef struct _GtkWidget GtkWidget; +typedef struct _GtkImage GtkImage; typedef struct _GtkDialog GtkDialog; typedef struct _GtkFileFilter GtkFileFilter; @@ -88,6 +90,8 @@ public: QGtk3FileDialogHelper(); ~QGtk3FileDialogHelper(); + GtkImage *previewImage() const; + bool show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent) override; void exec() override; void hide() override; @@ -108,6 +112,7 @@ private: static void onSelectionChanged(GtkDialog *dialog, QGtk3FileDialogHelper *helper); static void onCurrentFolderChanged(QGtk3FileDialogHelper *helper); static void onFilterChanged(QGtk3FileDialogHelper *helper); + static void onUpdatePreview(GtkDialog *dialog, QGtk3FileDialogHelper *helper); void applyOptions(); void setNameFilters(const QStringList &filters); void selectFileInternal(const QUrl &filename); @@ -118,6 +123,7 @@ private: QHash _filters; QHash _filterNames; QScopedPointer d; + GtkWidget *previewWidget; }; class QGtk3FontDialogHelper : public QPlatformFontDialogHelper diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp index 077955eb4e..5c8a3dddf7 100644 --- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp +++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp @@ -153,7 +153,7 @@ bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const case ColorDialog: return true; case FileDialog: - return useNativeFileDialog(); + return true; case FontDialog: return true; default: @@ -167,8 +167,6 @@ QPlatformDialogHelper *QGtk3Theme::createPlatformDialogHelper(DialogType type) c case ColorDialog: return new QGtk3ColorDialogHelper; case FileDialog: - if (!useNativeFileDialog()) - return nullptr; return new QGtk3FileDialogHelper; case FontDialog: return new QGtk3FontDialogHelper; diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp index fb65f6d909..66c3a54a07 100644 --- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp +++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp @@ -57,9 +57,11 @@ public: ~QXdgDesktopPortalThemePrivate() { delete baseTheme; + delete gtkTheme; } QPlatformTheme *baseTheme; + QPlatformTheme *gtkTheme; }; QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() @@ -85,11 +87,17 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme() break; } // No error message; not having a theme plugin is allowed. + + // Try to use gtk's open directory dialog if there are no fallback theme + d->gtkTheme = QPlatformThemeFactory::create(QLatin1String("gtk3"), nullptr); } // 3) Fall back on the built-in "null" platform theme. if (!d->baseTheme) d->baseTheme = new QPlatformTheme; + + if (!d->gtkTheme) + d->gtkTheme = new QPlatformTheme; } QPlatformMenuItem* QXdgDesktopPortalTheme::createPlatformMenuItem() const @@ -133,6 +141,8 @@ QPlatformDialogHelper* QXdgDesktopPortalTheme::createPlatformDialogHelper(Dialog if (type == FileDialog) { if (d->baseTheme->usePlatformNativeDialog(type)) return new QXdgDesktopPortalFileDialog(static_cast(d->baseTheme->createPlatformDialogHelper(type))); + else if (d->gtkTheme->usePlatformNativeDialog(type)) + return new QXdgDesktopPortalFileDialog(static_cast(d->gtkTheme->createPlatformDialogHelper(type))); return new QXdgDesktopPortalFileDialog; } diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index bf339ca5c5..4cdf9189ad 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -5161,6 +5161,17 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, return; // Fully transparent. Q_D(QWidget); + + // Patch: save and restore dirtyOpaqueChildren field. + // + // Just like in QWidget::grab() this field should be restored + // after the d->render() call, because it will be set to 1 and + // opaqueChildren field will be filled with empty region in + // case the widget is hidden (because all the opaque children + // will be skipped in isVisible() check). + // + const bool oldDirtyOpaqueChildren = d->dirtyOpaqueChildren; + const bool inRenderWithPainter = d->extra && d->extra->inRenderWithPainter; const QRegion toBePainted = !inRenderWithPainter ? d->prepareToRender(sourceRegion, renderFlags) : sourceRegion; @@ -5182,6 +5193,10 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, if (!inRenderWithPainter && (opacity < 1.0 || (target->devType() == QInternal::Printer))) { d->render_helper(painter, targetOffset, toBePainted, renderFlags); d->extra->inRenderWithPainter = inRenderWithPainter; + + // Patch: save and restore dirtyOpaqueChildren field. + d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; + return; } @@ -5214,6 +5229,9 @@ void QWidget::render(QPainter *painter, const QPoint &targetOffset, d->setSharedPainter(oldPainter); d->extra->inRenderWithPainter = inRenderWithPainter; + + // Patch: save and restore dirtyOpaqueChildren field. + d->dirtyOpaqueChildren = oldDirtyOpaqueChildren; } static void sendResizeEvents(QWidget *target) @@ -8968,7 +8986,8 @@ bool QWidget::event(QEvent *event) case QEvent::KeyPress: { QKeyEvent *k = (QKeyEvent *)event; bool res = false; - if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier? + // Patch: Enable Ctrl+Tab and Ctrl+Shift+Tab / Ctrl+Backtab handle in-app. + if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier))) { if (k->key() == Qt::Key_Backtab || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) res = focusNextPrevChild(false); diff --git a/src/widgets/util/qsystemtrayicon_qpa.cpp b/src/widgets/util/qsystemtrayicon_qpa.cpp index c0bf058681..1c8b627d01 100644 --- a/src/widgets/util/qsystemtrayicon_qpa.cpp +++ b/src/widgets/util/qsystemtrayicon_qpa.cpp @@ -93,6 +93,10 @@ void QSystemTrayIconPrivate::updateMenu_sys() if (qpa_sys && menu) { addPlatformMenu(menu); qpa_sys->updateMenu(menu->platformMenu()); + + // Patch: Create a rich os x tray icon (pixel-perfect, theme switching). + } else if (qpa_sys) { + qpa_sys->updateMenu(nullptr); } #endif } diff --git a/src/widgets/widgets/qabstractscrollarea.cpp b/src/widgets/widgets/qabstractscrollarea.cpp index 598d173144..fd2e636563 100644 --- a/src/widgets/widgets/qabstractscrollarea.cpp +++ b/src/widgets/widgets/qabstractscrollarea.cpp @@ -655,15 +655,21 @@ scrolling range. QSize QAbstractScrollArea::maximumViewportSize() const { Q_D(const QAbstractScrollArea); - int hsbExt = d->hbar->sizeHint().height(); - int vsbExt = d->vbar->sizeHint().width(); + // Patch: Count the sizeHint of the bar only if it is displayed. + //int hsbExt = d->hbar->sizeHint().height(); + //int vsbExt = d->vbar->sizeHint().width(); int f = 2 * d->frameWidth; QSize max = size() - QSize(f + d->left + d->right, f + d->top + d->bottom); - if (d->vbarpolicy == Qt::ScrollBarAlwaysOn) + // Patch: Count the sizeHint of the bar only if it is displayed. + if (d->vbarpolicy == Qt::ScrollBarAlwaysOn) { + int vsbExt = d->vbar->sizeHint().width(); max.rwidth() -= vsbExt; - if (d->hbarpolicy == Qt::ScrollBarAlwaysOn) + } + if (d->hbarpolicy == Qt::ScrollBarAlwaysOn) { + int hsbExt = d->hbar->sizeHint().height(); max.rheight() -= hsbExt; + } return max; }