2014-11-15 03:39:10 +00:00
|
|
|
//
|
2014-11-15 22:47:46 +00:00
|
|
|
// ASProjDoc.swift
|
2014-11-15 03:39:10 +00:00
|
|
|
// AVRsack
|
|
|
|
//
|
|
|
|
// Created by Matthias Neeracher on 11/15/14.
|
|
|
|
// Copyright (c) 2014 Aere Perennius. All rights reserved.
|
|
|
|
//
|
|
|
|
|
2014-12-07 05:20:57 +00:00
|
|
|
import Swift
|
2014-11-15 03:39:10 +00:00
|
|
|
import Cocoa
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
private var keyboardHandler : ACEKeyboardHandler = .ace
|
2014-11-17 04:39:39 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func pushToFront( list: inout [String], front: String) {
|
2014-12-07 05:20:57 +00:00
|
|
|
let kMaxRecents = 8
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
if let existing = list.index(of: front) {
|
2014-12-07 05:20:57 +00:00
|
|
|
if existing == 0 {
|
|
|
|
return
|
|
|
|
} else {
|
2016-11-13 11:03:51 +00:00
|
|
|
list.remove(at: existing)
|
2014-12-07 05:20:57 +00:00
|
|
|
}
|
|
|
|
} else if list.count >= kMaxRecents {
|
|
|
|
list.removeLast()
|
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
list.insert(front, at: 0)
|
2014-12-07 05:20:57 +00:00
|
|
|
}
|
|
|
|
|
2015-03-16 04:39:09 +00:00
|
|
|
class ASProjDoc: NSDocument, NSOutlineViewDelegate, NSMenuDelegate, NSOpenSavePanelDelegate, ACEViewDelegate {
|
2014-11-16 18:39:32 +00:00
|
|
|
@IBOutlet weak var editor : ACEView!
|
2015-07-11 00:58:26 +00:00
|
|
|
@IBOutlet weak var auxEdit : ACEView!
|
|
|
|
@IBOutlet weak var editors : NSStackView!
|
2014-11-16 18:39:32 +00:00
|
|
|
@IBOutlet weak var outline : NSOutlineView!
|
2014-12-07 05:20:57 +00:00
|
|
|
@IBOutlet weak var boardTool: NSPopUpButton!
|
|
|
|
@IBOutlet weak var progTool : NSPopUpButton!
|
|
|
|
@IBOutlet weak var portTool : NSPopUpButton!
|
2015-03-14 20:15:27 +00:00
|
|
|
@IBOutlet weak var printView: ACEView!
|
2014-12-07 05:20:57 +00:00
|
|
|
|
2014-11-28 13:18:53 +00:00
|
|
|
let files = ASFileTree()
|
|
|
|
let builder = ASBuilder()
|
2014-11-16 18:39:32 +00:00
|
|
|
var mainEditor : ASFileNode?
|
2016-11-13 11:03:51 +00:00
|
|
|
var currentTheme : ACETheme = .xcode
|
2014-11-17 04:39:39 +00:00
|
|
|
var fontSize : UInt = 12
|
2014-12-29 06:35:07 +00:00
|
|
|
var themeObserver : AnyObject!
|
|
|
|
var serialObserver : AnyObject!
|
2014-12-10 02:51:26 +00:00
|
|
|
dynamic var board = "uno"
|
|
|
|
dynamic var programmer = "arduino"
|
|
|
|
dynamic var port : String = ""
|
2014-12-07 05:20:57 +00:00
|
|
|
var recentBoards = [String]()
|
|
|
|
var recentProgrammers = [String]()
|
2016-11-14 00:37:13 +00:00
|
|
|
var logModified = Date.distantPast
|
2014-12-08 01:55:19 +00:00
|
|
|
var logSize = 0
|
2016-11-13 11:03:51 +00:00
|
|
|
var updateLogTimer : Timer?
|
2015-03-16 04:39:09 +00:00
|
|
|
var printingDone : () -> () = {}
|
2016-11-14 00:37:13 +00:00
|
|
|
var printModDate : Date?
|
2015-03-16 04:39:09 +00:00
|
|
|
var printRevision : String?
|
2015-03-16 14:11:16 +00:00
|
|
|
var printShowPanel = false
|
2015-07-11 00:58:26 +00:00
|
|
|
var jumpingToIssue = false
|
|
|
|
var currentIssueLine = -1
|
2015-03-16 04:39:09 +00:00
|
|
|
|
2014-12-07 05:20:57 +00:00
|
|
|
let kVersionKey = "Version"
|
|
|
|
let kCurVersion = 1.0
|
|
|
|
let kFilesKey = "Files"
|
|
|
|
let kThemeKey = "Theme"
|
|
|
|
let kFontSizeKey = "FontSize"
|
|
|
|
let kBindingsKey = "Bindings"
|
|
|
|
let kBoardKey = "Board"
|
|
|
|
let kProgrammerKey = "Programmer"
|
|
|
|
let kPortKey = "Port"
|
|
|
|
let kRecentBoardsKey = "RecentBoards"
|
|
|
|
let kRecentProgrammersKey = "RecentProgrammers"
|
2014-11-17 04:39:39 +00:00
|
|
|
|
2014-11-17 01:29:55 +00:00
|
|
|
// MARK: Initialization / Finalization
|
|
|
|
|
2014-11-15 03:39:10 +00:00
|
|
|
override init() {
|
|
|
|
super.init()
|
2016-11-13 11:03:51 +00:00
|
|
|
let userDefaults = UserDefaults.standard
|
|
|
|
if let themeName = userDefaults.string(forKey: kThemeKey) {
|
|
|
|
if let themeId = ACEView.themeIdByName(themeName: themeName) {
|
2014-12-31 03:16:43 +00:00
|
|
|
currentTheme = themeId
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
if let handlerName = userDefaults.string(forKey: kBindingsKey) {
|
|
|
|
if let handlerId = ACEView.handlerIdByName(handlerName: handlerName) {
|
2014-12-31 03:16:43 +00:00
|
|
|
keyboardHandler = handlerId
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-09 05:54:30 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
fontSize = UInt(userDefaults.integer(forKey: kFontSizeKey))
|
|
|
|
board = userDefaults.string(forKey: kBoardKey)!
|
|
|
|
programmer = userDefaults.string(forKey: kProgrammerKey)!
|
|
|
|
port = userDefaults.string(forKey: kPortKey)!
|
|
|
|
recentBoards = userDefaults.object(forKey: kRecentBoardsKey) as! [String]
|
|
|
|
recentProgrammers = userDefaults.object(forKey: kRecentProgrammersKey) as! [String]
|
2014-12-01 02:34:53 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
let nc = NotificationCenter.default
|
2016-11-13 12:13:40 +00:00
|
|
|
themeObserver = nc.addObserver(forName: NSNotification.Name(kBindingsKey), object: nil, queue: nil, using: { (NSNotification) in
|
2015-07-10 22:48:07 +00:00
|
|
|
self.editor?.setKeyboardHandler(keyboardHandler)
|
2014-12-09 05:54:30 +00:00
|
|
|
})
|
2016-11-13 12:13:40 +00:00
|
|
|
serialObserver = nc.addObserver(forName: NSNotification.Name(kASSerialPortsChanged), object: nil, queue: nil, using: { (NSNotification) in
|
2015-07-10 22:48:07 +00:00
|
|
|
if self.portTool != nil {
|
|
|
|
self.rebuildPortMenu()
|
|
|
|
}
|
2014-12-09 05:54:30 +00:00
|
|
|
})
|
2014-12-01 02:34:53 +00:00
|
|
|
updateLogTimer =
|
2016-11-13 11:03:51 +00:00
|
|
|
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ASProjDoc.updateLog(_:)), userInfo: nil, repeats: true)
|
2014-11-15 03:39:10 +00:00
|
|
|
}
|
2014-11-16 20:10:04 +00:00
|
|
|
override func finalize() {
|
|
|
|
saveCurEditor()
|
2016-11-13 11:03:51 +00:00
|
|
|
NotificationCenter.default.removeObserver(themeObserver)
|
|
|
|
NotificationCenter.default.removeObserver(serialObserver)
|
2014-11-16 20:10:04 +00:00
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
override func windowControllerDidLoadNib(_ aController: NSWindowController) {
|
2014-11-15 03:39:10 +00:00
|
|
|
super.windowControllerDidLoadNib(aController)
|
2014-11-17 04:39:39 +00:00
|
|
|
editor.setShowPrintMargin(false)
|
|
|
|
editor.setTheme(currentTheme)
|
|
|
|
editor.setKeyboardHandler(keyboardHandler)
|
|
|
|
editor.setFontSize(fontSize)
|
2015-03-16 04:39:09 +00:00
|
|
|
editor.delegate = self
|
|
|
|
|
2015-07-11 00:58:26 +00:00
|
|
|
auxEdit.setShowPrintMargin(false)
|
|
|
|
auxEdit.setTheme(currentTheme)
|
|
|
|
auxEdit.setKeyboardHandler(keyboardHandler)
|
|
|
|
auxEdit.setFontSize(fontSize)
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
editors.setViews([editor], in: .top)
|
2015-07-11 00:58:26 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
outline.register(forDraggedTypes: [files.kLocalReorderPasteboardType])
|
|
|
|
outline.setDraggingSourceOperationMask(NSDragOperation.every, forLocal: true)
|
|
|
|
outline.setDraggingSourceOperationMask([], forLocal: false)
|
2015-12-25 02:00:20 +00:00
|
|
|
|
2016-11-14 00:37:13 +00:00
|
|
|
outline.dataSource = files
|
2014-11-16 20:10:04 +00:00
|
|
|
files.apply() { node in
|
|
|
|
if let group = node as? ASFileGroup {
|
|
|
|
if group.expanded {
|
|
|
|
self.outline.expandItem(node)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-11-13 12:13:40 +00:00
|
|
|
outlineViewSelectionDidChange(Notification(name: Notification.Name(""), object: nil))
|
2014-12-07 05:20:57 +00:00
|
|
|
menuNeedsUpdate(boardTool.menu!)
|
|
|
|
menuNeedsUpdate(progTool.menu!)
|
2014-12-09 05:54:30 +00:00
|
|
|
rebuildPortMenu()
|
2016-11-13 11:03:51 +00:00
|
|
|
updateChangeCount(.changeCleared)
|
2014-11-15 03:39:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override class func autosavesInPlace() -> Bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
override var windowNibName: String? {
|
|
|
|
// Returns the nib file name of the document
|
|
|
|
// If you need to use a subclass of NSWindowController or if your document supports multiple NSWindowControllers, you should remove this property and override -makeWindowControllers instead.
|
2014-11-15 22:47:46 +00:00
|
|
|
return "ASProjDoc"
|
2014-11-15 03:39:10 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 01:29:55 +00:00
|
|
|
// MARK: Load / Save
|
|
|
|
|
|
|
|
func saveCurEditor() {
|
|
|
|
if let file = (mainEditor as? ASFileItem) {
|
2015-11-16 01:56:33 +00:00
|
|
|
do {
|
2016-11-14 00:37:13 +00:00
|
|
|
try editor.string().write(to: file.url, atomically: true, encoding: String.Encoding.utf8)
|
2015-11-16 01:56:33 +00:00
|
|
|
} catch _ {
|
|
|
|
}
|
2014-11-17 01:29:55 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-14 00:37:13 +00:00
|
|
|
|
|
|
|
override func data(ofType typeName: String) throws -> Data {
|
2014-11-17 04:39:39 +00:00
|
|
|
let data = [kVersionKey: kCurVersion,
|
2016-11-13 11:03:51 +00:00
|
|
|
kThemeKey: ACEThemeNames.name(for: currentTheme),
|
2014-11-17 04:39:39 +00:00
|
|
|
kFontSizeKey: fontSize,
|
2014-12-07 05:20:57 +00:00
|
|
|
kFilesKey: files.propertyList(),
|
|
|
|
kBoardKey: board,
|
|
|
|
kProgrammerKey: programmer,
|
2015-07-10 22:48:07 +00:00
|
|
|
kPortKey: port,
|
|
|
|
kRecentBoardsKey: recentBoards,
|
|
|
|
kRecentProgrammersKey: recentProgrammers
|
2014-12-07 05:20:57 +00:00
|
|
|
]
|
2016-11-14 00:37:13 +00:00
|
|
|
return try PropertyListSerialization.data(fromPropertyList: data, format:.xml, options:0)
|
2014-11-15 03:39:10 +00:00
|
|
|
}
|
|
|
|
|
2015-01-12 03:24:29 +00:00
|
|
|
func updateProjectURL() {
|
2016-11-13 11:03:51 +00:00
|
|
|
files.setProjectURL(url: fileURL!)
|
|
|
|
builder.setProjectURL(url: fileURL!)
|
2015-01-12 03:24:29 +00:00
|
|
|
}
|
|
|
|
|
2016-11-14 00:37:13 +00:00
|
|
|
func importProject(url: URL) throws {
|
|
|
|
let existingProject = url.appendingPathComponent(url.lastPathComponent+".avrsackproj")
|
|
|
|
if let hasProject = try? existingProject.checkResourceIsReachable(), hasProject {
|
2014-11-16 18:39:32 +00:00
|
|
|
fileURL = existingProject
|
2016-11-14 00:37:13 +00:00
|
|
|
try read(from: existingProject, ofType:"Project")
|
2015-11-16 01:56:33 +00:00
|
|
|
return
|
2014-11-16 18:39:32 +00:00
|
|
|
}
|
|
|
|
let filesInProject =
|
2016-11-14 00:37:13 +00:00
|
|
|
(try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil,
|
|
|
|
options: .skipsHiddenFiles))
|
2015-01-12 03:24:29 +00:00
|
|
|
updateProjectURL()
|
2014-11-16 18:39:32 +00:00
|
|
|
for file in filesInProject {
|
2016-11-14 00:37:13 +00:00
|
|
|
files.addFileURL(url: file)
|
2014-11-16 18:39:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-14 00:37:13 +00:00
|
|
|
override func read(from url: URL, ofType typeName: String) throws {
|
2014-11-16 18:39:32 +00:00
|
|
|
if typeName == "Arduino Source File" {
|
2016-11-14 00:37:13 +00:00
|
|
|
let projectURL = url.deletingPathExtension().appendingPathExtension("avrsackproj")
|
|
|
|
try importProject(url: url.deletingLastPathComponent())
|
2015-11-16 01:56:33 +00:00
|
|
|
fileURL = projectURL
|
2016-11-14 00:37:13 +00:00
|
|
|
try write(to: projectURL, ofType: "Project", for: .saveAsOperation, originalContentsURL: nil)
|
2015-11-16 01:56:33 +00:00
|
|
|
} else {
|
2015-04-29 03:21:23 +00:00
|
|
|
fileURL = url
|
2016-11-14 00:37:13 +00:00
|
|
|
try super.read(from: url, ofType: typeName)
|
2014-11-16 20:10:04 +00:00
|
|
|
}
|
2014-11-16 18:39:32 +00:00
|
|
|
}
|
2016-11-14 00:37:13 +00:00
|
|
|
override func read(from data: Data, ofType typeName: String) throws {
|
2015-02-14 16:43:20 +00:00
|
|
|
if typeName != ("Project" as String) {
|
2015-11-16 01:56:33 +00:00
|
|
|
throw NSError(domain: "AVRSack", code: 0, userInfo: nil)
|
2014-11-17 01:29:55 +00:00
|
|
|
}
|
2015-01-12 03:24:29 +00:00
|
|
|
updateProjectURL()
|
2015-04-04 21:43:47 +00:00
|
|
|
let projectData =
|
2016-11-14 00:37:13 +00:00
|
|
|
(try PropertyListSerialization.propertyList(from: data, options:[], format:nil)) as! NSDictionary
|
2015-02-14 16:43:20 +00:00
|
|
|
let projectVersion = projectData[kVersionKey] as! Double
|
2014-11-17 01:29:55 +00:00
|
|
|
assert(projectVersion <= floor(kCurVersion+1.0), "Project version too new for this app")
|
2015-02-14 16:43:20 +00:00
|
|
|
if let themeName = projectData[kThemeKey] as? String {
|
2016-11-14 00:37:13 +00:00
|
|
|
if let themeId = ACEView.themeIdByName(themeName: themeName) {
|
2014-12-31 03:16:43 +00:00
|
|
|
currentTheme = themeId
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if let fontSz = projectData[kFontSizeKey] as? Int {
|
|
|
|
fontSize = UInt(fontSz)
|
|
|
|
}
|
2016-11-13 12:00:29 +00:00
|
|
|
files.readPropertyList(prop: projectData[kFilesKey] as? Dictionary<String, AnyObject> ?? [:])
|
2014-12-07 05:20:57 +00:00
|
|
|
board = (projectData[kBoardKey] as? String) ?? board
|
|
|
|
programmer = (projectData[kProgrammerKey] as? String) ?? programmer
|
|
|
|
port = (projectData[kPortKey] as? String) ?? port
|
|
|
|
recentBoards = (projectData[kRecentBoardsKey] as? [String]) ?? recentBoards
|
|
|
|
recentProgrammers = (projectData[kRecentProgrammersKey] as? [String]) ?? recentProgrammers
|
2016-11-13 11:03:51 +00:00
|
|
|
updateChangeCount(.changeCleared)
|
2014-11-17 01:29:55 +00:00
|
|
|
}
|
2015-04-29 03:21:23 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
override func duplicate(_ sender: AnyObject?) {
|
|
|
|
let app = NSApplication.shared().delegate as! ASApplication
|
2016-11-14 00:37:13 +00:00
|
|
|
app.openTemplate(template: fileURL!.deletingLastPathComponent(), fromReadOnly:false)
|
2015-04-29 03:21:23 +00:00
|
|
|
}
|
|
|
|
|
2015-11-16 01:56:33 +00:00
|
|
|
func updateLog(_: AnyObject?) {
|
2014-12-01 02:34:53 +00:00
|
|
|
if let logNode = mainEditor as? ASLogNode {
|
2016-11-14 00:37:13 +00:00
|
|
|
guard let fileURL = fileURL else { return }
|
|
|
|
let url = fileURL.deletingLastPathComponent().appendingPathComponent(logNode.path)
|
|
|
|
if let values = try? url.resourceValues(forKeys: [.attributeModificationDateKey, .fileSizeKey]) {
|
|
|
|
if values.attributeModificationDate!.compare(logModified) == .orderedDescending
|
|
|
|
|| values.fileSize! != logSize
|
|
|
|
{
|
|
|
|
var enc : String.Encoding = .utf8
|
|
|
|
let newText = try? String(contentsOf: url, usedEncoding:&enc)
|
|
|
|
editor.setString(newText ?? "")
|
|
|
|
editor.gotoLine(1000000000, column: 0, animated: true)
|
|
|
|
logModified = values.attributeModificationDate!
|
|
|
|
logSize = values.fileSize!
|
|
|
|
currentIssueLine = -1
|
|
|
|
}
|
2014-12-01 02:34:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func selectNode(selection: ASFileNode?) {
|
2014-11-16 18:39:32 +00:00
|
|
|
if selection !== mainEditor {
|
|
|
|
saveCurEditor()
|
|
|
|
}
|
|
|
|
if let file = (selection as? ASFileItem) {
|
2016-11-14 00:37:13 +00:00
|
|
|
var enc : String.Encoding = .utf8
|
|
|
|
let contents = try? String(contentsOf:file.url, usedEncoding:&enc)
|
|
|
|
editor.setString(contents ?? "")
|
2015-03-14 20:15:27 +00:00
|
|
|
editor.setMode(file.type.aceMode)
|
2014-11-17 04:39:39 +00:00
|
|
|
editor.alphaValue = 1.0
|
2014-12-01 02:34:53 +00:00
|
|
|
mainEditor = selection
|
2015-11-16 01:56:33 +00:00
|
|
|
} else if selection is ASLogNode {
|
2014-12-01 02:34:53 +00:00
|
|
|
editor.setString("")
|
2016-11-13 11:03:51 +00:00
|
|
|
editor.setMode(.text)
|
2014-12-01 02:34:53 +00:00
|
|
|
editor.alphaValue = 0.8
|
2016-11-13 11:03:51 +00:00
|
|
|
logModified = NSDate.distantPast
|
2014-12-08 01:55:19 +00:00
|
|
|
logSize = -1
|
2014-12-01 02:34:53 +00:00
|
|
|
mainEditor = selection
|
|
|
|
updateLog(nil)
|
2014-11-17 04:39:39 +00:00
|
|
|
} else {
|
|
|
|
editor.alphaValue = 0.0
|
2014-11-16 18:39:32 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-09 05:13:45 +00:00
|
|
|
func selectNodeInOutline(selection: ASFileNode) {
|
2016-11-14 00:37:13 +00:00
|
|
|
let selectedIndexes = IndexSet(integer: outline.row(forItem: selection))
|
2014-12-09 05:13:45 +00:00
|
|
|
outline.selectRowIndexes(selectedIndexes, byExtendingSelection: false)
|
|
|
|
}
|
2015-01-12 00:53:35 +00:00
|
|
|
func selectedFiles() -> [ASFileItem] {
|
|
|
|
var selection = [ASFileItem]()
|
2016-11-14 00:37:13 +00:00
|
|
|
for index in outline.selectedRowIndexes {
|
|
|
|
if let file = self.outline.item(atRow: index) as? ASFileItem {
|
2015-01-12 00:53:35 +00:00
|
|
|
selection.append(file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return selection
|
|
|
|
}
|
2014-12-01 02:34:53 +00:00
|
|
|
|
2015-03-14 20:15:27 +00:00
|
|
|
// MARK: Printing
|
|
|
|
|
2016-11-14 00:37:13 +00:00
|
|
|
override func print(withSettings printSettings: [String : AnyObject], showPrintPanel: Bool, delegate: AnyObject?, didPrint didPrintSelector: Selector?, contextInfo: UnsafeMutablePointer<Void>?) {
|
2015-03-16 04:39:09 +00:00
|
|
|
printingDone =
|
|
|
|
{ () -> () in
|
2015-03-16 15:02:35 +00:00
|
|
|
InvokeCallback(delegate, didPrintSelector, contextInfo);
|
2015-03-16 04:39:09 +00:00
|
|
|
}
|
2016-11-14 00:37:13 +00:00
|
|
|
printModDate = nil
|
2015-03-16 15:02:09 +00:00
|
|
|
if let logNode = mainEditor as? ASLogNode {
|
2016-11-14 00:37:13 +00:00
|
|
|
if let url = fileURL?.deletingLastPathComponent().appendingPathComponent(logNode.path),
|
|
|
|
let values = try? url.resourceValues(forKeys: [.attributeModificationDateKey])
|
|
|
|
{
|
|
|
|
printModDate = values.attributeModificationDate
|
2015-03-16 15:02:09 +00:00
|
|
|
}
|
2016-11-14 00:37:13 +00:00
|
|
|
}
|
|
|
|
if printModDate == nil {
|
2015-03-16 15:02:09 +00:00
|
|
|
printModDate = mainEditor?.modDate()
|
|
|
|
}
|
2015-03-16 04:39:09 +00:00
|
|
|
printRevision = mainEditor?.revision()
|
2015-03-16 14:11:16 +00:00
|
|
|
printShowPanel = showPrintPanel
|
2015-03-16 04:39:09 +00:00
|
|
|
|
|
|
|
editor.print(self)
|
|
|
|
}
|
|
|
|
|
2015-03-16 14:11:16 +00:00
|
|
|
func printInformation() -> NSPrintInfo! {
|
2015-11-16 01:56:33 +00:00
|
|
|
let info = printInfo.copy() as! NSPrintInfo
|
2015-03-16 04:39:09 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Minimize margins
|
|
|
|
//
|
|
|
|
let kXMargin : CGFloat = 50.0
|
|
|
|
let kYMargin : CGFloat = 50.0
|
|
|
|
let paperSize = info.paperSize
|
|
|
|
var maxBounds = info.imageablePageBounds
|
|
|
|
|
|
|
|
if paperSize.width - maxBounds.size.width < kXMargin {
|
|
|
|
let adjust = kXMargin-paperSize.width+maxBounds.size.width
|
|
|
|
maxBounds.origin.x += 0.5*adjust
|
|
|
|
maxBounds.size.width -= adjust
|
|
|
|
}
|
|
|
|
if paperSize.height - maxBounds.size.height < kYMargin {
|
|
|
|
let adjust = kYMargin-paperSize.height+maxBounds.size.height
|
|
|
|
maxBounds.origin.y += 0.5*adjust
|
|
|
|
maxBounds.size.height -= adjust
|
|
|
|
}
|
|
|
|
info.leftMargin = maxBounds.origin.x
|
|
|
|
info.bottomMargin = maxBounds.origin.y
|
|
|
|
info.topMargin = paperSize.height-maxBounds.size.height-info.bottomMargin
|
|
|
|
info.rightMargin = paperSize.width-maxBounds.size.width-info.leftMargin
|
|
|
|
|
|
|
|
return info
|
|
|
|
}
|
|
|
|
|
2015-03-16 14:11:16 +00:00
|
|
|
func startPrintOperation(printOp: NSPrintOperation) {
|
2015-11-16 01:56:33 +00:00
|
|
|
if let editorName = mainEditor?.name {
|
|
|
|
printOp.jobTitle = editorName
|
|
|
|
} else if let fileName = fileURL?.lastPathComponent {
|
2016-11-13 11:03:51 +00:00
|
|
|
printOp.jobTitle = (fileName as NSString).deletingLastPathComponent
|
2015-11-16 01:56:33 +00:00
|
|
|
} else {
|
|
|
|
printOp.jobTitle = "Untitled"
|
|
|
|
}
|
2015-03-16 14:11:16 +00:00
|
|
|
printOp.showsPrintPanel = printShowPanel
|
2015-03-16 04:39:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func printHeaderHeight() -> Float {
|
|
|
|
return 41.0
|
|
|
|
}
|
|
|
|
|
|
|
|
func printFooterHeight() -> Float {
|
|
|
|
return 20.0
|
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func drawPrintHeader(forPage pageNo: Int32, in r: NSRect) {
|
2015-03-16 04:39:09 +00:00
|
|
|
var rect = r
|
|
|
|
rect.origin.y += 5.0
|
|
|
|
rect.size.height -= 5.0
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
let ctx = NSGraphicsContext.current()!
|
2015-03-16 04:39:09 +00:00
|
|
|
ctx.saveGraphicsState()
|
|
|
|
NSColor(white: 0.95, alpha: 1.0).setFill()
|
|
|
|
var wideBox = rect
|
|
|
|
wideBox.size.height = 20.0
|
|
|
|
wideBox.origin.y += 0.5*(rect.size.height-wideBox.size.height)
|
|
|
|
NSRectFill(wideBox)
|
|
|
|
|
|
|
|
NSColor(white: 0.7, alpha: 1.0).setFill()
|
|
|
|
var pageNoBox = rect
|
|
|
|
pageNoBox.size.width = 50.0
|
|
|
|
pageNoBox.origin.x += 0.5*(rect.size.width-pageNoBox.size.width)
|
|
|
|
NSRectFill(pageNoBox)
|
|
|
|
ctx.restoreGraphicsState()
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
let pageNoFont = NSFont.userFixedPitchFont(ofSize: 25.0)!
|
2015-03-16 04:39:09 +00:00
|
|
|
let pageNoAttr = [
|
|
|
|
NSFontAttributeName: pageNoFont,
|
2016-11-13 11:03:51 +00:00
|
|
|
NSForegroundColorAttributeName: NSColor.white,
|
2015-03-16 04:39:09 +00:00
|
|
|
NSStrokeWidthAttributeName: -5.0]
|
|
|
|
let pageNoStr = "\(pageNo)"
|
2016-11-13 11:03:51 +00:00
|
|
|
let pageNoSize = pageNoStr.size(withAttributes: pageNoAttr)
|
2015-03-16 04:39:09 +00:00
|
|
|
let pageNoAt = NSPoint(
|
|
|
|
x: pageNoBox.origin.x+0.5*(pageNoBox.size.width-pageNoSize.width),
|
|
|
|
y: pageNoBox.origin.y+3.5)
|
2016-11-13 11:03:51 +00:00
|
|
|
pageNoStr.draw(at: pageNoAt, withAttributes:pageNoAttr)
|
2015-03-16 04:39:09 +00:00
|
|
|
|
|
|
|
let kXOffset : CGFloat = 5.0
|
2016-11-13 11:03:51 +00:00
|
|
|
let titleFont = NSFont.userFont(ofSize: 12.0)!
|
2015-03-16 04:39:09 +00:00
|
|
|
let titleAttr = [NSFontAttributeName:titleFont]
|
|
|
|
var titleAt = NSPoint(
|
|
|
|
x: wideBox.origin.x+kXOffset,
|
|
|
|
y: wideBox.origin.y+0.5*(wideBox.size.height-titleFont.ascender+titleFont.descender))
|
|
|
|
|
|
|
|
if let fileNameStr = mainEditor?.name {
|
2016-11-13 11:03:51 +00:00
|
|
|
fileNameStr.draw(at: titleAt, withAttributes:titleAttr)
|
2015-03-16 04:39:09 +00:00
|
|
|
}
|
2015-11-16 01:56:33 +00:00
|
|
|
if let projectNameStr = fileURL?.lastPathComponent {
|
2016-11-13 11:03:51 +00:00
|
|
|
let projectNameTrimmed = (projectNameStr as NSString).deletingPathExtension
|
|
|
|
let projectNameSize = projectNameTrimmed.size(withAttributes: titleAttr)
|
2015-03-16 04:39:09 +00:00
|
|
|
titleAt.x = wideBox.origin.x+wideBox.size.width-projectNameSize.width-kXOffset
|
2016-11-13 11:03:51 +00:00
|
|
|
projectNameTrimmed.draw(at: titleAt, withAttributes:titleAttr)
|
2015-03-16 04:39:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func drawPrintFooter(forPage pageNo: Int32, in r: NSRect) {
|
2015-03-16 04:39:09 +00:00
|
|
|
var rect = r
|
|
|
|
rect.size.height -= 5.0
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
let ctx = NSGraphicsContext.current()!
|
2015-03-16 04:39:09 +00:00
|
|
|
ctx.saveGraphicsState()
|
|
|
|
NSColor(white: 0.95, alpha: 1.0).setFill()
|
|
|
|
NSRectFill(rect)
|
|
|
|
ctx.restoreGraphicsState()
|
|
|
|
|
|
|
|
let kXOffset : CGFloat = 5.0
|
2016-11-13 11:03:51 +00:00
|
|
|
let footFont = NSFont.userFixedPitchFont(ofSize: 10.0)!
|
2015-03-16 04:39:09 +00:00
|
|
|
let footAttr = [NSFontAttributeName:footFont]
|
|
|
|
var footAt = NSPoint(
|
|
|
|
x: rect.origin.x+kXOffset,
|
|
|
|
y: rect.origin.y+0.5*(rect.size.height-footFont.ascender+footFont.descender))
|
|
|
|
|
|
|
|
if let revisionStr = printRevision {
|
2016-11-13 11:03:51 +00:00
|
|
|
revisionStr.draw(at: footAt, withAttributes:footAttr)
|
2015-03-16 04:39:09 +00:00
|
|
|
}
|
|
|
|
if let modDate = printModDate
|
|
|
|
{
|
2016-11-13 11:03:51 +00:00
|
|
|
let dateFormatter = DateFormatter()
|
2015-03-16 04:39:09 +00:00
|
|
|
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
|
2016-11-14 00:37:13 +00:00
|
|
|
let modDateStr = dateFormatter.string(from: modDate)
|
|
|
|
let modDateSize = modDateStr.size(withAttributes: footAttr)
|
2015-03-16 04:39:09 +00:00
|
|
|
footAt.x = rect.origin.x+rect.size.width-modDateSize.width-kXOffset
|
2016-11-14 00:37:13 +00:00
|
|
|
modDateStr.draw(at: footAt, withAttributes:footAttr)
|
2015-03-16 04:39:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 14:11:16 +00:00
|
|
|
func endPrintOperation() {
|
2015-03-16 04:39:09 +00:00
|
|
|
printingDone()
|
2015-03-14 20:15:27 +00:00
|
|
|
}
|
|
|
|
|
2014-12-01 02:34:53 +00:00
|
|
|
// MARK: Outline View Delegate
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func outlineViewSelectionDidChange(_ notification: Notification) {
|
|
|
|
willChangeValue(forKey: "hasSelection")
|
2015-07-11 00:58:26 +00:00
|
|
|
if !jumpingToIssue {
|
2016-11-13 11:03:51 +00:00
|
|
|
editors.setViews([], in: .bottom)
|
2015-07-11 00:58:26 +00:00
|
|
|
}
|
2015-01-12 00:53:35 +00:00
|
|
|
if outline.numberOfSelectedRows < 2 {
|
2016-11-13 11:03:51 +00:00
|
|
|
selectNode(selection: outline.item(atRow: outline.selectedRow) as! ASFileNode?)
|
2015-01-12 00:53:35 +00:00
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
didChangeValue(forKey: "hasSelection")
|
2014-12-01 02:34:53 +00:00
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
func outlineViewItemDidExpand(_ notification: Notification) {
|
2015-02-14 16:43:20 +00:00
|
|
|
let group = notification.userInfo!["NSObject"] as! ASFileGroup
|
2014-11-16 20:10:04 +00:00
|
|
|
group.expanded = true
|
2016-11-13 11:03:51 +00:00
|
|
|
updateChangeCount(.changeDone)
|
2014-11-16 20:10:04 +00:00
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
func outlineViewItemDidCollapse(_ notification: Notification) {
|
2015-02-14 16:43:20 +00:00
|
|
|
let group = notification.userInfo!["NSObject"] as! ASFileGroup
|
2014-11-16 20:10:04 +00:00
|
|
|
group.expanded = false
|
2016-11-13 11:03:51 +00:00
|
|
|
updateChangeCount(.changeDone)
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
func outlineView(_ outlineView: NSOutlineView, willDisplayCell cell: AnyObject, for tableColumn: NSTableColumn?, item: AnyObject) {
|
2015-01-11 01:53:50 +00:00
|
|
|
if let textCell = cell as? NSTextFieldCell {
|
2016-11-14 00:37:13 +00:00
|
|
|
textCell.textColor = NSColor.black
|
2015-01-11 01:53:50 +00:00
|
|
|
if item === files.root || item === files.buildLog || item === files.uploadLog || item === files.disassembly {
|
2016-11-13 11:03:51 +00:00
|
|
|
textCell.font = NSFont.boldSystemFont(ofSize: 13.0)
|
2015-01-11 01:53:50 +00:00
|
|
|
} else {
|
2016-11-13 11:03:51 +00:00
|
|
|
textCell.font = NSFont.systemFont(ofSize: 13.0)
|
2015-02-14 16:43:20 +00:00
|
|
|
if !(item as! ASFileNode).exists() {
|
2016-11-14 00:37:13 +00:00
|
|
|
textCell.textColor = NSColor.red
|
2015-01-11 01:53:50 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-01 02:34:53 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
func outlineView(_ outlineView: NSOutlineView, shouldTrackCell cell: NSCell, for tableColumn: NSTableColumn?, item: AnyObject) -> Bool {
|
|
|
|
return outlineView.isRowSelected(outlineView.row(forItem: item))
|
2015-12-25 02:00:20 +00:00
|
|
|
}
|
2015-01-12 00:53:35 +00:00
|
|
|
|
|
|
|
// MARK: File manipulation
|
2015-11-16 01:56:33 +00:00
|
|
|
@IBAction func delete(_: AnyObject) {
|
2015-01-12 00:53:35 +00:00
|
|
|
let selection = selectedFiles()
|
|
|
|
var name : String
|
|
|
|
var ref : String
|
|
|
|
if selection.count == 1 {
|
2016-11-14 00:37:13 +00:00
|
|
|
name = "file “\(selection[0].url.lastPathComponent)”"
|
2015-01-12 00:53:35 +00:00
|
|
|
ref = "reference to it"
|
|
|
|
} else {
|
|
|
|
name = "\(selection.count) selected files"
|
|
|
|
ref = "references to them"
|
|
|
|
}
|
|
|
|
let alert = NSAlert()
|
|
|
|
alert.messageText =
|
|
|
|
"Do you want to move the \(name) to the Trash, or only remove the \(ref)?"
|
2016-11-13 11:03:51 +00:00
|
|
|
alert.addButton(withTitle: "Move to Trash")
|
|
|
|
alert.addButton(withTitle: selection.count == 1 ? "Remove Reference" : "Remove References")
|
|
|
|
alert.addButton(withTitle: "Cancel")
|
2015-11-16 01:56:33 +00:00
|
|
|
alert.buttons[0].keyEquivalent = ""
|
|
|
|
alert.buttons[1].keyEquivalent = "\r"
|
2016-11-13 11:03:51 +00:00
|
|
|
alert.beginSheetModal(for: outline.window!) { (response) in
|
2015-01-12 00:53:35 +00:00
|
|
|
if response != NSAlertThirdButtonReturn {
|
|
|
|
if response == NSAlertFirstButtonReturn {
|
2016-11-14 00:37:13 +00:00
|
|
|
NSWorkspace.shared().recycle(selection.map {$0.url}, completionHandler:nil)
|
2015-01-12 00:53:35 +00:00
|
|
|
}
|
|
|
|
self.files.apply { (node) in
|
|
|
|
if let group = node as? ASFileGroup {
|
|
|
|
for file in selection {
|
2016-11-13 11:03:51 +00:00
|
|
|
for (groupIdx, groupItem) in group.children.enumerated() {
|
2015-01-12 00:53:35 +00:00
|
|
|
if file as ASFileNode === groupItem {
|
2016-11-13 11:03:51 +00:00
|
|
|
group.children.remove(at: groupIdx)
|
2015-01-12 00:53:35 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.outline.deselectAll(self)
|
|
|
|
self.outline.reloadData()
|
2016-11-13 11:03:51 +00:00
|
|
|
self.updateChangeCount(.changeDone)
|
2015-01-12 00:53:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-16 01:56:33 +00:00
|
|
|
@IBAction func add(_: AnyObject) {
|
2015-01-12 00:53:35 +00:00
|
|
|
let panel = NSOpenPanel()
|
|
|
|
panel.canChooseFiles = true
|
|
|
|
panel.canChooseDirectories = false
|
|
|
|
panel.allowsMultipleSelection = true
|
|
|
|
panel.allowedFileTypes = ["h", "hpp", "hh", "c", "cxx", "c++", "cpp", "cc", "ino", "s", "md"]
|
|
|
|
panel.delegate = self
|
2016-11-13 11:03:51 +00:00
|
|
|
panel.beginSheetModal(for: outline.window!, completionHandler: { (returnCode: Int) -> Void in
|
2015-01-12 00:53:35 +00:00
|
|
|
if returnCode == NSFileHandlingPanelOKButton {
|
2016-11-13 11:03:51 +00:00
|
|
|
for url in panel.urls {
|
|
|
|
self.files.addFileURL(url: url)
|
2015-01-12 00:53:35 +00:00
|
|
|
}
|
|
|
|
self.outline.deselectAll(self)
|
|
|
|
self.outline.reloadData()
|
2016-11-13 11:03:51 +00:00
|
|
|
self.updateChangeCount(.changeDone)
|
2015-01-12 00:53:35 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func panel(_ panel:AnyObject, shouldEnable url:URL) -> Bool {
|
2016-11-14 00:37:13 +00:00
|
|
|
guard let values = try? url.resourceValues(forKeys: [.fileResourceIdentifierKey]),
|
|
|
|
let resourceID = values.fileResourceIdentifier
|
|
|
|
else {
|
|
|
|
return true
|
2015-11-16 01:56:33 +00:00
|
|
|
}
|
2016-11-14 00:37:13 +00:00
|
|
|
var shouldEnable = true
|
2015-01-12 00:53:35 +00:00
|
|
|
files.apply {(node) in
|
2016-11-14 00:37:13 +00:00
|
|
|
if let file = node as? ASFileItem,
|
|
|
|
let values = try? file.url.resourceValues(forKeys: [.fileResourceIdentifierKey]),
|
|
|
|
let thisID = values.fileResourceIdentifier
|
|
|
|
{
|
|
|
|
if resourceID.isEqual(thisID) {
|
|
|
|
shouldEnable = false
|
2015-01-12 00:53:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return shouldEnable
|
|
|
|
}
|
|
|
|
|
|
|
|
var hasSelection : Bool {
|
|
|
|
return selectedFiles().count > 0
|
|
|
|
}
|
|
|
|
|
2016-11-14 00:37:13 +00:00
|
|
|
func createFileAtURL(url: URL) {
|
2016-11-13 11:03:51 +00:00
|
|
|
let type = ASFileType.guessForURL(url: url)
|
2015-01-12 03:24:29 +00:00
|
|
|
var firstPfx = ""
|
|
|
|
var prefix = ""
|
|
|
|
var lastPfx = ""
|
|
|
|
switch type {
|
|
|
|
case .Header, .CFile:
|
|
|
|
firstPfx = "/*"
|
|
|
|
prefix = " *"
|
|
|
|
lastPfx = " */"
|
|
|
|
case .CppFile, .Arduino:
|
|
|
|
prefix = "//"
|
|
|
|
case .AsmFile:
|
|
|
|
prefix = ";"
|
|
|
|
case .Markdown:
|
|
|
|
firstPfx = "<!---"
|
|
|
|
prefix = " "
|
|
|
|
lastPfx = " -->"
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
var header = ""
|
2015-02-14 16:43:20 +00:00
|
|
|
if prefix != ("" as String) {
|
2015-01-12 03:24:29 +00:00
|
|
|
if firstPfx == "" {
|
|
|
|
firstPfx = prefix
|
|
|
|
}
|
|
|
|
if lastPfx == "" {
|
|
|
|
lastPfx = prefix
|
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
let dateFmt = DateFormatter()
|
2015-01-12 03:24:29 +00:00
|
|
|
dateFmt.dateFormat = "yyyy-MM-dd"
|
|
|
|
header = firstPfx + "\n" +
|
2016-11-14 00:37:13 +00:00
|
|
|
prefix + " Project: " + fileURL!.deletingLastPathComponent().lastPathComponent + "\n" +
|
|
|
|
prefix + " File: " + url.lastPathComponent + "\n" +
|
|
|
|
prefix + " Created: " + dateFmt.string(from: Date()) + "\n" +
|
2015-01-12 03:24:29 +00:00
|
|
|
lastPfx + "\n\n"
|
|
|
|
}
|
2015-11-16 01:56:33 +00:00
|
|
|
do {
|
2016-11-14 00:37:13 +00:00
|
|
|
try header.write(to: url, atomically: true, encoding: String.Encoding.utf8)
|
2015-11-16 01:56:33 +00:00
|
|
|
} catch _ {
|
|
|
|
}
|
2016-11-13 11:03:51 +00:00
|
|
|
files.addFileURL(url: url)
|
2015-01-12 03:24:29 +00:00
|
|
|
outline.reloadData()
|
|
|
|
}
|
|
|
|
|
2015-11-16 01:56:33 +00:00
|
|
|
@IBAction func createFile(_: AnyObject) {
|
2015-01-12 04:46:26 +00:00
|
|
|
let savePanel = NSSavePanel()
|
|
|
|
savePanel.allowedFileTypes =
|
2015-11-16 01:56:33 +00:00
|
|
|
[kUTTypeCSource as String, kUTTypeCHeader as String, kUTTypeCPlusPlusSource as String, kUTTypeAssemblyLanguageSource as String,
|
2015-01-12 04:46:26 +00:00
|
|
|
"public.assembly-source", "net.daringfireball.markdown"]
|
2016-11-13 11:03:51 +00:00
|
|
|
savePanel.beginSheetModal(for: outline.window!, completionHandler: { (returnCode) -> Void in
|
2015-01-12 04:46:26 +00:00
|
|
|
if returnCode == NSFileHandlingPanelOKButton {
|
2016-11-13 11:03:51 +00:00
|
|
|
self.createFileAtURL(url: savePanel.url!)
|
2015-01-12 04:46:26 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func importLibrary(_ lib: String) {
|
2015-03-23 02:17:57 +00:00
|
|
|
var includes = ""
|
2016-11-13 11:03:51 +00:00
|
|
|
let fileManager = FileManager.default
|
|
|
|
if let files = try? fileManager.contentsOfDirectory(atPath: lib) {
|
2015-11-16 01:56:33 +00:00
|
|
|
for file in files {
|
|
|
|
if file.hasSuffix(".h") {
|
|
|
|
includes += "#include <\(file)>\n"
|
|
|
|
}
|
2015-03-23 02:17:57 +00:00
|
|
|
}
|
|
|
|
}
|
2015-11-16 01:56:33 +00:00
|
|
|
let text = editor.string() as NSString
|
2015-03-23 02:17:57 +00:00
|
|
|
var insert = NSMakeRange(text.length, 0)
|
2016-11-13 11:03:51 +00:00
|
|
|
let postHeaderComments = try! NSRegularExpression(pattern: "((?:\\s+|/\\*.*?\\*/|//.*?\\n)*)(.*?\\n)", options: .dotMatchesLineSeparators)
|
|
|
|
if let match = postHeaderComments.firstMatch(in: text as String, options:.anchored, range:NSMakeRange(0, text.length)) {
|
|
|
|
let range = match.rangeAt(2)
|
2015-03-23 02:17:57 +00:00
|
|
|
insert.location = range.location
|
2016-11-13 11:03:51 +00:00
|
|
|
let content = text.substring(with: range)
|
2015-03-23 02:17:57 +00:00
|
|
|
if !content.hasPrefix("#include") {
|
|
|
|
includes += "\n"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
editor.setString(text.replacingCharacters(in: insert, with: includes))
|
2015-03-23 02:17:57 +00:00
|
|
|
}
|
|
|
|
|
2014-11-17 04:39:39 +00:00
|
|
|
// MARK: Editor configuration
|
|
|
|
|
2014-11-17 04:53:33 +00:00
|
|
|
@IBAction func changeTheme(item: NSMenuItem) {
|
2016-11-13 11:03:51 +00:00
|
|
|
currentTheme = ACETheme(rawValue: UInt(item.tag)) ?? .xcode
|
2014-11-17 04:39:39 +00:00
|
|
|
editor.setTheme(currentTheme)
|
2016-11-13 11:03:51 +00:00
|
|
|
UserDefaults.standard.set(
|
|
|
|
ACEThemeNames.humanName(for: currentTheme), forKey: kThemeKey)
|
|
|
|
updateChangeCount(.changeDone)
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
|
|
|
@IBAction func changeKeyboardHandler(item: NSMenuItem) {
|
|
|
|
keyboardHandler = ACEKeyboardHandler(rawValue: UInt(item.tag))!
|
2016-11-13 11:03:51 +00:00
|
|
|
UserDefaults.standard.set(
|
|
|
|
ACEKeyboardHandlerNames.humanName(for: keyboardHandler), forKey: kBindingsKey)
|
2016-11-13 12:13:40 +00:00
|
|
|
NotificationCenter.default.post(name: NSNotification.Name(kBindingsKey), object: item)
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
override func validateUserInterfaceItem(_ anItem: NSValidatedUserInterfaceItem) -> Bool {
|
2014-11-17 04:39:39 +00:00
|
|
|
if let menuItem = anItem as? NSMenuItem {
|
2016-11-14 00:37:13 +00:00
|
|
|
if menuItem.action == #selector(ASProjDoc.changeTheme(item:)) {
|
2015-03-14 20:15:27 +00:00
|
|
|
menuItem.state = (UInt(menuItem.tag) == currentTheme.rawValue ? NSOnState : NSOffState)
|
2014-11-17 04:39:39 +00:00
|
|
|
return true
|
2016-11-14 00:37:13 +00:00
|
|
|
} else if menuItem.action == #selector(ASProjDoc.changeKeyboardHandler(item:)) {
|
2014-11-17 04:39:39 +00:00
|
|
|
menuItem.state = (menuItem.tag == Int(keyboardHandler.rawValue) ? NSOnState : NSOffState)
|
2014-12-31 09:03:30 +00:00
|
|
|
return true
|
2016-11-14 00:37:13 +00:00
|
|
|
} else if menuItem.action == #selector(ASProjDoc.serialConnect(sender:)) {
|
2014-12-31 09:03:30 +00:00
|
|
|
menuItem.title = port
|
|
|
|
|
2014-11-17 04:39:39 +00:00
|
|
|
return true
|
2016-11-14 00:37:13 +00:00
|
|
|
} else if menuItem.action == #selector(ASLibraries.importStandardLibrary(menuItem:)) ||
|
|
|
|
menuItem.action == #selector(ASLibraries.importContribLibrary(menuItem:))
|
2015-03-23 02:17:57 +00:00
|
|
|
{
|
|
|
|
return mainEditor is ASFileItem
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return super.validateUserInterfaceItem(anItem)
|
|
|
|
}
|
|
|
|
|
2015-11-16 01:56:33 +00:00
|
|
|
@IBAction func makeTextLarger(_: AnyObject) {
|
2014-11-17 04:39:39 +00:00
|
|
|
fontSize += 1
|
|
|
|
editor.setFontSize(fontSize)
|
2016-11-13 11:03:51 +00:00
|
|
|
updateChangeCount(.changeDone)
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
2015-11-16 01:56:33 +00:00
|
|
|
@IBAction func makeTextSmaller(_: AnyObject) {
|
2014-11-17 04:39:39 +00:00
|
|
|
if fontSize > 6 {
|
|
|
|
fontSize -= 1
|
|
|
|
editor.setFontSize(fontSize)
|
2016-11-13 11:03:51 +00:00
|
|
|
updateChangeCount(.changeDone)
|
2014-11-17 04:39:39 +00:00
|
|
|
}
|
2014-11-15 03:39:10 +00:00
|
|
|
}
|
2015-07-11 00:58:26 +00:00
|
|
|
|
|
|
|
// MARK: Issues
|
|
|
|
@IBAction func jumpToIssue(sender: AnyObject) {
|
|
|
|
let direction : Int = (sender as! NSMenuItem).tag
|
2016-11-13 11:03:51 +00:00
|
|
|
if editors.views(in: .bottom).count == 0 {
|
|
|
|
editors.addView(auxEdit, in: .bottom)
|
2015-07-11 00:58:26 +00:00
|
|
|
|
2016-11-14 00:37:13 +00:00
|
|
|
let url = fileURL?.deletingLastPathComponent().appendingPathComponent(files.buildLog.path)
|
2015-07-11 00:58:26 +00:00
|
|
|
if url == nil {
|
|
|
|
return
|
|
|
|
}
|
2016-11-14 00:37:13 +00:00
|
|
|
var enc : String.Encoding = .utf8
|
|
|
|
let contents = try? String(contentsOf:url!, usedEncoding:&enc)
|
|
|
|
auxEdit.setString(contents ?? "")
|
2016-11-13 11:03:51 +00:00
|
|
|
editor.setMode(.text)
|
2015-07-11 00:58:26 +00:00
|
|
|
editor.alphaValue = 1.0
|
|
|
|
}
|
2016-11-14 00:37:13 +00:00
|
|
|
let buildLog = auxEdit.string().components(separatedBy: "\n")
|
2015-11-16 01:56:33 +00:00
|
|
|
let issueRe = try! NSRegularExpression(pattern: "(\\S+?):(\\d+):.*", options: [])
|
2015-07-11 00:58:26 +00:00
|
|
|
|
|
|
|
currentIssueLine += direction
|
|
|
|
while currentIssueLine > -1 && currentIssueLine < buildLog.count {
|
|
|
|
let line = buildLog[currentIssueLine]
|
2015-11-16 01:56:33 +00:00
|
|
|
let range = NSMakeRange(0, line.utf16.count)
|
2016-11-14 00:37:13 +00:00
|
|
|
if let match = issueRe.firstMatch(in: line, options:.anchored, range:range) {
|
|
|
|
let file = match.rangeAt(1)
|
|
|
|
let lineTxt = match.rangeAt(2)
|
2015-07-11 00:58:26 +00:00
|
|
|
let nsline = line as NSString
|
2016-11-14 00:37:13 +00:00
|
|
|
let lineNo = Int(nsline.substring(with: lineTxt))!
|
|
|
|
let fileName = nsline.substring(with: file) as NSString
|
|
|
|
let fileURL : URL
|
2015-07-11 00:58:26 +00:00
|
|
|
|
|
|
|
if fileName.hasPrefix("../../") {
|
2016-11-14 00:37:13 +00:00
|
|
|
fileURL = files.dir.appendingPathComponent(fileName.substring(from: 6))
|
2015-07-11 00:58:26 +00:00
|
|
|
} else {
|
2016-11-14 00:37:13 +00:00
|
|
|
fileURL = URL(fileURLWithPath:fileName as String).standardizedFileURL
|
2015-07-11 00:58:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
jumpingToIssue = true
|
2016-11-14 00:37:13 +00:00
|
|
|
if let values = try? fileURL.resourceValues(forKeys: [.fileResourceIdentifierKey]),
|
|
|
|
let resourceID = values.fileResourceIdentifier
|
|
|
|
{
|
2015-11-16 01:56:33 +00:00
|
|
|
files.apply {(node) in
|
2016-11-14 00:37:13 +00:00
|
|
|
if let file = node as? ASFileItem,
|
|
|
|
let values = try? file.url.resourceValues(forKeys: [.fileResourceIdentifierKey]),
|
|
|
|
let thisID = values.fileResourceIdentifier,
|
|
|
|
resourceID.isEqual(thisID)
|
|
|
|
{
|
|
|
|
self.selectNodeInOutline(selection: node)
|
|
|
|
self.editor.gotoLine(lineNo, column:0, animated:true)
|
2015-07-11 00:58:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
jumpingToIssue = false
|
|
|
|
|
|
|
|
auxEdit.gotoLine(currentIssueLine+1, column:0, animated: true)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
currentIssueLine += direction
|
|
|
|
}
|
|
|
|
}
|
2014-11-24 13:49:47 +00:00
|
|
|
|
|
|
|
// MARK: Build / Upload
|
|
|
|
|
2015-11-16 01:56:33 +00:00
|
|
|
@IBAction func buildProject(_: AnyObject) {
|
2016-11-13 11:03:51 +00:00
|
|
|
selectNodeInOutline(selection: files.buildLog)
|
|
|
|
builder.buildProject(board: board, files: files)
|
2014-11-24 13:49:47 +00:00
|
|
|
}
|
2014-12-01 03:26:55 +00:00
|
|
|
|
2015-11-16 01:56:33 +00:00
|
|
|
@IBAction func cleanProject(_: AnyObject) {
|
2014-12-01 03:26:55 +00:00
|
|
|
builder.cleanProject()
|
2016-11-13 11:03:51 +00:00
|
|
|
selectNodeInOutline(selection: files.buildLog)
|
2014-12-01 03:26:55 +00:00
|
|
|
}
|
2014-12-07 05:20:57 +00:00
|
|
|
|
2014-12-09 05:54:30 +00:00
|
|
|
func rebuildPortMenu() {
|
2016-11-13 11:03:51 +00:00
|
|
|
willChangeValue(forKey: "hasValidPort")
|
2014-12-09 05:54:30 +00:00
|
|
|
portTool.removeAllItems()
|
2016-11-13 11:03:51 +00:00
|
|
|
portTool.addItem(withTitle: "Title")
|
|
|
|
portTool.addItems(withTitles: ASSerial.ports())
|
2014-12-09 05:54:30 +00:00
|
|
|
portTool.setTitle(port)
|
2016-11-13 11:03:51 +00:00
|
|
|
didChangeValue(forKey: "hasValidPort")
|
2014-12-09 05:54:30 +00:00
|
|
|
}
|
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
func menuNeedsUpdate(_ menu: NSMenu) {
|
2014-12-07 05:20:57 +00:00
|
|
|
switch menu.title {
|
|
|
|
case "Boards":
|
2016-11-13 11:03:51 +00:00
|
|
|
ASHardware.instance().buildBoardsMenu(menu: menu, recentBoards: recentBoards,
|
2016-11-14 00:37:13 +00:00
|
|
|
target: self, selector: #selector(ASProjDoc.selectBoard(item:)))
|
2014-12-07 05:20:57 +00:00
|
|
|
boardTool.setTitle(selectedBoard)
|
|
|
|
case "Programmers":
|
2016-11-13 11:03:51 +00:00
|
|
|
ASHardware.instance().buildProgrammersMenu(menu: menu, recentProgrammers: recentProgrammers,
|
2016-11-14 00:37:13 +00:00
|
|
|
target: self, selector: #selector(ASProjDoc.selectProgrammer(item:)))
|
2014-12-07 05:20:57 +00:00
|
|
|
progTool.setTitle(selectedProgrammer)
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2014-12-02 12:57:18 +00:00
|
|
|
}
|
2014-12-07 05:20:57 +00:00
|
|
|
|
|
|
|
var selectedBoard : String {
|
|
|
|
get {
|
|
|
|
let boardProps = ASHardware.instance().boards[board]
|
|
|
|
return boardProps?["name"] ?? ""
|
|
|
|
}
|
|
|
|
set (newBoard) {
|
|
|
|
for (ident, prop) in ASHardware.instance().boards {
|
|
|
|
if prop["name"] == newBoard {
|
2014-12-10 02:51:26 +00:00
|
|
|
board = ident
|
2014-12-07 05:20:57 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
pushToFront(list: &recentBoards, front: board)
|
2014-12-07 05:20:57 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
let userDefaults = UserDefaults.standard
|
|
|
|
var globalBoards = userDefaults.object(forKey:kRecentBoardsKey) as! [String]
|
|
|
|
pushToFront(list: &globalBoards, front: board)
|
|
|
|
userDefaults.set(globalBoards, forKey: kRecentBoardsKey)
|
2014-12-02 12:57:18 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
updateChangeCount(.changeDone)
|
2014-12-07 05:20:57 +00:00
|
|
|
menuNeedsUpdate(boardTool.menu!)
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2014-12-03 14:38:13 +00:00
|
|
|
}
|
2014-12-02 12:57:18 +00:00
|
|
|
}
|
|
|
|
|
2014-12-07 05:20:57 +00:00
|
|
|
@IBAction func selectBoard(item: AnyObject) {
|
2015-02-14 16:43:20 +00:00
|
|
|
selectedBoard = (item as! NSMenuItem).title
|
2014-12-07 05:20:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var selectedProgrammer : String {
|
|
|
|
get {
|
|
|
|
let progProps = ASHardware.instance().programmers[programmer]
|
|
|
|
return progProps?["name"] ?? ""
|
|
|
|
}
|
|
|
|
set (newProg) {
|
|
|
|
for (ident, prop) in ASHardware.instance().programmers {
|
|
|
|
if prop["name"] == newProg {
|
2014-12-10 02:51:26 +00:00
|
|
|
programmer = ident
|
2014-12-07 05:20:57 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
pushToFront(list: &recentProgrammers, front: programmer)
|
2014-12-07 05:20:57 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
let userDefaults = UserDefaults.standard
|
|
|
|
var globalProgs = userDefaults.object(forKey:kRecentProgrammersKey) as! [String]
|
|
|
|
pushToFront(list: &globalProgs, front: programmer)
|
|
|
|
userDefaults.set(globalProgs, forKey: kRecentProgrammersKey)
|
2014-12-07 05:20:57 +00:00
|
|
|
|
2016-11-13 11:03:51 +00:00
|
|
|
updateChangeCount(.changeDone)
|
2014-12-07 05:20:57 +00:00
|
|
|
progTool.setTitle(newProg)
|
|
|
|
menuNeedsUpdate(progTool.menu!)
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2014-12-03 14:38:13 +00:00
|
|
|
}
|
2014-12-02 12:57:18 +00:00
|
|
|
}
|
|
|
|
|
2014-12-07 05:20:57 +00:00
|
|
|
@IBAction func selectProgrammer(item: AnyObject) {
|
2015-02-14 16:43:20 +00:00
|
|
|
selectedProgrammer = (item as! NSMenuItem).title
|
2014-12-07 05:20:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func selectPort(item: AnyObject) {
|
2015-02-14 16:43:20 +00:00
|
|
|
port = (item as! NSPopUpButton).titleOfSelectedItem!
|
2014-12-07 05:20:57 +00:00
|
|
|
portTool.setTitle(port)
|
|
|
|
}
|
2014-12-08 01:41:19 +00:00
|
|
|
|
|
|
|
var hasUploadProtocol : Bool {
|
|
|
|
get {
|
2014-12-08 03:42:45 +00:00
|
|
|
if let proto = ASHardware.instance().boards[board]?["upload.protocol"] {
|
2015-02-14 16:43:20 +00:00
|
|
|
return proto != ("" as String)
|
2014-12-08 03:42:45 +00:00
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
2014-12-08 01:41:19 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-08 03:42:45 +00:00
|
|
|
class func keyPathsForValuesAffectingHasUploadProtocol() -> NSSet {
|
|
|
|
return NSSet(object: "board")
|
|
|
|
}
|
|
|
|
|
2014-12-09 05:54:30 +00:00
|
|
|
var hasValidPort : Bool {
|
|
|
|
get {
|
2015-11-16 01:56:33 +00:00
|
|
|
return ASSerial.ports().contains(port)
|
2014-12-09 05:54:30 +00:00
|
|
|
}
|
|
|
|
}
|
2014-12-10 02:51:26 +00:00
|
|
|
class func keyPathsForValuesAffectingHasValidPort() -> NSSet {
|
|
|
|
return NSSet(object: "port")
|
|
|
|
}
|
2014-12-09 05:54:30 +00:00
|
|
|
|
2014-12-08 03:42:45 +00:00
|
|
|
var canUpload : Bool {
|
|
|
|
get {
|
2015-02-14 16:43:20 +00:00
|
|
|
return hasValidPort && (hasUploadProtocol || programmer != ("" as String))
|
2014-12-08 03:42:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
class func keyPathsForValuesAffectingCanUpload() -> NSSet {
|
2014-12-09 05:54:30 +00:00
|
|
|
return NSSet(objects: "hasValidPort", "hasUploadProtocol", "programmer")
|
2014-12-08 03:42:45 +00:00
|
|
|
}
|
|
|
|
|
2014-12-08 04:35:58 +00:00
|
|
|
@IBAction func uploadProject(sender: AnyObject) {
|
|
|
|
builder.continuation = {
|
2016-11-13 11:03:51 +00:00
|
|
|
self.selectNodeInOutline(selection: self.files.uploadLog)
|
2016-11-13 12:27:19 +00:00
|
|
|
DispatchQueue.main.async(execute: {
|
2016-11-13 11:03:51 +00:00
|
|
|
self.builder.uploadProject(board: self.board, programmer:self.programmer, port:self.port)
|
2015-01-02 06:26:10 +00:00
|
|
|
})
|
2014-12-08 04:35:58 +00:00
|
|
|
}
|
|
|
|
buildProject(sender)
|
2014-12-08 02:59:47 +00:00
|
|
|
}
|
2015-01-02 15:17:10 +00:00
|
|
|
|
|
|
|
@IBAction func uploadTerminal(sender: AnyObject) {
|
2016-11-13 11:03:51 +00:00
|
|
|
builder.uploadProject(board: board, programmer:programmer, port:port, mode:.Interactive)
|
2015-01-07 09:30:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func burnBootloader(sender: AnyObject) {
|
2016-11-13 11:03:51 +00:00
|
|
|
self.selectNodeInOutline(selection: self.files.uploadLog)
|
|
|
|
builder.uploadProject(board: board, programmer:programmer, port:port, mode:.BurnBootloader)
|
2015-01-02 15:17:10 +00:00
|
|
|
}
|
|
|
|
|
2014-12-10 14:30:51 +00:00
|
|
|
@IBAction func disassembleProject(sender: AnyObject) {
|
|
|
|
builder.continuation = {
|
2016-11-13 11:03:51 +00:00
|
|
|
self.selectNodeInOutline(selection: self.files.disassembly)
|
|
|
|
self.builder.disassembleProject(board: self.board)
|
2014-12-10 14:30:51 +00:00
|
|
|
}
|
|
|
|
buildProject(sender)
|
|
|
|
}
|
2014-12-29 06:35:07 +00:00
|
|
|
|
|
|
|
@IBAction func serialConnect(sender: AnyObject) {
|
2016-11-13 11:03:51 +00:00
|
|
|
ASSerialWin.showWindowWithPort(port: port)
|
2014-12-29 06:35:07 +00:00
|
|
|
}
|
2014-11-15 03:39:10 +00:00
|
|
|
}
|
|
|
|
|