macOS: Introduce QMacKeyValueObserver and use in theme and style

Change-Id: I2d21c883628933543ae5a66b694ff7503119bc4a
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Tor Arne Vestbø 2019-05-29 17:00:40 +02:00
parent 99636127d1
commit 2dfe0f8f68
6 changed files with 105 additions and 98 deletions

View File

@ -514,5 +514,36 @@ Q_CONSTRUCTOR_FUNCTION(qt_apple_check_os_version);
// -------------------------------------------------------------------------
void QMacKeyValueObserver::addObserver()
{
[object addObserver:observer forKeyPath:keyPath
options:NSKeyValueObservingOptionNew context:callback.get()];
}
void QMacKeyValueObserver::removeObserver() {
if (object)
[object removeObserver:observer forKeyPath:keyPath context:callback.get()];
object = nil;
}
KeyValueObserver *QMacKeyValueObserver::observer = [[KeyValueObserver alloc] init];
QT_END_NAMESPACE
@implementation KeyValueObserver
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
{
Q_UNUSED(keyPath);
Q_UNUSED(object);
Q_UNUSED(change);
(*reinterpret_cast<QMacKeyValueObserver::Callback*>(context))();
}
@end
QT_BEGIN_NAMESPACE
// -------------------------------------------------------------------------
QT_END_NAMESPACE

View File

@ -67,6 +67,7 @@
#ifdef __OBJC__
#include <Foundation/Foundation.h>
#include <functional>
#endif
#include "qstring.h"
@ -334,6 +335,60 @@ public:
private:
id observer = nil;
};
QT_END_NAMESPACE
@interface QT_MANGLE_NAMESPACE(KeyValueObserver) : NSObject
@end
QT_NAMESPACE_ALIAS_OBJC_CLASS(KeyValueObserver);
QT_BEGIN_NAMESPACE
class Q_CORE_EXPORT QMacKeyValueObserver
{
public:
using Callback = std::function<void()>;
QMacKeyValueObserver() {}
// Note: QMacKeyValueObserver must not outlive the object observed!
QMacKeyValueObserver(id object, NSString *keyPath, Callback callback)
: object(object), keyPath(keyPath), callback(new Callback(callback)) { addObserver(); }
QMacKeyValueObserver(const QMacKeyValueObserver &other)
: QMacKeyValueObserver(other.object, other.keyPath, *other.callback.get()) {}
QMacKeyValueObserver(QMacKeyValueObserver &&other) { swap(other, *this); }
~QMacKeyValueObserver() { removeObserver(); }
QMacKeyValueObserver &operator=(const QMacKeyValueObserver &other) {
QMacKeyValueObserver tmp(other);
swap(tmp, *this);
return *this;
}
QMacKeyValueObserver &operator=(QMacKeyValueObserver &&other) {
QMacKeyValueObserver tmp(std::move(other));
swap(tmp, *this);
return *this;
}
void removeObserver();
private:
void swap(QMacKeyValueObserver &first, QMacKeyValueObserver &second) {
std::swap(first.object, second.object);
std::swap(first.keyPath, second.keyPath);
std::swap(first.callback, second.callback);
}
void addObserver();
id object = nil;
NSString *keyPath = nullptr;
std::unique_ptr<Callback> callback;
static KeyValueObserver *observer;
};
#endif
// -------------------------------------------------------------------------

View File

@ -87,7 +87,7 @@ private:
QMacNotificationObserver m_systemColorObserver;
mutable QHash<QPlatformTheme::Palette, QPalette*> m_palettes;
mutable QHash<QPlatformTheme::Font, QFont*> m_fonts;
QT_MANGLE_NAMESPACE(QCocoaThemeAppAppearanceObserver) *m_appearanceObserver;
QMacKeyValueObserver m_appearanceObserver;
};
QT_END_NAMESPACE

View File

