blob: 03cef0362714cda5ea83d67d479b75cd6428a6bc [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).
dynamic var explicitlyRecursive: Bool {
return recursive == NSOnState
}
/// This node's recursive UI state (NSOnState/NSOffState/NSMixedState).
dynamic var recursive: Int {
get {
guard let entry = entry as? UISourcePath else { return NSOffState }
if entry.recursive { return NSOnState }
for child in children as! [SourcePathNode] {
if child.recursive != NSOffState {
return NSMixedState
}
}
return NSOffState
}
set {
// No work needs to be done for mixed state, as it does not affect the underlying values.
if newValue == NSMixedState { return }
guard let entry = entry as? UISourcePath else { return }
let enabled = newValue == NSOnState
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
}
}
}
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 == NSOnState || newValue == hasRecursiveEnabledParent { return }
setChildrenHaveRecursiveParent(newValue)
}
}
// TODO(abaire): Use a custom control to override nextState: such that it's never set to mixed via user interaction.
func validateRecursive(_ ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
if let value = ioValue.pointee as? NSNumber {
if value.intValue == NSMixedState {
ioValue.pointee = NSNumber(value: NSOnState 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 = NSOffState
}
}
}
}
// 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 {
dynamic var sourceFilterContentArray: [SourcePathNode] = []
@IBOutlet weak var sourceFilterOutlineView: NSOutlineView!
override func viewDidLoad() {
super.viewDidLoad()
let sourceTargetColumn = sourceFilterOutlineView.tableColumn(withIdentifier: "sourceTargets")!
sourceFilterOutlineView.sortDescriptors = [sourceTargetColumn.sortDescriptorPrototype!]
}
// MARK: - WizardSubviewProtocol
weak var presentingWizardViewController: ConfigEditorWizardViewController? = nil
func wizardSubviewWillActivateMovingForward() {
let document = representedObject as! TulsiGeneratorConfigDocument
sourceFilterContentArray = []
document.updateSourcePaths(populateOutlineView)
// TODO(abaire): Set when toggling selection instead.
document.updateChangeCount(.changeDone) // TODO(abaire): Implement undo functionality.
}
// 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 == NSOnState {
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]
}
}