blob: ed5aab45ae0de20cb8bf5e8237820d164eab373f [file] [log] [blame]
// Copyright 2016 The Tulsi Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import Cocoa
// Models a node in the source filter picker.
final class SourcePathNode: UISelectableOutlineViewNode {
/// Whether or not this specific node is set as recursive-enabled (rather than some child marking
/// it as mixed state).
@objc dynamic var explicitlyRecursive: Bool {
return recursive == NSControl.StateValue.on.rawValue
}
/// This node's recursive UI state (NSOnState/NSOffState/NSMixedState).
@objc dynamic var recursive: Int {
get {
guard let entry = entry as? UISourcePath else { return NSControl.StateValue.off.rawValue }
if entry.recursive { return NSControl.StateValue.on.rawValue }
for child in children as! [SourcePathNode] {
if child.recursive != NSControl.StateValue.off.rawValue {
return NSControl.StateValue.mixed.rawValue
}
}
return NSControl.StateValue.off.rawValue
}
set {
// No work needs to be done for mixed state, as it does not affect the underlying values.
if newValue == NSControl.StateValue.mixed.rawValue { return }
guard let entry = entry as? UISourcePath else { return }
let enabled = newValue == NSControl.StateValue.on.rawValue
willChangeValue(forKey: "explicitlyRecursive")
entry.recursive = enabled
didChangeValue(forKey: "explicitlyRecursive")
// If this node is newly recursive, force hasRecursiveEnabledParent, otherwise have children
// inherit this node's status.
setChildrenHaveRecursiveParent(enabled || hasRecursiveEnabledParent)
// Notify KVO that this node's ancestors have also changed state.
var ancestor = parent
while ancestor != nil {
ancestor!.willChangeValue(forKey: "recursive")
ancestor!.didChangeValue(forKey: "recursive")
ancestor = ancestor!.parent
}
}
}
@objc dynamic var hasRecursiveEnabledParent: Bool = false {
willSet {
// If this node is recursive its children will still have a recursive parent and there's no
// need to update them.
if recursive == NSControl.StateValue.on.rawValue || newValue == hasRecursiveEnabledParent { return }
setChildrenHaveRecursiveParent(newValue)
}
}
@objc func validateRecursive(_ ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
if let value = ioValue.pointee as? NSNumber {
if value.intValue == NSControl.StateValue.mixed.rawValue {
ioValue.pointee = NSNumber(value: NSControl.StateValue.on.rawValue as Int)
}
}
}
// MARK: - Private methods
fileprivate func setChildrenHaveRecursiveParent(_ newValue: Bool) {
for child in children as! [SourcePathNode] {
child.hasRecursiveEnabledParent = newValue
// Children of a recursive-enabled node may not be recursive themselves (it's redundant and
// potentially confusing).
if newValue {
child.recursive = NSControl.StateValue.off.rawValue
}
}
}
}
// Controller for the view allowing users to select a subset of the source files to include in the
// generated Xcode project.
final class ConfigEditorSourceFilterViewController: NSViewController, WizardSubviewProtocol {
@objc dynamic var sourceFilterContentArray: [SourcePathNode] = []
@IBOutlet weak var sourceFilterOutlineView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
let sourceTargetColumn = sourceFilterOutlineView.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sourceTargets"))!
sourceFilterOutlineView.sortDescriptors = [sourceTargetColumn.sortDescriptorPrototype!]
}
// MARK: - WizardSubviewProtocol
weak var presentingWizardViewController: ConfigEditorWizardViewController? = nil
func wizardSubviewWillActivateMovingForward() {
let document = representedObject as! TulsiGeneratorConfigDocument
sourceFilterContentArray = []
document.updateSourcePaths(populateOutlineView)
document.updateChangeCount(.changeDone)
}
// MARK: - Private methods
private func populateOutlineView(_ sourcePaths: [UISourcePath]) {
// Decompose each rule and merge into a tree of subelements.
let componentDelimiters = CharacterSet(charactersIn: "/:")
let splitSourcePaths = sourcePaths.map() {
$0.path.components(separatedBy: componentDelimiters)
}
var recursiveNodes = [SourcePathNode]()
let topNode = SourcePathNode(name: "")
for i in 0 ..< splitSourcePaths.count {
let label = splitSourcePaths[i]
var node = topNode
elementLoop: for element in label {
if element == "" {
continue
}
for child in node.children as! [SourcePathNode] {
if child.name == element {
node = child
continue elementLoop
}
}
let newNode = SourcePathNode(name: element)
node.addChild(newNode)
node = newNode
}
node.entry = sourcePaths[i]
if node.recursive == NSControl.StateValue.on.rawValue {
recursiveNodes.append(node)
}
}
// Patch up the recursive status now that the entire tree is constructed.
for node in recursiveNodes {
node.setChildrenHaveRecursiveParent(true)
}
sourceFilterContentArray = topNode.children as! [SourcePathNode]
}
}