diff --git a/MorphicManualTester/MorphicManualTester/ContentView.swift b/MorphicManualTester/MorphicManualTester/ContentView.swift index 3a4670ea..44518bd6 100644 --- a/MorphicManualTester/MorphicManualTester/ContentView.swift +++ b/MorphicManualTester/MorphicManualTester/ContentView.swift @@ -40,6 +40,18 @@ struct ContentView: View { Button(action: registry.loadSolution) { Text("Load a Different Registry") } + Button(action: registry.testWordEBasic) { + Text("EBasic") + } + Button(action: registry.testWordDBasic) { + Text("DBasic") + } + Button(action: registry.testWordEEssentials) { + Text("EEssen") + } + Button(action: registry.testWordDEssentials) { + Text("DEssen") + } .padding([.top, .bottom, .trailing]) } .padding(.vertical, 0.0) diff --git a/MorphicManualTester/MorphicManualTester/RegistryManager.swift b/MorphicManualTester/MorphicManualTester/RegistryManager.swift index 20b3c76d..dfbeb324 100644 --- a/MorphicManualTester/MorphicManualTester/RegistryManager.swift +++ b/MorphicManualTester/MorphicManualTester/RegistryManager.swift @@ -214,6 +214,29 @@ class RegistryManager: ObservableObject load = "NO REGISTRY LOADED" autoApply = true solutions = [SolutionCollection]() + word = MorphicWord() + } + + private var word: MorphicWord + + func testWordRefresh() { + WordRibbonUIAutomation.RefreshRibbon() + } + + func testWordEBasic() { + word.enableBasicsTab() + } + + func testWordDBasic() { + word.disableBasicsTab() + } + + func testWordEEssentials() { + word.enableEssentialsTab() + } + + func testWordDEssentials() { + word.disableEssentialsTab() } func loadSolution() { diff --git a/MorphicSettings/MorphicSettings.xcodeproj/project.pbxproj b/MorphicSettings/MorphicSettings.xcodeproj/project.pbxproj index b8df228f..4767443b 100644 --- a/MorphicSettings/MorphicSettings.xcodeproj/project.pbxproj +++ b/MorphicSettings/MorphicSettings.xcodeproj/project.pbxproj @@ -84,6 +84,11 @@ 9DD4F4A42517B0F200ADCC25 /* LanguageAndRegionPreferencesElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DD4F4A32517B0F200ADCC25 /* LanguageAndRegionPreferencesElement.swift */; }; 9DE45B962517114100209421 /* DisplaysUIAutomation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE45B952517114100209421 /* DisplaysUIAutomation.swift */; }; 9DE45B9F251712EB00209421 /* DisplaysPreferencesElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE45B9E251712EB00209421 /* DisplaysPreferencesElement.swift */; }; + DF98E14D267D79D0000D730A /* EmptyTemplate.xml in Resources */ = {isa = PBXBuildFile; fileRef = DF98E14B267D79D0000D730A /* EmptyTemplate.xml */; }; + DF98E14E267D79D0000D730A /* ComponentTemplate.xml in Resources */ = {isa = PBXBuildFile; fileRef = DF98E14C267D79D0000D730A /* ComponentTemplate.xml */; }; + DFF3F9F7265DEA52001F0430 /* WordRibbonUIAutomations.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFF3F9F6265DEA52001F0430 /* WordRibbonUIAutomations.swift */; }; + DFF3F9FE265F4EC8001F0430 /* WordApplicationElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFF3F9FD265F4EC8001F0430 /* WordApplicationElement.swift */; }; + DFF3FA042669D8F3001F0430 /* MorphicWord.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFF3FA032669D8F3001F0430 /* MorphicWord.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -190,6 +195,11 @@ 9DD4F4A32517B0F200ADCC25 /* LanguageAndRegionPreferencesElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageAndRegionPreferencesElement.swift; sourceTree = ""; }; 9DE45B952517114100209421 /* DisplaysUIAutomation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaysUIAutomation.swift; sourceTree = ""; }; 9DE45B9E251712EB00209421 /* DisplaysPreferencesElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplaysPreferencesElement.swift; sourceTree = ""; }; + DF98E14B267D79D0000D730A /* EmptyTemplate.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = EmptyTemplate.xml; sourceTree = ""; }; + DF98E14C267D79D0000D730A /* ComponentTemplate.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = ComponentTemplate.xml; sourceTree = ""; }; + DFF3F9F6265DEA52001F0430 /* WordRibbonUIAutomations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordRibbonUIAutomations.swift; sourceTree = ""; }; + DFF3F9FD265F4EC8001F0430 /* WordApplicationElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordApplicationElement.swift; sourceTree = ""; }; + DFF3FA032669D8F3001F0430 /* MorphicWord.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MorphicWord.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -252,6 +262,7 @@ 9DD22FB924DCB8B800C2FA52 /* SpeechUIAutomations.swift */, 31F9AEE224A7F7EA0026EBDA /* VoiceOverUIAutomations.swift */, 31F9AEE424A803B90026EBDA /* ZoomUIAutomations.swift */, + DFF3F9F6265DEA52001F0430 /* WordRibbonUIAutomations.swift */, ); path = Automations; sourceTree = ""; @@ -325,6 +336,7 @@ 31A0A5792405967600166668 /* MorphicSettings */ = { isa = PBXGroup; children = ( + DFF3FA022669D8CE001F0430 /* Office */, 9D0AF73524A0FB49007E177E /* AccessibilityUI */, 314EB6DC24A6A65F00CF3D48 /* Automations */, 31A16BC72423E86C0081A72C /* Audio */, @@ -438,6 +450,7 @@ 3137AC4524B0FC0D00CE3808 /* Menus */, 315EECA024A5839F0039C61D /* Tables */, 315EECA324A583C00039C61D /* SystemPrefs */, + DFF3F9FC265F4E98001F0430 /* OtherApps */, ); path = AccessibilityUI; sourceTree = ""; @@ -462,6 +475,24 @@ path = PropertyList; sourceTree = ""; }; + DFF3F9FC265F4E98001F0430 /* OtherApps */ = { + isa = PBXGroup; + children = ( + DFF3F9FD265F4EC8001F0430 /* WordApplicationElement.swift */, + ); + path = OtherApps; + sourceTree = ""; + }; + DFF3FA022669D8CE001F0430 /* Office */ = { + isa = PBXGroup; + children = ( + DF98E14C267D79D0000D730A /* ComponentTemplate.xml */, + DF98E14B267D79D0000D730A /* EmptyTemplate.xml */, + DFF3FA032669D8F3001F0430 /* MorphicWord.swift */, + ); + path = Office; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -561,6 +592,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + DF98E14D267D79D0000D730A /* EmptyTemplate.xml in Resources */, + DF98E14E267D79D0000D730A /* ComponentTemplate.xml in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -595,6 +628,7 @@ 31F9AEEC24AA54BF0026EBDA /* PopUpButtonElement.swift in Sources */, 31F9AEE524A803B90026EBDA /* ZoomUIAutomations.swift in Sources */, 9DD4F4A42517B0F200ADCC25 /* LanguageAndRegionPreferencesElement.swift in Sources */, + DFF3F9FE265F4EC8001F0430 /* WordApplicationElement.swift in Sources */, 312DBFF024A27BCF002FB951 /* Solution.swift in Sources */, 31A16BCB2423E86C0081A72C /* MorphicLaunchDaemonsAndAgents.swift in Sources */, 314EB6E224A6B34300CF3D48 /* TabElement.swift in Sources */, @@ -626,6 +660,7 @@ 9DD4F49A2517AF1600ADCC25 /* KeyboardPreferencesElement.swift in Sources */, 9D4B24BF25143D0000F0B2E2 /* GeneralUIAutomations.swift in Sources */, 31CE56E22448C8F700A52A7B /* SettingHandler.swift in Sources */, + DFF3FA042669D8F3001F0430 /* MorphicWord.swift in Sources */, 315EEC8724A536A60039C61D /* TabGroupElement.swift in Sources */, 9DD22FBA24DCB8B800C2FA52 /* SpeechUIAutomations.swift in Sources */, 9DE45B962517114100209421 /* DisplaysUIAutomation.swift in Sources */, @@ -639,6 +674,7 @@ 312DBFFB24A2C66A002FB951 /* ApplySession.swift in Sources */, 314EB6DE24A6A67700CF3D48 /* UIAutomation.swift in Sources */, 9DE45B9F251712EB00209421 /* DisplaysPreferencesElement.swift in Sources */, + DFF3F9F7265DEA52001F0430 /* WordRibbonUIAutomations.swift in Sources */, 312DBFF324A28556002FB951 /* DefaultsReadUIWriteSettingHandler.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/MorphicSettings/MorphicSettings/AccessibilityUI/ApplicationElement.swift b/MorphicSettings/MorphicSettings/AccessibilityUI/ApplicationElement.swift index b1b7b5b9..71ce4946 100644 --- a/MorphicSettings/MorphicSettings/AccessibilityUI/ApplicationElement.swift +++ b/MorphicSettings/MorphicSettings/AccessibilityUI/ApplicationElement.swift @@ -56,7 +56,7 @@ public class ApplicationElement: UIElement { open(hide: true, completion: completion) } - public func open(hide: Bool, completion: @escaping (_ success: Bool) -> Void) { + public func open(hide: Bool, attachOnly: Bool = false, completion: @escaping (_ success: Bool) -> Void) { guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleIdentifier) else { os_log(.error, log: logger, "Failed to find url for application %{public}s", bundleIdentifier) completion(false) @@ -96,6 +96,10 @@ public class ApplicationElement: UIElement { } runningApplication = NSRunningApplication.runningApplications(withBundleIdentifier: bundleIdentifier).first if runningApplication == nil { + if(attachOnly) { + completion(false) + return + } MorphicProcess.openProcess(at: url, arguments: [], activate: false, hide: hide) { (runningApplication, error) in DispatchQueue.main.async { diff --git a/MorphicSettings/MorphicSettings/AccessibilityUI/MorphicA11yUIElement.swift b/MorphicSettings/MorphicSettings/AccessibilityUI/MorphicA11yUIElement.swift index 382b3270..2ae24934 100644 --- a/MorphicSettings/MorphicSettings/AccessibilityUI/MorphicA11yUIElement.swift +++ b/MorphicSettings/MorphicSettings/AccessibilityUI/MorphicA11yUIElement.swift @@ -34,6 +34,10 @@ public struct MorphicA11yUIElement { // role // https://developer.apple.com/documentation/appkit/nsaccessibility/role public let role: NSAccessibility.Role + // + // subrole + // https://develpoer.apple.com/documentation/appkit/nsaccessibility/subrole + public let subrole: NSAccessibility.Subrole internal init?(axUiElement: AXUIElement) { // axUiElement @@ -57,12 +61,22 @@ public struct MorphicA11yUIElement { // role if self.supportedAttributes.contains(.role) == true { guard let roleAsString: String = MorphicA11yUIElement.value(forAttribute: .role, forAXUIElement: self.axUiElement) else { - return nil + return nil } self.role = NSAccessibility.Role(rawValue: roleAsString) } else { self.role = .unknown } + // + // role + if self.supportedAttributes.contains(.subrole) == true { + guard let subroleAsString: String = MorphicA11yUIElement.value(forAttribute: .subrole, forAXUIElement: self.axUiElement) else { + return nil + } + self.subrole = NSAccessibility.Subrole(rawValue: subroleAsString) + } else { + self.subrole = .unknown + } } public static func createFromProcess(processIdentifier: pid_t) throws -> MorphicA11yUIElement? { diff --git a/MorphicSettings/MorphicSettings/AccessibilityUI/OtherApps/WordApplicationElement.swift b/MorphicSettings/MorphicSettings/AccessibilityUI/OtherApps/WordApplicationElement.swift new file mode 100644 index 00000000..ca8514ca --- /dev/null +++ b/MorphicSettings/MorphicSettings/AccessibilityUI/OtherApps/WordApplicationElement.swift @@ -0,0 +1,38 @@ +// Copyright 2020 Raising the Floor - International +// +// Licensed under the New BSD license. You may not use this file except in +// compliance with this License. +// +// You may obtain a copy of the License at +// https://github.com/GPII/universal/blob/master/LICENSE.txt +// +// The R&D leading to these results received funding from the: +// * Rehabilitation Services Administration, US Dept. of Education under +// grant H421A150006 (APCP) +// * National Institute on Disability, Independent Living, and +// Rehabilitation Research (NIDILRR) +// * Administration for Independent Living & Dept. of Education under grants +// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC) +// * European Union's Seventh Framework Programme (FP7/2007-2013) grant +// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All) +// * William and Flora Hewlett Foundation +// * Ontario Ministry of Research and Innovation +// * Canadian Foundation for Innovation +// * Adobe Foundation +// * Consumer Electronics Association Foundation + +import Foundation +import MorphicCore + +public class WordApplicationElement: ApplicationElement { + + public static let bundleIdentifier = "com.microsoft.Word" + + public init() { + super.init(bundleIdentifier: WordApplicationElement.bundleIdentifier) + } + + public required init(accessibilityElement: MorphicA11yUIElement?) { + fatalError("init(accessibilityElement:) has not been implemented") + } +} diff --git a/MorphicSettings/MorphicSettings/AccessibilityUI/UIElement.swift b/MorphicSettings/MorphicSettings/AccessibilityUI/UIElement.swift index 2b7a283c..d74658c1 100644 --- a/MorphicSettings/MorphicSettings/AccessibilityUI/UIElement.swift +++ b/MorphicSettings/MorphicSettings/AccessibilityUI/UIElement.swift @@ -43,6 +43,10 @@ public class UIElement { return descendant(role: .checkBox, title: titled) } + public func firstCheckbox() -> CheckboxElement? { + return descendant(role: .checkBox) + } + public func table(titled: String) -> TableElement? { return descendant(role: .table, title: titled) } @@ -80,7 +84,7 @@ public class UIElement { } private func descendant(role: NSAccessibility.Role, title: String) -> ElementType? { - guard let accessibilityElement = accessibilityDescendant(role: role, title: title) else { + guard let accessibilityElement = accessibilityDescendant(role: role, title: title) else { return nil } return ElementType(accessibilityElement: accessibilityElement) @@ -165,6 +169,25 @@ public class UIElement { } return nil } + + public func closeButton() -> MorphicA11yUIElement? { + guard let children = accessibilityElement.children() else { + return nil + } + var stack = children + var i = 0 + while i < stack.count { + let candidate = stack[i] + if candidate.subrole == NSAccessibility.Subrole.closeButton { + return candidate + } + if let children = candidate.children() { + stack.append(contentsOf: children) + } + i += 1 + } + return nil + } public func perform(action: Action, completion: @escaping (_ success: Bool, _ nextTarget: UIElement?) -> Void) { switch action { diff --git a/MorphicSettings/MorphicSettings/Automations/WordRibbonUIAutomations.swift b/MorphicSettings/MorphicSettings/Automations/WordRibbonUIAutomations.swift new file mode 100644 index 00000000..bca8b3ee --- /dev/null +++ b/MorphicSettings/MorphicSettings/Automations/WordRibbonUIAutomations.swift @@ -0,0 +1,154 @@ +// Copyright 2020 Raising the Floor - International +// +// Licensed under the New BSD license. You may not use this file except in +// compliance with this License. +// +// You may obtain a copy of the License at +// https://github.com/GPII/universal/blob/master/LICENSE.txt +// +// The R&D leading to these results received funding from the: +// * Rehabilitation Services Administration, US Dept. of Education under +// grant H421A150006 (APCP) +// * National Institute on Disability, Independent Living, and +// Rehabilitation Research (NIDILRR) +// * Administration for Independent Living & Dept. of Education under grants +// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC) +// * European Union's Seventh Framework Programme (FP7/2007-2013) grant +// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All) +// * William and Flora Hewlett Foundation +// * Ontario Ministry of Research and Innovation +// * Canadian Foundation for Innovation +// * Adobe Foundation +// * Consumer Electronics Association Foundation + +import Cocoa +import MorphicCore +import OSLog + +private let logger = OSLog(subsystem: "MorphicSettings", category: "WordRibbonUIAutomation") + +public class WordRibbonUIAutomation: UIAutomation { + public required init() { + } + + public func apply(_ value: Interoperable?, completion: @escaping (Bool) -> Void) { + WordRibbonUIAutomation.RefreshRibbon() //making this default action for now + } + + public static func RefreshRibbon() { + let app = WordApplicationElement() + app.open(hide: true, attachOnly: true, completion: { + success in + guard success else { + os_log("No open Word instance found") + return //if we add outputs, this should be marked as a success + } + do { + for window: WindowElement in app.windows! { + if(window.accessibilityElement.subrole == .dialog) { + let close = window.closeButton() + try close?.perform(action: .press) + } + } + } catch { + return + } + guard WordRibbonUIAutomation.NewDocument(app: app) else { + os_log("Could not open new Word document") + return + } + let nwindow = app.windows?.first + guard WordRibbonUIAutomation.CallPreferences(app: app) else { + os_log("Could not open Word preferences") + return + } + for window: WindowElement in app.windows! { + let windowTitle: String = window.title! + if(windowTitle == "Word Preferences") { + do{ //sends preferences window offscreen for duration + try window.accessibilityElement.setValue(CGPoint(x: 0, y: 100000), forAttribute: .position) + } catch {} + window.perform(action: .press(buttonTitle: "Ribbon & Toolbar")) { (success, _) in + if(!success) { + os_log("Error switching to Ribbon tab") + return + } + do { + let check = window.firstCheckbox() + try check?.accessibilityElement.perform(action: .press) + try check?.accessibilityElement.perform(action: .press) + window.perform(action: .press(buttonTitle: "Save"), completion: { (success2, _) in + if(!success2) { + os_log("Error applying settings") + return + } + do { + let close = window.closeButton() + try close?.perform(action: .press) + let nclose = nwindow?.closeButton() + try nclose?.perform(action: .press) + return + } catch { + os_log("Error closing preferences window") + return + } + }) + } catch { + os_log("Error selecting checkbox in preferences") + return + } + } + return + } + } + }); + } + + private static func CallPreferences(app: WordApplicationElement) -> Bool { + guard let topMenu: MorphicA11yUIElement = app.accessibilityElement.value(forAttribute: .menuBar) else { + return false + } + do { + for menuHeader: MorphicA11yUIElement in topMenu.children()! { + let headerTitle: String = menuHeader.value(forAttribute: NSAccessibility.Attribute.title)! + if(headerTitle == "Word") { + let subMenu: MorphicA11yUIElement = (menuHeader.children()?.first)! + for menuItem: MorphicA11yUIElement in subMenu.children()! { + let itemTitle: String = menuItem.value(forAttribute: NSAccessibility.Attribute.title)! + if(itemTitle == "Preferences...") { + try menuItem.perform(action: NSAccessibility.Action.press) + return true + } + } + } + } + } catch { + return false + } + return false + } + + private static func NewDocument(app: WordApplicationElement) -> Bool { + guard let topMenu: MorphicA11yUIElement = app.accessibilityElement.value(forAttribute: .menuBar) else { + return false + } + do { + for menuHeader: MorphicA11yUIElement in topMenu.children()! { + let headerTitle: String = menuHeader.value(forAttribute: NSAccessibility.Attribute.title)! + if(headerTitle == "File") { + let subMenu: MorphicA11yUIElement = (menuHeader.children()?.first)! + for menuItem: MorphicA11yUIElement in subMenu.children()! { + let itemTitle: String = menuItem.value(forAttribute: NSAccessibility.Attribute.title)! + if(itemTitle == "New Document") { + try menuItem.perform(action: NSAccessibility.Action.press) + return true + } + } + } + } + } catch { + return false + } + return false + } +} diff --git a/MorphicSettings/MorphicSettings/Office/ComponentTemplate.xml b/MorphicSettings/MorphicSettings/Office/ComponentTemplate.xml new file mode 100644 index 00000000..c9795c0f --- /dev/null +++ b/MorphicSettings/MorphicSettings/Office/ComponentTemplate.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MorphicSettings/MorphicSettings/Office/EmptyTemplate.xml b/MorphicSettings/MorphicSettings/Office/EmptyTemplate.xml new file mode 100644 index 00000000..9a7184b4 --- /dev/null +++ b/MorphicSettings/MorphicSettings/Office/EmptyTemplate.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/MorphicSettings/MorphicSettings/Office/MorphicWord.swift b/MorphicSettings/MorphicSettings/Office/MorphicWord.swift new file mode 100644 index 00000000..cfef82f0 --- /dev/null +++ b/MorphicSettings/MorphicSettings/Office/MorphicWord.swift @@ -0,0 +1,211 @@ +// Copyright 2020 Raising the Floor - International +// +// Licensed under the New BSD license. You may not use this file except in +// compliance with this License. +// +// You may obtain a copy of the License at +// https://github.com/GPII/universal/blob/master/LICENSE.txt +// +// The R&D leading to these results received funding from the: +// * Rehabilitation Services Administration, US Dept. of Education under +// grant H421A150006 (APCP) +// * National Institute on Disability, Independent Living, and +// Rehabilitation Research (NIDILRR) +// * Administration for Independent Living & Dept. of Education under grants +// H133E080022 (RERC-IT) and H133E130028/90RE5003-01-00 (UIITA-RERC) +// * European Union's Seventh Framework Programme (FP7/2007-2013) grant +// agreement nos. 289016 (Cloud4all) and 610510 (Prosperity4All) +// * William and Flora Hewlett Foundation +// * Ontario Ministry of Research and Innovation +// * Canadian Foundation for Innovation +// * Adobe Foundation +// * Consumer Electronics Association Foundation + +import Foundation +import MorphicCore + +public class MorphicWord { + public init() { + path = (NSSearchPathForDirectoriesInDomains(.allLibrariesDirectory, .userDomainMask, true).first ?? "") + "/Containers/com.microsoft.Word/Data/Library/Preferences/Word.officeUI" + backupPath = path + ".bak" + _filemanager = FileManager() + originalString = "" + currentSettings = XMLDocument() + CapturePrefs(original: true) + + } + + public func CapturePrefs(original: Bool = false) { + var capture: String = "" + do { + if _filemanager.fileExists(atPath: path) { + try capture = String(contentsOfFile: path, encoding: .utf8) + if capture == "" { + throw NSError() + } + } else { + if !_filemanager.fileExists(atPath: backupPath) { + try "".write(toFile: backupPath, atomically: false, encoding: .utf8) //writes an empty backup file to stop the backup on the write phase + } + currentSettings = LoadEmptyTemplate() + return + } + if original { //takes the captured string down as a secondary backup + originalString = capture + } + currentSettings = try XMLDocument(xmlString: capture, options: .nodePreserveNamespaceOrder) + let tabs: XMLElement? = currentSettings.rootElement()?.elements(forName: "mso:ribbon").first?.elements(forName: "mso:tabs").first + if tabs == nil { //anything missing the tabs element will break all functionality + throw NSError() + } + } catch { //responds to any error by just going forward with no customizations and backing up whatever is currently there + currentSettings = LoadEmptyTemplate() + do { + if _filemanager.fileExists(atPath: backupPath) { + try _filemanager.copyItem(atPath: path, toPath: backupPath) + } + } catch {} + } + } + + public func enableBasicsTab() { + CapturePrefs() + let template = LoadComponentTemplate() + let tabparent: XMLElement? = (currentSettings.rootElement()?.elements(forName: "mso:ribbon").first?.elements(forName: "mso:tabs").first) + if tabparent != nil { + let cstabs = tabparent!.elements(forName: "mso:tab") + let tptabs = template.rootElement()?.elements(forName: "mso:ribbon").first?.elements(forName: "mso:tabs").first?.elements(forName: "mso:tab") + if tptabs != nil { + for tab in cstabs { + if tab.attribute(forName: "id")?.stringValue == "mso_c5.30BBE710" { + tab.detach() + } + } + for tab in tptabs! { + if tab.attribute(forName: "id")?.stringValue == "mso_c5.30BBE710" { + tab.detach() + tabparent!.insertChild(tab, at: 0) + savePrefs() + return + } + } + } + } + } + + public func disableBasicsTab() { + CapturePrefs() + let cstabs = currentSettings.rootElement()?.elements(forName: "mso:ribbon").first?.elements(forName: "mso:tabs").first?.elements(forName: "mso:tab") + if cstabs != nil { + for tab in cstabs! { + if tab.attribute(forName: "id")?.stringValue == "mso_c5.30BBE710" { + tab.detach() + savePrefs() + return + } + } + } + } + + public func enableEssentialsTab() { + CapturePrefs() + let template = LoadComponentTemplate() + let tabparent: XMLElement? = (currentSettings.rootElement()?.elements(forName: "mso:ribbon").first?.elements(forName: "mso:tabs").first) + if tabparent != nil { + let cstabs = tabparent!.elements(forName: "mso:tab") + let tptabs = template.rootElement()?.elements(forName: "mso:ribbon").first?.elements(forName: "mso:tabs").first?.elements(forName: "mso:tab") + if tptabs != nil { + for tab in cstabs { + if tab.attribute(forName: "id")?.stringValue == "mso_c13.30C490B2" { + tab.detach() + } + } + for tab in tptabs! { + if tab.attribute(forName: "id")?.stringValue == "mso_c13.30C490B2" { + tab.detach() + var index = 0 + for tab2 in cstabs { //inserts first unless it sees morphic basics, then it goes immediately after it + if tab2.attribute(forName: "id")?.stringValue == "mso_c5.30BBE710" { + index = tab2.index + 1 + break + } + } + tabparent!.insertChild(tab, at: index) + savePrefs() + return + } + } + } + } + } + + public func disableEssentialsTab() { + CapturePrefs() + let cstabs = currentSettings.rootElement()?.elements(forName: "mso:ribbon").first?.elements(forName: "mso:tabs").first?.elements(forName: "mso:tab") + if cstabs != nil { + for tab in cstabs! { + if(tab.attribute(forName: "id")?.stringValue == "mso_c13.30C490B2") { + tab.detach() + savePrefs() + return + } + } + } + } + + public func savePrefs() { + do { + if _filemanager.fileExists(atPath: path) && !_filemanager.fileExists(atPath: backupPath) { + try _filemanager.copyItem(atPath: path, toPath: backupPath) + } + if _filemanager.fileExists(atPath: path) { + try _filemanager.removeItem(atPath: path) + } + try currentSettings.xmlString.write(toFile: path, atomically: false, encoding: .utf8) + WordRibbonUIAutomation.RefreshRibbon() + } catch { + return + } + } + + public func restoreOriginal() { + do { + if _filemanager.fileExists(atPath: backupPath) { + if _filemanager.fileExists(atPath: path) { + try _filemanager.removeItem(atPath: path) + } + try _filemanager.copyItem(atPath: backupPath, toPath: path) + try _filemanager.removeItem(atPath: backupPath) + } + else { + try _filemanager.removeItem(atPath: path) + try originalString.write(toFile: path, atomically: false, encoding: .utf8) + } + WordRibbonUIAutomation.RefreshRibbon() + } catch { + return + } + } + + private func LoadEmptyTemplate() -> XMLDocument { + var reply: XMLDocument = XMLDocument() + do { + reply = try XMLDocument(contentsOf: Bundle(for: type(of: self)).url(forResource: "EmptyTemplate", withExtension: "xml")!, options: .nodePreserveNamespaceOrder) + } catch {} + return reply + } + + private func LoadComponentTemplate() -> XMLDocument { + var reply: XMLDocument = XMLDocument() + do { + reply = try XMLDocument(contentsOf: Bundle(for: type(of: self)).url(forResource: "ComponentTemplate", withExtension: "xml")!, options: .nodePreserveNamespaceOrder) + } catch {} + return reply + } + + private let path: String + private let backupPath: String + private let _filemanager: FileManager + private var originalString: String + private var currentSettings: XMLDocument +}