/* * vector sigma (https://github.com/vectorsigma72) * Copyright 2020 vector sigma All Rights Reserved. * * The source code contained or described herein and all documents related * to the source code ("Material") are owned by vector sigma. * Title to the Material remains with vector sigma or its suppliers and licensors. * The Material is proprietary of vector sigma and is protected by worldwide copyright. * No part of the Material may be used, copied, reproduced, modified, published, * uploaded, posted, transmitted, distributed, or disclosed in any way without * vector sigma's prior express written permission. * * No license under any patent, copyright, trade secret or other intellectual * property right is granted to or conferred upon you by disclosure or delivery * of the Materials, either expressly, by implication, inducement, estoppel or * otherwise. Any license under such intellectual property rights must be * express and approved by vector sigma in writing. * * Unless otherwise agreed by vector sigma in writing, you may not remove or alter * this notice or any other notice embedded in Materials by vector sigma in any way. * * The license is granted for the CloverBootloader project (i.e. https://github.com/CloverHackyColor/CloverBootloader) * and all the users as long as the Material is used only within the * source code and for the exclusive use of CloverBootloader, which must * be free from any type of payment or commercial service for the license to be valid. */ import Cocoa //MARK: PEOutlineView (NSOutlineView) @available(OSX 10.10, *) final class PEOutlineView: NSOutlineView, NSMenuDelegate { var wrongValue : Bool = false var scrollTimer : Timer? = nil var editorVC : PlistEditorVC? override func drawGrid(inClipRect clipRect: NSRect) { let nRow = self.numberOfRows if nRow > 0 { let lastRowRect = self.rect(ofRow: nRow - 1) let myClipRect = NSMakeRect(0, 0, lastRowRect.size.width, lastRowRect.size.height) let finalClipRect : NSRect = NSIntersectionRect(clipRect, myClipRect) super.drawGrid(inClipRect: finalClipRect) } } override func scrollWheel(with event: NSEvent) { super.scrollWheel(with: event) NotificationCenter.default.post(name: PEOutLineViewEndScrolling, object: nil) if (self.scrollTimer != nil) { if self.scrollTimer!.isValid { self.scrollTimer?.invalidate() } self.scrollTimer = nil } self.scrollTimer = Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(endScrolling), userInfo: nil, repeats: false) } @objc func endScrolling() { self.enumerateAvailableRowViews { _, row in if let cv = self.view(atColumn: 0, row: row, makeIfNecessary: false) as? PETableCellView { cv.hideButtons() if let rv = cv.superview as? PETableRowView { rv.setBorderType() } } } } override func menu(for event: NSEvent) -> NSMenu? { let point = self.convert(event.locationInWindow, from: nil) let row = self.row(at: point) let item = self.item(atRow: row) if (item == nil) { return nil } self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false) return self.getBaseMenu(outlineView: self, for: item!) } func getBaseMenu(outlineView: PEOutlineView, for item: Any) -> NSMenu { let node: PENode = item as! PENode // get a key path for the node! (like Boot->Arguments-> etc) // to do that create an array.. let keyPaths : NSMutableArray = NSMutableArray() var parent: PENode? = node while (parent != nil) && (parent != outlineView.item(atRow: 0) as? PENode) { var key : String = (parent?.ro as! TagData).key let type = ((parent?.parent as! PENode).ro as! TagData).type if type == .Array { // we need the index in this case! let index : Int = (parent?.parent as! PENode).mutableChildren.index(of: parent!) //key = arrayPrefixId + String(index) + arraySuffixId key = localizedItem + " " + String(index) } if keyPaths.count == 0 { keyPaths.add(key) } else { keyPaths.insert(key, at: 0) } parent = parent?.parent as? PENode } let menu = NSMenu(title: "Contextual Menu") let row = outlineView.row(forItem: item) if row > 0 && (outlineView.editorVC?.isEditable)! { menu.addItem(withTitle: "Cut".locale, action: #selector(outlineView.cut(_:)), keyEquivalent: "X") } menu.addItem(withTitle: "Copy".locale, action: #selector(outlineView.copy(_:)), keyEquivalent: "C") if (outlineView.editorVC?.isEditable)! { menu.addItem(withTitle: "Paste".locale, action: #selector(outlineView.paste(_:)), keyEquivalent: "V") } else { return menu } return menu } func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) { print("menu(_ menu: NSMenu, willHighlight item: NSMenuItem?)") } /* Override the validation for the textfield: Don't accept first responder on textfield if the event is called with a menu (left mouse down) if the row is not selected don't become first responder! if already selected 1 click its enough to begin editing! */ override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool { if responder is PETextField { //let field = responder as! PETextField if (event != nil) { if event?.type == NSEvent.EventType.rightMouseDown { return false } else if event?.type == NSEvent.EventType.leftMouseDown { let selected = self.selectedRow if selected > 0 { let point = self.convert((event?.locationInWindow)!, from: nil) let row = self.row(at: point) if self.selectedRow == row { return true } self.selectRowIndexes(IndexSet(integer: selected), byExtendingSelection: false) } return false } } } return super.validateProposedFirstResponder(responder, for: event) } @objc func cut(_: Any) { if !(self.editorVC?.isEditable)! { NSSound.beep() return } let selected = self.selectedRow if selected >= 0 { // don't cut Root! if selected == 0 { NSSound.beep() return } if let item = self.item(atRow: selected) { let root = PENode(representedObject: TagData(key: "Root", type: .Dictionary, value: nil)) let parent = (item as! PENode).parent if parent == nil { NSSound.beep() return } root.mutableChildren.add((item as! PENode).copy()) let plist = gConvertPENodeToPlist(node: root) let pasteboard = NSPasteboard.general pasteboard.declareTypes([NSPasteboard.PasteboardType.string], owner: nil) pasteboard.setString(plist, forType: NSPasteboard.PasteboardType.string) // cut let indexChild = parent?.children?.firstIndex(of: item as! PENode) self.undo_Cut(item: item as! PENode, inParent:parent as! PENode, indexChild: indexChild!) } else { NSSound.beep() } } else { NSSound.beep() } } @objc func copy(_: Any) { let selected = self.selectedRow if selected > 0 { let item = self.item(atRow: selected) let root = PENode(representedObject: TagData(key: "Root", type: .Dictionary, value: nil)) root.mutableChildren.add((item! as! PENode).copy()) let plist = gConvertPENodeToPlist(node: root) let pasteboard = NSPasteboard.general pasteboard.declareTypes([NSPasteboard.PasteboardType.string], owner: nil) pasteboard.setString(plist, forType: NSPasteboard.PasteboardType.string) } else { NSSound.beep() } } @objc func paste(_: Any) { // paste the NSPasteboardTypeString content, if is a plist and if a row is selected. //FIXME: check paste op on the root if !(self.editorVC?.isEditable)! { NSSound.beep() return } let selected = self.selectedRow if selected >= 0 { let plistType = (self.item(atRow: 0) as! PENode).tagdata!.type if plistType == .Dictionary || plistType == .Array { let node = self.item(atRow: selected) as! PENode let pasteboard = NSPasteboard.general var newNode : PENode let data = pasteboard.string(forType: NSPasteboard.PasteboardType.string)!.data(using: .utf8)! let parser = PlistParser(fromData: data, fileType: .xmlPlistv1) if (parser.root != nil) { newNode = parser.root!.mutableChildren[0] as! PENode } else { let data = TagData(key: localizedNewItem, type: .String, value: pasteboard.string(forType: NSPasteboard.PasteboardType.string)! as NSString) newNode = PENode(representedObject: data) } // Are we pasting on Root and is a contenitor? if (node == self.item(atRow: 0) as! PENode) { if !self.isItemExpanded(node) { self.expandItem(node) } gDeduplicateKeyInParent(parent: node, newNode: newNode) self.undo_Paste(item: newNode, inParent: node, indexChild: 0) } else { let parent = node.parent var indexChild = (parent?.children?.firstIndex(of: node))! as Int indexChild += 1 // isLeaf strictly means "no children", but Dictionaries and Array are contenitors anyway! if node.tagdata!.type == .Array || node.tagdata!.type == .Dictionary { let isExpanded = self.isItemExpanded(node) if isExpanded { gDeduplicateKeyInParent(parent: node, newNode: newNode) self.undo_Paste(item: newNode, inParent: node, indexChild: 0) } else { gDeduplicateKeyInParent(parent: parent as! PENode, newNode: newNode) self.undo_Paste(item: newNode, inParent: parent! as! PENode, indexChild: indexChild) } } else { gDeduplicateKeyInParent(parent: parent as! PENode, newNode: newNode) self.undo_Paste(item: newNode, inParent: parent! as! PENode, indexChild: indexChild) } } } else { // plist is not a dictionary or array NSSound.beep() } } else { // no selected row NSSound.beep() } } //MARK: add/remove functions (called from PETableCellViewKey) @objc func addNewItemFromCell(node: PENode, parent: PENode) { self.editorVC?.isAddingNewItem = true let newItemName = localizedNewItem let newNode = PENode(representedObject: TagData(key: newItemName, type: .String, value: "" as NSString)) var indexChild = (parent.children?.firstIndex(of: node))! as Int indexChild += 1 // isLeaf strictly means "no children", but Dictionaries and Array are contenitors anyway! if node.tagdata!.type == .Array || node.tagdata!.type == .Dictionary { if self.isItemExpanded(node) { gDeduplicateKeyInParent(parent: node, newNode: newNode) self.undo_Add(item: newNode, inParent: node, indexChild: 0, target: self) } else { gDeduplicateKeyInParent(parent: parent, newNode: newNode) self.undo_Add(item: newNode, inParent: parent, indexChild: indexChild, target: self) } } else { gDeduplicateKeyInParent(parent: parent, newNode: newNode) self.undo_Add(item: newNode, inParent: parent, indexChild: indexChild, target: self) } self.editorVC?.isAddingNewItem = false } func removeItemFromCell(node: PENode, parent: PENode) { let indexChild = parent.mutableChildren.index(of: node) self.undo_Remove(item: node, inParent: parent, indexChild: indexChild, target: self) } }