@ -80,57 +80,22 @@
#include <CoreServices/CoreServices.h>
#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
@interface QT_MANGLE_NAMESPACE(QCocoaThemeAppAppearanceObserver) : NSObject
@property (readonly, nonatomic) QCocoaTheme *theme;
- (instancetype)initWithTheme:(QCocoaTheme *)theme;
@end
QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaThemeAppAppearanceObserver);
@implementation QCocoaThemeAppAppearanceObserver
- (instancetype)initWithTheme:(QCocoaTheme *)theme
{
if ((self = [super init])) {
_theme = theme;
[NSApp addObserver:self forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nullptr];
}
return self;
}
- (void)dealloc
{
[NSApp removeObserver:self forKeyPath:@"effectiveAppearance"];
[super dealloc];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
{
Q_UNUSED(change);
Q_UNUSED(context);
Q_ASSERT(object == NSApp);
Q_ASSERT([keyPath isEqualToString:@"effectiveAppearance"]);
if (__builtin_available(macOS 10.14, *))
NSAppearance.currentAppearance = NSApp.effectiveAppearance;
self.theme->handleSystemThemeChange();
}
@end
#endif // QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
QT_BEGIN_NAMESPACE
const char *QCocoaTheme::name = "cocoa";
QCocoaTheme::QCocoaTheme()
: m_systemPalette(nullptr), m_appearanceObserver(nil)
: m_systemPalette(nullptr)
{
#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave)
m_appearanceObserver = [[QCocoaThemeAppAppearanceObserver alloc] initWithTheme:this];
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) {
m_appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [this] {
if (__builtin_available(macOS 10.14, *))
NSAppearance.currentAppearance = NSApp.effectiveAppearance;
handleSystemThemeChange();
});
}
#endif
m_systemColorObserver = QMacNotificationObserver(nil,
@ -141,9 +106,6 @@ QCocoaTheme::QCocoaTheme()
QCocoaTheme::~QCocoaTheme()
{
if (m_appearanceObserver)
[m_appearanceObserver release];
reset();
qDeleteAll(m_fonts);
}

View File

@ -144,41 +144,6 @@ static QWindow *qt_getWindow(const QWidget *widget)
return widget ? widget->window()->windowHandle() : 0;
}
@interface QT_MANGLE_NAMESPACE(NotificationReceiver) : NSObject
@end
QT_NAMESPACE_ALIAS_OBJC_CLASS(NotificationReceiver);
@implementation NotificationReceiver
{
QMacStylePrivate *privateStyle;
}
- (instancetype)initWithPrivateStyle:(QMacStylePrivate *)style
{
if (self = [super init])
privateStyle = style;
return self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
{
Q_UNUSED(keyPath);
Q_UNUSED(object);
Q_UNUSED(change);
Q_UNUSED(context);
Q_ASSERT([keyPath isEqualToString:@"effectiveAppearance"]);
Q_ASSERT(object == NSApp);
for (NSView *b : privateStyle->cocoaControls)
[b release];
privateStyle->cocoaControls.clear();
}
@end
@interface QT_MANGLE_NAMESPACE(QIndeterminateProgressIndicator) : NSProgressIndicator
@property (readonly, nonatomic) NSInteger animators;
@ -2080,7 +2045,6 @@ void QMacStylePrivate::resolveCurrentNSView(QWindow *window) const
QMacStyle::QMacStyle()
: QCommonStyle(*new QMacStylePrivate)
{
Q_D(QMacStyle);
QMacAutoReleasePool pool;
static QMacNotificationObserver scrollbarStyleObserver(nil,
@ -2093,26 +2057,21 @@ QMacStyle::QMacStyle()
QCoreApplication::sendEvent(o, &event);
});
d->receiver = [[NotificationReceiver alloc] initWithPrivateStyle:d];
#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
Q_D(QMacStyle);
// FIXME: Tie this logic into theme change, or even polish/unpolish
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave) {
[NSApplication.sharedApplication addObserver:d->receiver forKeyPath:@"effectiveAppearance"
options:NSKeyValueObservingOptionNew context:nullptr];
d->appearanceObserver = QMacKeyValueObserver(NSApp, @"effectiveAppearance", [&d] {
for (NSView *b : d->cocoaControls)
[b release];
d->cocoaControls.clear();
});
}
#endif
}
QMacStyle::~QMacStyle()
{
Q_D(QMacStyle);
QMacAutoReleasePool pool;
#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_14)
if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSMojave)
[NSApplication.sharedApplication removeObserver:d->receiver forKeyPath:@"effectiveAppearance"];
#endif
[d->receiver release];
}
void QMacStyle::polish(QPalette &)

View File

@ -149,7 +149,6 @@ Q_FORWARD_DECLARE_MUTABLE_CG_TYPE(CGContext);
Q_FORWARD_DECLARE_OBJC_CLASS(NSView);
Q_FORWARD_DECLARE_OBJC_CLASS(NSCell);
Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(NotificationReceiver));
QT_BEGIN_NAMESPACE
@ -288,13 +287,14 @@ public:
static QVector<QPointer<QObject> > scrollBars;
mutable QPointer<QFocusFrame> focusWidget;
QT_MANGLE_NAMESPACE(NotificationReceiver) *receiver;
mutable NSView *backingStoreNSView;
mutable QHash<CocoaControl, NSView *> cocoaControls;
mutable QHash<CocoaControl, NSCell *> cocoaCells;
QFont smallSystemFont;
QFont miniSystemFont;
QMacKeyValueObserver appearanceObserver;
};
QT_END_NAMESPACE