Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Use UserDefaultsObservation
  • Loading branch information
1024jp committed Dec 25, 2018
1 parent 37908dc commit 4d5b9a1
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 312 deletions.
33 changes: 12 additions & 21 deletions CotEditor/Sources/DocumentViewController.swift
Expand Up @@ -34,6 +34,7 @@ final class DocumentViewController: NSSplitViewController, NSMenuItemValidation,
// MARK: Private Properties

private var appearanceObserver: NSKeyValueObservation?
private var defaultsObservers: [UserDefaultsObservation] = []

@IBOutlet private weak var splitViewItem: NSSplitViewItem?
@IBOutlet private weak var statusBarItem: NSSplitViewItem?
Expand All @@ -45,25 +46,7 @@ final class DocumentViewController: NSSplitViewController, NSMenuItemValidation,

deinit {
self.appearanceObserver?.invalidate()

UserDefaults.standard.removeObserver(self, forKeyPath: DefaultKeys.theme.rawValue)
UserDefaults.standard.removeObserver(self, forKeyPath: DefaultKeys.showInvisibles.rawValue)
}


override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {

switch keyPath {
case DefaultKeys.theme.rawValue?:
guard let name = change?[.newKey] as? String else { return }
self.setTheme(name: name)

case DefaultKeys.showInvisibles.rawValue?:
self.showsInvisibles = change?[.newKey] as! Bool

default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
self.defaultsObservers.forEach { $0.invalidate() }
}


Expand Down Expand Up @@ -98,8 +81,16 @@ final class DocumentViewController: NSSplitViewController, NSMenuItemValidation,
NotificationCenter.default.addObserver(self, selector: #selector(didUpdateTheme),
name: didUpdateSettingNotification,
object: ThemeManager.shared)
UserDefaults.standard.addObserver(self, forKeyPath: DefaultKeys.theme.rawValue, options: .new, context: nil)
UserDefaults.standard.addObserver(self, forKeyPath: DefaultKeys.showInvisibles.rawValue, options: .new, context: nil)

// observe defaults change
self.defaultsObservers = [
UserDefaults.standard.observe(key: .theme) { [unowned self] _ in
self.setTheme(name: ThemeManager.shared.defaultSettingName())
},
UserDefaults.standard.observe(key: .showInvisibles, options: [.new]) { [unowned self] change in
self.showsInvisibles = change.new!
},
]

// observe appearance change for theme toggle
self.appearanceObserver = self.view.observe(\.effectiveAppearance) { [unowned self] (_, _) in
Expand Down
26 changes: 7 additions & 19 deletions CotEditor/Sources/DocumentWindowController.swift
Expand Up @@ -30,6 +30,8 @@ final class DocumentWindowController: NSWindowController {

// MARK: Private Properties

private var windowAlphaObserver: UserDefaultsObservation?

@IBOutlet private var toolbarController: ToolbarController?


Expand All @@ -38,24 +40,7 @@ final class DocumentWindowController: NSWindowController {
// MARK: Lifecycle

deinit {
UserDefaults.standard.removeObserver(self, forKeyPath: DefaultKeys.windowAlpha.rawValue)
}



// MARK: KVO

/// apply user defaults change
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {

switch keyPath {
case DefaultKeys.windowAlpha.rawValue?:
(self.window as? DocumentWindow)?.backgroundAlpha = UserDefaults.standard[.windowAlpha]
self.contentViewController?.view.needsDisplay = true

default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
self.windowAlphaObserver?.invalidate()
}


Expand All @@ -76,7 +61,10 @@ final class DocumentWindowController: NSWindowController {
(self.window as! DocumentWindow).backgroundAlpha = UserDefaults.standard[.windowAlpha]

// observe opacity setting change
UserDefaults.standard.addObserver(self, forKeyPath: DefaultKeys.windowAlpha.rawValue, context: nil)
self.windowAlphaObserver = UserDefaults.standard.observe(key: .windowAlpha, options: [.new]) { [unowned self] change in
(self.window as? DocumentWindow)?.backgroundAlpha = change.new!
self.contentViewController?.view.needsDisplay = true
}
}


Expand Down
233 changes: 115 additions & 118 deletions CotEditor/Sources/EditorTextView.swift
Expand Up @@ -77,33 +77,11 @@ final class EditorTextView: NSTextView, CurrentLineHighlighting, Themable {
private var particalCompletionWord: String?
private lazy var completionTask = Debouncer(delay: .seconds(0)) { [unowned self] in self.performCompletion() } // NSTextView cannot be weak

private var defaultsObservers: [UserDefaultsObservation] = []
private var windowOpacityObserver: NSObjectProtocol?
private var scrollObserver: NSObjectProtocol?
private var resizeObserver: NSObjectProtocol?

private let observedDefaultKeys: [DefaultKeys] = [
.autoExpandTab,
.autoIndent,
.enableSmartIndent,
.smartInsertAndDelete,
.balancesBrackets,
.checkSpellingAsType,
.pageGuideColumn,
.enableSmartQuotes,
.enableSmartDashes,
.tabWidth,
.hangingIndentWidth,
.enablesHangingIndent,
.autoLinkDetection,
.fontName,
.fontSize,
.shouldAntialias,
.lineHeight,
.highlightCurrentLine,
.highlightSelectionInstance,
.overscrollRate,
]



// MARK: -
Expand Down Expand Up @@ -171,17 +149,13 @@ final class EditorTextView: NSTextView, CurrentLineHighlighting, Themable {

self.invalidateDefaultParagraphStyle()

// observe change of defaults
for key in self.observedDefaultKeys {
UserDefaults.standard.addObserver(self, forKeyPath: key.rawValue, options: .new, context: nil)
}
// observe change in defaults
self.defaultsObservers = self.observeDefaults()
}


deinit {
for key in self.observedDefaultKeys {
UserDefaults.standard.removeObserver(self, forKeyPath: key.rawValue)
}
self.defaultsObservers.forEach { $0.invalidate() }
self.removeNotificationObservers()
}

Expand Down Expand Up @@ -878,94 +852,6 @@ final class EditorTextView: NSTextView, CurrentLineHighlighting, Themable {



// MARK: KVO

/// apply change of user setting
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {

switch keyPath {
case DefaultKeys.autoExpandTab.rawValue?:
self.isAutomaticTabExpansionEnabled = change?[.newKey] as! Bool

case DefaultKeys.autoIndent.rawValue?:
self.isAutomaticIndentEnabled = change?[.newKey] as! Bool

case DefaultKeys.enableSmartIndent.rawValue?:
self.isSmartIndentEnabled = change?[.newKey] as! Bool

case DefaultKeys.balancesBrackets.rawValue?:
self.balancesBrackets = change?[.newKey] as! Bool

case DefaultKeys.shouldAntialias.rawValue?:
self.usesAntialias = change?[.newKey] as! Bool

case DefaultKeys.smartInsertAndDelete.rawValue?:
self.smartInsertDeleteEnabled = change?[.newKey] as! Bool

case DefaultKeys.enableSmartQuotes.rawValue?:
self.isAutomaticQuoteSubstitutionEnabled = change?[.newKey] as! Bool

case DefaultKeys.enableSmartDashes.rawValue?:
self.isAutomaticDashSubstitutionEnabled = change?[.newKey] as! Bool

case DefaultKeys.checkSpellingAsType.rawValue?:
self.isContinuousSpellCheckingEnabled = change?[.newKey] as! Bool

case DefaultKeys.autoLinkDetection.rawValue?:
self.isAutomaticLinkDetectionEnabled = change?[.newKey] as! Bool
if self.isAutomaticLinkDetectionEnabled {
self.detectLinkIfNeeded()
} else {
if let textStorage = self.textStorage {
textStorage.removeAttribute(.link, range: textStorage.mutableString.range)
}
}

case DefaultKeys.pageGuideColumn.rawValue?:
self.setNeedsDisplay(self.visibleRect, avoidAdditionalLayout: true)

case DefaultKeys.tabWidth.rawValue?:
self.tabWidth = change?[.newKey] as! Int

case DefaultKeys.fontName.rawValue, DefaultKeys.fontSize.rawValue?:
self.resetFont(nil)

case DefaultKeys.lineHeight.rawValue?:
self.lineHeight = change?[.newKey] as! CGFloat

// reset visible area
self.centerSelectionInVisibleArea(self)

case DefaultKeys.enablesHangingIndent.rawValue, DefaultKeys.hangingIndentWidth.rawValue?:
let wholeRange = self.string.nsRange
if keyPath == DefaultKeys.enablesHangingIndent.rawValue, !(change?[.newKey] as! Bool) {
if let paragraphStyle = self.defaultParagraphStyle {
self.textStorage?.addAttribute(.paragraphStyle, value: paragraphStyle, range: wholeRange)
} else {
self.textStorage?.removeAttribute(.paragraphStyle, range: wholeRange)
}
} else {
(self.layoutManager as? LayoutManager)?.invalidateIndent(in: wholeRange)
}

case DefaultKeys.highlightCurrentLine.rawValue?:
self.setNeedsDisplay(self.visibleRect, avoidAdditionalLayout: true)

case DefaultKeys.highlightSelectionInstance.rawValue?:
if (change?[.newKey] as! Bool) == false {
self.layoutManager?.removeTemporaryAttribute(.roundedBackgroundColor, forCharacterRange: self.string.nsRange)
}

case DefaultKeys.overscrollRate.rawValue?:
self.invalidateOverscrollRate()

default:
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}



// MARK: Protocol

/// apply current state to related menu items and toolbar items
Expand Down Expand Up @@ -1462,6 +1348,117 @@ final class EditorTextView: NSTextView, CurrentLineHighlighting, Themable {
.forEach { self.layoutManager?.addTemporaryAttribute(.roundedBackgroundColor, value: self.instanceHighlightColor, forCharacterRange: $0) }
}


/// observe defaults to apply the change immediately
private func observeDefaults() -> [UserDefaultsObservation] {

let keys: [DefaultKeys] = [
.autoExpandTab,
.autoIndent,
.enableSmartIndent,
.balancesBrackets,
.shouldAntialias,
.smartInsertAndDelete,
.enableSmartQuotes,
.enableSmartDashes,
.checkSpellingAsType,
.autoLinkDetection,
.pageGuideColumn,
.overscrollRate,
.tabWidth,
.fontName,
.fontSize,
.lineHeight,
.highlightCurrentLine,
.highlightSelectionInstance,
.enablesHangingIndent,
.hangingIndentWidth,
]

return UserDefaults.standard.observe(keys: keys, options: [.new]) { [unowned self] (key, change) in

let new = change.new
switch key {
case .autoExpandTab:
self.isAutomaticTabExpansionEnabled = new as! Bool

case .autoIndent:
self.isAutomaticIndentEnabled = new as! Bool

case .enableSmartIndent:
self.isSmartIndentEnabled = new as! Bool

case .balancesBrackets:
self.balancesBrackets = new as! Bool

case .shouldAntialias:
self.usesAntialias = new as! Bool

case .smartInsertAndDelete:
self.smartInsertDeleteEnabled = new as! Bool

case .enableSmartQuotes:
self.isAutomaticQuoteSubstitutionEnabled = new as! Bool

case .enableSmartDashes:
self.isAutomaticDashSubstitutionEnabled = new as! Bool

case .checkSpellingAsType:
self.isContinuousSpellCheckingEnabled = new as! Bool

case .autoLinkDetection:
self.isAutomaticLinkDetectionEnabled = new as! Bool
if self.isAutomaticLinkDetectionEnabled {
self.detectLinkIfNeeded()
} else if let textStorage = self.textStorage {
textStorage.removeAttribute(.link, range: NSRange(0..<textStorage.length))
}

case .pageGuideColumn:
self.setNeedsDisplay(self.visibleRect, avoidAdditionalLayout: true)

case .overscrollRate:
self.invalidateOverscrollRate()

case .tabWidth:
self.tabWidth = new as! Int

case .fontName, .fontSize:
self.resetFont(nil)

case .lineHeight:
self.lineHeight = new as! CGFloat
self.centerSelectionInVisibleArea(self) // reset visible area

case .highlightCurrentLine:
self.setNeedsDisplay(self.visibleRect, avoidAdditionalLayout: true)

case .highlightSelectionInstance:
if !(new as! Bool) {
self.layoutManager?.removeTemporaryAttribute(.roundedBackgroundColor, forCharacterRange: self.string.nsRange)
}

case .enablesHangingIndent:
let wholeRange = self.string.nsRange
if !(new as! Bool) {
if let paragraphStyle = self.defaultParagraphStyle {
self.textStorage?.addAttribute(.paragraphStyle, value: paragraphStyle, range: wholeRange)
} else {
self.textStorage?.removeAttribute(.paragraphStyle, range: wholeRange)
}
} else {
(self.layoutManager as? LayoutManager)?.invalidateIndent(in: wholeRange)
}

case .hangingIndentWidth:
(self.layoutManager as? LayoutManager)?.invalidateIndent(in: self.string.nsRange)

default:
preconditionFailure()
}
}
}

}


Expand Down

0 comments on commit 4d5b9a1

Please sign in to comment.