diff --git a/src/corelib/kernel/qcore_mac_objc.mm b/src/corelib/kernel/qcore_mac_objc.mm index 266faca..cf9dafb 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 48060a2..fff3271 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 65e6063..fcf19a1 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 f8a91e9..50c60b2 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 b70b29e..9519894 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 3381fdb..6036f05 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/qtextengine.cpp b/src/gui/text/qtextengine.cpp index a783458..9a1fe9f 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 '~': 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..a89dfe228c 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -3041,6 +3041,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/linuxaccessibility/constant_mappings.cpp b/src/platformsupport/linuxaccessibility/constant_mappings.cpp index fce2919..4a7d0f7 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 81a7302..42bab9a 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/platforminputcontexts/platforminputcontexts.pro b/src/plugins/platforminputcontexts/platforminputcontexts.pro index ed6b1b8..d17c6ba 100644 --- a/src/plugins/platforminputcontexts/platforminputcontexts.pro +++ b/src/plugins/platforminputcontexts/platforminputcontexts.pro @@ -2,7 +2,8 @@ TEMPLATE = subdirs QT_FOR_CONFIG += gui-private qtHaveModule(dbus) { -!mac:!win32:SUBDIRS += ibus +# Patch: Adding fcitx/hime/nimf input context plugin to our static build. +!mac:!win32:SUBDIRS += ibus fcitx hime nimf } qtConfig(xcb): SUBDIRS += compose diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm index 2cf6672..ef25bb4 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 350ae4b..457bb34 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 597cfa8..579d797 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/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm index ad75127..9a9d196 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 9de3268..8b281c9 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 9504513..811f3d6 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 7d511bf..da3879c 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 ce67e46..a60edc1 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/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index bf339ca..4cdf918 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 c0bf058..1c8b627 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 598d173..fd2e636 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; }