mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-11-22 11:25:42 +01:00
Plist Editor for Cover.app
Introduced a real Property List Editor. Fixed Theme manager engine. Other minor fixes
This commit is contained in:
parent
5a85883180
commit
c8969630f3
@ -7,11 +7,17 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
952B4D202426A3FF00BFB8AB /* PETypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 952B4D1F2426A3FF00BFB8AB /* PETypes.swift */; };
|
||||
952EB6AF24018B4500BEB0F8 /* gfxutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 952EB6A924018B4400BEB0F8 /* gfxutil.c */; };
|
||||
952EB6B024018B4500BEB0F8 /* utils.c in Sources */ = {isa = PBXBuildFile; fileRef = 952EB6AA24018B4400BEB0F8 /* utils.c */; };
|
||||
952EB6B124018B4500BEB0F8 /* efidevp.c in Sources */ = {isa = PBXBuildFile; fileRef = 952EB6AE24018B4500BEB0F8 /* efidevp.c */; };
|
||||
9533718323709517003F1AF4 /* bootsectors-install in Resources */ = {isa = PBXBuildFile; fileRef = 9533718223709517003F1AF4 /* bootsectors-install */; };
|
||||
9533718523709A36003F1AF4 /* bootsectors-install in Copy CloverV2 */ = {isa = PBXBuildFile; fileRef = 9533718223709517003F1AF4 /* bootsectors-install */; };
|
||||
9534B79B242CDC53003F323E /* Replace.png in Resources */ = {isa = PBXBuildFile; fileRef = 9534B799242CDC52003F323E /* Replace.png */; };
|
||||
9534B79C242CDC53003F323E /* Find.png in Resources */ = {isa = PBXBuildFile; fileRef = 9534B79A242CDC52003F323E /* Find.png */; };
|
||||
9534B79F242CDC9B003F323E /* remove.png in Resources */ = {isa = PBXBuildFile; fileRef = 9534B79D242CDC9B003F323E /* remove.png */; };
|
||||
9534B7A0242CDC9B003F323E /* add.png in Resources */ = {isa = PBXBuildFile; fileRef = 9534B79E242CDC9B003F323E /* add.png */; };
|
||||
953596692422A77E00DE2D5B /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 953596682422A77D00DE2D5B /* QuartzCore.framework */; };
|
||||
953BC20323720C0A0039755D /* FixedWidthViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 953BC20223720C0A0039755D /* FixedWidthViews.swift */; };
|
||||
9542ABC02373783400DC03E6 /* boot1-install in Copy tools */ = {isa = PBXBuildFile; fileRef = 9542ABBD2373780C00DC03E6 /* boot1-install */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
9542ABC22373786700DC03E6 /* CloverV2 in Copy CloverV2 */ = {isa = PBXBuildFile; fileRef = 9542ABC12373786700DC03E6 /* CloverV2 */; };
|
||||
@ -19,13 +25,36 @@
|
||||
954BBE99238196EE0032425F /* Locale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954BBE98238196EE0032425F /* Locale.swift */; };
|
||||
954DECD523899F5F006A9876 /* Bootmanager.png in Resources */ = {isa = PBXBuildFile; fileRef = 954DECD423899F5F006A9876 /* Bootmanager.png */; };
|
||||
954FCFD92391818300C9273C /* NSWindowFix.m in Sources */ = {isa = PBXBuildFile; fileRef = 954FCFD82391818300C9273C /* NSWindowFix.m */; };
|
||||
955005ED242531E300AB3979 /* PE_Undo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955005EC242531E300AB3979 /* PE_Undo.swift */; };
|
||||
95509171238B1E3A00933A7E /* daemonInstaller in Copy tools */ = {isa = PBXBuildFile; fileRef = 9550916E238B1DFA00933A7E /* daemonInstaller */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
9552D748236F33CA00C93377 /* CloverDaemonNew in Copy tools */ = {isa = PBXBuildFile; fileRef = 9552D745236F33A700C93377 /* CloverDaemonNew */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
955500E1241ACCCA00618F57 /* Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500E0241ACCCA00618F57 /* Document.swift */; };
|
||||
955500E7241AD12800618F57 /* PEFormatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500E6241AD12800618F57 /* PEFormatters.swift */; };
|
||||
955500E9241AD1E700618F57 /* PlistParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500E8241AD1E700618F57 /* PlistParser.swift */; };
|
||||
955500EB241AD31900618F57 /* PEValueTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500EA241AD31900618F57 /* PEValueTransformer.swift */; };
|
||||
955500ED241AD38500618F57 /* PENode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500EC241AD38500618F57 /* PENode.swift */; };
|
||||
955500EF241AD48200618F57 /* TagData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500EE241AD48100618F57 /* TagData.swift */; };
|
||||
95550103241AD7C300618F57 /* PETableCellViewPop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500F4241AD7C200618F57 /* PETableCellViewPop.swift */; };
|
||||
95550104241AD7C300618F57 /* PESearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500F5241AD7C200618F57 /* PESearchField.swift */; };
|
||||
95550106241AD7C300618F57 /* PETableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500F7241AD7C200618F57 /* PETableCellView.swift */; };
|
||||
95550107241AD7C300618F57 /* PEPopUpButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500F8241AD7C200618F57 /* PEPopUpButton.swift */; };
|
||||
95550108241AD7C300618F57 /* PEFindButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500F9241AD7C200618F57 /* PEFindButton.swift */; };
|
||||
95550109241AD7C300618F57 /* PEMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500FA241AD7C200618F57 /* PEMenuItem.swift */; };
|
||||
9555010A241AD7C300618F57 /* ReplaceButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500FB241AD7C200618F57 /* ReplaceButton.swift */; };
|
||||
9555010B241AD7C300618F57 /* PEReplaceField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500FC241AD7C200618F57 /* PEReplaceField.swift */; };
|
||||
9555010C241AD7C300618F57 /* PEOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500FD241AD7C200618F57 /* PEOutlineView.swift */; };
|
||||
9555010D241AD7C300618F57 /* PETableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500FE241AD7C200618F57 /* PETableRowView.swift */; };
|
||||
9555010E241AD7C300618F57 /* PETextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955500FF241AD7C200618F57 /* PETextField.swift */; };
|
||||
95550110241BEEF300618F57 /* PE_Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9555010F241BEEF200618F57 /* PE_Notifications.swift */; };
|
||||
95550112241BEF4F00618F57 /* PE_Serialize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95550111241BEF4F00618F57 /* PE_Serialize.swift */; };
|
||||
95557D6D2436808D00C95FDE /* PE_Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95557D6C2436808D00C95FDE /* PE_Utils.swift */; };
|
||||
9555AF26238EE53300108C33 /* InstallerOutline.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9555AF25238EE53300108C33 /* InstallerOutline.swift */; };
|
||||
9555AF28238EFDAD00108C33 /* DriversCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9555AF27238EFDAD00108C33 /* DriversCollection.swift */; };
|
||||
955689DB23A2728000AD323C /* IO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955689DA23A2728000AD323C /* IO.swift */; };
|
||||
9556CAF5238DF75600082671 /* InstallerOutline.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9556CAF3238DF75600082671 /* InstallerOutline.xib */; };
|
||||
9558518E23BA3486002CB3D8 /* Speaker.png in Resources */ = {isa = PBXBuildFile; fileRef = 9558518D23BA3485002CB3D8 /* Speaker.png */; };
|
||||
9559D524243540ED0007FCF3 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9559D523243540ED0007FCF3 /* Cocoa.framework */; };
|
||||
9559D52624355A810007FCF3 /* PEConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9559D52524355A800007FCF3 /* PEConstraints.swift */; };
|
||||
955BEE1523C6B49C00425AB0 /* ThemeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955BEE1223C6B49C00425AB0 /* ThemeView.swift */; };
|
||||
955BEE1623C6B49C00425AB0 /* ThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955BEE1323C6B49C00425AB0 /* ThemeManager.swift */; };
|
||||
955BEE1723C6B49C00425AB0 /* ThemeManagerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 955BEE1423C6B49C00425AB0 /* ThemeManagerVC.swift */; };
|
||||
@ -34,8 +63,15 @@
|
||||
955F7C6D238DCD170019D088 /* DiskArbitration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 955F7C6C238DCD160019D088 /* DiskArbitration.framework */; };
|
||||
9560906A238C61E200ACD7F7 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 95609068238C61E200ACD7F7 /* MainMenu.xib */; };
|
||||
956090B7238C890600ACD7F7 /* Installer.xib in Resources */ = {isa = PBXBuildFile; fileRef = 956090B9238C890600ACD7F7 /* Installer.xib */; };
|
||||
9567B29B242D95A200DAD128 /* PE_Conversions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9567B29A242D95A200DAD128 /* PE_Conversions.swift */; };
|
||||
95696B7B2401829800AFAD37 /* GengConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95696B7A2401829800AFAD37 /* GengConfig.swift */; };
|
||||
9569EC42238DD772003AD72C /* Settings.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9569EC44238DD772003AD72C /* Settings.xib */; };
|
||||
9571A224241BFF44000D6645 /* PlistEditorVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9571A223241BFF44000D6645 /* PlistEditorVC.swift */; };
|
||||
9571A226241C01DB000D6645 /* PlistEditorWC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9571A225241C01DB000D6645 /* PlistEditorWC.swift */; };
|
||||
9571A228241C2B0C000D6645 /* PEAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9571A227241C2B0C000D6645 /* PEAlert.swift */; };
|
||||
9571A22A241C2E7F000D6645 /* PlistEditor.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9571A229241C2E7F000D6645 /* PlistEditor.storyboard */; };
|
||||
9571A22F241CF089000D6645 /* PE_Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9571A22E241CF088000D6645 /* PE_Localized.swift */; };
|
||||
957CDBF0242E4845006FD266 /* HTTPErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 957CDBEF242E4845006FD266 /* HTTPErrors.swift */; };
|
||||
9580402D2413F63700F09F2C /* kmeans.c in Sources */ = {isa = PBXBuildFile; fileRef = 9580400A2413F63400F09F2C /* kmeans.c */; };
|
||||
9580402E2413F63700F09F2C /* blur.c in Sources */ = {isa = PBXBuildFile; fileRef = 9580400B2413F63400F09F2C /* blur.c */; };
|
||||
958040302413F63700F09F2C /* nearest.c in Sources */ = {isa = PBXBuildFile; fileRef = 958040132413F63400F09F2C /* nearest.c */; };
|
||||
@ -44,7 +80,7 @@
|
||||
958040372413F63700F09F2C /* mediancut.c in Sources */ = {isa = PBXBuildFile; fileRef = 9580401E2413F63600F09F2C /* mediancut.c */; };
|
||||
958040392413F63700F09F2C /* mempool.c in Sources */ = {isa = PBXBuildFile; fileRef = 958040232413F63600F09F2C /* mempool.c */; };
|
||||
9580403D2413F8CA00F09F2C /* lodepng.c in Sources */ = {isa = PBXBuildFile; fileRef = 9580403C2413F8C900F09F2C /* lodepng.c */; };
|
||||
958040402414070F00F09F2C /* PNG8Image.m in Sources */ = {isa = PBXBuildFile; fileRef = 9580403F2414070F00F09F2C /* PNG8Image.m */; };
|
||||
958040402414070F00F09F2C /* ThemeImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 9580403F2414070F00F09F2C /* ThemeImage.m */; };
|
||||
958861DA235F75FB00B64173 /* Driver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 958861D9235F75FB00B64173 /* Driver.swift */; };
|
||||
95C515222369BAF500E4A3A8 /* NVRAM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C515212369BAF500E4A3A8 /* NVRAM.swift */; };
|
||||
95C5152F236A0A7400E4A3A8 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C5152E236A0A7400E4A3A8 /* SettingsView.swift */; };
|
||||
@ -188,6 +224,7 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
952B4D1F2426A3FF00BFB8AB /* PETypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PETypes.swift; sourceTree = "<group>"; };
|
||||
952EB6A924018B4400BEB0F8 /* gfxutil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = gfxutil.c; sourceTree = "<group>"; };
|
||||
952EB6AA24018B4400BEB0F8 /* utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = utils.c; sourceTree = "<group>"; };
|
||||
952EB6AB24018B4500BEB0F8 /* gfxutil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gfxutil.h; sourceTree = "<group>"; };
|
||||
@ -195,6 +232,11 @@
|
||||
952EB6AD24018B4500BEB0F8 /* efidevp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = efidevp.h; sourceTree = "<group>"; };
|
||||
952EB6AE24018B4500BEB0F8 /* efidevp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = efidevp.c; sourceTree = "<group>"; };
|
||||
9533718223709517003F1AF4 /* bootsectors-install */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "bootsectors-install"; sourceTree = SOURCE_ROOT; };
|
||||
9534B799242CDC52003F323E /* Replace.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Replace.png; sourceTree = "<group>"; };
|
||||
9534B79A242CDC52003F323E /* Find.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Find.png; sourceTree = "<group>"; };
|
||||
9534B79D242CDC9B003F323E /* remove.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = remove.png; sourceTree = "<group>"; };
|
||||
9534B79E242CDC9B003F323E /* add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = add.png; sourceTree = "<group>"; };
|
||||
953596682422A77D00DE2D5B /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
|
||||
953BC20223720C0A0039755D /* FixedWidthViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixedWidthViews.swift; sourceTree = "<group>"; };
|
||||
9542ABB82373780C00DC03E6 /* boot1-inst.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "boot1-inst.xcodeproj"; path = "../CloverPackage/utils/boot1-install/boot1-inst.xcodeproj"; sourceTree = "<group>"; };
|
||||
9542ABC12373786700DC03E6 /* CloverV2 */ = {isa = PBXFileReference; lastKnownFileType = folder; name = CloverV2; path = ../CloverPackage/CloverV2; sourceTree = "<group>"; };
|
||||
@ -204,14 +246,37 @@
|
||||
954FCFD023917A8A00C9273C /* Clover-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Clover-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
954FCFD72391818300C9273C /* NSWindowFix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSWindowFix.h; sourceTree = "<group>"; };
|
||||
954FCFD82391818300C9273C /* NSWindowFix.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSWindowFix.m; sourceTree = "<group>"; };
|
||||
955005EC242531E300AB3979 /* PE_Undo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PE_Undo.swift; sourceTree = "<group>"; };
|
||||
95509169238B1DFA00933A7E /* daemonInstaller.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = daemonInstaller.xcodeproj; path = daemonInstaller/daemonInstaller.xcodeproj; sourceTree = "<group>"; };
|
||||
95524B83238051F3005F6425 /* CloverLogOut.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CloverLogOut.xcodeproj; path = CloverLogOut/CloverLogOut.xcodeproj; sourceTree = "<group>"; };
|
||||
9552D740236F33A700C93377 /* CloverDaemonNew.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CloverDaemonNew.xcodeproj; path = CloverDaemonNew/CloverDaemonNew.xcodeproj; sourceTree = "<group>"; };
|
||||
955500E0241ACCCA00618F57 /* Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Document.swift; sourceTree = "<group>"; };
|
||||
955500E6241AD12800618F57 /* PEFormatters.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PEFormatters.swift; sourceTree = "<group>"; };
|
||||
955500E8241AD1E700618F57 /* PlistParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlistParser.swift; sourceTree = "<group>"; };
|
||||
955500EA241AD31900618F57 /* PEValueTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PEValueTransformer.swift; sourceTree = "<group>"; };
|
||||
955500EC241AD38500618F57 /* PENode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PENode.swift; sourceTree = "<group>"; };
|
||||
955500EE241AD48100618F57 /* TagData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TagData.swift; sourceTree = "<group>"; };
|
||||
955500F4241AD7C200618F57 /* PETableCellViewPop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PETableCellViewPop.swift; sourceTree = "<group>"; };
|
||||
955500F5241AD7C200618F57 /* PESearchField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PESearchField.swift; sourceTree = "<group>"; };
|
||||
955500F7241AD7C200618F57 /* PETableCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PETableCellView.swift; sourceTree = "<group>"; };
|
||||
955500F8241AD7C200618F57 /* PEPopUpButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PEPopUpButton.swift; sourceTree = "<group>"; };
|
||||
955500F9241AD7C200618F57 /* PEFindButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PEFindButton.swift; sourceTree = "<group>"; };
|
||||
955500FA241AD7C200618F57 /* PEMenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PEMenuItem.swift; sourceTree = "<group>"; };
|
||||
955500FB241AD7C200618F57 /* ReplaceButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplaceButton.swift; sourceTree = "<group>"; };
|
||||
955500FC241AD7C200618F57 /* PEReplaceField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PEReplaceField.swift; sourceTree = "<group>"; };
|
||||
955500FD241AD7C200618F57 /* PEOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PEOutlineView.swift; sourceTree = "<group>"; };
|
||||
955500FE241AD7C200618F57 /* PETableRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PETableRowView.swift; sourceTree = "<group>"; };
|
||||
955500FF241AD7C200618F57 /* PETextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PETextField.swift; sourceTree = "<group>"; };
|
||||
9555010F241BEEF200618F57 /* PE_Notifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PE_Notifications.swift; sourceTree = "<group>"; };
|
||||
95550111241BEF4F00618F57 /* PE_Serialize.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PE_Serialize.swift; sourceTree = "<group>"; };
|
||||
95557D6C2436808D00C95FDE /* PE_Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PE_Utils.swift; sourceTree = "<group>"; };
|
||||
9555AF25238EE53300108C33 /* InstallerOutline.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstallerOutline.swift; sourceTree = "<group>"; };
|
||||
9555AF27238EFDAD00108C33 /* DriversCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DriversCollection.swift; sourceTree = "<group>"; };
|
||||
955689DA23A2728000AD323C /* IO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IO.swift; sourceTree = "<group>"; };
|
||||
9556CAF4238DF75600082671 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/InstallerOutline.xib; sourceTree = "<group>"; };
|
||||
9558518D23BA3485002CB3D8 /* Speaker.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Speaker.png; sourceTree = "<group>"; };
|
||||
9559D523243540ED0007FCF3 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||
9559D52524355A800007FCF3 /* PEConstraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PEConstraints.swift; sourceTree = "<group>"; };
|
||||
955BEE1223C6B49C00425AB0 /* ThemeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeView.swift; sourceTree = "<group>"; };
|
||||
955BEE1323C6B49C00425AB0 /* ThemeManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeManager.swift; sourceTree = "<group>"; };
|
||||
955BEE1423C6B49C00425AB0 /* ThemeManagerVC.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeManagerVC.swift; sourceTree = "<group>"; };
|
||||
@ -296,6 +361,7 @@
|
||||
95609101238C89C300ACD7F7 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Installer.strings; sourceTree = "<group>"; };
|
||||
95609103238C89C600ACD7F7 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Installer.strings; sourceTree = "<group>"; };
|
||||
95609105238C89C900ACD7F7 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Installer.strings; sourceTree = "<group>"; };
|
||||
9567B29A242D95A200DAD128 /* PE_Conversions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PE_Conversions.swift; sourceTree = "<group>"; };
|
||||
95696B7A2401829800AFAD37 /* GengConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GengConfig.swift; sourceTree = "<group>"; };
|
||||
9569EC43238DD772003AD72C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/Settings.xib; sourceTree = "<group>"; };
|
||||
9569EC46238DD77A003AD72C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Settings.strings; sourceTree = "<group>"; };
|
||||
@ -336,6 +402,12 @@
|
||||
9569EC8C238DD7C6003AD72C /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Settings.strings; sourceTree = "<group>"; };
|
||||
9569EC8E238DD7C8003AD72C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Settings.strings; sourceTree = "<group>"; };
|
||||
9569EC90238DD7CA003AD72C /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Settings.strings; sourceTree = "<group>"; };
|
||||
9571A223241BFF44000D6645 /* PlistEditorVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlistEditorVC.swift; sourceTree = "<group>"; };
|
||||
9571A225241C01DB000D6645 /* PlistEditorWC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlistEditorWC.swift; sourceTree = "<group>"; };
|
||||
9571A227241C2B0C000D6645 /* PEAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PEAlert.swift; sourceTree = "<group>"; };
|
||||
9571A229241C2E7F000D6645 /* PlistEditor.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = PlistEditor.storyboard; sourceTree = "<group>"; };
|
||||
9571A22E241CF088000D6645 /* PE_Localized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PE_Localized.swift; sourceTree = "<group>"; };
|
||||
957CDBEF242E4845006FD266 /* HTTPErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPErrors.swift; sourceTree = "<group>"; };
|
||||
9580400A2413F63400F09F2C /* kmeans.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = kmeans.c; path = "libimagequant-2.12.6/kmeans.c"; sourceTree = SOURCE_ROOT; };
|
||||
9580400B2413F63400F09F2C /* blur.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = blur.c; path = "libimagequant-2.12.6/blur.c"; sourceTree = SOURCE_ROOT; };
|
||||
958040132413F63400F09F2C /* nearest.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = nearest.c; path = "libimagequant-2.12.6/nearest.c"; sourceTree = SOURCE_ROOT; };
|
||||
@ -352,8 +424,8 @@
|
||||
958040232413F63600F09F2C /* mempool.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = mempool.c; path = "libimagequant-2.12.6/mempool.c"; sourceTree = SOURCE_ROOT; };
|
||||
9580403B2413F86A00F09F2C /* lodepng.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = lodepng.h; sourceTree = "<group>"; };
|
||||
9580403C2413F8C900F09F2C /* lodepng.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = lodepng.c; sourceTree = "<group>"; };
|
||||
9580403E2414070F00F09F2C /* PNG8Image.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PNG8Image.h; sourceTree = "<group>"; };
|
||||
9580403F2414070F00F09F2C /* PNG8Image.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PNG8Image.m; sourceTree = "<group>"; };
|
||||
9580403E2414070F00F09F2C /* ThemeImage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ThemeImage.h; sourceTree = "<group>"; };
|
||||
9580403F2414070F00F09F2C /* ThemeImage.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThemeImage.m; sourceTree = "<group>"; };
|
||||
958861D9235F75FB00B64173 /* Driver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Driver.swift; sourceTree = "<group>"; };
|
||||
95C515212369BAF500E4A3A8 /* NVRAM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NVRAM.swift; sourceTree = "<group>"; };
|
||||
95C5152E236A0A7400E4A3A8 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
@ -380,6 +452,8 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9559D524243540ED0007FCF3 /* Cocoa.framework in Frameworks */,
|
||||
953596692422A77E00DE2D5B /* QuartzCore.framework in Frameworks */,
|
||||
955F7C6D238DCD170019D088 /* DiskArbitration.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -411,6 +485,8 @@
|
||||
954C3DEF237DF0250059C698 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9559D523243540ED0007FCF3 /* Cocoa.framework */,
|
||||
953596682422A77D00DE2D5B /* QuartzCore.framework */,
|
||||
955F7C6C238DCD160019D088 /* DiskArbitration.framework */,
|
||||
954C3DF0237DF0250059C698 /* ServiceManagement.framework */,
|
||||
);
|
||||
@ -441,10 +517,59 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
955500DD241ACBD800618F57 /* Plist Editor */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
955500F0241AD75B00618F57 /* Views */,
|
||||
9567B29A242D95A200DAD128 /* PE_Conversions.swift */,
|
||||
9571A22E241CF088000D6645 /* PE_Localized.swift */,
|
||||
955005EC242531E300AB3979 /* PE_Undo.swift */,
|
||||
955500E0241ACCCA00618F57 /* Document.swift */,
|
||||
955500EE241AD48100618F57 /* TagData.swift */,
|
||||
955500EC241AD38500618F57 /* PENode.swift */,
|
||||
955500E8241AD1E700618F57 /* PlistParser.swift */,
|
||||
955500E6241AD12800618F57 /* PEFormatters.swift */,
|
||||
955500EA241AD31900618F57 /* PEValueTransformer.swift */,
|
||||
9555010F241BEEF200618F57 /* PE_Notifications.swift */,
|
||||
95550111241BEF4F00618F57 /* PE_Serialize.swift */,
|
||||
95557D6C2436808D00C95FDE /* PE_Utils.swift */,
|
||||
952B4D1F2426A3FF00BFB8AB /* PETypes.swift */,
|
||||
);
|
||||
path = "Plist Editor";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
955500F0241AD75B00618F57 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9571A229241C2E7F000D6645 /* PlistEditor.storyboard */,
|
||||
9571A225241C01DB000D6645 /* PlistEditorWC.swift */,
|
||||
9571A223241BFF44000D6645 /* PlistEditorVC.swift */,
|
||||
9559D52524355A800007FCF3 /* PEConstraints.swift */,
|
||||
955500F9241AD7C200618F57 /* PEFindButton.swift */,
|
||||
955500FB241AD7C200618F57 /* ReplaceButton.swift */,
|
||||
955500FA241AD7C200618F57 /* PEMenuItem.swift */,
|
||||
955500F8241AD7C200618F57 /* PEPopUpButton.swift */,
|
||||
955500FC241AD7C200618F57 /* PEReplaceField.swift */,
|
||||
955500F5241AD7C200618F57 /* PESearchField.swift */,
|
||||
955500FD241AD7C200618F57 /* PEOutlineView.swift */,
|
||||
955500F7241AD7C200618F57 /* PETableCellView.swift */,
|
||||
955500F4241AD7C200618F57 /* PETableCellViewPop.swift */,
|
||||
955500FE241AD7C200618F57 /* PETableRowView.swift */,
|
||||
955500FF241AD7C200618F57 /* PETextField.swift */,
|
||||
9571A227241C2B0C000D6645 /* PEAlert.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9558518C23BA3472002CB3D8 /* images */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9558518D23BA3485002CB3D8 /* Speaker.png */,
|
||||
9534B79E242CDC9B003F323E /* add.png */,
|
||||
9534B79D242CDC9B003F323E /* remove.png */,
|
||||
9534B79A242CDC52003F323E /* Find.png */,
|
||||
9534B799242CDC52003F323E /* Replace.png */,
|
||||
955BEE1A23C7171000425AB0 /* check.png */,
|
||||
);
|
||||
path = images;
|
||||
sourceTree = "<group>";
|
||||
@ -452,7 +577,9 @@
|
||||
955BEE1123C6B43C00425AB0 /* ThemeManager */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
95803FFB2413F58B00F09F2C /* libimagequant */,
|
||||
955BEE1323C6B49C00425AB0 /* ThemeManager.swift */,
|
||||
957CDBEF242E4845006FD266 /* HTTPErrors.swift */,
|
||||
955BEE1423C6B49C00425AB0 /* ThemeManagerVC.swift */,
|
||||
955BEE1223C6B49C00425AB0 /* ThemeView.swift */,
|
||||
955BEE1823C6B4B300425AB0 /* ThemeManager.xib */,
|
||||
@ -518,7 +645,7 @@
|
||||
95E68AC8235B862F002B37A5 /* Clover */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
95803FFB2413F58B00F09F2C /* libimagequant */,
|
||||
955500DD241ACBD800618F57 /* Plist Editor */,
|
||||
952EB6A824018B2400BEB0F8 /* GfxUtil */,
|
||||
955BEE1123C6B43C00425AB0 /* ThemeManager */,
|
||||
95E68AC9235B862F002B37A5 /* AppDelegate.swift */,
|
||||
@ -537,7 +664,6 @@
|
||||
95E50075238ABA56002F3869 /* Tasks.swift */,
|
||||
953BC20223720C0A0039755D /* FixedWidthViews.swift */,
|
||||
9533718223709517003F1AF4 /* bootsectors-install */,
|
||||
955BEE1A23C7171000425AB0 /* check.png */,
|
||||
9569EC44238DD772003AD72C /* Settings.xib */,
|
||||
95609068238C61E200ACD7F7 /* MainMenu.xib */,
|
||||
95E68AD9235B8666002B37A5 /* Installer */,
|
||||
@ -549,8 +675,8 @@
|
||||
954FCFD023917A8A00C9273C /* Clover-Bridging-Header.h */,
|
||||
954FCFD72391818300C9273C /* NSWindowFix.h */,
|
||||
954FCFD82391818300C9273C /* NSWindowFix.m */,
|
||||
9580403E2414070F00F09F2C /* PNG8Image.h */,
|
||||
9580403F2414070F00F09F2C /* PNG8Image.m */,
|
||||
9580403E2414070F00F09F2C /* ThemeImage.h */,
|
||||
9580403F2414070F00F09F2C /* ThemeImage.m */,
|
||||
);
|
||||
path = Clover;
|
||||
sourceTree = "<group>";
|
||||
@ -575,7 +701,7 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 95E68AD6235B8632002B37A5 /* Build configuration list for PBXNativeTarget "Clover" */;
|
||||
buildPhases = (
|
||||
9542ABAA23735EE800DC03E6 /* Zip CloverV2 */,
|
||||
9542ABAA23735EE800DC03E6 /* zip CloverV2 */,
|
||||
95E68AC2235B862F002B37A5 /* Sources */,
|
||||
95E68AC4235B862F002B37A5 /* Resources */,
|
||||
95E68AE9235C70BE002B37A5 /* Copy CloverV2 */,
|
||||
@ -735,7 +861,10 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9534B79B242CDC53003F323E /* Replace.png in Resources */,
|
||||
9571A22A241C2E7F000D6645 /* PlistEditor.storyboard in Resources */,
|
||||
9556CAF5238DF75600082671 /* InstallerOutline.xib in Resources */,
|
||||
9534B7A0242CDC9B003F323E /* add.png in Resources */,
|
||||
9558518E23BA3486002CB3D8 /* Speaker.png in Resources */,
|
||||
956090B7238C890600ACD7F7 /* Installer.xib in Resources */,
|
||||
954DECD523899F5F006A9876 /* Bootmanager.png in Resources */,
|
||||
@ -744,6 +873,8 @@
|
||||
955BEE1B23C7171100425AB0 /* check.png in Resources */,
|
||||
9560906A238C61E200ACD7F7 /* MainMenu.xib in Resources */,
|
||||
9533718323709517003F1AF4 /* bootsectors-install in Resources */,
|
||||
9534B79F242CDC9B003F323E /* remove.png in Resources */,
|
||||
9534B79C242CDC53003F323E /* Find.png in Resources */,
|
||||
955BEE1923C6B4B300425AB0 /* ThemeManager.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -751,7 +882,7 @@
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
9542ABAA23735EE800DC03E6 /* Zip CloverV2 */ = {
|
||||
9542ABAA23735EE800DC03E6 /* zip CloverV2 */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
@ -760,7 +891,7 @@
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Zip CloverV2";
|
||||
name = "zip CloverV2";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
@ -785,7 +916,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "rm -rf \"${TARGET_BUILD_DIR}\"/Clover.app/Contents/SharedSupport/CloverV2/rcScripts\nrm -rf \"${TARGET_BUILD_DIR}\"/Clover.app/Contents/SharedSupport/CloverV2/ThirdParty\n";
|
||||
shellScript = "cd ..\n\nif [[ ! -d \"${PWD}\"/CloverPackage ]]; then\n echo \"Error. Cannot found CloverPackage directory.\"\n exit 1\nfi\n\nwithV2=\"${PWD}/CloverPackage/sym/.withV2\"\n\nif [[ -f \"${withV2}\" ]]; then\nrm -rf \"${TARGET_BUILD_DIR}\"/Clover.app/Contents/SharedSupport/CloverV2/rcScripts\nrm -rf \"${TARGET_BUILD_DIR}\"/Clover.app/Contents/SharedSupport/CloverV2/ThirdParty\n\nthemeDir=\"${TARGET_BUILD_DIR}\"/Clover.app/Contents/SharedSupport/CloverV2/themespkg\nif [[ -d \"${themeDir}\"/Clovy ]]; then\ncd \"${themeDir}\"\nrm -f *.plist\nfind . -type d -not -name 'Clovy' -not -name \".*\" -print0 | xargs -0 -I {} rm -rf {}\nfi\nelse\nrm -rf \"${TARGET_BUILD_DIR}\"/Clover.app/Contents/SharedSupport/CloverV2/*\nfi\n\nrm -f \"${withV2}\"\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
@ -794,38 +925,67 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
955500EF241AD48200618F57 /* TagData.swift in Sources */,
|
||||
955500ED241AD38500618F57 /* PENode.swift in Sources */,
|
||||
95E68AE5235B89D9002B37A5 /* Installer.swift in Sources */,
|
||||
952EB6B124018B4500BEB0F8 /* efidevp.c in Sources */,
|
||||
9555010A241AD7C300618F57 /* ReplaceButton.swift in Sources */,
|
||||
958040312413F63700F09F2C /* libimagequant.c in Sources */,
|
||||
9571A226241C01DB000D6645 /* PlistEditorWC.swift in Sources */,
|
||||
9571A224241BFF44000D6645 /* PlistEditorVC.swift in Sources */,
|
||||
955500E9241AD1E700618F57 /* PlistParser.swift in Sources */,
|
||||
957CDBF0242E4845006FD266 /* HTTPErrors.swift in Sources */,
|
||||
95550107241AD7C300618F57 /* PEPopUpButton.swift in Sources */,
|
||||
95E68AE1235B86A1002B37A5 /* Shared.swift in Sources */,
|
||||
952B4D202426A3FF00BFB8AB /* PETypes.swift in Sources */,
|
||||
9571A228241C2B0C000D6645 /* PEAlert.swift in Sources */,
|
||||
95550106241AD7C300618F57 /* PETableCellView.swift in Sources */,
|
||||
95E68ACC235B862F002B37A5 /* ViewController.swift in Sources */,
|
||||
95E68AE2235B86A1002B37A5 /* Disks.swift in Sources */,
|
||||
954FCFD92391818300C9273C /* NSWindowFix.m in Sources */,
|
||||
9580403D2413F8CA00F09F2C /* lodepng.c in Sources */,
|
||||
958040302413F63700F09F2C /* nearest.c in Sources */,
|
||||
958040372413F63700F09F2C /* mediancut.c in Sources */,
|
||||
9555010B241AD7C300618F57 /* PEReplaceField.swift in Sources */,
|
||||
95550112241BEF4F00618F57 /* PE_Serialize.swift in Sources */,
|
||||
95557D6D2436808D00C95FDE /* PE_Utils.swift in Sources */,
|
||||
95550109241AD7C300618F57 /* PEMenuItem.swift in Sources */,
|
||||
95E68ADF235B86A1002B37A5 /* Releases.swift in Sources */,
|
||||
95550103241AD7C300618F57 /* PETableCellViewPop.swift in Sources */,
|
||||
9559D52624355A810007FCF3 /* PEConstraints.swift in Sources */,
|
||||
954BBE99238196EE0032425F /* Locale.swift in Sources */,
|
||||
9555010C241AD7C300618F57 /* PEOutlineView.swift in Sources */,
|
||||
95E68ACA235B862F002B37A5 /* AppDelegate.swift in Sources */,
|
||||
955BEE1523C6B49C00425AB0 /* ThemeView.swift in Sources */,
|
||||
958861DA235F75FB00B64173 /* Driver.swift in Sources */,
|
||||
958040402414070F00F09F2C /* PNG8Image.m in Sources */,
|
||||
958040402414070F00F09F2C /* ThemeImage.m in Sources */,
|
||||
95E68AE3235B86A1002B37A5 /* bdmesg.swift in Sources */,
|
||||
9580402E2413F63700F09F2C /* blur.c in Sources */,
|
||||
95C5152F236A0A7400E4A3A8 /* SettingsView.swift in Sources */,
|
||||
9555AF28238EFDAD00108C33 /* DriversCollection.swift in Sources */,
|
||||
952EB6AF24018B4500BEB0F8 /* gfxutil.c in Sources */,
|
||||
9580402D2413F63700F09F2C /* kmeans.c in Sources */,
|
||||
958040392413F63700F09F2C /* mempool.c in Sources */,
|
||||
958040332413F63700F09F2C /* pam.c in Sources */,
|
||||
953BC20323720C0A0039755D /* FixedWidthViews.swift in Sources */,
|
||||
95696B7B2401829800AFAD37 /* GengConfig.swift in Sources */,
|
||||
95E50076238ABA56002F3869 /* Tasks.swift in Sources */,
|
||||
955005ED242531E300AB3979 /* PE_Undo.swift in Sources */,
|
||||
958040392413F63700F09F2C /* mempool.c in Sources */,
|
||||
955500EB241AD31900618F57 /* PEValueTransformer.swift in Sources */,
|
||||
958040332413F63700F09F2C /* pam.c in Sources */,
|
||||
95550104241AD7C300618F57 /* PESearchField.swift in Sources */,
|
||||
9571A22F241CF089000D6645 /* PE_Localized.swift in Sources */,
|
||||
953BC20323720C0A0039755D /* FixedWidthViews.swift in Sources */,
|
||||
95550110241BEEF300618F57 /* PE_Notifications.swift in Sources */,
|
||||
95696B7B2401829800AFAD37 /* GengConfig.swift in Sources */,
|
||||
9555010E241AD7C300618F57 /* PETextField.swift in Sources */,
|
||||
95C515222369BAF500E4A3A8 /* NVRAM.swift in Sources */,
|
||||
95C51536236B1F7700E4A3A8 /* RunAtLogin.swift in Sources */,
|
||||
95550108241AD7C300618F57 /* PEFindButton.swift in Sources */,
|
||||
9555010D241AD7C300618F57 /* PETableRowView.swift in Sources */,
|
||||
95E68AE0235B86A1002B37A5 /* Extensions.swift in Sources */,
|
||||
952EB6B024018B4500BEB0F8 /* utils.c in Sources */,
|
||||
9567B29B242D95A200DAD128 /* PE_Conversions.swift in Sources */,
|
||||
955500E7241AD12800618F57 /* PEFormatters.swift in Sources */,
|
||||
955689DB23A2728000AD323C /* IO.swift in Sources */,
|
||||
955500E1241ACCCA00618F57 /* Document.swift in Sources */,
|
||||
9555AF26238EE53300108C33 /* InstallerOutline.swift in Sources */,
|
||||
955BEE1723C6B49C00425AB0 /* ThemeManagerVC.swift in Sources */,
|
||||
955BEE1623C6B49C00425AB0 /* ThemeManager.swift in Sources */,
|
||||
@ -1141,7 +1301,7 @@
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1.16;
|
||||
CURRENT_PROJECT_VERSION = 1.17;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Clover/Frameworks",
|
||||
@ -1166,11 +1326,13 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Clover/Frameworks",
|
||||
"$(PROJECT_DIR)/Clover/Library",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.16;
|
||||
MARKETING_VERSION = 1.17;
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.slice.Clover;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_INCLUDE_PATHS = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Clover/Clover-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@ -1187,7 +1349,7 @@
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1.16;
|
||||
CURRENT_PROJECT_VERSION = 1.17;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Clover/Frameworks",
|
||||
@ -1211,11 +1373,13 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Clover/Frameworks",
|
||||
"$(PROJECT_DIR)/Clover/Library",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.16;
|
||||
MARKETING_VERSION = 1.17;
|
||||
OTHER_LDFLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.slice.Clover;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_INCLUDE_PATHS = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Clover/Clover-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
|
@ -14,6 +14,9 @@ let localeBundle = Bundle(path: Bundle.main.sharedSupportPath! + "/Lang.bundle")
|
||||
|
||||
@NSApplicationMain
|
||||
final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
||||
var canDrawConcurrently : Bool = false
|
||||
var documentsPaths : [String] = [String]()
|
||||
var havefinishLaunching : Bool = false
|
||||
let CloverRevision : Int = Int(findCloverRevision() ?? "0") ?? 0
|
||||
var isInstalling : Bool = false
|
||||
var isInstallerOpen : Bool = false
|
||||
@ -43,6 +46,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
||||
|
||||
self.daContext.deallocate()
|
||||
}
|
||||
|
||||
func applicationDidBecomeActive(_ notification: Notification) {
|
||||
// print("applicationDidBecomeActive")
|
||||
}
|
||||
|
||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||
/*
|
||||
@ -72,7 +79,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
||||
}
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
let appImage = NSImage(named: "NSApplicationIcon")
|
||||
self.havefinishLaunching = true
|
||||
let appImage : NSImage? = NSImage(named: "NSApplicationIcon")?.copy() as? NSImage
|
||||
if #available(OSX 10.10, *) {
|
||||
let size = self.statusItem.button!.frame.height - 3
|
||||
appImage?.size = NSMakeSize(size, size)
|
||||
@ -136,6 +144,17 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
||||
CFRunLoopRun()
|
||||
}
|
||||
|
||||
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
|
||||
if !self.havefinishLaunching {
|
||||
if let files : NSArray = UDs.value(forKey: "Docs") as? NSArray {
|
||||
for f in files {
|
||||
loadPlist(at: f as! String)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@objc func showPopover(_ sender: Any?) {
|
||||
if (self.popover == nil) {
|
||||
self.popover = NSPopover()
|
||||
@ -159,7 +178,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
||||
self.popover?.show(relativeTo: v.bounds, of: v, preferredEdge: NSRectEdge.maxY)
|
||||
}
|
||||
}
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
//NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,6 +187,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
||||
}
|
||||
|
||||
@objc func reFreshDisksList() {
|
||||
(self.settingsWC?.contentViewController as? SettingsViewController)?.searchDisks()
|
||||
(self.settingsWC?.contentViewController as? SettingsViewController)?.searchESPDisks()
|
||||
(self.installerWC?.contentViewController as? InstallerViewController)?.populateTargets()
|
||||
(self.installerOutWC?.contentViewController as? InstallerOutViewController)?.populateTargets()
|
||||
@ -181,7 +201,5 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15504" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15702" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15504"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15702"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
@ -343,268 +342,6 @@
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Format" id="jxT-CU-nIS">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Format" id="GEO-Iw-cKr">
|
||||
<items>
|
||||
<menuItem title="Font" id="Gi5-1S-RQB">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Font" systemMenu="font" id="aXa-aM-Jaq">
|
||||
<items>
|
||||
<menuItem title="Show Fonts" keyEquivalent="t" id="Q5e-8K-NDq">
|
||||
<connections>
|
||||
<action selector="orderFrontFontPanel:" target="YLy-65-1bz" id="WHr-nq-2xA"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Bold" tag="2" keyEquivalent="b" id="GB9-OM-e27">
|
||||
<connections>
|
||||
<action selector="addFontTrait:" target="YLy-65-1bz" id="hqk-hr-sYV"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Italic" tag="1" keyEquivalent="i" id="Vjx-xi-njq">
|
||||
<connections>
|
||||
<action selector="addFontTrait:" target="YLy-65-1bz" id="IHV-OB-c03"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Underline" keyEquivalent="u" id="WRG-CD-K1S">
|
||||
<connections>
|
||||
<action selector="underline:" target="-1" id="FYS-2b-JAY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="5gT-KC-WSO"/>
|
||||
<menuItem title="Bigger" tag="3" keyEquivalent="+" id="Ptp-SP-VEL">
|
||||
<connections>
|
||||
<action selector="modifyFont:" target="YLy-65-1bz" id="Uc7-di-UnL"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Smaller" tag="4" keyEquivalent="-" id="i1d-Er-qST">
|
||||
<connections>
|
||||
<action selector="modifyFont:" target="YLy-65-1bz" id="HcX-Lf-eNd"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="kx3-Dk-x3B"/>
|
||||
<menuItem title="Kern" id="jBQ-r6-VK2">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Kern" id="tlD-Oa-oAM">
|
||||
<items>
|
||||
<menuItem title="Use Default" id="GUa-eO-cwY">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="useStandardKerning:" target="-1" id="6dk-9l-Ckg"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use None" id="cDB-IK-hbR">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="turnOffKerning:" target="-1" id="U8a-gz-Maa"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Tighten" id="46P-cB-AYj">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="tightenKerning:" target="-1" id="hr7-Nz-8ro"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Loosen" id="ogc-rX-tC1">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="loosenKerning:" target="-1" id="8i4-f9-FKE"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Ligatures" id="o6e-r0-MWq">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Ligatures" id="w0m-vy-SC9">
|
||||
<items>
|
||||
<menuItem title="Use Default" id="agt-UL-0e3">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="useStandardLigatures:" target="-1" id="7uR-wd-Dx6"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use None" id="J7y-lM-qPV">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="turnOffLigatures:" target="-1" id="iX2-gA-Ilz"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Use All" id="xQD-1f-W4t">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="useAllLigatures:" target="-1" id="KcB-kA-TuK"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Baseline" id="OaQ-X3-Vso">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Baseline" id="ijk-EB-dga">
|
||||
<items>
|
||||
<menuItem title="Use Default" id="3Om-Ey-2VK">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="unscript:" target="-1" id="0vZ-95-Ywn"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Superscript" id="Rqc-34-cIF">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="superscript:" target="-1" id="3qV-fo-wpU"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Subscript" id="I0S-gh-46l">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="subscript:" target="-1" id="Q6W-4W-IGz"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Raise" id="2h7-ER-AoG">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="raiseBaseline:" target="-1" id="4sk-31-7Q9"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Lower" id="1tx-W0-xDw">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="lowerBaseline:" target="-1" id="OF1-bc-KW4"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="Ndw-q3-faq"/>
|
||||
<menuItem title="Show Colors" keyEquivalent="C" id="bgn-CT-cEk">
|
||||
<connections>
|
||||
<action selector="orderFrontColorPanel:" target="-1" id="mSX-Xz-DV3"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="iMs-zA-UFJ"/>
|
||||
<menuItem title="Copy Style" keyEquivalent="c" id="5Vv-lz-BsD">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="copyFont:" target="-1" id="GJO-xA-L4q"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste Style" keyEquivalent="v" id="vKC-jM-MkH">
|
||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteFont:" target="-1" id="JfD-CL-leO"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="Text" id="Fal-I4-PZk">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Text" id="d9c-me-L2H">
|
||||
<items>
|
||||
<menuItem title="Align Left" keyEquivalent="{" id="ZM1-6Q-yy1">
|
||||
<connections>
|
||||
<action selector="alignLeft:" target="-1" id="zUv-R1-uAa"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Center" keyEquivalent="|" id="VIY-Ag-zcb">
|
||||
<connections>
|
||||
<action selector="alignCenter:" target="-1" id="spX-mk-kcS"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Justify" id="J5U-5w-g23">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="alignJustified:" target="-1" id="ljL-7U-jND"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Align Right" keyEquivalent="}" id="wb2-vD-lq4">
|
||||
<connections>
|
||||
<action selector="alignRight:" target="-1" id="r48-bG-YeY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="4s2-GY-VfK"/>
|
||||
<menuItem title="Writing Direction" id="H1b-Si-o9J">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Writing Direction" id="8mr-sm-Yjd">
|
||||
<items>
|
||||
<menuItem title="Paragraph" enabled="NO" id="ZvO-Gk-QUH">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem id="YGs-j5-SAR">
|
||||
<string key="title"> Default</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeBaseWritingDirectionNatural:" target="-1" id="qtV-5e-UBP"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="Lbh-J2-qVU">
|
||||
<string key="title"> Left to Right</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeBaseWritingDirectionLeftToRight:" target="-1" id="S0X-9S-QSf"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="jFq-tB-4Kx">
|
||||
<string key="title"> Right to Left</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeBaseWritingDirectionRightToLeft:" target="-1" id="5fk-qB-AqJ"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="swp-gr-a21"/>
|
||||
<menuItem title="Selection" enabled="NO" id="cqv-fj-IhA">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
</menuItem>
|
||||
<menuItem id="Nop-cj-93Q">
|
||||
<string key="title"> Default</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeTextWritingDirectionNatural:" target="-1" id="lPI-Se-ZHp"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="BgM-ve-c93">
|
||||
<string key="title"> Left to Right</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeTextWritingDirectionLeftToRight:" target="-1" id="caW-Bv-w94"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem id="RB4-Sm-HuC">
|
||||
<string key="title"> Right to Left</string>
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="makeTextWritingDirectionRightToLeft:" target="-1" id="EXD-6r-ZUu"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem isSeparatorItem="YES" id="fKy-g9-1gm"/>
|
||||
<menuItem title="Show Ruler" id="vLm-3I-IUL">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<connections>
|
||||
<action selector="toggleRuler:" target="-1" id="FOx-HJ-KwY"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Copy Ruler" keyEquivalent="c" id="MkV-Pr-PK5">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="copyRuler:" target="-1" id="71i-fW-3W2"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Paste Ruler" keyEquivalent="v" id="LVM-kO-fVI">
|
||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
|
||||
<connections>
|
||||
<action selector="pasteRuler:" target="-1" id="cSh-wd-qM2"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
</items>
|
||||
</menu>
|
||||
</menuItem>
|
||||
<menuItem title="View" id="H8h-7b-M4v">
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="View" id="HyV-fh-RgO">
|
||||
@ -677,19 +414,5 @@
|
||||
</items>
|
||||
<point key="canvasLocation" x="107" y="-456"/>
|
||||
</menu>
|
||||
<window title="Clover" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="335" y="390" width="480" height="360"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1600" height="877"/>
|
||||
<view key="contentView" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="Zl5-uA-O9b"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="140" y="-120"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
|
@ -25,7 +25,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Check now" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="UyZ-5I-L9X">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="checkNow:" target="tSJ-3G-dnL" id="UY0-EP-xVG"/>
|
||||
@ -35,7 +35,7 @@
|
||||
<rect key="frame" x="14" y="206" width="311" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Update" id="58l-pb-zEc">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -45,7 +45,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Run at login" bezelStyle="regularSquare" imagePosition="left" inset="2" id="J8R-ej-fRl">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="runAtLogin:" target="tSJ-3G-dnL" id="Kfi-uU-9Tq"/>
|
||||
@ -64,7 +64,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="lkK-DQ-QK6">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="tIP-gO-fKT"/>
|
||||
</popUpButtonCell>
|
||||
<userDefinedRuntimeAttributes>
|
||||
@ -95,7 +95,7 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<box verticalHuggingPriority="750" fixedFrame="YES" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="bsQ-GS-duU">
|
||||
<rect key="frame" x="123" y="497" width="133" height="5"/>
|
||||
<rect key="frame" x="150" y="497" width="106" height="5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</box>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="zDK-XE-o2Z">
|
||||
@ -103,7 +103,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="E23-ao-JFs">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="installClover:" target="tSJ-3G-dnL" id="9VG-7Q-ccY"/>
|
||||
@ -114,7 +114,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Button" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="EJg-qm-L18">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="updateClover:" target="tSJ-3G-dnL" id="kpg-zk-tVv"/>
|
||||
@ -138,7 +138,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Uninstall" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="w6z-A1-CZt">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="unInstallDaemon:" target="tSJ-3G-dnL" id="9K2-E3-t1N"/>
|
||||
@ -153,7 +153,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Install" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="omz-9K-nb4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="installDaemon:" target="tSJ-3G-dnL" id="lQr-TV-nz0"/>
|
||||
@ -164,7 +164,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="*Disable Sleep Proxy Client" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="kZm-NL-hOA">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="disableSleepProxy:" target="tSJ-3G-dnL" id="jia-ue-L5p"/>
|
||||
@ -184,7 +184,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="*Make filesystem read-write" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="v3W-6S-CK6">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="makeRootRW:" target="tSJ-3G-dnL" id="KQZ-a6-GKv"/>
|
||||
@ -226,7 +226,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="bevel" bezelStyle="rounded" image="NSInfo" imagePosition="only" alignment="center" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="8hx-sN-OfR">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="goToTopic:" target="tSJ-3G-dnL" id="azU-qH-5SJ"/>
|
||||
@ -237,7 +237,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="bevel" bezelStyle="rounded" image="NSStopProgressFreestandingTemplate" imagePosition="only" alignment="center" focusRingType="none" imageScaling="proportionallyDown" inset="2" id="2fO-JJ-w1L">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="closeApp:" target="tSJ-3G-dnL" id="Hjx-e6-JXH"/>
|
||||
@ -246,7 +246,7 @@
|
||||
<tabView fixedFrame="YES" type="noTabsNoBorder" translatesAutoresizingMaskIntoConstraints="NO" id="PGs-4a-XRc" customClass="LITabView" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="522" width="342" height="120"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Tab" identifier="" id="BAk-3x-mLp">
|
||||
<view key="view" ambiguous="YES" id="ENG-5U-eLE">
|
||||
@ -257,7 +257,7 @@
|
||||
<rect key="frame" x="18" y="101" width="306" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Current revision" id="Re2-vf-XQ6">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -266,7 +266,7 @@
|
||||
<rect key="frame" x="18" y="78" width="306" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Boot Device:" id="omZ-xs-Wjn">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -275,7 +275,7 @@
|
||||
<rect key="frame" x="18" y="39" width="306" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="config path:" id="iii-3P-wo8">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -448,7 +448,7 @@
|
||||
<rect key="frame" x="18" y="64" width="306" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Boot type:" id="pWw-6m-fZo">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -457,7 +457,7 @@
|
||||
<rect key="frame" x="18" y="27" width="306" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="firmware:" id="kyW-hT-U5H">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -487,7 +487,7 @@
|
||||
<tabView fixedFrame="YES" type="noTabsNoBorder" translatesAutoresizingMaskIntoConstraints="NO" id="vwp-hc-6yE" customClass="LITabView" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="358" width="342" height="113"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem identifier="Mount" id="F4n-I1-4Gq">
|
||||
<view key="view" id="E05-QZ-1bf">
|
||||
@ -499,7 +499,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" id="Hzi-CJ-xVH">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="Vb3-8W-CMc"/>
|
||||
</popUpButtonCell>
|
||||
<userDefinedRuntimeAttributes>
|
||||
@ -515,7 +515,7 @@
|
||||
<rect key="frame" x="21" y="74" width="122" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Mount" id="s5K-bS-gZa">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -534,7 +534,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="*auto mount" bezelStyle="regularSquare" imagePosition="left" inset="2" id="tdl-nw-YFd">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="autoMount:" target="tSJ-3G-dnL" id="FNN-jm-HUm"/>
|
||||
@ -545,7 +545,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="umount" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="bhl-e0-nVD">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="umount:" target="tSJ-3G-dnL" id="KqE-Db-Uf1"/>
|
||||
@ -563,7 +563,7 @@
|
||||
<rect key="frame" x="21" y="96" width="303" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Theme:" id="7yo-8b-ZHc">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
@ -571,8 +571,8 @@
|
||||
<comboBox verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="MMH-mb-fPM">
|
||||
<rect key="frame" x="23" y="65" width="302" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<comboBoxCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="NVRAM" drawsBackground="YES" completes="NO" numberOfVisibleItems="5" id="kdy-IM-YSv">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<comboBoxCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" placeholderString="NVRAM" drawsBackground="YES" completes="NO" numberOfVisibleItems="5" id="kdy-IM-YSv">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<objectValues>
|
||||
@ -582,7 +582,7 @@
|
||||
</objectValues>
|
||||
</comboBoxCell>
|
||||
<connections>
|
||||
<action selector="themeBoxSelected:" target="tSJ-3G-dnL" id="76K-Tu-HPV"/>
|
||||
<outlet property="delegate" destination="tSJ-3G-dnL" id="kY4-3y-8Vx"/>
|
||||
</connections>
|
||||
</comboBox>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AzG-cL-pFW">
|
||||
@ -590,7 +590,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="Manager" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="I4v-1L-brN">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="openThemeManager:" target="tSJ-3G-dnL" id="PE4-2e-cP2"/>
|
||||
@ -651,7 +651,7 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="hlk-UE-ZPw" id="7ZL-JE-fT7">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="zSa-aR-ghV">
|
||||
<items>
|
||||
<menuItem title="Item 1" state="on" id="hlk-UE-ZPw"/>
|
||||
@ -732,16 +732,94 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="5qs-Bm-r0y">
|
||||
<rect key="frame" x="18" y="53" width="143" height="32"/>
|
||||
<rect key="frame" x="171" y="84" width="157" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="clover-genconfig" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="135-sE-mfh">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="generateConfig:" target="tSJ-3G-dnL" id="5ip-Lq-aa3"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ooY-tQ-8dD">
|
||||
<rect key="frame" x="22" y="5" width="303" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="KS1-1T-fcC" id="lzk-kj-5l5">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="m7p-bq-UM3">
|
||||
<items>
|
||||
<menuItem title="Item 1" state="on" id="KS1-1T-fcC"/>
|
||||
<menuItem title="Item 2" id="RWc-UL-7IK"/>
|
||||
<menuItem title="Item 3" id="iXP-Hh-Bj7"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="openCloverPlist:" target="tSJ-3G-dnL" id="4Y7-lE-cSg"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="2zc-8N-Nsb">
|
||||
<rect key="frame" x="22" y="30" width="303" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="B7A-dh-kcd" id="XFT-Gr-fJX">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<menu key="menu" id="Jkx-eL-cSD">
|
||||
<items>
|
||||
<menuItem title="Item 1" state="on" id="B7A-dh-kcd"/>
|
||||
<menuItem title="Item 2" id="L2p-wI-PSk"/>
|
||||
<menuItem title="Item 3" id="SAz-SB-gPB"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="volumePopUpPressed:" target="tSJ-3G-dnL" id="2pa-Wc-SqV"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="tzT-4o-NKo">
|
||||
<rect key="frame" x="24" y="94" width="133" height="16"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Property List (plist)" id="SPn-Ok-F68">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VMb-q0-gGi">
|
||||
<rect key="frame" x="203" y="54" width="125" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="New" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="7pm-6a-6q8">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="createNewPlist:" target="tSJ-3G-dnL" id="3Pe-8z-jrp"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="08m-oF-xfG">
|
||||
<rect key="frame" x="171" y="54" width="40" height="32"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="push" title="..." bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="No7-07-kb3">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="searchPanelForPlist:" target="tSJ-3G-dnL" id="vOr-fo-bKY"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6kD-FR-cDL">
|
||||
<rect key="frame" x="24" y="63" width="133" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="auto save" bezelStyle="regularSquare" imagePosition="left" lineBreakMode="truncatingTail" inset="2" id="zbE-mt-ZUk">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="autosaveDocuments:" target="tSJ-3G-dnL" id="yWo-19-eVf"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
@ -751,7 +829,7 @@
|
||||
<rect key="frame" x="14" y="477" width="137" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="selectOne" id="dvq-Ib-bsz">
|
||||
<font key="font" metaFont="label" size="13"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment width="32"/>
|
||||
<segment width="32" selected="YES" tag="1"/>
|
||||
@ -783,6 +861,7 @@
|
||||
<connections>
|
||||
<outlet property="appVersionField" destination="V27-tg-6O6" id="pxR-3a-E0x"/>
|
||||
<outlet property="autoMountButton" destination="l6m-qm-d70" id="0qW-yE-Mqu"/>
|
||||
<outlet property="autoSaveButton" destination="6kD-FR-cDL" id="mv9-Gm-Kj8"/>
|
||||
<outlet property="boardIdField" destination="MXF-sZ-EBb" id="vbJ-VJ-QcR"/>
|
||||
<outlet property="bootDeviceField" destination="tPD-gh-zbP" id="vYU-oa-H3q"/>
|
||||
<outlet property="bootTypeField" destination="00r-9S-unY" id="ukc-fU-Jdl"/>
|
||||
@ -791,6 +870,7 @@
|
||||
<outlet property="configPathField" destination="K34-kh-yUP" id="zkK-f3-0uj"/>
|
||||
<outlet property="currentRevField" destination="f5t-Ia-85K" id="D7C-Ny-Ehi"/>
|
||||
<outlet property="disbaleSleepProxyButton" destination="wDl-KZ-OP0" id="neq-3a-Zq9"/>
|
||||
<outlet property="disksPEPopUp" destination="2zc-8N-Nsb" id="BPe-W1-rI5"/>
|
||||
<outlet property="disksPopUp" destination="mcY-kx-lap" id="Ie2-4t-td5"/>
|
||||
<outlet property="firmwareVendorfield" destination="88K-Sj-rjo" id="aQy-Dd-8G5"/>
|
||||
<outlet property="infoButton" destination="f2Z-bB-hZf" id="pBL-T9-zoS"/>
|
||||
@ -800,10 +880,13 @@
|
||||
<outlet property="makeRootRWButton" destination="y9x-U8-ayg" id="MWS-t0-efx"/>
|
||||
<outlet property="modelField" destination="SQN-cD-fjL" id="HaD-SS-j8b"/>
|
||||
<outlet property="nativeNVRAMField" destination="SjP-er-6Hp" id="7W6-DR-kpm"/>
|
||||
<outlet property="newPlistButton" destination="VMb-q0-gGi" id="3rg-AE-yd6"/>
|
||||
<outlet property="oemBoardIdField" destination="GcV-id-YTZ" id="yvt-rO-i17"/>
|
||||
<outlet property="oemProductField" destination="Aad-0P-4R6" id="O85-Y0-ZXY"/>
|
||||
<outlet property="oemVendorField" destination="84n-SC-fET" id="UbL-NZ-faa"/>
|
||||
<outlet property="openDocumentButton" destination="08m-oF-xfG" id="mLu-Fp-IAA"/>
|
||||
<outlet property="openThemeButton" destination="AzG-cL-pFW" id="U9N-0u-T2f"/>
|
||||
<outlet property="plistsPopUp" destination="ooY-tQ-8dD" id="PrN-EH-dkp"/>
|
||||
<outlet property="progressBar" destination="DtR-XE-DfU" id="7sf-c8-y5u"/>
|
||||
<outlet property="runAtLoginButton" destination="g5h-bF-3le" id="tXL-6T-4Mj"/>
|
||||
<outlet property="snField" destination="0u3-c1-rfb" id="TUb-GS-mTb"/>
|
||||
|
@ -18,8 +18,9 @@
|
||||
#define DEBUG DEBUG_BACKUP // restore original
|
||||
#endif
|
||||
*/
|
||||
|
||||
#import "NSWindowFix.h"
|
||||
#import "PNG8Image.h"
|
||||
#import "ThemeImage.h"
|
||||
#import "gfxutil.h"
|
||||
#import "efidevp.h"
|
||||
|
||||
@ -288,9 +289,7 @@ struct CUSTOM_TOOL_ENTRY {
|
||||
UINT8 VolumeType;
|
||||
};
|
||||
|
||||
/**
|
||||
Set of Search & replace bytes for VideoBiosPatchBytes().
|
||||
**/
|
||||
// Set of Search & replace bytes for VideoBiosPatchBytes().
|
||||
typedef struct _VBIOS_PATCH_BYTES {
|
||||
VOID *Find;
|
||||
VOID *Replace;
|
||||
@ -644,3 +643,4 @@ typedef struct {
|
||||
DEV_PROPERTY *ArbProperties;
|
||||
|
||||
} SETTINGS_DATA;
|
||||
|
||||
|
@ -285,7 +285,9 @@ func getVolumes() -> [String] {
|
||||
for b in all {
|
||||
let bsd : String = b as! String
|
||||
if let mp = getMountPoint(from: bsd) {
|
||||
mounted.append(mp)
|
||||
if mp != "/private/var/vm" {
|
||||
mounted.append(mp)
|
||||
}
|
||||
}
|
||||
}
|
||||
return mounted
|
||||
|
@ -9,6 +9,68 @@
|
||||
import Cocoa
|
||||
|
||||
extension String {
|
||||
/// Create `Data` from hexadecimal string representation
|
||||
///
|
||||
/// This takes a hexadecimal representation and creates a `Data` object. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed.
|
||||
///
|
||||
/// - returns: Data represented by this hexadecimal string.
|
||||
|
||||
func hexadecimal() -> Data? {
|
||||
var data = Data(capacity: self.count / 2)
|
||||
|
||||
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
|
||||
regex.enumerateMatches(in: self, range: NSMakeRange(0, utf16.count)) { match, flags, stop in
|
||||
let byteString = (self as NSString).substring(with: match!.range)
|
||||
var num = UInt8(byteString, radix: 16)!
|
||||
data.append(&num, count: 1)
|
||||
}
|
||||
|
||||
guard data.count > 0 else { return nil }
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
/// - returns: Bool value indicating that our string start with the same prefix ignoring casing.
|
||||
func hasPrefixIgnoringCase(_ str: String) -> Bool {
|
||||
return self.lowercased().hasPrefix(str.lowercased())
|
||||
}
|
||||
|
||||
/// - returns: only digits contained from the given string
|
||||
var keepNumericsOnly: String {
|
||||
return self.components(separatedBy: CharacterSet(charactersIn: "0123456789").inverted).joined(separator: "")
|
||||
}
|
||||
|
||||
//: ### Base64 encoding a string
|
||||
func base64Encoded() -> String? {
|
||||
if let data = self.data(using: .utf8) {
|
||||
return data.base64EncodedString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//: ### Base64 decoding a string
|
||||
func base64Decoded() -> String? {
|
||||
if let data = Data(base64Encoded: self) {
|
||||
return String(data: data, encoding: .utf8)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//: ### Base64 encoding data
|
||||
func base64EncodedHex() -> String? {
|
||||
if let data = self.hexadecimal() {
|
||||
return data.base64EncodedString()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//: ### Base64 decoding data
|
||||
func base64DecodedHex() -> String? {
|
||||
if let data = Data(base64Encoded: self, options: Data.Base64DecodingOptions.ignoreUnknownCharacters) {
|
||||
return data.hexadecimal()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func noSpaces() -> String {
|
||||
return self.trimmingCharacters(in: CharacterSet.whitespaces)
|
||||
@ -17,27 +79,60 @@ extension String {
|
||||
var lastPath: String {
|
||||
return (self as NSString).lastPathComponent
|
||||
}
|
||||
|
||||
var fileExtension: String {
|
||||
return (self as NSString).pathExtension
|
||||
}
|
||||
|
||||
var deletingLastPath: String {
|
||||
return (self as NSString).deletingLastPathComponent
|
||||
}
|
||||
|
||||
var deletingFileExtension: String {
|
||||
return (self as NSString).deletingPathExtension
|
||||
}
|
||||
|
||||
var componentsPath: [String] {
|
||||
return (self as NSString).pathComponents
|
||||
}
|
||||
|
||||
func addPath(_ path: String) -> String {
|
||||
let nsSt = self as NSString
|
||||
return nsSt.appendingPathComponent(path)
|
||||
}
|
||||
|
||||
func appendingFileExtension(ext: String) -> String? {
|
||||
let nsSt = self as NSString
|
||||
return nsSt.appendingPathExtension(ext)
|
||||
}
|
||||
|
||||
/// Replace any occurences of a string (word) with a new one (newWord)
|
||||
///
|
||||
/// - returns: String where word search is case insensitive while newWord replacing is case sensitive..
|
||||
|
||||
func replacingOccurrencesOf(inSensitive word: String, withSensitive newWord: String) -> String {
|
||||
//print("word = \(word)")
|
||||
//print("newWord = \(newWord)")
|
||||
var newText = self
|
||||
if let range = newText.lowercased().range(of: word.lowercased()) {
|
||||
newText = newText.replacingOccurrences(of: word,
|
||||
with: newWord,
|
||||
options: String.CompareOptions.caseInsensitive,
|
||||
range: range)
|
||||
}
|
||||
// check again
|
||||
let r = newText.lowercased().range(of: word.lowercased())
|
||||
if (r != nil) {
|
||||
newText = newText.replacingOccurrencesOf(inSensitive: word, withSensitive: newWord)
|
||||
}
|
||||
return newText
|
||||
}
|
||||
|
||||
// NSUserInterfaceItemIdentifier
|
||||
func interfaceId() -> NSUserInterfaceItemIdentifier {
|
||||
return NSUserInterfaceItemIdentifier(self)
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/25554986/how-to-convert-string-to-unsafepointeruint8-and-length#
|
||||
func toPointer() -> UnsafePointer<UInt8>? {
|
||||
guard let data = self.data(using: String.Encoding.utf8) else { return nil }
|
||||
@ -59,6 +154,50 @@ extension String {
|
||||
|
||||
return UnsafePointer<UInt8>(buffer)
|
||||
}
|
||||
|
||||
/// Escape XML special characthers such:
|
||||
/// & as &, \ as ", ' as &apos, < as <, and > as >
|
||||
var escapingXMLCharacters: String {
|
||||
get {
|
||||
/*
|
||||
" "
|
||||
' '
|
||||
< <
|
||||
> >
|
||||
& &
|
||||
*/
|
||||
|
||||
var s = self
|
||||
s = s.replacingOccurrences(of: "&", with: "&")
|
||||
s = s.replacingOccurrences(of: "\"", with: """)
|
||||
s = s.replacingOccurrences(of: "'", with: "'")
|
||||
s = s.replacingOccurrences(of: "<", with: "<")
|
||||
s = s.replacingOccurrences(of: ">", with: ">")
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert XML characters such:
|
||||
/// & to &, " to \ , ' to &apos, < to <, and > to >
|
||||
var convertingXMLCharacters: String {
|
||||
get {
|
||||
/*
|
||||
" "
|
||||
' '
|
||||
< <
|
||||
> >
|
||||
& &
|
||||
*/
|
||||
|
||||
var s = self
|
||||
s = s.replacingOccurrences(of: "&", with: "&")
|
||||
s = s.replacingOccurrences(of: """, with: "\"")
|
||||
s = s.replacingOccurrences(of: "'", with: "'")
|
||||
s = s.replacingOccurrences(of: "<", with: "<")
|
||||
s = s.replacingOccurrences(of: ">", with: ">")
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSNumber {
|
||||
@ -99,6 +238,23 @@ extension UInt32 {
|
||||
}
|
||||
|
||||
extension Data {
|
||||
/// Create hexadecimal string representation of `Data` object.
|
||||
///
|
||||
/// - returns: `String` representation of this `Data` object.
|
||||
|
||||
func hexadecimal() -> String {
|
||||
return map { String(format: "%02x", $0) }
|
||||
.joined(separator: "")
|
||||
}
|
||||
|
||||
func castToCPointer<T>() -> T {
|
||||
let mem = UnsafeMutablePointer<T>.allocate(capacity: 1)
|
||||
_ = self.copyBytes(to: UnsafeMutableBufferPointer(start: mem, count: 1))
|
||||
let val = mem.move()
|
||||
mem.deallocate()
|
||||
return val
|
||||
}
|
||||
|
||||
func toPointer() -> UnsafePointer<UInt8>? {
|
||||
|
||||
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: self.count)
|
||||
@ -225,3 +381,23 @@ extension io_object_t {
|
||||
extension NSBitmapImageRep {
|
||||
var png: Data? { representation(using: .png, properties: [:]) }
|
||||
}
|
||||
|
||||
extension NSView {
|
||||
func removeAllConstraints() {
|
||||
self.removeConstraints(self.constraints)
|
||||
for view in self.subviews {
|
||||
view.removeAllConstraints()
|
||||
}
|
||||
}
|
||||
|
||||
@IBInspectable var cornerRadius: CGFloat {
|
||||
get {
|
||||
return self.layer?.cornerRadius ?? 0
|
||||
} set {
|
||||
self.wantsLayer = true
|
||||
self.layer?.masksToBounds = true
|
||||
self.layer?.cornerRadius = CGFloat(Int(newValue * 100)) / 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,10 +4,67 @@
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>plist</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>GenericDocumentIcon.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>XML PropertyList v1</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>com.apple.property-list</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).Document</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>plist</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>GenericDocumentIcon.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Binary PropertyList v1</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>com.apple.property-list</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).Document</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>xml</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>GenericDocumentIcon.icns</string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>XML</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>public.xml</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).Document</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string></string>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
@ -20,6 +77,8 @@
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>LSUIElement</key>
|
||||
|
@ -16,7 +16,10 @@ extension String {
|
||||
if preferred.count > 0 {
|
||||
table = preferred[0]
|
||||
}
|
||||
|
||||
if localeBundle == nil {
|
||||
print("localeBundle is nil")
|
||||
return self
|
||||
}
|
||||
var result = localeBundle?.localizedString(forKey: self, value: nil, table: table)
|
||||
|
||||
if result == self {
|
||||
|
@ -19,6 +19,7 @@
|
||||
}
|
||||
return [self canBecomeKeyWindow];
|
||||
*/
|
||||
|
||||
return YES;
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
@ -1,107 +0,0 @@
|
||||
//
|
||||
// PNG8Image.m
|
||||
// Clover
|
||||
//
|
||||
// Created by vector sigma on 07/03/2020.
|
||||
// Copyright © 2020 CloverHackyColor. All rights reserved.
|
||||
//
|
||||
|
||||
#import "PNG8Image.h"
|
||||
|
||||
@implementation PNG8Image
|
||||
- (nullable NSData *)png8ImageDataAtPath:(NSString *_Nonnull)imagePath
|
||||
error:(NSError *_Nullable*_Nullable)errorPtr {
|
||||
NSString *domain = @"org.slice.Clover.PNG8Image.Error";
|
||||
|
||||
// Load PNG file and decode it as raw RGBA pixels
|
||||
// This uses lodepng library for PNG reading (not part of libimagequant)
|
||||
const char *input_png_file_path = [imagePath UTF8String];
|
||||
unsigned int width, height;
|
||||
unsigned char *raw_rgba_pixels;
|
||||
unsigned int status = lodepng_decode32_file(&raw_rgba_pixels, &width, &height, input_png_file_path);
|
||||
if (status) {
|
||||
NSString *desc = [NSString stringWithFormat:@"Can't load %s: %s\n",
|
||||
input_png_file_path,
|
||||
lodepng_error_text(status)];
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:1
|
||||
userInfo:userInfo];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Use libimagequant to make a palette for the RGBA pixels
|
||||
|
||||
liq_attr *handle = liq_attr_create();
|
||||
liq_image *input_image = liq_image_create_rgba(handle, raw_rgba_pixels, width, height, 0);
|
||||
// You could set more options here, like liq_set_quality
|
||||
liq_result *quantization_result;
|
||||
if (liq_image_quantize(input_image, handle, &quantization_result) != LIQ_OK) {
|
||||
NSString *desc = @"Quantization failed\n";
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:2
|
||||
userInfo:userInfo];
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Use libimagequant to make new image pixels from the palette
|
||||
|
||||
size_t pixels_size = width * height;
|
||||
unsigned char *raw_8bit_pixels = malloc(pixels_size);
|
||||
liq_set_dithering_level(quantization_result, 1.0);
|
||||
|
||||
liq_write_remapped_image(quantization_result, input_image, raw_8bit_pixels, pixels_size);
|
||||
const liq_palette *palette = liq_get_palette(quantization_result);
|
||||
|
||||
// Save converted pixels as a PNG file
|
||||
// This uses lodepng library for PNG writing (not part of libimagequant)
|
||||
|
||||
LodePNGState state;
|
||||
lodepng_state_init(&state);
|
||||
state.info_raw.colortype = LCT_PALETTE;
|
||||
state.info_raw.bitdepth = 8;
|
||||
state.info_png.color.colortype = LCT_PALETTE;
|
||||
state.info_png.color.bitdepth = 8;
|
||||
|
||||
for(int i=0; i < palette->count; i++) {
|
||||
lodepng_palette_add(&state.info_png.color, palette->entries[i].r, palette->entries[i].g, palette->entries[i].b, palette->entries[i].a);
|
||||
lodepng_palette_add(&state.info_raw, palette->entries[i].r, palette->entries[i].g, palette->entries[i].b, palette->entries[i].a);
|
||||
}
|
||||
|
||||
unsigned char *output_file_data;
|
||||
size_t output_file_size;
|
||||
unsigned int out_status = lodepng_encode(&output_file_data, &output_file_size, raw_8bit_pixels, width, height, &state);
|
||||
if (out_status) {
|
||||
NSString *desc = [NSString stringWithFormat:@"Can't encode image: %s\n",
|
||||
lodepng_error_text(out_status)];
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:3
|
||||
userInfo:userInfo];
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithBytes: output_file_data length: output_file_size];
|
||||
NSImage *convertedImage = [[NSImage alloc] initWithData:data];
|
||||
if (convertedImage == nil) {
|
||||
NSString *desc = @"Can't convert data to NSImage\n";
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:4
|
||||
userInfo:userInfo];
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
liq_result_destroy(quantization_result); // Must be freed only after you're done using the palette
|
||||
liq_image_destroy(input_image);
|
||||
liq_attr_destroy(handle);
|
||||
|
||||
free(raw_8bit_pixels);
|
||||
lodepng_state_cleanup(&state);
|
||||
|
||||
return data;
|
||||
}
|
||||
@end
|
148
CloverApp/Clover/Plist Editor/Document.swift
Normal file
148
CloverApp/Clover/Plist Editor/Document.swift
Normal file
@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@available(OSX 10.11, *)
|
||||
final class Document: NSDocument {
|
||||
var data : Data?
|
||||
var windowController : PlistEditorWC?
|
||||
var contentViewController: PlistEditorVC!
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
self.undoManager?.disableUndoRegistration()
|
||||
}
|
||||
|
||||
override class var autosavesInPlace: Bool {
|
||||
return UDs.bool(forKey: kAutoSavePlistsKey)
|
||||
}
|
||||
|
||||
override func canAsynchronouslyWrite(to url: URL,
|
||||
ofType typeName: String,
|
||||
for saveOperation: NSDocument.SaveOperationType) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override class func canConcurrentlyReadDocuments(ofType: String) -> Bool {
|
||||
return (ofType == "XML PropertyList v1" ||
|
||||
ofType == "Binary PropertyList v1" ||
|
||||
ofType == "XML")
|
||||
}
|
||||
|
||||
override func removeWindowController(_ windowController: NSWindowController) {
|
||||
super.removeWindowController(windowController)
|
||||
let documents = NSDocumentController.shared.documents
|
||||
if documents.count == 1 {
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
}
|
||||
}
|
||||
|
||||
override func makeWindowControllers() {
|
||||
var parser : PlistParser?
|
||||
if self.data != nil && self.fileType != nil { // loading a file
|
||||
parser = PlistParser(fromData: self.data!, fileType: PEFileType(rawValue: self.fileType!)!)
|
||||
if (parser?.root == nil) {
|
||||
Swift.print("Document " + ((self.fileURL?.absoluteString) ?? "Untitled".locale) + " was ureadable")
|
||||
parser = nil // will beep
|
||||
}
|
||||
} else if self.data == nil && self.fileType != nil { // loading an empty document
|
||||
// Empty document??
|
||||
parser = PlistParser(fromPath: "")
|
||||
}
|
||||
|
||||
if parser != nil {
|
||||
if parser!.isBinary {
|
||||
self.fileType = PEFileType.binaryPlistv1.rawValue
|
||||
}
|
||||
|
||||
self.windowController = PlistEditorWC.loadFromNib(parser: parser!)
|
||||
addWindowController(self.windowController!)
|
||||
if (self.fileURL != nil) {
|
||||
self.windowController?.window?.representedFilename = (self.fileURL?.path)!
|
||||
self.windowController?.window?.setFrameAutosaveName(NSWindow.FrameAutosaveName((self.fileURL?.path)!))
|
||||
}
|
||||
if !NSDocumentController.shared.documents.contains(self) {
|
||||
NSDocumentController.shared.addDocument(self)
|
||||
}
|
||||
|
||||
if let vc = self.windowController?.contentViewController as? PlistEditorVC {
|
||||
contentViewController = vc
|
||||
vc.rootNode = parser?.root
|
||||
}
|
||||
} else {
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
|
||||
override func write(to url: URL, ofType typeName: String) throws {
|
||||
if let vc = self.windowController?.contentViewController as? PlistEditorVC {
|
||||
switch typeName {
|
||||
case PEFileType.xmlPlistv1.rawValue:
|
||||
do {
|
||||
try vc.save().write(to: url, options: [])
|
||||
} catch {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
}
|
||||
break
|
||||
case PEFileType.xml.rawValue:
|
||||
do {
|
||||
try vc.save().write(to: url, options: [])
|
||||
|
||||
} catch {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
}
|
||||
break
|
||||
case PEFileType.binaryPlistv1.rawValue:
|
||||
let data = vc.convertToBinaryPlist()
|
||||
if (data == nil) {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
}
|
||||
do {
|
||||
try data?.write(to: url, options: [])
|
||||
} catch {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
}
|
||||
break
|
||||
default:
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func read(from data: Data, ofType typeName: String) throws {
|
||||
if typeName == PEFileType.xmlPlistv1.rawValue ||
|
||||
typeName == PEFileType.binaryPlistv1.rawValue ||
|
||||
typeName == PEFileType.xml.rawValue {
|
||||
self.data = data
|
||||
} else {
|
||||
throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
248
CloverApp/Clover/Plist Editor/PEFormatters.swift
Normal file
248
CloverApp/Clover/Plist Editor/PEFormatters.swift
Normal file
@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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: UTC Date formatters
|
||||
func utcDateFormatter() -> DateFormatter {
|
||||
let formatter = DateFormatter()
|
||||
formatter.timeZone = TimeZone(abbreviation: "UTC")
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||
return formatter
|
||||
}
|
||||
|
||||
func utcStringToDate(_ str: String) -> Date {
|
||||
return utcDateFormatter().date(from: str)!
|
||||
}
|
||||
|
||||
func utcDateToString(_ str: Date) -> String {
|
||||
return utcDateFormatter().string(from: str)
|
||||
}
|
||||
|
||||
//MARK: localized Date formatters to return current timezone and custom date style
|
||||
func localizedDateFormatter() -> DateFormatter {
|
||||
let formatter = DateFormatter()
|
||||
formatter.timeZone = TimeZone.current
|
||||
formatter.locale = Locale.current
|
||||
formatter.dateStyle = .medium
|
||||
formatter.timeStyle = .medium
|
||||
return formatter
|
||||
}
|
||||
|
||||
func localizedStringToDate(_ str: String) -> Date {
|
||||
return localizedDateFormatter().date(from: str)!
|
||||
}
|
||||
|
||||
func localizedStringToDateS(_ str: String) -> Date? { /* safe version that can safely be nil */
|
||||
return localizedDateFormatter().date(from: str)
|
||||
}
|
||||
|
||||
func localizedDateToString(_ date: Date) -> String {
|
||||
return localizedDateFormatter().string(from: date)
|
||||
}
|
||||
|
||||
func funcyDateFromUser(_ str: String) -> Date? {
|
||||
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.date.rawValue)
|
||||
let result: NSTextCheckingResult? = detector?.firstMatch(in: str, options: [], range: NSRange(location: 0, length: str.count))
|
||||
if result?.resultType == .date {
|
||||
let date: Date? = result?.date
|
||||
if date != nil {
|
||||
return date!
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
//MARK: Number formatters
|
||||
func localizedNumberFormatter() -> NumberFormatter {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.locale = Locale.current
|
||||
formatter.numberStyle = .decimal
|
||||
//formatter.groupingSeparator = formatter.locale.groupingSeparator
|
||||
formatter.minimumFractionDigits = 0
|
||||
formatter.maximumFractionDigits = 8
|
||||
return formatter
|
||||
}
|
||||
|
||||
func localizedStringFrom(number: NSNumber) -> String {
|
||||
return localizedNumberFormatter().string(from: number)!
|
||||
}
|
||||
|
||||
/*
|
||||
what will goes in the Outlineview cell is a localized string that use custom decimal/thousand separator
|
||||
*/
|
||||
func numberFromLocalizedString(string: String) -> NSNumber {
|
||||
// check if is an hex string
|
||||
if string.hasPrefix("0x") || string.hasPrefix("0X") {
|
||||
//get string after 0x
|
||||
let comp = string.lowercased().components(separatedBy: "0x")
|
||||
if comp.count == 2 && stringContainsOnlyHexChars(string: comp[1]) {
|
||||
let scanner = Scanner(string: string)
|
||||
var value: UInt64 = 0
|
||||
if scanner.scanHexInt64(&value) {
|
||||
return NSNumber(value: value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nf = localizedNumberFormatter()
|
||||
let separator = nf.decimalSeparator
|
||||
var isNegative : Bool = false
|
||||
var str : String = string
|
||||
|
||||
if str.count == 0 {
|
||||
return NSNumber(value: 0) // length is 0 .. number is 0
|
||||
}
|
||||
|
||||
// check if is a negative number
|
||||
if str.hasPrefix("-") {
|
||||
isNegative = true
|
||||
}
|
||||
|
||||
// if the first char is the decimal separator insert a 0 (e.g. ",2" to "0,2")
|
||||
if str.hasPrefix(separator!) {
|
||||
str = "0" + str
|
||||
}
|
||||
|
||||
// clean the string from non numerical part (but keep the decimal separator)
|
||||
let setToKeep = CharacterSet.init(charactersIn: "0123456789" + separator!).inverted
|
||||
str = str.components(separatedBy: setToKeep).joined()
|
||||
// check the lenght again..
|
||||
if str.count == 0 {
|
||||
return NSNumber(value: 0)
|
||||
}
|
||||
// remove the separator if is the last char
|
||||
if str.hasSuffix(separator!) {
|
||||
str = String(str.dropLast())
|
||||
}
|
||||
// check the lenght again..
|
||||
if str.count == 0 {
|
||||
return NSNumber(value: 0)
|
||||
}
|
||||
/*
|
||||
check if we have 0 or at least one separator
|
||||
(if more than one truncate before the second!)
|
||||
*/
|
||||
if (str.range(of: separator!) != nil) {
|
||||
let arr = str.components(separatedBy: separator!)
|
||||
if arr.count > 1 {
|
||||
str = arr[0] + "." + arr[1]
|
||||
}
|
||||
}
|
||||
|
||||
// readd the subtraction operator if was there
|
||||
if str != "0" && isNegative {
|
||||
str = "-" + str
|
||||
}
|
||||
|
||||
//determine if is integer or double
|
||||
var num : NSNumber? = nil
|
||||
if (str.range(of: ".") != nil) {
|
||||
num = NSNumber(value: (str as NSString).doubleValue)
|
||||
} else {
|
||||
num = NSNumber(value: (str as NSString).integerValue)
|
||||
}
|
||||
|
||||
if (num != nil) {
|
||||
return num!
|
||||
}
|
||||
return NSNumber(value: 0)
|
||||
}
|
||||
|
||||
//MARK: Data to string/string to Data
|
||||
/*
|
||||
We expect the data enclosed in "<>", must have valid hex characters,
|
||||
must be multiple of 2
|
||||
*/
|
||||
func isHexStringValid(string: String) -> String {
|
||||
var hex = string.lowercased()
|
||||
if hex.count == 0 {
|
||||
return DataEmptyString
|
||||
}
|
||||
|
||||
let hexSet : CharacterSet = CharacterSet(charactersIn: "0123456789abcdef")
|
||||
|
||||
// remove blank spaces
|
||||
hex = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
hex = hex.replacingOccurrences(of: " ", with: "")
|
||||
if hex.count == 0 {
|
||||
return DataEmptyString
|
||||
}
|
||||
|
||||
// first char must be '<'
|
||||
if !hex.hasPrefix("<") {
|
||||
return DataMissingOpenTag
|
||||
}
|
||||
|
||||
// well, is still >=2? (like "<>")
|
||||
if hex.count < 2 {
|
||||
return DataMissingEndTag
|
||||
}
|
||||
// last char must be '>'
|
||||
if !hex.hasSuffix(">") {
|
||||
return DataMissingEndTag
|
||||
}
|
||||
|
||||
// contains only valid hex characters? testw/o "<>"
|
||||
var copy : String = String(hex.dropFirst(1))
|
||||
copy = String(copy.dropLast(1))
|
||||
|
||||
if copy.trimmingCharacters(in: hexSet).count > 0 {
|
||||
return DataIllegalHex
|
||||
}
|
||||
|
||||
if copy.count % 2 != 0 { // ----> w/o considering "<>"
|
||||
return DataOddBytes
|
||||
}
|
||||
|
||||
return "HexSuccess"
|
||||
}
|
||||
|
||||
func stringContainsOnlyHexChars(string: String) -> Bool {
|
||||
var hex = string.lowercased()
|
||||
if hex.count == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
let hexSet : CharacterSet = CharacterSet(charactersIn: "0123456789abcdef")
|
||||
|
||||
// remove blanck spaces
|
||||
hex = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
if hex.count == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// contains only valid hex characters?
|
||||
if (hex.rangeOfCharacter(from: hexSet) == nil) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
108
CloverApp/Clover/Plist Editor/PENode.swift
Normal file
108
CloverApp/Clover/Plist Editor/PENode.swift
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
final class PENode: NSTreeNode, NSCopying, NSCoding {
|
||||
var pattern : String? = nil // -> this isn't copied with .copy()
|
||||
internal var ro: Any?
|
||||
var isRootNode : Bool? = nil
|
||||
|
||||
var highLightPattern: String? {
|
||||
get {
|
||||
return self.pattern
|
||||
}
|
||||
set {
|
||||
self.pattern = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var count: Int {
|
||||
get {
|
||||
return (self.children == nil) ? 0 : self.children!.count
|
||||
}
|
||||
}
|
||||
|
||||
override var representedObject: Any? {
|
||||
get {
|
||||
return self.ro
|
||||
} set {
|
||||
self.ro = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var isExpandable: Bool {
|
||||
get {
|
||||
return (self.tagdata?.type == .Dictionary || self.tagdata?.type == .Array)
|
||||
}
|
||||
}
|
||||
|
||||
weak var peparent: PENode? {
|
||||
get {
|
||||
return self.parent as? PENode
|
||||
}
|
||||
}
|
||||
|
||||
var tagdata: TagData? {
|
||||
get {
|
||||
return (self.representedObject as! TagData)
|
||||
} set {
|
||||
self.representedObject = newValue
|
||||
}
|
||||
}
|
||||
|
||||
required convenience init(coder decoder: NSCoder) {
|
||||
self.init(representedObject:decoder.decodeObject(forKey: "TagData"))
|
||||
self.mutableChildren.addObjects(from: decoder.decodeObject(forKey: "childrens") as! [Any])
|
||||
}
|
||||
|
||||
func encode(with coder: NSCoder) {
|
||||
coder.encode(self.representedObject, forKey: "TagData")
|
||||
coder.encode(self.mutableChildren, forKey: "childrens")
|
||||
}
|
||||
|
||||
required override init(representedObject modelObject: Any?) {
|
||||
self.ro = modelObject
|
||||
super.init(representedObject: self.ro)
|
||||
}
|
||||
|
||||
func copy(with zone: NSZone? = nil) -> Any {
|
||||
let nodeCopy = PENode(representedObject: self.tagdata!.copy())
|
||||
self.reAddChildToParent(newNode: nodeCopy, origNode: self)
|
||||
return nodeCopy
|
||||
}
|
||||
|
||||
private func reAddChildToParent(newNode: PENode, origNode: PENode) {
|
||||
for item in origNode.mutableChildren {
|
||||
let origChild = item as! PENode
|
||||
let nodeCopy = PENode(representedObject: origChild.tagdata!.copy())
|
||||
newNode.mutableChildren.add(nodeCopy)
|
||||
self.reAddChildToParent(newNode: nodeCopy, origNode: origChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
77
CloverApp/Clover/Plist Editor/PETypes.swift
Normal file
77
CloverApp/Clover/Plist Editor/PETypes.swift
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 Foundation
|
||||
|
||||
//MARK: Plist Editor custom types
|
||||
|
||||
typealias PEArray = [Any]
|
||||
typealias PEDictionary = [String : Any]
|
||||
typealias PEInt = Int
|
||||
typealias PEReal = Double
|
||||
|
||||
//MARK: Plist Editor file types
|
||||
enum PEFileType : String {
|
||||
case xmlPlistv1 = "XML PropertyList v1"
|
||||
case binaryPlistv1 = "Binary PropertyList v1"
|
||||
case xml = "XML"
|
||||
}
|
||||
|
||||
//MARK: supported plist object type
|
||||
enum PlistTag : Int {
|
||||
case Dictionary = 0
|
||||
case Array = 1
|
||||
case String = 2
|
||||
case Number = 3
|
||||
case Bool = 4
|
||||
case Date = 5
|
||||
case Data = 6
|
||||
}
|
||||
|
||||
//MARK: PETableCellView behaviour
|
||||
enum PETableCellViewType: Int {
|
||||
case key
|
||||
case value
|
||||
case tags
|
||||
case bool
|
||||
}
|
||||
|
||||
//MARK: Autosave
|
||||
let kAutoSavePlistsKey : String = "autoSavePlists"
|
||||
|
||||
//MARK: Paste board types
|
||||
let kMyPBoardTypeXml = NSPasteboard.PasteboardType(rawValue: "public.xml")
|
||||
let kMyPBoardTypeData = NSPasteboard.PasteboardType(rawValue: "PBoardType.data")
|
||||
|
||||
//MARK: Header and footer
|
||||
let xml1Header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">"
|
||||
let xml1Footer = "</plist>"
|
||||
|
||||
let binaryPlistHeader = "bplist00"
|
||||
|
||||
|
||||
|
299
CloverApp/Clover/Plist Editor/PEValueTransformer.swift
Normal file
299
CloverApp/Clover/Plist Editor/PEValueTransformer.swift
Normal file
@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@available(OSX 10.11, *)
|
||||
extension PlistEditorVC {
|
||||
@objc func boolPopUpPressed(sender: PEPopUpButton) {
|
||||
let originalValue : Bool = sender.node!.tagdata!.value as! Bool
|
||||
let actualValue = (sender.title == localizedYes) ? true : false
|
||||
|
||||
if originalValue != actualValue {
|
||||
self.outline.undo_SetBoolValue(node: sender.node!,
|
||||
sender: sender,
|
||||
originalValue: originalValue,
|
||||
actualValue: actualValue)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func typePopUpPressed(sender: PEPopUpButton) {
|
||||
let originalType = sender.node!.tagdata!.type
|
||||
let actualType = sender.selectedItem?.representedObject as! PlistTag
|
||||
if originalType != actualType {
|
||||
let newTree : TagData? = sender.node?.tagdata?.copy() as? TagData
|
||||
|
||||
newTree?.type = actualType
|
||||
/*
|
||||
switching between contenitors types (Array/Dictionary) can save some memory
|
||||
since we need don't need to backup the childrens.
|
||||
Reloading the view it's enough to show children correctly
|
||||
*/
|
||||
|
||||
switch originalType {
|
||||
case .Dictionary:
|
||||
if actualType == .Array {
|
||||
var oldKeys : PEArray = PEArray()
|
||||
var newKeys : PEArray = PEArray()
|
||||
|
||||
var index : Int = 0
|
||||
for n in sender.node!.mutableChildren {
|
||||
let ex : PENode = n as! PENode
|
||||
oldKeys.append(ex.tagdata!.key)
|
||||
newKeys.append("\(localizedItem) \(index)")
|
||||
index+=1
|
||||
}
|
||||
|
||||
self.outline.undo_ChangeContenitor(node: sender.node!,
|
||||
sender: sender,
|
||||
originalType: gPlistTagStr(tag: originalType),
|
||||
actualType: gPlistTagStr(tag: actualType),
|
||||
oldKeys: oldKeys,
|
||||
newKeys: newKeys)
|
||||
} else {
|
||||
if actualType == .String {
|
||||
newTree?.value = ""
|
||||
} else if actualType == .Number {
|
||||
newTree?.value = 0
|
||||
} else if actualType == .Bool {
|
||||
newTree?.value = false
|
||||
} else if actualType == .Data {
|
||||
newTree?.value = Data()
|
||||
} else if actualType == .Date {
|
||||
newTree?.value = Date()
|
||||
}
|
||||
self.outline.undo_ChangeType(node: sender.node!,
|
||||
newData: newTree!,
|
||||
oldData: sender.node!.tagdata!,
|
||||
newChilds: nil,
|
||||
oldChilds: sender.node!.children,
|
||||
sender: sender)
|
||||
}
|
||||
break
|
||||
case .Array:
|
||||
if actualType == .Dictionary {
|
||||
var oldKeys : PEArray = PEArray()
|
||||
var newKeys : PEArray = PEArray()
|
||||
|
||||
var index : Int = 0
|
||||
for n in sender.node!.mutableChildren {
|
||||
let _ : PENode = n as! PENode
|
||||
oldKeys.append("\(localizedItem) \(index)")
|
||||
if index == 0 {
|
||||
newKeys.append(localizedNewItem)
|
||||
} else {
|
||||
newKeys.append("\(localizedNewItem) - \(index)")
|
||||
}
|
||||
|
||||
index+=1
|
||||
}
|
||||
self.outline.undo_ChangeContenitor(node: sender.node!,
|
||||
sender: sender,
|
||||
originalType: gPlistTagStr(tag: originalType),
|
||||
actualType: gPlistTagStr(tag: actualType),
|
||||
oldKeys: oldKeys,
|
||||
newKeys: newKeys)
|
||||
} else {
|
||||
if actualType == .String {
|
||||
newTree?.value = ""
|
||||
} else if actualType == .Number {
|
||||
newTree?.value = 0
|
||||
} else if actualType == .Bool {
|
||||
newTree?.value = false
|
||||
} else if actualType == .Data {
|
||||
newTree?.value = Data()
|
||||
} else if actualType == .Date {
|
||||
newTree?.value = Date()
|
||||
}
|
||||
|
||||
self.outline.undo_ChangeType(node: sender.node!,
|
||||
newData: newTree!,
|
||||
oldData: sender.node!.tagdata!,
|
||||
newChilds: nil,
|
||||
oldChilds: sender.node!.children,
|
||||
sender: sender)
|
||||
}
|
||||
break
|
||||
case .String:
|
||||
let oldString = sender.node?.tagdata?.value as! String
|
||||
if actualType == .Number {
|
||||
let nv = localizedNumberFormatter().number(from: oldString) ?? 0
|
||||
newTree?.value = nv
|
||||
} else if actualType == .Bool {
|
||||
// check if old string is compatible with a bool value
|
||||
var bv : Bool = false
|
||||
let localized = localizedYes
|
||||
let firstChar = localized[localized.startIndex]
|
||||
if oldString.hasPrefix(String(firstChar).lowercased())
|
||||
|| oldString.hasPrefix(String(firstChar).uppercased()) {
|
||||
bv = true
|
||||
}
|
||||
newTree?.value = bv
|
||||
} else if actualType == .Data {
|
||||
// da migliorare (se la stringa era un hex valido usare quella!!)
|
||||
// check if old string is compatible with bytes
|
||||
var dv : Data? = nil
|
||||
|
||||
if isHexStringValid(string: oldString) == "HexSuccess" {
|
||||
dv = oldString.hexadecimal()
|
||||
}
|
||||
|
||||
if (dv == nil) {
|
||||
dv = oldString.data(using: String.Encoding.utf8)
|
||||
}
|
||||
|
||||
if (dv == nil) {
|
||||
dv = Data()
|
||||
}
|
||||
newTree?.value = dv!
|
||||
} else if actualType == .Date {
|
||||
// check if old string is compatible with date
|
||||
var dv : Date? = nil
|
||||
dv = localizedStringToDateS(oldString) // first try with "S" version that can be nil
|
||||
|
||||
if (dv == nil) {
|
||||
dv = funcyDateFromUser(oldString) // secondly try to detect it (NSDataDetector + NSTextCheckingResult)
|
||||
}
|
||||
if (dv == nil) {
|
||||
dv = Date() // no way, init a new one..
|
||||
}
|
||||
newTree?.value = dv!
|
||||
} else if actualType == .Dictionary {
|
||||
newTree?.value = nil
|
||||
} else if actualType == .Array {
|
||||
newTree?.value = nil
|
||||
}
|
||||
self.outline.undo_ChangeType(node: sender.node!,
|
||||
newData: newTree!,
|
||||
oldData: sender.node!.tagdata!,
|
||||
newChilds: nil,
|
||||
oldChilds: sender.node!.children,
|
||||
sender: sender)
|
||||
break
|
||||
case .Bool:
|
||||
if actualType == .String {
|
||||
var ns : String = "0"
|
||||
if (sender.node?.tagdata?.value as! NSNumber).boolValue {
|
||||
ns = "1"
|
||||
}
|
||||
newTree?.value = ns
|
||||
} else if actualType == .Number {
|
||||
var nn : Int = 0
|
||||
if (sender.node?.tagdata?.value as! NSNumber).boolValue {
|
||||
nn = 1
|
||||
}
|
||||
newTree?.value = nn
|
||||
} else if actualType == .Data {
|
||||
newTree?.value = Data()
|
||||
} else if actualType == .Date {
|
||||
newTree?.value = Date()
|
||||
} else if actualType == .Dictionary {
|
||||
newTree?.value = nil
|
||||
} else if actualType == .Array {
|
||||
newTree?.value = nil
|
||||
}
|
||||
self.outline.undo_ChangeType(node: sender.node!,
|
||||
newData: newTree!,
|
||||
oldData: sender.node!.tagdata!,
|
||||
newChilds: nil,
|
||||
oldChilds: sender.node!.children,
|
||||
sender: sender)
|
||||
break
|
||||
case .Number:
|
||||
if actualType == .String {
|
||||
let str = localizedNumberFormatter().string(from: sender.node!.tagdata!.value as! NSNumber)
|
||||
newTree?.value = str ?? ""
|
||||
} else if actualType == .Bool {
|
||||
var nb : Bool = false
|
||||
if (sender.node?.tagdata?.value as! NSNumber).boolValue {
|
||||
nb = true
|
||||
}
|
||||
newTree?.value = nb
|
||||
} else if actualType == .Data {
|
||||
newTree?.value = Data()
|
||||
} else if actualType == .Date {
|
||||
newTree?.value = Date()
|
||||
} else if actualType == .Dictionary {
|
||||
newTree?.value = nil
|
||||
} else if actualType == .Array {
|
||||
newTree?.value = nil
|
||||
}
|
||||
self.outline.undo_ChangeType(node: sender.node!,
|
||||
newData: newTree!,
|
||||
oldData: sender.node!.tagdata!,
|
||||
newChilds: nil,
|
||||
oldChilds: sender.node!.children,
|
||||
sender: sender)
|
||||
break
|
||||
case .Data:
|
||||
if actualType == .String {
|
||||
newTree?.value = "\(sender.node!.tagdata!.value!)"
|
||||
} else if actualType == .Bool {
|
||||
newTree?.value = false
|
||||
} else if actualType == .Number {
|
||||
newTree?.value = 0
|
||||
} else if actualType == .Date {
|
||||
newTree?.value = Date()
|
||||
} else if actualType == .Dictionary {
|
||||
newTree?.value = nil
|
||||
} else if actualType == .Array {
|
||||
newTree?.value = nil
|
||||
}
|
||||
self.outline.undo_ChangeType(node: sender.node!,
|
||||
newData: newTree!,
|
||||
oldData: sender.node!.tagdata!,
|
||||
newChilds: nil,
|
||||
oldChilds: sender.node!.children,
|
||||
sender: sender)
|
||||
break
|
||||
case .Date:
|
||||
if actualType == .String {
|
||||
let dateStr = localizedDateToString(sender.node!.tagdata!.value as! Date)
|
||||
newTree?.value = dateStr
|
||||
} else if actualType == .Bool {
|
||||
newTree?.value = false
|
||||
} else if actualType == .Number {
|
||||
newTree?.value = 0
|
||||
} else if actualType == .Data {
|
||||
newTree?.value = Data()
|
||||
} else if actualType == .Dictionary {
|
||||
newTree?.value = nil
|
||||
} else if actualType == .Array {
|
||||
newTree?.value = nil
|
||||
}
|
||||
self.outline.undo_ChangeType(node: sender.node!,
|
||||
newData: newTree!,
|
||||
oldData: sender.node!.tagdata!,
|
||||
newChilds: nil,
|
||||
oldChilds: sender.node!.children,
|
||||
sender: sender)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
210
CloverApp/Clover/Plist Editor/PE_Conversions.swift
Normal file
210
CloverApp/Clover/Plist Editor/PE_Conversions.swift
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 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 Foundation
|
||||
|
||||
//MARK: PENode to Plist
|
||||
///Conver the given PENode to a XML Property List v1
|
||||
func gConvertPENodeToPlist(node: PENode?) -> String {
|
||||
var plist : String = ""
|
||||
if (node == nil) {
|
||||
return plist
|
||||
}
|
||||
|
||||
let ro = node!.tagdata!
|
||||
let type = node!.tagdata!.type
|
||||
if type == .Dictionary {
|
||||
if node!.count == 0 {
|
||||
plist = xml1Header + "\n" + "<dict/>" + "\n" + xml1Footer
|
||||
} else {
|
||||
plist = xml1Header + "\n" + gSerializeNodeToPlist(node: node!, file: "", indentation: 0) + xml1Footer
|
||||
}
|
||||
} else if type == .Array {
|
||||
if node!.count == 0 {
|
||||
plist = xml1Header + "\n" + "<array/>" + "\n" + xml1Footer
|
||||
} else {
|
||||
plist = xml1Header + "\n" + gSerializeNodeToPlist(node: node!, file: "", indentation: 0) + xml1Footer
|
||||
}
|
||||
} else if type == .String {
|
||||
plist = xml1Header + "\n" + "<string>" + (ro.value as! String) + "</string>" + "\n" + xml1Footer
|
||||
} else if type == .Data {
|
||||
let data = ro.value as! NSData
|
||||
let strBase64 : String = (data as Data).base64EncodedString(options: .endLineWithLineFeed)
|
||||
plist = xml1Header + "\n" + "<data>" + strBase64 + "</data>" + "\n" + xml1Footer
|
||||
} else if type == .Date {
|
||||
plist = xml1Header + "\n" + "<date>" + utcDateToString(ro.value as! Date) + "</date>" + "\n" + xml1Footer
|
||||
} else if type == .Number {
|
||||
let strNum = "\(node!.tagdata!.value!)"
|
||||
if (strNum as NSString).range(of: ".").location != NSNotFound {
|
||||
plist = xml1Header + "\n" + "<real>" + strNum + "</real>" + "\n" + xml1Footer
|
||||
} else {
|
||||
plist = xml1Header + "\n" + "<integer>" + strNum + "</integer>" + "\n" + xml1Footer
|
||||
}
|
||||
} else if type == .Bool {
|
||||
let n = node!.tagdata!.value as! NSNumber
|
||||
plist = xml1Header + "\n" + (n.boolValue ? "<true/>" : "<false/>") + "\n" + xml1Footer
|
||||
}
|
||||
|
||||
return plist
|
||||
}
|
||||
|
||||
///This func is meant to be called only by the gConvertPENodeToPlist(node: ) function
|
||||
func gSerializeNodeToPlist(node: PENode, file: String, indentation: Int) -> String {
|
||||
var str : String = ""
|
||||
var indent : String = ""
|
||||
// isRoot means that is still empty and we need to add the open/close tag for the Dictionary (or array)
|
||||
let isRoot : Bool = (node.isRootNode == nil) ? false : node.isRootNode!
|
||||
var i : Int = indentation <= 0 ? 1 : indentation
|
||||
let indentChild : Int = isRoot ? i : i + 1
|
||||
while i != 0 {
|
||||
indent = indent + "\t"
|
||||
i -= 1
|
||||
}
|
||||
|
||||
let type = node.tagdata!.type
|
||||
if type == .Dictionary {
|
||||
if !isRoot {
|
||||
if node.peparent != nil && node.peparent!.tagdata!.type != .Array {
|
||||
str += indent + "<key>" + node.tagdata!.key + "</key>" + "\n"
|
||||
}
|
||||
} else {
|
||||
str = "<dict>\n"
|
||||
}
|
||||
if node.mutableChildren.count > 0 {
|
||||
/*
|
||||
<key>New item</key>
|
||||
<dict>
|
||||
<key>test</key>
|
||||
<string>hi</string>
|
||||
</dict>
|
||||
*/
|
||||
if !isRoot {
|
||||
str = str + indent + "<dict>" + "\n"
|
||||
}
|
||||
for child in node.mutableChildren {
|
||||
str = str + gSerializeNodeToPlist(node: child as! PENode, file: str, indentation: indentChild)
|
||||
}
|
||||
if !isRoot {
|
||||
str = str + indent + "</dict>" + "\n"
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
<key>New item</key>
|
||||
<dict/>
|
||||
*/
|
||||
str = str + indent + "<dict/>" + "\n"
|
||||
}
|
||||
} else if type == .Array {
|
||||
if !isRoot {
|
||||
str += indent + "<key>" + node.tagdata!.key + "</key>" + "\n"
|
||||
} else {
|
||||
str = "<array>\n"
|
||||
}
|
||||
if node.mutableChildren.count > 0 {
|
||||
/*
|
||||
<key>New item</key>
|
||||
<array>
|
||||
<string>hi</string>
|
||||
</array>
|
||||
*/
|
||||
if !isRoot {
|
||||
str = str + indent + "<array>" + "\n"
|
||||
}
|
||||
for child in node.mutableChildren {
|
||||
str = str + gSerializeNodeToPlist(node: child as! PENode, file: str, indentation: indentChild)
|
||||
}
|
||||
if !isRoot {
|
||||
str = str + indent + "</array>" + "\n"
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
<key>New item</key>
|
||||
<array/>
|
||||
*/
|
||||
str = str + indent + "<array/>" + "\n"
|
||||
}
|
||||
} else if type == .String {
|
||||
/*
|
||||
<key>test</key>
|
||||
<string>hi</string>
|
||||
*/
|
||||
if node.peparent!.tagdata!.type != .Array {
|
||||
str = str + indent + "<key>" + node.tagdata!.key + "</key>" + "\n"
|
||||
} else if isRoot {
|
||||
str = str + indent + "<key>" + localizedNewItem + "</key>" + "\n"
|
||||
}
|
||||
str = str + indent + "<string>" + (node.tagdata!.value as! String) + "</string>" + "\n"
|
||||
} else if type == .Data {
|
||||
if node.peparent!.tagdata!.type != .Array {
|
||||
str = str + indent + "<key>" + node.tagdata!.key + "</key>" + "\n"
|
||||
} else if isRoot {
|
||||
str = str + indent + "<key>" + localizedNewItem + "</key>" + "\n"
|
||||
}
|
||||
let data = node.tagdata!.value as! NSData
|
||||
let strBase64 : String = (data as Data).base64EncodedString(options: .endLineWithLineFeed)
|
||||
str = str + indent + "<data>" + strBase64 + "</data>" + "\n"
|
||||
} else if type == .Date {
|
||||
if node.peparent!.tagdata!.type != .Array {
|
||||
str = str + indent + "<key>" + node.tagdata!.key + "</key>" + "\n"
|
||||
} else if isRoot {
|
||||
str = str + indent + "<key>" + localizedNewItem + "</key>" + "\n"
|
||||
}
|
||||
str = str + indent + "<date>" + utcDateToString(node.tagdata!.value as! Date) + "</date>" + "\n"
|
||||
} else if type == .Number {
|
||||
if node.peparent!.tagdata!.type != .Array {
|
||||
str = str + indent + "<key>" + node.tagdata!.key + "</key>" + "\n"
|
||||
} else if isRoot {
|
||||
str = str + indent + "<key>" + localizedNewItem + "</key>" + "\n"
|
||||
}
|
||||
let strNum = "\(node.tagdata!.value!)"
|
||||
if (strNum as NSString).range(of: ".").location != NSNotFound {
|
||||
// <real>1.2</real>
|
||||
str = str + indent + "<real>" + strNum + "</real>" + "\n"
|
||||
} else {
|
||||
// <integer>1</integer>
|
||||
str = str + indent + "<integer>" + strNum + "</integer>" + "\n"
|
||||
}
|
||||
} else if type == .Bool {
|
||||
if node.peparent!.tagdata!.type != .Array {
|
||||
str = str + indent + "<key>" + node.tagdata!.key + "</key>" + "\n"
|
||||
} else if isRoot {
|
||||
str = str + indent + "<key>" + localizedNewItem + "</key>" + "\n"
|
||||
}
|
||||
let n = node.tagdata!.value as! NSNumber
|
||||
str += indent + (n.boolValue ? "<true/>" : "<false/>") + "\n"
|
||||
}
|
||||
|
||||
if isRoot {
|
||||
if node.tagdata!.type == .Array {
|
||||
str = str + "</array>\n"
|
||||
} else {
|
||||
str = str + "</dict>\n"
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
87
CloverApp/Clover/Plist Editor/PE_Localized.swift
Normal file
87
CloverApp/Clover/Plist Editor/PE_Localized.swift
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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: Undo and Redo
|
||||
let localizedUndo = "Undo".locale
|
||||
let localizedRedo = "Redo".locale
|
||||
|
||||
let localizedUndoRedoTyping = "typing".locale
|
||||
let localizedUndoRedoChangeType = "change type".locale
|
||||
let localizedUndoRedoChangeBoolValue = "change bool value".locale
|
||||
let localizedUndoRedoReplaceDuplicateKey = "replace duplicate key".locale
|
||||
let localizedUndoRedoMoveItem = "move item".locale
|
||||
let localizedUndoRedoPasteItem = "paste Item".locale
|
||||
let localizedUndoRedoRemoveItem = "remove Item".locale
|
||||
let localizedUndoRedoCutItem = "cut Item".locale
|
||||
let localizedUndoRedoAddNewItem = "add new Item".locale
|
||||
|
||||
//MARK: localized words for the outline
|
||||
let localizedHeaderKey = "Key".locale
|
||||
let localizedHeaderType = "Type".locale
|
||||
let localizedHeaderValue = "Value".locale
|
||||
|
||||
//MARK: Plist tags
|
||||
let localizedUnsupported = "Unsupported".locale
|
||||
let localizedDictionary = "Dictionary".locale
|
||||
let localizedArray = "Array".locale
|
||||
let localizedString = "String".locale
|
||||
let localizedNumber = "Number".locale
|
||||
let localizedBool = "Bool".locale
|
||||
let localizedDate = "Date".locale
|
||||
let localizedData = "Data".locale
|
||||
let localizedYes = "YES".locale
|
||||
let localizedNo = "NO".locale
|
||||
|
||||
//MARK: Placheholders
|
||||
let localizedItem = "Item".locale
|
||||
let localizedItems = "Items".locale
|
||||
let localizedUntiteled = "Untiteled".locale
|
||||
let localizedNewItem = "New Item".locale
|
||||
let localizedBytes = "bytes".locale
|
||||
|
||||
//MARK: Wrong editing
|
||||
let DataEmptyString = "No data".locale
|
||||
let DataMissingOpenTag = "missing '<' at the beginning".locale
|
||||
let DataMissingEndTag = "missing '>' at the end".locale
|
||||
let DataIllegalHex = "Your data contains illegal characters".locale
|
||||
let DataOddBytes = "bytes count is odd, must be even".locale
|
||||
let localizedKeepEditing = "Keep editing".locale
|
||||
|
||||
let localizedInvalidValueMsgText = "Invalid value detected!".locale
|
||||
let localizedInvalidValueInfoText = "Your edit is not valid. Do you want to restore last valid value or keep editing?".locale
|
||||
|
||||
//MARK: Search & Replace
|
||||
let localizedSearch = "Search".locale
|
||||
let localizedReplace = "Replace".locale
|
||||
|
||||
// the 3 string below is for an alert that indicate you that you are attempting to have a duplicate key in the same Dictionary that is not allowed
|
||||
let localizedDuplicateKeyMsgText = "Duplicate key in the Dictionary!".locale
|
||||
let localizedDuplicateKeyInfoText = "'%@' is already present in the Dictionary. Do you want to undo the editing or replace the existing key?".locale
|
||||
// text for the buttons of this alert
|
||||
let localizedDuplicateKeyReplaceButton = "Replace existing key".locale
|
35
CloverApp/Clover/Plist Editor/PE_Notifications.swift
Normal file
35
CloverApp/Clover/Plist Editor/PE_Notifications.swift
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 Foundation
|
||||
|
||||
//MARK: custom notifications
|
||||
let PEOutLineViewEndScrolling = Notification.Name("PEOutLineViewEndScrolling")
|
||||
let PESearchFieldTextDidChange = Notification.Name("PESearchFieldTextDidChange")
|
||||
let ChoiceButtonDidChangeState = Notification.Name("ChoiceButtonDidChangeState")
|
||||
|
||||
|
50
CloverApp/Clover/Plist Editor/PE_Serialize.swift
Normal file
50
CloverApp/Clover/Plist Editor/PE_Serialize.swift
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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 Foundation
|
||||
|
||||
/**
|
||||
A `PropertyListSerialization` for the given `Data` object.
|
||||
- Parameter data: a `Data` object.
|
||||
`Date` or `Data`.
|
||||
- Returns: a `PropertyListFormat.xml` object.
|
||||
- Discussion: The given data must be a Fundation object.
|
||||
*/
|
||||
func serialize(data: Data) -> Any? {
|
||||
var any : Any?
|
||||
var format = PropertyListSerialization.PropertyListFormat.xml
|
||||
do {
|
||||
try any = PropertyListSerialization.propertyList(from: data,
|
||||
options: .mutableContainersAndLeaves,
|
||||
format: &format) as Any
|
||||
|
||||
} catch {
|
||||
any = PEDictionary()
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
return any
|
||||
}
|
603
CloverApp/Clover/Plist Editor/PE_Undo.swift
Normal file
603
CloverApp/Clover/Plist Editor/PE_Undo.swift
Normal file
@ -0,0 +1,603 @@
|
||||
/*
|
||||
* 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: Undo and redo support
|
||||
@available(OSX 10.11, *)
|
||||
extension PEOutlineView {
|
||||
// MARK: Undo Manager object
|
||||
|
||||
/// Prepares PEOutlineView for the undo operation
|
||||
func prepareUndoObj() -> AnyObject {
|
||||
// nil is not expecte here. Be happy if throw an exception
|
||||
return self.undoManager?.prepare(withInvocationTarget: self) as AnyObject
|
||||
}
|
||||
|
||||
// MARK: Undo drag Item
|
||||
/// Undo operation when an Item is dragged here an there in PEOutlineView
|
||||
@objc func undo_DragAndDrop(item: PENode,
|
||||
fromParent: PENode,
|
||||
fromIndex: Int,
|
||||
toParent: PENode,
|
||||
toIndex: Int) {
|
||||
self.prepareUndoObj().undo_DragAndDrop(item: item,
|
||||
fromParent: toParent,
|
||||
fromIndex: toIndex,
|
||||
toParent: fromParent,
|
||||
toIndex: fromIndex)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoMoveItem)
|
||||
let toParentCount = toParent.mutableChildren.count
|
||||
let new = item.copy() as! PENode
|
||||
if fromParent == toParent {
|
||||
var mutatingToIndex = toIndex
|
||||
if fromIndex < toIndex {
|
||||
mutatingToIndex -= 1
|
||||
}
|
||||
fromParent.mutableChildren.removeObject(at: fromIndex)
|
||||
fromParent.mutableChildren.insert(new, at: mutatingToIndex)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.reloadItem(fromParent, reloadChildren: true)
|
||||
}
|
||||
} else {
|
||||
self.editorVC?.asyncExpand(item: toParent, expandParentOnly: false)
|
||||
// drag and past from to another parent
|
||||
if toParentCount == 0 {
|
||||
toParent.mutableChildren.add(new)
|
||||
DispatchQueue.main.async {
|
||||
self.animator().moveItem(at: fromIndex,
|
||||
inParent: fromParent,
|
||||
to: toIndex,
|
||||
inParent: toParent)
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
gDeduplicateKeyInParent(parent:toParent, newNode: new)
|
||||
|
||||
toParent.mutableChildren.insert(new, at: toIndex)
|
||||
fromParent.mutableChildren.removeObject(at: fromIndex)
|
||||
|
||||
|
||||
self.animator().moveItem(at: fromIndex,
|
||||
inParent: fromParent,
|
||||
to: toIndex,
|
||||
inParent: toParent)
|
||||
self.reloadItem(toParent, reloadChildren: true)
|
||||
let row = self.row(forItem: new)
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// select the new dragged item
|
||||
DispatchQueue.main.async {
|
||||
let toBeSelected = self.row(forItem: new)
|
||||
if toBeSelected >= 0 {
|
||||
self.scrollRowToVisible(toBeSelected)
|
||||
self.selectRowIndexes(IndexSet(integer: toBeSelected), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo replace
|
||||
/// Undo operation for Search & Replace in PEOutlineView
|
||||
@objc func undo_FindAndReplace(nodes: NSArray, oldTreeData: NSArray, newTreeData: NSArray) {
|
||||
self.prepareUndoObj().undo_FindAndReplace(nodes: nodes,
|
||||
oldTreeData: newTreeData,
|
||||
newTreeData: oldTreeData)
|
||||
// select and expand, one by one, rows in oldNodes
|
||||
self.undoManager?.setActionName(localizedReplace.lowercased())
|
||||
|
||||
var index : Int = 0
|
||||
for n in nodes {
|
||||
let node = n as! PENode
|
||||
self.editorVC?.asyncExpand(item: node, expandParentOnly: true)
|
||||
|
||||
let newData = newTreeData.object(at: index) as? TagData
|
||||
node.tagdata?.key = (newData?.key)!
|
||||
node.tagdata?.value = newData?.value
|
||||
DispatchQueue.main.async {
|
||||
let row = self.row(forItem: node)
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: 0))
|
||||
self.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: 2))
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
index+=1
|
||||
}
|
||||
|
||||
self.editorVC?.performDelayedSearch()
|
||||
}
|
||||
|
||||
// MARK: Undo set key string
|
||||
/// Undo operation for a key editing (Key column) in PEOutlineView
|
||||
@objc func undo_SetKey(node: PENode, newKey: String, oldKey: String) {
|
||||
// we are setting the key for a specific row and ther's no need to reload the entire group
|
||||
self.prepareUndoObj().undo_SetKey(node: node,
|
||||
newKey: oldKey,
|
||||
oldKey: newKey)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoTyping)
|
||||
node.tagdata?.key = newKey
|
||||
|
||||
self.editorVC?.asyncExpand(item: node, expandParentOnly: true)
|
||||
//selecting the involved row, but we need to reload its view? isn't that already happened?
|
||||
DispatchQueue.main.async {
|
||||
self.reloadItem(node.parent, reloadChildren: false)
|
||||
let row = self.row(forItem: node)
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: 0))
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo Replace Existing key
|
||||
/// Undo operation for replacing a duplicate key-value pairs in PEOutlineView
|
||||
@objc func undo_ReplaceExisting(item: PENode,
|
||||
inParent: PENode,
|
||||
indexChild: Int,
|
||||
editedNode: PENode,
|
||||
oldKey: String,
|
||||
newKey: String) {
|
||||
|
||||
self.prepareUndoObj().redo_ReplaceExisting(item: item,
|
||||
inParent: inParent,
|
||||
indexChild: indexChild,
|
||||
editedNode: editedNode,
|
||||
oldKey: newKey,
|
||||
newKey: oldKey)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoReplaceDuplicateKey)
|
||||
// expand the node if isn't. (an item can be added to a not expanded group??)
|
||||
self.editorVC?.asyncExpand(item: item, expandParentOnly: true)
|
||||
inParent.mutableChildren.removeObject(at: indexChild)
|
||||
DispatchQueue.main.async {
|
||||
self.removeItems(at: IndexSet(integer: indexChild),
|
||||
inParent: inParent,
|
||||
withAnimation: [])
|
||||
|
||||
editedNode.tagdata?.key = newKey
|
||||
|
||||
// keep trak of the item because must be selected after the parent gets reloaded
|
||||
let row = self.row(forItem: editedNode)
|
||||
|
||||
//reload the parent. In this case the parent it's the same
|
||||
//self.outline.reloadItem(inParent, reloadChildren: true)
|
||||
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Redo Replace Existing key
|
||||
/// Reversed undo operation (Redo) for replacing a duplicate key-value pairs in PEOutlineView
|
||||
@objc func redo_ReplaceExisting(item: PENode,
|
||||
inParent: PENode,
|
||||
indexChild: Int,
|
||||
editedNode: PENode,
|
||||
oldKey: String,
|
||||
newKey: String) {
|
||||
|
||||
self.prepareUndoObj().undo_ReplaceExisting(item: item,
|
||||
inParent: inParent,
|
||||
indexChild: indexChild,
|
||||
editedNode: editedNode,
|
||||
oldKey: newKey,
|
||||
newKey: oldKey)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoReplaceDuplicateKey)
|
||||
// expand the node if isn't. (an item can be added to a not expanded group??)
|
||||
self.editorVC?.asyncExpand(item: item, expandParentOnly: true)
|
||||
|
||||
inParent.mutableChildren.insert(item, at: indexChild)
|
||||
editedNode.tagdata?.key = newKey
|
||||
DispatchQueue.main.async {
|
||||
self.insertItems(at: IndexSet(integer: indexChild),
|
||||
inParent: inParent,
|
||||
withAnimation: [])
|
||||
|
||||
|
||||
// keep trak of the item because must be selected after the parent gets reloaded
|
||||
let row = self.row(forItem: editedNode)
|
||||
|
||||
//reload the parent. In this case the parent it's the same
|
||||
self.reloadItem(inParent, reloadChildren: true)
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo set new value
|
||||
/// Undo operation (Redo) editing a value (Value Column) in PEOutlineView.
|
||||
/// See also undoableSetBoolValue(node: , sender:, originalValue:, actualValue:)
|
||||
@objc func undo_SetValue(node: PENode, newValue: Any, oldValue: Any) {
|
||||
let row = self.row(forItem: node)
|
||||
self.prepareUndoObj().undo_SetValue(node: node,
|
||||
newValue: oldValue,
|
||||
oldValue: newValue)
|
||||
|
||||
self.editorVC?.asyncExpand(item: node, expandParentOnly: true)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoTyping)
|
||||
node.tagdata?.value = newValue
|
||||
DispatchQueue.main.async {
|
||||
self.reloadItem(node.parent, reloadChildren: false)
|
||||
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: 2))
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo change to Bool tag
|
||||
/// Undo operation (Redo) editing a Bool value (Value Column) in PEOutlineView
|
||||
@objc func undo_SetBoolValue(node: PENode,
|
||||
sender: PEPopUpButton,
|
||||
originalValue: Bool,
|
||||
actualValue: Bool) {
|
||||
self.prepareUndoObj().undo_SetBoolValue(node: node,
|
||||
sender: sender,
|
||||
originalValue: actualValue,
|
||||
actualValue: originalValue)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoChangeBoolValue)
|
||||
node.tagdata?.value = actualValue
|
||||
|
||||
// root node cannot have parent and is always visible
|
||||
if (node.parent != nil) {
|
||||
self.editorVC?.asyncExpand(item: node, expandParentOnly: true)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let row = self.row(forItem: node)
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
|
||||
sender.animator().selectItem(withTitle:
|
||||
actualValue ? localizedYes : localizedNo)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo change tag
|
||||
/// Undo operation changing a tag (Tag Column) in PEOutlineView.
|
||||
@objc func undo_ChangeType(node: PENode,
|
||||
newData: TagData,
|
||||
oldData: TagData,
|
||||
newChilds: [Any]?,
|
||||
oldChilds: [Any]?,
|
||||
sender: PEPopUpButton) {
|
||||
|
||||
self.prepareUndoObj().undo_ChangeType(node: node,
|
||||
newData: oldData,
|
||||
oldData: newData,
|
||||
newChilds: oldChilds,
|
||||
oldChilds: newChilds,
|
||||
sender: sender)
|
||||
self.undoManager?.setActionName(localizedUndoRedoChangeType)
|
||||
|
||||
node.representedObject = newData
|
||||
node.mutableChildren.removeAllObjects()
|
||||
if let childrens = newChilds {
|
||||
node.mutableChildren.addObjects(from: childrens)
|
||||
}
|
||||
|
||||
// root node cannot have parent and is always visible
|
||||
if (node.parent != nil) {
|
||||
self.editorVC?.asyncExpand(item: node, expandParentOnly: true)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.reloadItem(node, reloadChildren: true)
|
||||
|
||||
let row = self.row(forItem: node)
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo change contenitor (Dictionary to Array and viceversa)
|
||||
/// Undo operation changing a Dictionary to Array and viceversa (Tag Column) in PEOutlineView.
|
||||
@objc func undo_ChangeContenitor(node: PENode,
|
||||
sender: PEPopUpButton,
|
||||
originalType: String,
|
||||
actualType: String,
|
||||
oldKeys: PEArray,
|
||||
newKeys: PEArray) {
|
||||
/*
|
||||
We cannot leave old keys, but instead we should:
|
||||
- Dictionary, must have localized "New Item -x" for each children
|
||||
- Array, must be localized "Item x" for each children
|
||||
*/
|
||||
|
||||
self.prepareUndoObj().undo_ChangeContenitor(node: node,
|
||||
sender: sender,
|
||||
originalType: actualType,
|
||||
actualType: originalType,
|
||||
oldKeys: newKeys,
|
||||
newKeys: oldKeys)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoChangeType)
|
||||
|
||||
// root node cannot have parent and is always visible
|
||||
if (node.parent != nil) {
|
||||
self.editorVC?.asyncExpand(item: node, expandParentOnly: false)
|
||||
}
|
||||
|
||||
let actualTag = gPlistTag(from: actualType)
|
||||
|
||||
if actualTag == .Dictionary {
|
||||
node.tagdata?.value = nil
|
||||
node.tagdata?.type = .Dictionary
|
||||
} else if actualTag == .Array {
|
||||
node.tagdata?.value = nil
|
||||
node.tagdata?.type = .Array
|
||||
}
|
||||
|
||||
var index : Int = 0
|
||||
for n in node.mutableChildren {
|
||||
let modded : PENode = n as! PENode
|
||||
modded.tagdata?.key = newKeys[index] as! String
|
||||
index+=1
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
sender.animator().selectItem(withTitle: gPlistTagStr(tag: actualTag).locale)
|
||||
}
|
||||
|
||||
self.editorVC?.asyncExpand(item: node, expandParentOnly: true)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let row = self.row(forItem: node)
|
||||
if self.isItemExpanded(node) {
|
||||
self.reloadItem(node, reloadChildren: true)
|
||||
} else {
|
||||
self.reloadItem(node, reloadChildren: false)
|
||||
}
|
||||
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo Paste
|
||||
/// Undo operation for "Paste" in PEOutlineView.
|
||||
/// See redo_PasteItem(item: , inParent: , indexChild: ) for the reversed operation.
|
||||
@objc func undo_Paste(item: PENode, inParent: PENode, indexChild: Int) {
|
||||
self.prepareUndoObj().redo_Paste(item: item,
|
||||
inParent: inParent,
|
||||
indexChild: indexChild)
|
||||
|
||||
self.editorVC?.asyncExpand(item: item, expandParentOnly: true)
|
||||
self.undoManager?.setActionName(localizedUndoRedoPasteItem)
|
||||
inParent.mutableChildren.insert(item, at: indexChild)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.insertItems(at: IndexSet(integer: indexChild), inParent: inParent, withAnimation: [])
|
||||
|
||||
// basically we are adding a new Item. Reload the parent and the cildrens..and select new item
|
||||
self.reloadItem(inParent, reloadChildren: true)
|
||||
// find new item that should be there now..
|
||||
let row = self.row(forItem: item)
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo Paste (reverse)
|
||||
/// Redo operation for "Paste" in PEOutlineView.
|
||||
/// See undo_PasteItem(item: , inParent: , indexChild: ) for the reversed operation.
|
||||
@objc func redo_Paste(item: PENode, inParent: PENode, indexChild: Int) {
|
||||
self.prepareUndoObj().undo_Paste(item: item,
|
||||
inParent: inParent,
|
||||
indexChild: indexChild)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoPasteItem)
|
||||
// expand the node if isn't
|
||||
self.editorVC?.asyncExpand(item: item, expandParentOnly: true)
|
||||
// find the row. After deleting it, it will be the row to select again (if exist, othewise select last row in the outline)
|
||||
DispatchQueue.main.async {
|
||||
let row = self.row(forItem: item)
|
||||
|
||||
inParent.mutableChildren.removeObject(at: indexChild)
|
||||
self.removeItems(at: IndexSet(integer: indexChild), inParent: inParent, withAnimation: [])
|
||||
|
||||
// basically we are removing a new Item just added. Reload the parent and the cildrens..
|
||||
self.reloadItem(inParent, reloadChildren: true)
|
||||
|
||||
//..and select the next row
|
||||
if row >= 0 {
|
||||
if (self.item(atRow: row) != nil) {
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
} else {
|
||||
// row it's out of bounds.. select last in the outline
|
||||
let lastRow = self.numberOfRows - 1
|
||||
if lastRow >= 0 {
|
||||
self.scrollRowToVisible(lastRow)
|
||||
self.selectRowIndexes(IndexSet(integer: lastRow), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo Cut
|
||||
/// Undo operation for "Cut" in PEOutlineView.
|
||||
/// See redo_Cut(item: , inParent: , indexChild: ) for the reversed operation.
|
||||
@objc func undo_Cut(item: PENode, inParent: PENode, indexChild: Int) {
|
||||
// find the row. After deleting it, it will be the row to select again (if exist, othewise select last row in the outline)
|
||||
self.prepareUndoObj().redo_Cut(item: item,
|
||||
inParent: inParent,
|
||||
indexChild: indexChild)
|
||||
self.editorVC?.asyncExpand(item: item, expandParentOnly: true)
|
||||
self.undoManager?.setActionName(localizedUndoRedoCutItem)
|
||||
inParent.mutableChildren.removeObject(at: indexChild)
|
||||
self.removeItems(at: IndexSet(integer: indexChild), inParent: inParent, withAnimation: [])
|
||||
// basically we are removing a new Item just added. Reload the parent and the cildrens..
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.reloadItem(inParent, reloadChildren: true)
|
||||
let row = self.row(forItem: item)
|
||||
//..and select the next row
|
||||
if row >= 0 {
|
||||
if (self.item(atRow: row) != nil) {
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
} else {
|
||||
// row it's out of bounds.. select last in the outline
|
||||
let lastRow = self.numberOfRows - 1
|
||||
if lastRow >= 0 {
|
||||
self.scrollRowToVisible(lastRow)
|
||||
self.selectRowIndexes(IndexSet(integer: lastRow), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Undo Cut (reverse)
|
||||
/// Redo operation for "Cut" in PEOutlineView.
|
||||
/// See undo_Cut(item: , inParent: , indexChild: ) for the reversed operation.
|
||||
@objc func redo_Cut(item: PENode, inParent: PENode, indexChild: Int) {
|
||||
self.prepareUndoObj().undo_Cut(item: item,
|
||||
inParent: inParent,
|
||||
indexChild: indexChild)
|
||||
|
||||
self.undoManager?.setActionName(localizedUndoRedoCutItem)
|
||||
// expand the node if isn't
|
||||
self.editorVC?.asyncExpand(item: item, expandParentOnly: true)
|
||||
|
||||
// we are adding an item..
|
||||
inParent.mutableChildren.insert(item, at: indexChild)
|
||||
DispatchQueue.main.async {
|
||||
self.insertItems(at: IndexSet(integer: indexChild), inParent: inParent, withAnimation: [])
|
||||
|
||||
// select the new one after reloading the group
|
||||
self.reloadItem(inParent, reloadChildren: true)
|
||||
let row = self.row(forItem: item)
|
||||
if row >= 0 {
|
||||
self.scrollRowToVisible(row)
|
||||
self.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Undo add item
|
||||
/// Undo operation adding New items in PEOutlineView.
|
||||
/// See undo_Remove(item: , inParent: , indexChild: ) for the reverse operation.
|
||||
@objc func undo_Add(item: PENode,
|
||||
inParent: PENode,
|
||||
indexChild: Int,
|
||||
target outlineView: PEOutlineView) {
|
||||
outlineView.prepareUndoObj().undo_Remove(item: item,
|
||||
inParent: inParent,
|
||||
indexChild: indexChild,
|
||||
target: outlineView)
|
||||
|
||||
outlineView.undoManager?.setActionName(localizedUndoRedoAddNewItem)
|
||||
|
||||
outlineView.editorVC?.asyncExpand(item: item, expandParentOnly: true)
|
||||
|
||||
inParent.mutableChildren.insert(item, at: indexChild)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
outlineView.insertItems(at: IndexSet(integer: indexChild), inParent: inParent, withAnimation: [])
|
||||
// select the new one after reloading the group
|
||||
outlineView.reloadItem(inParent, reloadChildren: true)
|
||||
let row = outlineView.row(forItem: item)
|
||||
if row >= 0 {
|
||||
outlineView.scrollRowToVisible(row)
|
||||
outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Redo remove Item
|
||||
/// Redo operation adding New items in PEOutlineView.
|
||||
/// See undo_Add(item: , inParent: , indexChild: ) for the reverse operation.
|
||||
@objc func undo_Remove(item: PENode,
|
||||
inParent: PENode,
|
||||
indexChild: Int,
|
||||
target outlineView: PEOutlineView) {
|
||||
// find the row. After deleting it, it will be the row to select again (if exist, othewise select last row in the outline)
|
||||
let row = outlineView.row(forItem: item)
|
||||
|
||||
outlineView.prepareUndoObj().undo_Add(item: item,
|
||||
inParent: inParent,
|
||||
indexChild: indexChild,
|
||||
target: outlineView)
|
||||
|
||||
outlineView.undoManager?.setActionName(localizedUndoRedoRemoveItem)
|
||||
// expand the node if isn't
|
||||
outlineView.editorVC?.asyncExpand(item: item, expandParentOnly: true)
|
||||
inParent.mutableChildren.removeObject(at: indexChild)
|
||||
DispatchQueue.main.async {
|
||||
outlineView.removeItems(at: IndexSet(integer: indexChild), inParent: inParent, withAnimation: [])
|
||||
|
||||
// Reload the parent and the cildrens..
|
||||
outlineView.reloadItem(inParent, reloadChildren: true)
|
||||
|
||||
//..and select the next row
|
||||
if row >= 0 {
|
||||
outlineView.scrollRowToVisible(row)
|
||||
if (outlineView.item(atRow: row) != nil) {
|
||||
outlineView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
|
||||
} else {
|
||||
// row it's out of bounds.. select last in the outline
|
||||
let lastRow = outlineView.numberOfRows - 1
|
||||
if lastRow >= 0 {
|
||||
outlineView.selectRowIndexes(IndexSet(integer: lastRow), byExtendingSelection: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
162
CloverApp/Clover/Plist Editor/PE_Utils.swift
Normal file
162
CloverApp/Clover/Plist Editor/PE_Utils.swift
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 Foundation
|
||||
|
||||
/**
|
||||
Assign a unique name for newNode if parent already contains it.
|
||||
- Parameter parent: the parent node in which newNode will be inserted.
|
||||
- Parameter newNode: the new node for which it's key must be unique.
|
||||
- Discussion: Call this method before insert new nodes in the parent node.
|
||||
*/
|
||||
func gDeduplicateKeyInParent(parent: PENode, newNode: PENode) {
|
||||
/* making an array of existing keys */
|
||||
var actualKeys = [String]()
|
||||
for item in parent.mutableChildren {
|
||||
actualKeys.append(((item as! PENode).tagdata?.key)!)
|
||||
}
|
||||
|
||||
/* new way */
|
||||
let newKey : String = newNode.tagdata!.key
|
||||
newNode.tagdata!.key = gProposedNewItem(with: newKey, in: actualKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
Propose a new key name for a node Dictionary's keys.
|
||||
- Parameter key: the actual key of the node or the desired one.
|
||||
- Parameter array: an array of String containing all the keys for the target node's childrens.
|
||||
- Returns: a `String` with a unique name for the given array. Will return the same string if the given array doesn't contains it.
|
||||
- Discussion: A dictioanry cannot have duplicate keys. This method ensure that a given key will be unique by adding a numeric suffix (key - n).
|
||||
*/
|
||||
func gProposedNewItem(with key: String, in array: [String]) -> String {
|
||||
var i : Int = 0
|
||||
var proposed : String = key
|
||||
|
||||
repeat {
|
||||
if !array.contains(proposed) {
|
||||
break
|
||||
}
|
||||
proposed = "\(key) - \(i)"
|
||||
i+=1
|
||||
} while true
|
||||
|
||||
return proposed
|
||||
}
|
||||
|
||||
/**
|
||||
A string representation for a given `PlistTag`.
|
||||
- Parameter tag: a `PlistTag`.
|
||||
- Returns: a `String` object.
|
||||
- Discussion: the returned string can be localized which is the main purpose.
|
||||
*/
|
||||
func gPlistTagStr(tag: PlistTag) -> String {
|
||||
switch tag {
|
||||
case .Dictionary:
|
||||
return "Dictionary"
|
||||
case .Array:
|
||||
return "Array"
|
||||
case .String:
|
||||
return "String"
|
||||
case .Number:
|
||||
return "Number"
|
||||
case .Bool:
|
||||
return "Bool"
|
||||
case .Date:
|
||||
return "Date"
|
||||
case .Data:
|
||||
return "Data"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A `PlistTag` representation for a given `PlistTag`.
|
||||
- Parameter str: a `String` which must be no other than `Dictionary`, `Array`, `String`, `Number`, `Bool`,
|
||||
`Date` or `Data`.
|
||||
- Returns: a `String` object.
|
||||
- Discussion: A `fatalError` occours if the given str is not avalid `PlistTag`.
|
||||
*/
|
||||
func gPlistTag(from str: String) -> PlistTag {
|
||||
switch str {
|
||||
case "Dictionary":
|
||||
return .Dictionary
|
||||
case "Array":
|
||||
return .Array
|
||||
case "String":
|
||||
return .String
|
||||
case "Number":
|
||||
return .Number
|
||||
case "Bool":
|
||||
return .Bool
|
||||
case "Date":
|
||||
return .Date
|
||||
case "Data":
|
||||
return .Data
|
||||
default:
|
||||
fatalError("plistTag(from str: String): unsupported str parameter \(str)")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Loading a plist file.
|
||||
- Parameter path: a `String` representing the path to a loadable plist file.
|
||||
- Discussion: The insternal Plist Editor is used for loading plist files while in 10.9/10.10, in order, PlistEdit Pro or Xcode are used.
|
||||
In 10.9/10.10 if mentioned programs aren't present the directory containing the desired file will be opened by the Finder.
|
||||
*/
|
||||
func loadPlist(at path: String) {
|
||||
if #available(OSX 10.11, *) {
|
||||
let dc = NSDocumentController.shared
|
||||
dc.openDocument(withContentsOf: URL(fileURLWithPath: path), display: true) {
|
||||
(document, documentWasAlreadyOpen, error) in
|
||||
if error != nil {
|
||||
print(error!.localizedDescription)
|
||||
NSSound.beep()
|
||||
} else {
|
||||
if (document != nil) {
|
||||
dc.addDocument(document!)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// check if a document is opened some where
|
||||
if NSDocumentController.shared.documents.count == 0 {
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
}
|
||||
// Use reccomended programs (hope) to avoid Text Edit
|
||||
var success = NSWorkspace.shared.openFile(path, withApplication: "PlistEdit Pro")
|
||||
if !success {
|
||||
success = NSWorkspace.shared.openFile(path, withApplication: "Xcode")
|
||||
}
|
||||
/* will it be Text Edit???
|
||||
if !success {
|
||||
success = NSWorkspace.shared.openFile(path)
|
||||
}*/
|
||||
|
||||
if !success { // open the directory path
|
||||
success = NSWorkspace.shared.openFile(path.deletingLastPath)
|
||||
}
|
||||
}
|
||||
}
|
555
CloverApp/Clover/Plist Editor/PlistParser.swift
Normal file
555
CloverApp/Clover/Plist Editor/PlistParser.swift
Normal file
@ -0,0 +1,555 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/*
|
||||
This is the minimum plist file (binary format) as a boolean true as root (the shorter value that can hold), and is 42 bytes:
|
||||
62 70 6C 69 73 74 30 30 09 08 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 09
|
||||
|
||||
Instead, the xml version have a length in bytes (xml1Header + "<true/>" + xml1Footer) of 179.
|
||||
Line feeds are present only in the header: this means that at least the header should be well formatted
|
||||
*/
|
||||
let minBinary : Int = 42
|
||||
let minXml : Int = 179
|
||||
|
||||
|
||||
final class PlistParser: NSObject, XMLParserDelegate {
|
||||
var root : PENode?
|
||||
var plistPath : String? = nil
|
||||
var error : String? = nil
|
||||
var isBinary : Bool = false
|
||||
var rootType : PlistTag = .String
|
||||
private var currentValue : String? = nil
|
||||
private var lastElement : String? = nil
|
||||
private var currentNode: PENode?
|
||||
|
||||
//MARK: initialization
|
||||
override init() {
|
||||
super.init()
|
||||
self.root = nil
|
||||
}
|
||||
|
||||
/// You already know the root node for the document.
|
||||
init(withNode node: PENode) {
|
||||
super.init()
|
||||
self.root = node
|
||||
}
|
||||
|
||||
/// PlistParser initialization from Data (either string or binary data).
|
||||
/// Usually fileType and data comes from NSDocument
|
||||
convenience init(fromData data: Data, fileType: PEFileType) {
|
||||
var node : PENode? = nil
|
||||
var any : Any?
|
||||
|
||||
var plistType : PEFileType = fileType
|
||||
|
||||
// don't trust NSDocument: check our self if this is a binary plist
|
||||
if data.count >= minBinary {
|
||||
let binaryHeaderData: Data = binaryPlistHeader.data(using: String.Encoding.utf8)!
|
||||
let fileheader = data.subdata(in: 0..<binaryHeaderData.count)
|
||||
|
||||
if fileheader == binaryHeaderData {
|
||||
plistType = .binaryPlistv1 // override!
|
||||
}
|
||||
}
|
||||
|
||||
if plistType == .binaryPlistv1 {
|
||||
any = serialize(data: data)
|
||||
if (any != nil) {
|
||||
node = PlistParser.populateTreeWith(plist: any!)
|
||||
}
|
||||
} else {
|
||||
any = serialize(data: data)
|
||||
// don't fail!
|
||||
if any == nil {
|
||||
any = PEDictionary()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
determine if this is a stupid plist (root is not a Dictionary nor Array),
|
||||
otherwise init with node.
|
||||
Why? "node" is used only with binary plist where its Dictionaries aren't sorted as happen in a xml plist..
|
||||
..but no need to use the parser if a plist is stupid
|
||||
*/
|
||||
|
||||
if (any != nil) {
|
||||
if any is NSString {
|
||||
node = PENode(representedObject: TagData(key: "", type: .String, value: any as! String))
|
||||
} else if any is NSNumber {
|
||||
let numberType = CFNumberGetType(any as! NSNumber)
|
||||
if numberType == .charType {
|
||||
// Bool
|
||||
node = PENode(representedObject: TagData(key: "", type: .Bool, value: any as! Bool))
|
||||
} else {
|
||||
// Number of any kind, but int or double..
|
||||
if any is PEInt {
|
||||
node = PENode(representedObject: TagData(key: "Root", type: .Number, value: any as! PEInt))
|
||||
} else {
|
||||
node = PENode(representedObject: TagData(key: "Root", type: .Number, value: any as! PEReal))
|
||||
}
|
||||
}
|
||||
} else if any is NSData {
|
||||
node = PENode(representedObject: TagData(key: "", type: .Data, value: any as! Data))
|
||||
} else if any is NSDate {
|
||||
node = PENode(representedObject: TagData(key: "", type: .Date, value: any as! Date))
|
||||
}
|
||||
}
|
||||
|
||||
if (node != nil) {
|
||||
self.init(withNode: node!)
|
||||
self.isBinary = true
|
||||
} else {
|
||||
self.init()
|
||||
let parser : XMLParser = XMLParser(data: data)
|
||||
self.currentValue = ""
|
||||
parser.delegate = self
|
||||
parser.parse()
|
||||
}
|
||||
}
|
||||
|
||||
/// PlistParser initialization from path. NSDocument uses data, so this is for custom initializations.
|
||||
convenience init(fromPath path: String) {
|
||||
var realOrNilPath : String? = path // this will be nil if the loaded file is not a plist (will avoid to overwrite it)
|
||||
var data : Data? = nil
|
||||
var isPlist : Bool = false
|
||||
var node : PENode? = nil
|
||||
var PropertyListSerializationError : String? = nil
|
||||
var any : Any?
|
||||
data = FileManager.default.contents(atPath: path)
|
||||
|
||||
// test if is a binary plist
|
||||
if (data != nil) {
|
||||
if (data?.count)! >= minBinary {
|
||||
let binaryHeaderData: Data = binaryPlistHeader.data(using: String.Encoding.utf8)!
|
||||
let presumedHeader = data?.subdata(in: 0 ..< binaryHeaderData.count)
|
||||
if presumedHeader == binaryHeaderData {
|
||||
isPlist = true
|
||||
any = serialize(data: data!)
|
||||
|
||||
// don't fail!
|
||||
if any == nil {
|
||||
any = PEDictionary()
|
||||
PropertyListSerializationError = "PlistParser: Unknown error loading binary plist file"
|
||||
}
|
||||
node = PlistParser.populateTreeWith(plist: any!)
|
||||
}
|
||||
}
|
||||
|
||||
// test if is a xml plist
|
||||
if !isPlist {
|
||||
if (data?.count)! >= minXml {
|
||||
let xmlHeaderData: Data = xml1Header.data(using: String.Encoding.utf8)!
|
||||
let presumedHeader = data?.subdata(in: 0 ..< xmlHeaderData.count)
|
||||
if presumedHeader == xmlHeaderData {
|
||||
isPlist = true
|
||||
any = serialize(data: data!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
determine if this is a stupid plist (root is not a Dictionary nor Array),
|
||||
otherwise init with node.
|
||||
Why? "node" is used only with binary plist where its Dictionaries aren't sorted as happen in a xml plist..
|
||||
..but no need to use the parser if a plist is stupid
|
||||
*/
|
||||
|
||||
if (any != nil) {
|
||||
if any is NSString {
|
||||
node = PENode(representedObject: TagData(key: "", type: .String, value: any as! String))
|
||||
} else if any is NSNumber {
|
||||
let numberType = CFNumberGetType(any as! NSNumber)
|
||||
if numberType == .charType {
|
||||
node = PENode(representedObject: TagData(key: "Root", type: .Bool, value: any as! Bool))
|
||||
} else {
|
||||
// Number of any kind, but int or double..
|
||||
if any is PEInt {
|
||||
node = PENode(representedObject: TagData(key: "Root", type: .Number, value: any as! PEInt))
|
||||
} else {
|
||||
node = PENode(representedObject: TagData(key: "Root", type: .Number, value: any as! PEReal))
|
||||
}
|
||||
}
|
||||
} else if any is NSData {
|
||||
node = PENode(representedObject: TagData(key: "", type: .Data, value: any as! Data))
|
||||
} else if any is NSDate {
|
||||
node = PENode(representedObject: TagData(key: "", type: .Date, value: any as! Date))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (node != nil) {
|
||||
self.init(withNode: node!)
|
||||
self.error = PropertyListSerializationError
|
||||
self.isBinary = true
|
||||
} else {
|
||||
self.init(fromData: (data ?? (xml1Header + "\n<dict/>\n" + xml1Footer).data(using: String.Encoding.utf8))!,
|
||||
fileType: PEFileType.xmlPlistv1)
|
||||
}
|
||||
|
||||
|
||||
if isPlist {
|
||||
realOrNilPath = path
|
||||
}
|
||||
self.plistPath = realOrNilPath
|
||||
}
|
||||
|
||||
func parser(_ parser: XMLParser,
|
||||
didStartElement elementName: String,
|
||||
namespaceURI: String?,
|
||||
qualifiedName qName: String?,
|
||||
attributes attributeDict: [String : String] = [:]) {
|
||||
switch elementName {
|
||||
case "dict":
|
||||
if (self.root == nil) {
|
||||
self.rootType = .Dictionary
|
||||
self.root = self.getNewDictNode()
|
||||
self.currentNode = self.root
|
||||
} else {
|
||||
if lastElement == "array" || self.currentNode!.tagdata!.type == .Array {
|
||||
let new = self.getNewDictNode()
|
||||
self.currentNode?.mutableChildren.add(new)
|
||||
self.currentNode = new
|
||||
break
|
||||
} else if self.lastElement == "key" {
|
||||
self.currentNode?.tagdata?.type = .Dictionary
|
||||
self.currentNode?.tagdata?.value = nil
|
||||
} else {
|
||||
let new = self.getNewDictNode()
|
||||
self.currentNode?.parent?.mutableChildren.add(new)
|
||||
self.currentNode = new
|
||||
}
|
||||
}
|
||||
break
|
||||
case "array":
|
||||
if (self.root == nil) {
|
||||
self.rootType = .Array
|
||||
self.root = self.getNewArrayNode()
|
||||
self.currentNode = self.root
|
||||
} else {
|
||||
if self.lastElement == "key" {
|
||||
self.currentNode?.tagdata?.type = .Array
|
||||
self.currentNode?.tagdata?.value = nil
|
||||
} else {
|
||||
let new = self.getNewArrayNode()
|
||||
self.currentNode?.parent?.mutableChildren.add(new)
|
||||
self.currentNode = new
|
||||
}
|
||||
}
|
||||
break
|
||||
case "key":
|
||||
let new = self.getNewStringNode()
|
||||
self.currentNode?.mutableChildren.add(new)
|
||||
self.currentNode = new
|
||||
break
|
||||
case "string":
|
||||
if self.lastElement != "key" {
|
||||
let new = self.getNewStringNode()
|
||||
self.addNewChildren(newNode: new)
|
||||
}
|
||||
break
|
||||
case "date":
|
||||
if self.lastElement != "key" {
|
||||
let new = self.getNewDateNode()
|
||||
self.addNewChildren(newNode: new)
|
||||
}
|
||||
break
|
||||
case "data":
|
||||
if self.lastElement != "key" {
|
||||
let new = self.getNewDataNode()
|
||||
self.addNewChildren(newNode: new)
|
||||
}
|
||||
break
|
||||
case "real":
|
||||
if self.lastElement != "key" {
|
||||
let new = self.getNewNumberNode()
|
||||
self.addNewChildren(newNode: new)
|
||||
}
|
||||
break
|
||||
case "integer":
|
||||
if self.lastElement != "key" {
|
||||
let new = self.getNewNumberNode()
|
||||
self.addNewChildren(newNode: new)
|
||||
}
|
||||
break
|
||||
case "true":
|
||||
if self.lastElement != "key" {
|
||||
let new = self.getNewBoolNode()
|
||||
self.addNewChildren(newNode: new)
|
||||
}
|
||||
break
|
||||
case "false":
|
||||
if self.lastElement != "key" {
|
||||
let new = self.getNewBoolNode()
|
||||
self.addNewChildren(newNode: new)
|
||||
}
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
self.lastElement = elementName
|
||||
}
|
||||
|
||||
//MARK: XMLParser functions
|
||||
func parser(_ parser: XMLParser,
|
||||
didEndElement elementName: String,
|
||||
namespaceURI: String?,
|
||||
qualifiedName qName: String?) {
|
||||
if (self.root != nil) {
|
||||
let value : String? = self.currentValue?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
||||
switch elementName {
|
||||
case "dict":
|
||||
self.goback()
|
||||
break
|
||||
case "array":
|
||||
self.goback()
|
||||
break
|
||||
case "key":
|
||||
self.currentNode?.tagdata?.key = value?.escapingXMLCharacters ?? ""
|
||||
break
|
||||
case "string":
|
||||
self.currentNode?.tagdata?.value = value!.escapingXMLCharacters
|
||||
self.currentNode?.tagdata?.type = .String
|
||||
self.goback()
|
||||
break
|
||||
case "date":
|
||||
self.currentNode?.tagdata?.value = utcStringToDate(value!)
|
||||
self.currentNode?.tagdata?.type = .Date
|
||||
self.goback()
|
||||
break
|
||||
case "data":
|
||||
let data : Data = Data(base64Encoded: value!, options: Data.Base64DecodingOptions.ignoreUnknownCharacters)!
|
||||
self.currentNode?.tagdata?.value = data
|
||||
self.currentNode?.tagdata?.type = .Data
|
||||
self.goback()
|
||||
break
|
||||
case "real":
|
||||
self.currentNode?.tagdata?.value = PEReal(value!)!
|
||||
self.currentNode?.tagdata?.type = .Number
|
||||
self.goback()
|
||||
break
|
||||
case "integer":
|
||||
self.currentNode?.tagdata?.value = PEInt(value!)!
|
||||
self.currentNode?.tagdata?.type = .Number
|
||||
self.goback()
|
||||
break
|
||||
case "true":
|
||||
self.currentNode?.tagdata?.value = true
|
||||
self.currentNode?.tagdata?.type = .Bool
|
||||
self.goback()
|
||||
break
|
||||
case "false":
|
||||
self.currentNode?.tagdata?.value = false
|
||||
self.currentNode?.tagdata?.type = .Bool
|
||||
self.goback()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
self.lastElement = elementName
|
||||
self.currentValue = ""
|
||||
}
|
||||
|
||||
func parser(_ parser: XMLParser, foundCharacters string: String) {
|
||||
self.currentValue = self.currentValue?.appending(string)
|
||||
}
|
||||
|
||||
func parserDidStartDocument(_ parser: XMLParser) {
|
||||
}
|
||||
|
||||
func parserDidEndDocument(_ parser: XMLParser) {
|
||||
if self.rootType == .Dictionary || self.rootType == .Array {
|
||||
self.root?.tagdata?.key = "Root"
|
||||
}
|
||||
}
|
||||
|
||||
func parser(_ parser: XMLParser, foundIgnorableWhitespace whitespaceString: String) {
|
||||
|
||||
}
|
||||
|
||||
func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
|
||||
self.root = nil
|
||||
self.error = "\(parseError)".replacingOccurrences(of: "NSXML", with: "Plist")
|
||||
}
|
||||
|
||||
//MARK: Helper functions for XMLParser
|
||||
private func addNewChildren(newNode: PENode) {
|
||||
if (self.root == nil) {
|
||||
self.rootType = (newNode.tagdata?.type)!
|
||||
self.root = newNode
|
||||
self.currentNode = self.root
|
||||
} else {
|
||||
self.currentNode?.mutableChildren.add(newNode)
|
||||
self.currentNode = newNode
|
||||
}
|
||||
}
|
||||
|
||||
func isChildTag(tag: String) -> Bool {
|
||||
return tag == "string" ||
|
||||
tag == "date" ||
|
||||
tag == "data" ||
|
||||
tag == "real" ||
|
||||
tag == "integer" ||
|
||||
tag == "true" ||
|
||||
tag == "false"
|
||||
}
|
||||
|
||||
private func goback() {
|
||||
if ((self.currentNode?.peparent) == nil) {
|
||||
self.currentNode = self.root
|
||||
} else {
|
||||
self.currentNode = self.currentNode?.peparent
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: Get empty nodes
|
||||
private func getNewDictNode() -> PENode {
|
||||
return PENode(representedObject: TagData(key: localizedNewItem,
|
||||
type: .Dictionary,
|
||||
value: nil))
|
||||
}
|
||||
|
||||
private func getNewArrayNode() -> PENode {
|
||||
return PENode(representedObject: TagData(key: localizedNewItem,
|
||||
type: .Array,
|
||||
value: nil))
|
||||
}
|
||||
|
||||
private func getNewStringNode() -> PENode {
|
||||
return PENode(representedObject: TagData(key: localizedNewItem,
|
||||
type: .String,
|
||||
value: ""))
|
||||
}
|
||||
|
||||
private func getNewNumberNode() -> PENode {
|
||||
return PENode(representedObject: TagData(key: localizedNewItem,
|
||||
type: .Number,
|
||||
value: 0))
|
||||
}
|
||||
|
||||
private func getNewDataNode() -> PENode {
|
||||
return PENode(representedObject: TagData(key: localizedNewItem,
|
||||
type: .Data,
|
||||
value: Data()))
|
||||
}
|
||||
|
||||
private func getNewDateNode() -> PENode {
|
||||
return PENode(representedObject: TagData(key: localizedNewItem,
|
||||
type: .Date,
|
||||
value: Date()))
|
||||
}
|
||||
|
||||
private func getNewBoolNode() -> PENode {
|
||||
return PENode(representedObject: TagData(key: localizedNewItem,
|
||||
type: .Bool,
|
||||
value: false))
|
||||
}
|
||||
|
||||
//MARK: class Helper functions for binary plist
|
||||
|
||||
/// Populate a PENode with the contents of a plist
|
||||
class func populateTreeWith(plist: Any) -> PENode {
|
||||
let data: TagData = TagData(key: "Root", type: .Dictionary, value: nil) // initialization
|
||||
let node: PENode = PENode(representedObject: data)
|
||||
add(child: plist, parent: node)
|
||||
return node
|
||||
}
|
||||
|
||||
/// Adds a new PENode with the contents of a child value.
|
||||
/// This function recursively add nodes to the initial parent node
|
||||
class func add(child: Any, parent: PENode) {
|
||||
if child is PEDictionary {
|
||||
let dict = child as! PEDictionary
|
||||
for key in dict.keys {
|
||||
let curVal = dict[key]
|
||||
var type : PlistTag
|
||||
if curVal is PEDictionary {
|
||||
type = .Dictionary
|
||||
} else if curVal is PEArray {
|
||||
type = .Array
|
||||
} else if (curVal is PEInt || curVal is PEReal) {
|
||||
type = .Number
|
||||
} else if curVal is Bool {
|
||||
type = .Bool
|
||||
} else if curVal is String {
|
||||
type = .String
|
||||
} else if curVal is Data {
|
||||
type = .Data
|
||||
} else if curVal is Date {
|
||||
type = .Date
|
||||
} else {
|
||||
break
|
||||
//fatalError("add(child: Any, parent: PENode) unsupported tag for \(curVal ?? "Unknown")")
|
||||
}
|
||||
|
||||
let d = TagData(key: key,
|
||||
type: type,
|
||||
value: (type == .Array || type == .Dictionary) ? nil : curVal)
|
||||
let node = PENode(representedObject: d)
|
||||
parent.mutableChildren.add(node)
|
||||
add(child: curVal!, parent: node)
|
||||
}
|
||||
} else if child is PEArray {
|
||||
let arr = child as! PEArray
|
||||
for i in 0..<arr.count {
|
||||
let curVal = arr[i]
|
||||
var type : PlistTag
|
||||
if curVal is PEDictionary {
|
||||
type = .Dictionary
|
||||
} else if curVal is PEArray {
|
||||
type = .Array
|
||||
} else if (curVal is PEInt || curVal is PEReal) {
|
||||
type = .Number
|
||||
} else if curVal is Bool {
|
||||
type = .Bool
|
||||
} else if curVal is String {
|
||||
type = .String
|
||||
} else if curVal is Data {
|
||||
type = .Data
|
||||
} else if curVal is Date {
|
||||
type = .Date
|
||||
} else {
|
||||
break
|
||||
//fatalError("add(child: Any, parent: PENode) unsupported tag for \(curVal)")
|
||||
}
|
||||
|
||||
let d = TagData(key: "\(localizedItem) \(i)",
|
||||
type: type,
|
||||
value: (type == .Array || type == .Dictionary) ? nil : curVal)
|
||||
let node = PENode(representedObject: d)
|
||||
parent.mutableChildren.add(node)
|
||||
add(child: curVal, parent: node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
98
CloverApp/Clover/Plist Editor/TagData.swift
Normal file
98
CloverApp/Clover/Plist Editor/TagData.swift
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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: TagData
|
||||
final class TagData: NSObject, NSCoding, NSCopying {
|
||||
var key : String
|
||||
var type : PlistTag
|
||||
var value : Any?
|
||||
var placeHolder : String
|
||||
|
||||
required init(key: String, type: PlistTag, value: Any?) {
|
||||
self.key = key
|
||||
self.type = type
|
||||
self.value = value
|
||||
self.placeHolder = ""
|
||||
super.init()
|
||||
|
||||
self.type = type
|
||||
|
||||
// don't hold the value of contenitors, ....save the memory!!!!
|
||||
if self.type == .Array {
|
||||
self.value = nil
|
||||
} else if self.type == .Dictionary {
|
||||
self.value = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Welocome to NSCoding!
|
||||
required init(coder decoder: NSCoder) {
|
||||
self.key = decoder.decodeObject(forKey: "key") as? String ?? ""
|
||||
self.type = PlistTag(rawValue: (decoder.decodeObject(forKey: "type") as! NSNumber).intValue) ?? .String
|
||||
|
||||
let val = decoder.decodeObject(forKey: "value")
|
||||
switch self.type {
|
||||
case .String:
|
||||
self.value = val as! String
|
||||
case .Dictionary:
|
||||
self.value = nil
|
||||
case .Array:
|
||||
self.value = nil
|
||||
case .Number:
|
||||
if val is PEInt {
|
||||
self.value = val as! PEInt
|
||||
} else {
|
||||
self.value = val as! PEReal
|
||||
}
|
||||
case .Bool:
|
||||
self.value = val as! Bool
|
||||
case .Date:
|
||||
self.value = val as! Date
|
||||
case .Data:
|
||||
self.value = val as! Data
|
||||
}
|
||||
|
||||
self.placeHolder = ""
|
||||
}
|
||||
|
||||
func encode(with coder: NSCoder) {
|
||||
coder.encode(self.key, forKey: "key")
|
||||
coder.encode(NSNumber(value: self.type.rawValue), forKey: "type")
|
||||
coder.encode(self.value, forKey: "value")
|
||||
}
|
||||
|
||||
func copy(with zone: NSZone? = nil) -> Any {
|
||||
let keyCopy = "\(self.key)"
|
||||
let typeCopy = PlistTag.init(rawValue: self.type.rawValue)
|
||||
let valCopy = (self.value as AnyObject).copy()
|
||||
|
||||
return TagData(key: keyCopy, type: typeCopy!, value: valCopy)
|
||||
}
|
||||
}
|
||||
|
77
CloverApp/Clover/Plist Editor/Views/PEAlert.swift
Normal file
77
CloverApp/Clover/Plist Editor/Views/PEAlert.swift
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@available(OSX 10.11, *)
|
||||
final class PEAlert : NSAlert {
|
||||
var anyString : String? = nil
|
||||
var outline: PEOutlineView? = nil
|
||||
|
||||
init(in window: NSWindow?) {
|
||||
super.init()
|
||||
self.customizeAlert()
|
||||
}
|
||||
|
||||
func customizeAlert() {
|
||||
self.alertStyle = NSAlert.Style.warning
|
||||
|
||||
let allview = self.window.contentView?.subviews
|
||||
|
||||
for v in allview! {
|
||||
if v is NSImageView {
|
||||
let imgv = v as! NSImageView
|
||||
imgv.image = NSApp.applicationIconImage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func keepEditing() -> Bool {
|
||||
/* usato in Data/Date value (tutti gli altri saranno validi in qualche modo)
|
||||
Your entry is not valid. Do you want to keep editing and fix the error or cancel editing?
|
||||
Cancel
|
||||
Keep Editing
|
||||
*/
|
||||
var result = NSApplication.ModalResponse.alertFirstButtonReturn
|
||||
self.messageText = "Invalid value detected!".locale
|
||||
|
||||
self.informativeText = "Your edit is not valid,\nDo you want to restore last valid value or keep editing?".locale
|
||||
|
||||
self.addButton(withTitle: localizedKeepEditing)
|
||||
self.addButton(withTitle: "Undo".locale)
|
||||
|
||||
|
||||
self.beginSheetModal(for: self.window, completionHandler: { (modalResponse) -> Void in
|
||||
result = modalResponse
|
||||
if result == NSApplication.ModalResponse.alertFirstButtonReturn {
|
||||
print("key deleted")
|
||||
}
|
||||
})
|
||||
return result == NSApplication.ModalResponse.alertFirstButtonReturn
|
||||
}
|
||||
}
|
||||
|
71
CloverApp/Clover/Plist Editor/Views/PEConstraints.swift
Normal file
71
CloverApp/Clover/Plist Editor/Views/PEConstraints.swift
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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: add constraints to fit superview
|
||||
/**
|
||||
Add constraints to the new added subview to fit its superview.
|
||||
- Parameter superView: The super view.
|
||||
- Parameter subView: the subview that needs constraints.
|
||||
*/
|
||||
func gAddConstraintsToFit(superView: NSView, subView: NSView) {
|
||||
subView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
superView.addConstraint(NSLayoutConstraint(item: subView,
|
||||
attribute: .top,
|
||||
relatedBy: .equal,
|
||||
toItem: superView,
|
||||
attribute: .top,
|
||||
multiplier: 1.0,
|
||||
constant: 0.0))
|
||||
|
||||
superView.addConstraint(NSLayoutConstraint(item: subView,
|
||||
attribute: .leading,
|
||||
relatedBy: .equal,
|
||||
toItem: superView,
|
||||
attribute: .leading,
|
||||
multiplier: 1.0,
|
||||
constant: 0.0))
|
||||
|
||||
superView.addConstraint(NSLayoutConstraint(item: superView,
|
||||
attribute: .bottom,
|
||||
relatedBy: .equal,
|
||||
toItem: subView,
|
||||
attribute: .bottom,
|
||||
multiplier: 1.0,
|
||||
constant: 0.0))
|
||||
|
||||
superView.addConstraint(NSLayoutConstraint(item: superView,
|
||||
attribute: .trailing,
|
||||
relatedBy: .equal,
|
||||
toItem: subView,
|
||||
attribute: .trailing,
|
||||
multiplier: 1.0,
|
||||
constant: 0.0))
|
||||
}
|
||||
|
39
CloverApp/Clover/Plist Editor/Views/PEFindButton.swift
Normal file
39
CloverApp/Clover/Plist Editor/Views/PEFindButton.swift
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
final class FindButton: NSButton {
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
self.canDrawConcurrently = AppSD.canDrawConcurrently
|
||||
self.wantsLayer = true
|
||||
let image = NSImage(named: "Find")
|
||||
image?.isTemplate = true
|
||||
self.image = image
|
||||
}
|
||||
}
|
78
CloverApp/Clover/Plist Editor/Views/PEMenuItem.swift
Normal file
78
CloverApp/Clover/Plist Editor/Views/PEMenuItem.swift
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
final class PEMenuItem: NSMenuItem {
|
||||
internal var sep : Bool = false
|
||||
internal var d : String?
|
||||
public var isKey : Bool = false
|
||||
public var keyPath : String = ""
|
||||
public var node : PENode? = nil
|
||||
|
||||
public var useEditor : Bool = false
|
||||
|
||||
override var isSeparatorItem: Bool {
|
||||
get {
|
||||
return self.sep
|
||||
} set {
|
||||
self.sep = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var explanation: String? {
|
||||
get {
|
||||
return self.d
|
||||
} set {
|
||||
self.d = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override func copy(with zone: NSZone? = nil) -> Any {
|
||||
let copy = PEMenuItem()
|
||||
copy.menu = nil // nil allow to reuse this Item somewhere else..
|
||||
copy.title = self.title
|
||||
copy.keyEquivalent = self.keyEquivalent
|
||||
copy.image = self.image?.copy() as? NSImage
|
||||
copy.onStateImage = self.onStateImage.copy() as? NSImage
|
||||
copy.offStateImage = self.offStateImage?.copy() as? NSImage
|
||||
copy.mixedStateImage = self.mixedStateImage.copy() as? NSImage
|
||||
copy.representedObject = self.representedObject
|
||||
copy.submenu = self.submenu?.copy() as? NSMenu;
|
||||
copy.toolTip = self.toolTip;
|
||||
copy.target = self.target;
|
||||
copy.explanation = self.explanation
|
||||
copy.isKey = self.isKey
|
||||
copy.keyPath = self.keyPath
|
||||
copy.node = self.node
|
||||
copy.useEditor = self.useEditor
|
||||
copy.action = self.action
|
||||
return copy
|
||||
}
|
||||
}
|
||||
|
||||
|
336
CloverApp/Clover/Plist Editor/Views/PEOutlineView.swift
Normal file
336
CloverApp/Clover/Plist Editor/Views/PEOutlineView.swift
Normal file
@ -0,0 +1,336 @@
|
||||
/*
|
||||
* 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.11, *)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
133
CloverApp/Clover/Plist Editor/Views/PEPopUpButton.swift
Normal file
133
CloverApp/Clover/Plist Editor/Views/PEPopUpButton.swift
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@available(OSX 10.11, *)
|
||||
final class PEPopUpButton: NSPopUpButton {
|
||||
internal var n: PENode?
|
||||
internal var o: PEOutlineView?
|
||||
|
||||
var node: PENode? {
|
||||
get {
|
||||
return self.n
|
||||
} set {
|
||||
self.n = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override public var alignmentRectInsets: NSEdgeInsets {
|
||||
return NSEdgeInsetsMake(0, 0, 0, 0)
|
||||
}
|
||||
|
||||
override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
|
||||
if (self.outline != nil) {
|
||||
/*
|
||||
the following is needed when user click on the type column:
|
||||
He can have ongoing edits on some rows, so that changing first responder
|
||||
We can aesily inform the outline that We're playing with something else
|
||||
and have to force ask the user in case edits are wrong.
|
||||
This also prevent a crash.
|
||||
*/
|
||||
if (self.outline?.wrongValue)! {
|
||||
menu.cancelTrackingWithoutAnimation() // this grant the popup menu to not showup
|
||||
}
|
||||
self.window?.makeFirstResponder(self)
|
||||
self.window?.makeFirstResponder(self.outline)
|
||||
/*
|
||||
Also consider that PlistEditor() will resign first responder the PETextField involved,
|
||||
just to ensure that user will not be able to leave fields with invalid datas
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
var outline: PEOutlineView? {
|
||||
get {
|
||||
return self.o
|
||||
} set {
|
||||
self.o = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func setAsBool() {
|
||||
self.removeAllItems()
|
||||
self.addItems(withTitles: [localizedNo, localizedYes])
|
||||
}
|
||||
|
||||
func setAsRoot() {
|
||||
self.removeAllItems()
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .Dictionary).locale)
|
||||
self.lastItem?.representedObject = PlistTag.Dictionary
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .Dictionary).locale)
|
||||
self.lastItem?.representedObject = PlistTag.Dictionary
|
||||
}
|
||||
|
||||
func setAsAllType() {
|
||||
self.removeAllItems()
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .Dictionary).locale)
|
||||
self.lastItem?.representedObject = PlistTag.Dictionary
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .Array).locale)
|
||||
self.lastItem?.representedObject = PlistTag.Array
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .String).locale)
|
||||
self.lastItem?.representedObject = PlistTag.String
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .Number).locale)
|
||||
self.lastItem?.representedObject = PlistTag.Number
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .Bool).locale)
|
||||
self.lastItem?.representedObject = PlistTag.Bool
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .Data).locale)
|
||||
self.lastItem?.representedObject = PlistTag.Data
|
||||
self.addItem(withTitle: gPlistTagStr(tag: .Date).locale)
|
||||
self.lastItem?.representedObject = PlistTag.Date
|
||||
}
|
||||
}
|
||||
|
||||
class PEPopUpButtonCell: NSPopUpButtonCell {
|
||||
override func drawTitle(_ title: NSAttributedString,
|
||||
withFrame frame: NSRect,
|
||||
in controlView: NSView) -> NSRect {
|
||||
|
||||
/*
|
||||
overriding this method give to the title color the right white color
|
||||
instead of gray under dark mode. Can change in future so, again, the following method
|
||||
will help adjusting the title color. For now just returning super!
|
||||
*/
|
||||
return super.drawTitle(title, withFrame:frame, in:controlView)
|
||||
/*
|
||||
var attributedTitle = title
|
||||
|
||||
if let popUpButton = self.controlView as? PEPopUpButton {
|
||||
if let object = popUpButton.selectedItem?.representedObject as? Dictionary<String, String> {
|
||||
if let shortTitle = object["shortTitle"] {
|
||||
attributedTitle = NSAttributedString(string:shortTitle, attributes:title.attributes(at:0, effectiveRange:nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.drawTitle(attributedTitle, withFrame:frame, in:controlView)
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
60
CloverApp/Clover/Plist Editor/Views/PEReplaceField.swift
Normal file
60
CloverApp/Clover/Plist Editor/Views/PEReplaceField.swift
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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: PETextField (NSTextField)
|
||||
@available(OSX 10.11, *)
|
||||
final class PEReplaceField: NSSearchField {
|
||||
var editorVC : PlistEditorVC?
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
self.canDrawConcurrently = AppSD.canDrawConcurrently
|
||||
self.wantsLayer = true
|
||||
let c = self.cell as? NSSearchFieldCell
|
||||
let image = NSImage(named: "Replace")?.copy() as! NSImage
|
||||
image.isTemplate = true
|
||||
image.size = NSMakeSize(13, 13)
|
||||
c?.searchButtonCell?.image = image
|
||||
|
||||
c?.searchButtonCell?.imageScaling = .scaleNone
|
||||
self.placeholderString = localizedReplace
|
||||
}
|
||||
|
||||
override func textDidChange(_ aNotification: Notification) {
|
||||
NotificationCenter.default.post(name: PESearchFieldTextDidChange,
|
||||
object: self,
|
||||
userInfo: aNotification.userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
38
CloverApp/Clover/Plist Editor/Views/PESearchField.swift
Normal file
38
CloverApp/Clover/Plist Editor/Views/PESearchField.swift
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
final class PESearchField: NSSearchField {
|
||||
@IBOutlet var countLabel : NSTextField? = nil
|
||||
|
||||
override func textDidChange(_ aNotification: Notification) {
|
||||
NotificationCenter.default.post(name: PESearchFieldTextDidChange,
|
||||
object: self,
|
||||
userInfo: aNotification.userInfo)
|
||||
}
|
||||
}
|
157
CloverApp/Clover/Plist Editor/Views/PETableCellView.swift
Normal file
157
CloverApp/Clover/Plist Editor/Views/PETableCellView.swift
Normal file
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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
|
||||
import CoreData
|
||||
|
||||
//MARK: PETableCellView (NSTableCellView)
|
||||
@available(OSX 10.11, *)
|
||||
final class PETableCellView: NSTableCellView {
|
||||
@IBOutlet var addButton : NSButton!
|
||||
@IBOutlet var removeButton : NSButton!
|
||||
@IBOutlet var buttonsView: NSView!
|
||||
@IBOutlet var buttonsViewWidthConstraint: NSLayoutConstraint!
|
||||
|
||||
var trackingArea: NSTrackingArea?
|
||||
var scroller : NSScrollView? = nil
|
||||
var outline : PEOutlineView?
|
||||
var node : PENode?
|
||||
var type : PETableCellViewType = .key
|
||||
|
||||
var field: PETextField? {
|
||||
get {
|
||||
return self.textField as? PETextField
|
||||
}
|
||||
set {
|
||||
self.textField = newValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func setup(outline: PEOutlineView, column: Int, type: PETableCellViewType) {
|
||||
// actions
|
||||
if type == .key {
|
||||
self.addButton.ignoresMultiClick = true
|
||||
self.addButton.target = self
|
||||
self.addButton.action = #selector(self.addNewItem)
|
||||
|
||||
self.removeButton.ignoresMultiClick = true
|
||||
self.removeButton.target = self
|
||||
self.removeButton.action = #selector(self.removeItem)
|
||||
}
|
||||
|
||||
self.type = type
|
||||
self.outline = outline
|
||||
self.field?.column = column
|
||||
self.field?.delegate = self.outline?.editorVC
|
||||
self.field?.outline = self.outline
|
||||
self.scroller = outline.enclosingScrollView
|
||||
}
|
||||
|
||||
@objc func reloadItemInParent(_ aNotification: Notification) {
|
||||
let parent = self.node?.peparent
|
||||
let ro : TagData? = parent?.tagdata
|
||||
|
||||
if (parent != nil) && ((ro != nil)) && (ro!.type == .Array) {
|
||||
let childIndex : Int = parent!.mutableChildren.index(of: self.node!)
|
||||
DispatchQueue.main.async {
|
||||
self.textField?.animator().stringValue = "Item \(childIndex)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var backgroundStyle: NSView.BackgroundStyle {
|
||||
didSet {
|
||||
if #available(OSX 10.13, *) { /* do nothing */ } else {
|
||||
if self.backgroundStyle == .light {
|
||||
self.textField?.textColor = NSColor.controlTextColor
|
||||
} else if self.backgroundStyle == .dark {
|
||||
self.textField?.textColor = NSColor.alternateSelectedControlTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func addNewItem() {
|
||||
if (self.node != nil) && (self.node?.parent != nil) && !(self.outline?.wrongValue)! {
|
||||
self.outline?.addNewItemFromCell(node: self.node!, parent: self.node?.parent as! PENode)
|
||||
self.hideButtons()
|
||||
} else {
|
||||
//
|
||||
NSSound.beep()
|
||||
self.window?.makeFirstResponder(self.outline)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func removeItem() {
|
||||
let parent = self.node?.parent
|
||||
if (self.node != nil) && (parent != nil) && !(self.outline?.wrongValue)! {
|
||||
self.outline?.removeItemFromCell(node: self.node!, parent: parent as! PENode)
|
||||
} else {
|
||||
NSSound.beep()
|
||||
self.window?.makeFirstResponder(self.outline)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func hideButtons() {
|
||||
self.buttonsViewWidthConstraint.constant = 0
|
||||
}
|
||||
|
||||
override func mouseDown(with theEvent: NSEvent) { }
|
||||
|
||||
override func mouseUp(with theEvent: NSEvent) { }
|
||||
|
||||
override func mouseEntered(with theEvent: NSEvent) {
|
||||
if self.type == .key &&
|
||||
self.outline?.editorVC != nil &&
|
||||
self.outline!.editorVC!.isEditable &&
|
||||
self.window!.isKeyWindow {
|
||||
self.buttonsViewWidthConstraint.constant = 46
|
||||
}
|
||||
}
|
||||
|
||||
override func mouseExited(with event: NSEvent) {
|
||||
self.hideButtons()
|
||||
}
|
||||
|
||||
override func rightMouseDown(with event: NSEvent) { }
|
||||
|
||||
override func updateTrackingAreas() {
|
||||
if self.type == .key {
|
||||
if self.trackingArea != nil {
|
||||
self.removeTrackingArea(self.trackingArea!)
|
||||
}
|
||||
|
||||
self.trackingArea = NSTrackingArea(rect: self.bounds,
|
||||
options: [.activeAlways,/*.activeInKeyWindow, */.mouseEnteredAndExited] ,
|
||||
owner: self, userInfo: nil)
|
||||
|
||||
self.addTrackingArea(self.trackingArea!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
66
CloverApp/Clover/Plist Editor/Views/PETableCellViewPop.swift
Normal file
66
CloverApp/Clover/Plist Editor/Views/PETableCellViewPop.swift
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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: PETableCellViewPop (NSTableCellView)
|
||||
|
||||
@available(OSX 10.11, *)
|
||||
final class PETableCellViewPop: NSTableCellView {
|
||||
var type : PETableCellViewType = .tags
|
||||
@IBOutlet var popup : PEPopUpButton!
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame:frameRect)
|
||||
self.wantsLayer = true
|
||||
self.canDrawSubviewsIntoLayer = true
|
||||
self.canDrawConcurrently = AppSD.canDrawConcurrently
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
super.init(coder: coder)!
|
||||
}
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
}
|
||||
|
||||
func setup(with type: PETableCellViewType, outline: PEOutlineView) {
|
||||
self.type = type
|
||||
self.popup.outline = outline
|
||||
self.popup.target = self.popup?.outline?.editorVC
|
||||
switch self.type {
|
||||
case .bool:
|
||||
self.popup.action = #selector(self.popup.outline!.editorVC!.boolPopUpPressed)
|
||||
case .tags:
|
||||
self.popup.action = #selector(self.popup.outline!.editorVC!.typePopUpPressed)
|
||||
case .value: fallthrough
|
||||
case .key:
|
||||
break // not here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
190
CloverApp/Clover/Plist Editor/Views/PETableRowView.swift
Normal file
190
CloverApp/Clover/Plist Editor/Views/PETableRowView.swift
Normal file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
enum RowViewBorderType : Int {
|
||||
case none = 0
|
||||
case verticalOnly = 2
|
||||
case verticalAndBottom = 3
|
||||
}
|
||||
|
||||
@available(OSX 10.11, *)
|
||||
final class PETableRowView: NSTableRowView {
|
||||
internal var n: PENode?
|
||||
internal var o: PEOutlineView? = nil
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
self.wantsLayer = true
|
||||
self.canDrawSubviewsIntoLayer = true
|
||||
self.canDrawConcurrently = AppSD.canDrawConcurrently
|
||||
self.isEmphasized = true
|
||||
//self.postsBoundsChangedNotifications = true
|
||||
}
|
||||
|
||||
required init?(coder decoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
var outline: PEOutlineView? {
|
||||
get {
|
||||
return self.o
|
||||
} set {
|
||||
self.o = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var node: PENode? {
|
||||
get {
|
||||
return self.n
|
||||
} set {
|
||||
self.n = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func setBorderType() {
|
||||
super.setNeedsDisplay(self.bounds)
|
||||
}
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
var bt : RowViewBorderType = .none
|
||||
if self.isSelected {
|
||||
self.highLightAllBorders(in: dirtyRect)
|
||||
} else {
|
||||
let row : Int = self.outline?.selectedRow ?? -1
|
||||
if row >= 0 {
|
||||
if let selNode = self.outline?.item(atRow: row) as? PENode {
|
||||
// determine if self.node is a child (or sub child of selfNode)
|
||||
let ipsc = "\(self.node!.indexPath)".trimmingCharacters(in: CharacterSet(arrayLiteral: "]"))
|
||||
let ips = "\(selNode.indexPath)".trimmingCharacters(in: CharacterSet(arrayLiteral: "]")) + ","
|
||||
if ipsc.hasPrefix(ips) {
|
||||
bt = .verticalOnly
|
||||
let childs = selNode.mutableChildren
|
||||
if childs.count > 0 && (childs.lastObject as! PENode) == self.node {
|
||||
bt = .verticalAndBottom
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch bt {
|
||||
case .none: break // taken into account previously
|
||||
case.verticalAndBottom:
|
||||
self.highLightLeftBorder(in: dirtyRect)
|
||||
self.highLightRightBorder(in: dirtyRect)
|
||||
self.highLightBottomBorder(in: dirtyRect)
|
||||
case .verticalOnly:
|
||||
self.highLightLeftBorder(in: dirtyRect)
|
||||
self.highLightRightBorder(in: dirtyRect)
|
||||
}
|
||||
}
|
||||
|
||||
private func borderColor() -> NSColor {
|
||||
return NSColor.alternateSelectedControlColor
|
||||
}
|
||||
|
||||
private func lineWidth() -> CGFloat {
|
||||
return 2.5
|
||||
}
|
||||
|
||||
private func highLightAllBorders(in dirtyRect: NSRect) {
|
||||
let origin = NSMakePoint(dirtyRect.origin.x, dirtyRect.origin.y)
|
||||
var rect = NSRect.zero
|
||||
rect.origin = origin
|
||||
rect.size.width = dirtyRect.size.width
|
||||
rect.size.height = dirtyRect.size.height
|
||||
let path: NSBezierPath = NSBezierPath(rect: rect)
|
||||
path.lineWidth = self.lineWidth()
|
||||
self.borderColor().set()
|
||||
path.stroke()
|
||||
|
||||
}
|
||||
|
||||
private func highLightRightBorder(in dirtyRect: NSRect) {
|
||||
let origin = NSMakePoint(dirtyRect.origin.x, dirtyRect.origin.y)
|
||||
var rect = NSRect.zero
|
||||
rect.origin = origin
|
||||
rect.size.width = dirtyRect.size.width
|
||||
rect.size.height = dirtyRect.size.height
|
||||
let path: NSBezierPath = NSBezierPath()
|
||||
path.lineWidth = self.lineWidth()
|
||||
path.move(to: NSMakePoint(rect.size.width - 1, 0))
|
||||
// draw right vertical side
|
||||
path.line(to: NSMakePoint(rect.size.width - 1, rect.size.height))
|
||||
self.borderColor().set()
|
||||
path.stroke()
|
||||
}
|
||||
|
||||
private func highLightLeftBorder(in dirtyRect: NSRect) {
|
||||
let origin = NSMakePoint(dirtyRect.origin.x, dirtyRect.origin.y)
|
||||
var rect = NSRect.zero
|
||||
rect.origin = origin
|
||||
rect.size.width = dirtyRect.size.width
|
||||
rect.size.height = dirtyRect.size.height
|
||||
let path: NSBezierPath = NSBezierPath()
|
||||
path.lineWidth = self.lineWidth()
|
||||
path.move(to: NSMakePoint(1, 0))
|
||||
// draw right vertical side
|
||||
path.line(to: NSMakePoint(1, rect.size.width))
|
||||
self.borderColor().set()
|
||||
path.stroke()
|
||||
}
|
||||
|
||||
private func highLightBottomBorder(in dirtyRect: NSRect) {
|
||||
let origin = NSMakePoint(dirtyRect.origin.x, dirtyRect.origin.y)
|
||||
var rect = NSRect.zero
|
||||
rect.origin = origin
|
||||
rect.size.width = dirtyRect.size.width
|
||||
rect.size.height = dirtyRect.size.height
|
||||
let path: NSBezierPath = NSBezierPath()
|
||||
path.lineWidth = self.lineWidth()
|
||||
path.move(to: NSMakePoint(0, rect.size.height))
|
||||
// draw orizzontal-bottom side
|
||||
path.line(to: NSMakePoint(rect.size.width, rect.size.height))
|
||||
self.borderColor().set()
|
||||
path.stroke()
|
||||
}
|
||||
|
||||
private func isParent() -> Bool {
|
||||
let type : PlistTag = self.node!.tagdata!.type
|
||||
return type == .Array || type == .Dictionary
|
||||
}
|
||||
|
||||
private func getParent(of node: PENode?) -> PENode? {
|
||||
if let parent : PENode = node?.parent as? PENode {
|
||||
let root : PENode? = self.outline?.item(atRow: 0) as? PENode
|
||||
if parent != root {
|
||||
return parent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
102
CloverApp/Clover/Plist Editor/Views/PETextField.swift
Normal file
102
CloverApp/Clover/Plist Editor/Views/PETextField.swift
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* 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: PETextField (NSTextField)
|
||||
@available(OSX 10.11, *)
|
||||
final class PETextField: NSTextField, NSTextViewDelegate {
|
||||
var outline : PEOutlineView? = nil
|
||||
var node : PENode? = nil
|
||||
var column : Int = -1
|
||||
|
||||
override var intrinsicContentSize: NSSize {
|
||||
get {
|
||||
return NSMakeSize(self.frame.size.width, super.intrinsicContentSize.height)
|
||||
}
|
||||
}
|
||||
|
||||
override var lineBreakMode: NSLineBreakMode {
|
||||
get {
|
||||
return .byTruncatingTail
|
||||
}
|
||||
set {
|
||||
super.lineBreakMode = .byTruncatingTail
|
||||
}
|
||||
}
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
self.wantsLayer = true
|
||||
self.canDrawConcurrently = AppSD.canDrawConcurrently
|
||||
self.cell = PETextFieldCell()
|
||||
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
// doing nothing
|
||||
}
|
||||
|
||||
override func textShouldBeginEditing(_ textObject: NSText) -> Bool {
|
||||
return (self.outline?.editorVC?.isEditable)!
|
||||
}
|
||||
|
||||
override func textShouldEndEditing(_ textObject: NSText) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func textViewDidChangeSelection(_ notification: Notification) {
|
||||
if let cur = self.currentEditor() {
|
||||
if cur.selectedRange.length > 0 {
|
||||
self.textColor = NSColor.controlTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayer() {
|
||||
super.updateLayer()
|
||||
//if Thread.isMainThread {
|
||||
// self.invalidateIntrinsicContentSize()
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.11, *)
|
||||
final class PETextFieldCell : NSTextFieldCell {
|
||||
|
||||
override func drawingRect(forBounds rect: NSRect) -> NSRect {
|
||||
let height : CGFloat = 14//rect.size.height
|
||||
let y = ((rect.size.height - height) / 2)
|
||||
let newRect = NSRect(x: 0, y: y, width: rect.size.width, height: height)
|
||||
return super.drawingRect(forBounds: newRect)
|
||||
}
|
||||
}
|
||||
|
402
CloverApp/Clover/Plist Editor/Views/PlistEditor.storyboard
Normal file
402
CloverApp/Clover/Plist Editor/Views/PlistEditor.storyboard
Normal file
@ -0,0 +1,402 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment version="101100" identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15702"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Window Controller-->
|
||||
<scene sceneID="R2V-B0-nI4">
|
||||
<objects>
|
||||
<windowController storyboardIdentifier="Document Window Controller" id="jGA-0Y-lOj" customClass="PlistEditorWC" customModule="Clover" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<window key="window" title="Property List Editor" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="Ckk-yw-fiv">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="427" y="498" width="570" height="424"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
|
||||
<value key="minSize" type="size" width="570" height="63"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="jGA-0Y-lOj" id="98r-iN-zZc"/>
|
||||
</connections>
|
||||
</window>
|
||||
<connections>
|
||||
<segue destination="5gI-5U-AMq" kind="relationship" relationship="window.shadowedContentViewController" id="nsd-lR-9xd"/>
|
||||
</connections>
|
||||
</windowController>
|
||||
<customObject id="6f7-a7-6o1" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="82" y="344"/>
|
||||
</scene>
|
||||
<!--Plist EditorVC-->
|
||||
<scene sceneID="hIz-AP-VOD">
|
||||
<objects>
|
||||
<viewController id="5gI-5U-AMq" customClass="PlistEditorVC" customModule="Clover" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" wantsLayer="YES" id="rwV-zf-hpT">
|
||||
<rect key="frame" x="0.0" y="0.0" width="568" height="423"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<splitView arrangesAllSubviews="NO" dividerStyle="thin" translatesAutoresizingMaskIntoConstraints="NO" id="AXh-Wi-ja8">
|
||||
<rect key="frame" x="0.0" y="0.0" width="568" height="423"/>
|
||||
<subviews>
|
||||
<customView id="pl5-54-raW">
|
||||
<rect key="frame" x="0.0" y="0.0" width="568" height="58"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="f5f-vb-JDD" customClass="ReplaceButton" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="24" width="32" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="1ls-PK-4Lv"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="push" bezelStyle="rounded" imagePosition="overlaps" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="jFu-Mz-fHK">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="showReplaceViewWithSender:" target="5gI-5U-AMq" id="atr-1K-cE2"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CjQ-1i-oKD" customClass="FindButton" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="32" height="32"/>
|
||||
<buttonCell key="cell" type="push" bezelStyle="rounded" imagePosition="overlaps" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="rsa-xW-mCe">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="showFindViewWithSender:" target="5gI-5U-AMq" id="tUA-X4-eWw"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ilk-Ta-1NW">
|
||||
<rect key="frame" x="536" y="23" width="32" height="33"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="20" id="2BP-mm-vHy"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="push" bezelStyle="rounded" image="NSStopProgressTemplate" imagePosition="only" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="enu-el-QQ6">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<searchField wantsLayer="YES" verticalHuggingPriority="750" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7Wv-ZY-JzG" customClass="PESearchField" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="30" y="31" width="343" height="21"/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="oQI-NZ-Akk">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</searchFieldCell>
|
||||
<connections>
|
||||
<outlet property="countLabel" destination="jd2-u3-VLB" id="lgt-7a-34s"/>
|
||||
</connections>
|
||||
</searchField>
|
||||
<searchField wantsLayer="YES" verticalHuggingPriority="750" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bgd-lF-t3r" customClass="PEReplaceField" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="30" y="7" width="343" height="21"/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="Icu-Xk-a2R">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</searchFieldCell>
|
||||
</searchField>
|
||||
<segmentedControl verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="32r-Nm-cON">
|
||||
<rect key="frame" x="379" y="5" width="182" height="24"/>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="momentary" id="hrz-X2-ewv">
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment label="All" width="73"/>
|
||||
<segment label="Replace" width="105" tag="1"/>
|
||||
</segments>
|
||||
</segmentedCell>
|
||||
</segmentedControl>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jd2-u3-VLB">
|
||||
<rect key="frame" x="311" y="34" width="37" height="14"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="33" id="dsC-nE-aXf"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" alignment="right" title="Label" id="Hao-mW-Dwn">
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<segmentedControl verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="HDq-VH-JlL">
|
||||
<rect key="frame" x="379" y="29" width="163" height="24"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="159" id="Mhz-ZO-mK0"/>
|
||||
</constraints>
|
||||
<segmentedCell key="cell" borderStyle="border" alignment="left" style="rounded" trackingMode="momentary" id="I4H-p8-bsp">
|
||||
<font key="font" metaFont="system"/>
|
||||
<segments>
|
||||
<segment image="NSLeftFacingTriangleTemplate" width="78"/>
|
||||
<segment image="NSRightFacingTriangleTemplate" width="78" tag="1"/>
|
||||
</segments>
|
||||
</segmentedCell>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="bgd-lF-t3r" firstAttribute="baseline" secondItem="CjQ-1i-oKD" secondAttribute="firstBaseline" id="19i-v0-bLC"/>
|
||||
<constraint firstItem="32r-Nm-cON" firstAttribute="leading" secondItem="bgd-lF-t3r" secondAttribute="trailing" constant="8" symbolic="YES" id="7Cs-e5-3mY"/>
|
||||
<constraint firstItem="HDq-VH-JlL" firstAttribute="leading" secondItem="7Wv-ZY-JzG" secondAttribute="trailing" constant="8" symbolic="YES" id="7G5-bS-GBT"/>
|
||||
<constraint firstItem="Ilk-Ta-1NW" firstAttribute="leading" secondItem="HDq-VH-JlL" secondAttribute="trailing" constant="2" id="7Jt-3k-pUB"/>
|
||||
<constraint firstItem="f5f-vb-JDD" firstAttribute="trailing" secondItem="CjQ-1i-oKD" secondAttribute="trailing" id="8Sy-kh-PjV"/>
|
||||
<constraint firstItem="bgd-lF-t3r" firstAttribute="baseline" secondItem="32r-Nm-cON" secondAttribute="baseline" id="8mL-f1-sfK"/>
|
||||
<constraint firstItem="f5f-vb-JDD" firstAttribute="leading" secondItem="pl5-54-raW" secondAttribute="leading" constant="6" id="92x-jk-bMW"/>
|
||||
<constraint firstItem="jd2-u3-VLB" firstAttribute="firstBaseline" secondItem="HDq-VH-JlL" secondAttribute="baseline" id="DsB-IM-eLK"/>
|
||||
<constraint firstItem="f5f-vb-JDD" firstAttribute="top" secondItem="7Wv-ZY-JzG" secondAttribute="top" id="EUO-ki-9yh"/>
|
||||
<constraint firstItem="7Wv-ZY-JzG" firstAttribute="leading" secondItem="f5f-vb-JDD" secondAttribute="trailing" constant="4" id="GGZ-Bi-b5f"/>
|
||||
<constraint firstItem="7Wv-ZY-JzG" firstAttribute="baseline" secondItem="f5f-vb-JDD" secondAttribute="firstBaseline" id="Jef-9b-99Y"/>
|
||||
<constraint firstItem="CjQ-1i-oKD" firstAttribute="top" secondItem="bgd-lF-t3r" secondAttribute="top" id="P68-zx-2Ps"/>
|
||||
<constraint firstItem="7Wv-ZY-JzG" firstAttribute="baseline" secondItem="HDq-VH-JlL" secondAttribute="baseline" id="VRR-ns-fTy"/>
|
||||
<constraint firstItem="jd2-u3-VLB" firstAttribute="baseline" secondItem="HDq-VH-JlL" secondAttribute="firstBaseline" id="Vo7-0d-7vJ"/>
|
||||
<constraint firstItem="CjQ-1i-oKD" firstAttribute="top" secondItem="f5f-vb-JDD" secondAttribute="bottom" constant="3" id="W4d-UM-Itx"/>
|
||||
<constraint firstItem="7Wv-ZY-JzG" firstAttribute="leading" secondItem="bgd-lF-t3r" secondAttribute="leading" id="bOE-a9-1xQ"/>
|
||||
<constraint firstAttribute="height" constant="58" id="cSS-9y-hU6"/>
|
||||
<constraint firstAttribute="trailing" secondItem="32r-Nm-cON" secondAttribute="trailing" constant="9" id="e3U-pX-dS1"/>
|
||||
<constraint firstItem="HDq-VH-JlL" firstAttribute="top" secondItem="Ilk-Ta-1NW" secondAttribute="top" id="ibe-bf-QYG"/>
|
||||
<constraint firstItem="f5f-vb-JDD" firstAttribute="top" secondItem="pl5-54-raW" secondAttribute="top" constant="6" id="l8K-20-Ma8"/>
|
||||
<constraint firstItem="HDq-VH-JlL" firstAttribute="leading" secondItem="jd2-u3-VLB" secondAttribute="trailing" constant="35" id="m1y-bt-kbV"/>
|
||||
<constraint firstItem="jd2-u3-VLB" firstAttribute="centerY" secondItem="Ilk-Ta-1NW" secondAttribute="centerY" id="pLJ-Vu-Qou"/>
|
||||
<constraint firstItem="f5f-vb-JDD" firstAttribute="leading" secondItem="CjQ-1i-oKD" secondAttribute="leading" id="v7b-0r-gnh"/>
|
||||
<constraint firstItem="HDq-VH-JlL" firstAttribute="leading" secondItem="32r-Nm-cON" secondAttribute="leading" id="vLw-ST-pEh"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Ilk-Ta-1NW" secondAttribute="trailing" constant="6" id="ySS-D2-EAo"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
<customView fixedFrame="YES" id="Ejg-Hv-w6Y">
|
||||
<rect key="frame" x="0.0" y="59" width="568" height="364"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<scrollView fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="18" horizontalPageScroll="10" verticalLineScroll="18" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bIm-n2-Z3h">
|
||||
<rect key="frame" x="0.0" y="0.0" width="568" height="366"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<clipView key="contentView" ambiguous="YES" id="qrn-6L-y8h">
|
||||
<rect key="frame" x="1" y="0.0" width="566" height="365"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<outlineView verticalHuggingPriority="750" ambiguous="YES" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="18" headerView="7Nk-Jw-tEm" viewBased="YES" floatsGroupRows="NO" indentationPerLevel="16" outlineTableColumn="X9J-xy-hX5" id="RNB-nt-8rR" customClass="PEOutlineView" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="566" height="340"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
<tableColumn identifier="keyColumn" width="250" minWidth="130" maxWidth="10000" id="X9J-xy-hX5">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Key">
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="LVi-ta-OMQ">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="keyColumn" id="22d-da-f6t" customClass="PETableCellView" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="250" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="0hm-Vs-Zmm">
|
||||
<rect key="frame" x="2" y="0.0" width="247" height="18"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="5TZ-Az-hyl" customClass="PETextField" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="-2" y="0.0" width="251" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="18" id="7tz-s6-Lrv"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" title="Table View Cell" id="TeM-gI-jED" customClass="PETextFieldCell" customModule="Clover" customModuleProvider="target">
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="5TZ-Az-hyl" secondAttribute="trailing" id="RTM-O5-X7J"/>
|
||||
<constraint firstAttribute="bottom" secondItem="5TZ-Az-hyl" secondAttribute="bottom" id="Sw6-XX-cCD"/>
|
||||
<constraint firstItem="5TZ-Az-hyl" firstAttribute="top" secondItem="0hm-Vs-Zmm" secondAttribute="top" id="gqb-2d-jCL"/>
|
||||
<constraint firstItem="5TZ-Az-hyl" firstAttribute="leading" secondItem="0hm-Vs-Zmm" secondAttribute="leading" id="wsD-dw-QMl"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
<customView translatesAutoresizingMaskIntoConstraints="NO" id="mZc-eX-BQC">
|
||||
<rect key="frame" x="250" y="0.0" width="0.0" height="18"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" id="axH-cK-qTb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="18" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="bevel" bezelStyle="rounded" image="NSAddTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="1Rt-Pk-fry">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" id="nTZ-Fc-JgW">
|
||||
<rect key="frame" x="28" y="0.0" width="18" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="bevel" bezelStyle="rounded" image="NSRemoveTemplate" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" inset="2" id="eNR-b9-Q89">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" id="M9v-d8-uoy"/>
|
||||
</constraints>
|
||||
</customView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="0hm-Vs-Zmm" firstAttribute="centerY" secondItem="22d-da-f6t" secondAttribute="centerY" id="9M8-a7-Mqy"/>
|
||||
<constraint firstItem="0hm-Vs-Zmm" firstAttribute="top" secondItem="mZc-eX-BQC" secondAttribute="top" id="K8n-fy-scd"/>
|
||||
<constraint firstAttribute="trailing" secondItem="mZc-eX-BQC" secondAttribute="trailing" id="goB-Oz-t67"/>
|
||||
<constraint firstItem="0hm-Vs-Zmm" firstAttribute="bottom" secondItem="mZc-eX-BQC" secondAttribute="bottom" id="mPh-h6-8wP"/>
|
||||
<constraint firstItem="0hm-Vs-Zmm" firstAttribute="leading" secondItem="22d-da-f6t" secondAttribute="leading" constant="2" id="qMI-uM-dUi"/>
|
||||
<constraint firstItem="mZc-eX-BQC" firstAttribute="leading" secondItem="0hm-Vs-Zmm" secondAttribute="trailing" constant="1" id="y2K-l6-Sky"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="addButton" destination="axH-cK-qTb" id="msK-09-e63"/>
|
||||
<outlet property="buttonsView" destination="mZc-eX-BQC" id="Js0-jz-OfL"/>
|
||||
<outlet property="buttonsViewWidthConstraint" destination="M9v-d8-uoy" id="cmv-8N-K0l"/>
|
||||
<outlet property="removeButton" destination="nTZ-Fc-JgW" id="rve-XC-3MT"/>
|
||||
<outlet property="textField" destination="5TZ-Az-hyl" id="lnR-GR-tcS"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="typeColumn" width="88" minWidth="88" maxWidth="88" id="bGL-SR-Wh3">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" title="Tag">
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="XBx-pA-Iw5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="typeColumn" id="gHc-lQ-Qof" customClass="PETableCellViewPop" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="250" y="0.0" width="88" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton translatesAutoresizingMaskIntoConstraints="NO" id="k2B-pt-n9Q" customClass="PEPopUpButton" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="3" y="-1" width="82" height="20"/>
|
||||
<popUpButtonCell key="cell" type="bevel" bezelStyle="rounded" alignment="left" controlSize="small" lineBreakMode="truncatingTail" imageScaling="proportionallyDown" inset="2" id="4Az-is-Owj" customClass="PEPopUpButtonCell" customModule="Clover" customModuleProvider="target">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<menu key="menu" id="79b-Hx-UYz"/>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="k2B-pt-n9Q" firstAttribute="centerY" secondItem="gHc-lQ-Qof" secondAttribute="centerY" id="NQ1-Of-GiK"/>
|
||||
<constraint firstAttribute="trailing" secondItem="k2B-pt-n9Q" secondAttribute="trailing" constant="3" id="W6y-3k-U9t"/>
|
||||
<constraint firstItem="k2B-pt-n9Q" firstAttribute="top" secondItem="gHc-lQ-Qof" secondAttribute="top" constant="-1" id="YiU-2w-Tl6"/>
|
||||
<constraint firstItem="k2B-pt-n9Q" firstAttribute="leading" secondItem="gHc-lQ-Qof" secondAttribute="leading" constant="3" id="ift-jN-Nxm"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="popup" destination="k2B-pt-n9Q" id="mwx-yY-5NI"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
<tableColumn identifier="valueColumn" width="228" minWidth="30" maxWidth="3.4028234663852886e+38" id="0dr-UX-s5d">
|
||||
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left" title="Value">
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
</tableHeaderCell>
|
||||
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="Chl-KY-FFQ">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView id="evN-Wc-0mr" customClass="PETableCellView" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="338" y="0.0" width="228" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="dJN-3b-nDD" customClass="PETextField" customModule="Clover" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1" width="228" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="17" id="2fe-wh-Byc"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" title="Table View Cell" id="iif-L9-91I" customClass="PETextFieldCell" customModule="Clover" customModuleProvider="target">
|
||||
<font key="font" metaFont="message" size="11"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="dJN-3b-nDD" firstAttribute="centerX" secondItem="evN-Wc-0mr" secondAttribute="centerX" id="Oxi-u7-QXH"/>
|
||||
<constraint firstItem="dJN-3b-nDD" firstAttribute="leading" secondItem="evN-Wc-0mr" secondAttribute="leading" constant="2" id="kub-J2-9Xh"/>
|
||||
<constraint firstItem="dJN-3b-nDD" firstAttribute="centerY" secondItem="evN-Wc-0mr" secondAttribute="centerY" id="wqd-wO-Q9p"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<outlet property="textField" destination="dJN-3b-nDD" id="DHW-Y8-dLJ"/>
|
||||
</connections>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
</tableColumn>
|
||||
</tableColumns>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="5gI-5U-AMq" id="ZUW-ih-SLm"/>
|
||||
<outlet property="delegate" destination="5gI-5U-AMq" id="jbP-Bu-M4O"/>
|
||||
</connections>
|
||||
</outlineView>
|
||||
</subviews>
|
||||
</clipView>
|
||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="jjX-ox-rJd">
|
||||
<rect key="frame" x="1" y="92" width="362" height="15"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="RDZ-64-PQC">
|
||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</scroller>
|
||||
<tableHeaderView key="headerView" id="7Nk-Jw-tEm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="566" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</tableHeaderView>
|
||||
</scrollView>
|
||||
</subviews>
|
||||
</customView>
|
||||
</subviews>
|
||||
<holdingPriorities>
|
||||
<real value="58"/>
|
||||
<real value="250"/>
|
||||
</holdingPriorities>
|
||||
</splitView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="AXh-Wi-ja8" secondAttribute="bottom" id="2dM-ER-ind"/>
|
||||
<constraint firstAttribute="trailing" secondItem="AXh-Wi-ja8" secondAttribute="trailing" id="Cjs-Nq-cEH"/>
|
||||
<constraint firstItem="AXh-Wi-ja8" firstAttribute="leading" secondItem="rwV-zf-hpT" secondAttribute="leading" id="P0p-Uf-G8a"/>
|
||||
<constraint firstItem="AXh-Wi-ja8" firstAttribute="top" secondItem="rwV-zf-hpT" secondAttribute="top" id="nH3-EE-bKv"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="doneBtn" destination="Ilk-Ta-1NW" id="MPX-vf-UZi"/>
|
||||
<outlet property="editorView" destination="Ejg-Hv-w6Y" id="VPU-p2-yXS"/>
|
||||
<outlet property="findAndReplaceViewHeightConstraint" destination="cSS-9y-hU6" id="XwZ-ek-SRP"/>
|
||||
<outlet property="findView" destination="pl5-54-raW" id="3Bq-qj-6Nt"/>
|
||||
<outlet property="nextOrPreviousSegment" destination="HDq-VH-JlL" id="mne-oN-Gv7"/>
|
||||
<outlet property="outline" destination="RNB-nt-8rR" id="d7w-L8-eki"/>
|
||||
<outlet property="replaceField" destination="bgd-lF-t3r" id="Jcl-1E-d3y"/>
|
||||
<outlet property="replaceOneOrAllSegment" destination="32r-Nm-cON" id="VbA-Xa-A5S"/>
|
||||
<outlet property="scrollView" destination="bIm-n2-Z3h" id="E8b-BH-UyK"/>
|
||||
<outlet property="searchField" destination="7Wv-ZY-JzG" id="37Q-2C-M6c"/>
|
||||
<outlet property="showFindViewBtn" destination="CjQ-1i-oKD" id="44h-CB-tqw"/>
|
||||
<outlet property="showReplaceBtn" destination="f5f-vb-JDD" id="wUY-mZ-0zp"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<customObject id="2Tp-Fl-jBw" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="83" y="989"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="NSAddTemplate" width="11" height="11"/>
|
||||
<image name="NSLeftFacingTriangleTemplate" width="9" height="12"/>
|
||||
<image name="NSRemoveTemplate" width="11" height="11"/>
|
||||
<image name="NSRightFacingTriangleTemplate" width="9" height="12"/>
|
||||
<image name="NSStopProgressTemplate" width="11" height="11"/>
|
||||
</resources>
|
||||
</document>
|
1281
CloverApp/Clover/Plist Editor/Views/PlistEditorVC.swift
Normal file
1281
CloverApp/Clover/Plist Editor/Views/PlistEditorVC.swift
Normal file
File diff suppressed because it is too large
Load Diff
89
CloverApp/Clover/Plist Editor/Views/PlistEditorWC.swift
Normal file
89
CloverApp/Clover/Plist Editor/Views/PlistEditorWC.swift
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
@available(OSX 10.11, *)
|
||||
class PlistEditorWC: NSWindowController, NSWindowDelegate {
|
||||
var subview: PlistEditorWC?
|
||||
var fixed: Bool = false
|
||||
|
||||
override func windowDidLoad() {
|
||||
super.windowDidLoad()
|
||||
|
||||
self.window?.titlebarAppearsTransparent = false
|
||||
self.window?.isMovableByWindowBackground = true
|
||||
|
||||
self.window?.styleMask = [.titled, .miniaturizable, .closable, .resizable]
|
||||
|
||||
self.window?.collectionBehavior = [.fullScreenAuxiliary, .fullScreenPrimary]
|
||||
|
||||
if #available(OSX 10.12, *) {
|
||||
self.window?.tabbingMode = NSWindow.TabbingMode.preferred
|
||||
} else {
|
||||
self.shouldCascadeWindows = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func windowWillClose(_ notification: Notification) { }
|
||||
|
||||
func windowDidBecomeMain(_ notification: Notification) { }
|
||||
|
||||
func windowDidBecomeKey(_ notification: Notification) { }
|
||||
|
||||
func windowDidResignKey(_ notification: Notification) { }
|
||||
|
||||
class func loadFromNib(parser: PlistParser) -> PlistEditorWC {
|
||||
let id = NSStoryboard.SceneIdentifier("Document Window Controller")
|
||||
let wc = NSStoryboard(name: NSStoryboard.Name("PlistEditor"),
|
||||
bundle: nil).instantiateController(withIdentifier: id) as! PlistEditorWC
|
||||
(wc.contentViewController as? PlistEditorVC)?.parser = parser
|
||||
return wc
|
||||
}
|
||||
|
||||
override func newWindowForTab(_ sender: Any?) {
|
||||
let dc = NSDocumentController.shared
|
||||
do {
|
||||
let doc = try dc.makeUntitledDocument(ofType: "XML PropertyList v1") as? Document
|
||||
|
||||
doc?.makeWindowControllers()
|
||||
dc.addDocument(doc!) // <--- very important! w/o this call the document is not connected to main menu
|
||||
if #available(OSX 10.12, *) {
|
||||
self.window!.addTabbedWindow((doc?.windowController?.window)!, ordered: .above)
|
||||
}
|
||||
doc?.windowController?.window?.makeKeyAndOrderFront(self)
|
||||
} catch let error as NSError {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = error.localizedFailureReason ?? error.localizedDescription
|
||||
alert.informativeText = error.localizedRecoverySuggestion ?? ""
|
||||
alert.runModal()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
39
CloverApp/Clover/Plist Editor/Views/ReplaceButton.swift
Normal file
39
CloverApp/Clover/Plist Editor/Views/ReplaceButton.swift
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
final class ReplaceButton: NSButton {
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
self.canDrawConcurrently = AppSD.canDrawConcurrently
|
||||
self.wantsLayer = true
|
||||
let image = NSImage(named: "Replace")
|
||||
image?.isTemplate = true
|
||||
self.image = image
|
||||
}
|
||||
}
|
@ -54,11 +54,20 @@ func getLatestReleases(reply: @escaping (String?, String?, String?, String?) ->
|
||||
//applink = "/CloverHackyColor/CloverBootloader/releases/download/5099/Clover.app_v1.17_r5104.pkg.zip\" rel=\"nofollow\" class=\"d-flex flex-items-center min-width-0\">"
|
||||
applink = "https://github.com\(applink!.components(separatedBy: "\"")[0])"
|
||||
|
||||
if applink!.lastPath.hasPrefix("Clover.app_v") && applink!.hasSuffix(".zip") {
|
||||
// Clover.app_v1.17_r5104.pkg.zip
|
||||
if applink!.lastPath.hasPrefix("Clover.app_v") && applink!.hasSuffix(".pkg") {
|
||||
// Clover.app_v1.17.pkg
|
||||
appvers = applink!.components(separatedBy: "Clover.app_v")[1]
|
||||
//print(appvers)
|
||||
appvers = appvers!.components(separatedBy: "_r")[0]
|
||||
appvers = appvers!.components(separatedBy: ".pkg")[0]
|
||||
if let pkgName = applink?.deletingFileExtension.lastPath {
|
||||
let donwPath = NSHomeDirectory().addPath("Desktop/Clover_app_download").addPath(pkgName)
|
||||
if fm.fileExists(atPath: donwPath) {
|
||||
applink = nil
|
||||
appvers = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -12,13 +12,13 @@ import ServiceManagement
|
||||
extension AppDelegate {
|
||||
func setLaunchAtStartup() {
|
||||
let success : Bool = SMLoginItemSetEnabled(gHelperID, true)
|
||||
UserDefaults.standard.set(success, forKey: kRunAtLogin)
|
||||
UserDefaults.standard.synchronize()
|
||||
UDs.set(success, forKey: kRunAtLogin)
|
||||
UDs.synchronize()
|
||||
}
|
||||
|
||||
func removeLaunchAtStartup() {
|
||||
let success : Bool = SMLoginItemSetEnabled(gHelperID, false)
|
||||
UserDefaults.standard.set(success ? false : true, forKey: kRunAtLogin)
|
||||
UserDefaults.standard.synchronize()
|
||||
UDs.set(success ? false : true, forKey: kRunAtLogin)
|
||||
UDs.synchronize()
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,8 @@ final class SoundSlider : NSSlider {
|
||||
var field : NSTextField?
|
||||
}
|
||||
|
||||
final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSessionDownloadDelegate {
|
||||
final class SettingsViewController:
|
||||
NSViewController, NSTextFieldDelegate, NSComboBoxDelegate, URLSessionDownloadDelegate {
|
||||
// MARK: Variables
|
||||
@IBOutlet var tabViewInfo : LITabView!
|
||||
// tab 0
|
||||
@ -71,6 +72,12 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
@IBOutlet var soundDevicePopUp : NSPopUpButton!
|
||||
@IBOutlet var soundVolumeSlider : SoundSlider!
|
||||
@IBOutlet var soundVolumeField : NSTextField!
|
||||
// tab 3
|
||||
@IBOutlet var autoSaveButton : NSButton!
|
||||
@IBOutlet var newPlistButton : NSButton!
|
||||
@IBOutlet var openDocumentButton : NSButton!
|
||||
@IBOutlet var disksPEPopUp : NSPopUpButton!
|
||||
@IBOutlet var plistsPopUp : NSPopUpButton!
|
||||
|
||||
@IBOutlet var disbaleSleepProxyButton : NSButton!
|
||||
@IBOutlet var makeRootRWButton : NSButton!
|
||||
@ -134,7 +141,7 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
// sync
|
||||
self.tabViewFuncSelector.selectSegment(withTag: 0)
|
||||
self.tabViewFunc.selectTabViewItem(at: 0)
|
||||
|
||||
|
||||
AppSD.themeUser = UDs.string(forKey: kThemeUserKey) ?? kDefaultThemeUser
|
||||
AppSD.themeRepo = UDs.string(forKey: kThemeRepoKey) ?? kDefaultThemeRepo
|
||||
|
||||
@ -198,9 +205,23 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
self.oemVendorField.stringValue = getOEMVendor() ?? kNotAvailable.locale
|
||||
self.oemProductField.stringValue = getOEMProduct() ?? kNotAvailable.locale
|
||||
self.oemBoardIdField.stringValue = getOEMBoard() ?? kNotAvailable.locale
|
||||
|
||||
// tab 3
|
||||
self.plistsPopUp.removeAllItems()
|
||||
if #available(OSX 10.11, *) {
|
||||
self.autoSaveButton.state = UDs.bool(forKey: kAutoSavePlistsKey) ? .on : .off
|
||||
} else {
|
||||
self.autoSaveButton.isEnabled = false
|
||||
self.autoSaveButton.isHidden = true
|
||||
self.newPlistButton.isEnabled = false
|
||||
self.newPlistButton.isHidden = true
|
||||
}
|
||||
|
||||
self.setUpInfo()
|
||||
self.setUpdateButton()
|
||||
|
||||
if !fm.fileExists(atPath: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) {
|
||||
self.searchUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
func setUpInfo() {
|
||||
@ -280,8 +301,21 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
let daemonExist = fm.fileExists(atPath: kDaemonPath) && fm.fileExists(atPath: kLaunchPlistPath)
|
||||
self.unInstallDaemonButton.isEnabled = daemonExist
|
||||
|
||||
self.searchDisks()
|
||||
self.searchESPDisks()
|
||||
|
||||
if self.disksPEPopUp.indexOfSelectedItem == 0 {
|
||||
for item in self.disksPEPopUp.itemArray {
|
||||
if let disk = item.representedObject as? String {
|
||||
if self.bootDevice != nil && disk == self.bootDevice {
|
||||
self.disksPEPopUp.select(item)
|
||||
self.volumePopUpPressed(self.disksPEPopUp)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let itervals = ["never", "daily", "weekly", "monthly"]
|
||||
self.timeIntervalPopUp.removeAllItems()
|
||||
for i in itervals {
|
||||
@ -324,8 +358,13 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
|
||||
func setUpdateInformations() {
|
||||
let rev = findCloverRevision(at: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) ?? "0000"
|
||||
var title = "\("Install Clover".locale) \(rev)"
|
||||
var cloverAvailable = false
|
||||
var title = "Download".locale
|
||||
if fm.fileExists(atPath: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) {
|
||||
cloverAvailable = true
|
||||
let rev = findCloverRevision(at: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) ?? "0000"
|
||||
title = "\("Install Clover".locale) \(rev)"
|
||||
}
|
||||
self.installCloverButton.title = title
|
||||
|
||||
self.bootDevice = findBootPartitionDevice()
|
||||
@ -348,7 +387,7 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
let last : String = (self.lastReleaseRev == nil) ? kNotAvailable.locale : "r\(self.lastReleaseRev!)"
|
||||
title = "\("Update Clover available".locale): \(last)"
|
||||
|
||||
self.installCloverButton.isEnabled = true
|
||||
self.installCloverButton.isEnabled = cloverAvailable
|
||||
}
|
||||
|
||||
override var representedObject: Any? {
|
||||
@ -369,6 +408,50 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
|
||||
// MARK: Disks
|
||||
func searchDisks() {
|
||||
let selected = self.disksPEPopUp.selectedItem?.representedObject as? String
|
||||
self.disksPEPopUp.removeAllItems()
|
||||
self.disksPEPopUp.addItem(withTitle: "Select a disk..".locale)
|
||||
|
||||
for v in getVolumes() {
|
||||
if !isWritable(diskOrMtp: v) {
|
||||
continue
|
||||
}
|
||||
if !fm.fileExists(atPath: v.addPath("EFI/CLOVER")) {
|
||||
continue
|
||||
}
|
||||
if let disk = getBSDName(of: v) {
|
||||
if kBannedMedia.contains(getVolumeName(from: disk) ?? "") {
|
||||
continue
|
||||
}
|
||||
let parentDiskName : String = getMediaName(from: getBSDParent(of: disk) ?? "") ?? kNotAvailable.locale
|
||||
let fs = getFS(from: v) ?? kNotAvailable.locale
|
||||
let title : String = "\(disk), \(fs), \("mount point".locale): \(v), \(parentDiskName)"
|
||||
self.disksPEPopUp.addItem(withTitle: title)
|
||||
self.disksPEPopUp.lastItem?.representedObject = disk
|
||||
if disk == self.bootDevice {
|
||||
let image : NSImage = NSImage(named: "NSApplicationIcon")!.copy() as! NSImage
|
||||
image.size = NSMakeSize(16, 16)
|
||||
self.disksPEPopUp.lastItem?.image = image
|
||||
} else if let image : NSImage = getIconFor(volume: disk) {
|
||||
image.size = NSMakeSize(16, 16)
|
||||
self.disksPEPopUp.lastItem?.image = image
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selected != nil) {
|
||||
for item in self.disksPEPopUp.itemArray {
|
||||
if let d = item.representedObject as? String {
|
||||
if d == selected {
|
||||
self.disksPEPopUp.select(item)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func searchESPDisks() {
|
||||
self.unmountButton.isEnabled = false
|
||||
let selected = self.disksPopUp.selectedItem?.representedObject as? String
|
||||
@ -408,6 +491,7 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Mount ESPs
|
||||
@IBAction func mountESP(_ sender: NSPopUpButton!) {
|
||||
self.unmountButton.isEnabled = false
|
||||
@ -522,6 +606,7 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
NSSound.beep()
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.searchDisks()
|
||||
self.searchESPDisks()
|
||||
}
|
||||
}
|
||||
@ -551,7 +636,17 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
}
|
||||
|
||||
func comboBoxSelectionDidChange(_ notification: Notification) {
|
||||
if (notification.object as? NSComboBox) == self.themeBox {
|
||||
self.themeBoxSelected(self.themeBox)
|
||||
}
|
||||
}
|
||||
|
||||
func controlTextDidEndEditing(_ obj: Notification) {
|
||||
if (obj.object as? NSComboBox) == self.themeBox {
|
||||
self.themeBoxSelected(self.themeBox)
|
||||
return
|
||||
}
|
||||
var updateThemeRepo = false
|
||||
if let field = obj.object as? NSTextField {
|
||||
|
||||
@ -600,7 +695,7 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
if NSDictionary(dictionary: conf).write(toFile: fullPath,
|
||||
atomically: false) {
|
||||
NSWorkspace.shared.openFile(dir)
|
||||
loadPlist(at: fullPath)
|
||||
} else {
|
||||
NSSound.beep()
|
||||
}
|
||||
@ -610,6 +705,85 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func createNewPlist(_ sender: NSButton!) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
NSDocumentController.shared.newDocument(self)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func autosaveDocuments(_ sender: NSButton!) {
|
||||
UDs.set(sender.state == .on, forKey: kAutoSavePlistsKey)
|
||||
UDs.synchronize()
|
||||
}
|
||||
|
||||
@IBAction func searchPanelForPlist(_ sender: NSButton!) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
let op = NSOpenPanel()
|
||||
op.allowsMultipleSelection = false
|
||||
op.canChooseDirectories = false
|
||||
op.canCreateDirectories = false
|
||||
op.canChooseFiles = true
|
||||
op.allowedFileTypes = ["plist"]
|
||||
|
||||
// make the app regular
|
||||
NSApp.setActivationPolicy(.regular)
|
||||
|
||||
op.begin { (result) in
|
||||
if result == .OK {
|
||||
if let path = op.url?.path {
|
||||
loadPlist(at: path) // this will make the app regular again in 10.11+
|
||||
}
|
||||
} else {
|
||||
// check if a document is opened some where
|
||||
if NSDocumentController.shared.documents.count == 0 {
|
||||
NSApp.setActivationPolicy(.accessory)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func volumePopUpPressed(_ sender: NSPopUpButton!) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
self.plistsPopUp.removeAllItems()
|
||||
self.plistsPopUp.addItem(withTitle: "...")
|
||||
if let disk = sender.selectedItem?.representedObject as? String {
|
||||
if let mp = getMountPoint(from: disk) {
|
||||
let clover = mp.addPath("EFI/CLOVER")
|
||||
if fm.fileExists(atPath: clover) {
|
||||
if fm.fileExists(atPath: clover.addPath("config.plist")) {
|
||||
self.plistsPopUp.addItem(withTitle: "config.plist")
|
||||
self.plistsPopUp.lastItem?.representedObject = clover.addPath("config.plist")
|
||||
}
|
||||
let enumerator = fm.enumerator(atPath: clover)
|
||||
while let file = enumerator?.nextObject() as? String {
|
||||
if file.fileExtension == "plist" &&
|
||||
(file.range(of: "kexts/") == nil) &&
|
||||
!file.hasPrefix("themes/") &&
|
||||
file != "pref.plist" &&
|
||||
file != "config.plist"{
|
||||
self.plistsPopUp.addItem(withTitle: file)
|
||||
self.plistsPopUp.lastItem?.representedObject = clover.addPath(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func openCloverPlist(_ sender: NSPopUpButton!) {
|
||||
if let path = sender.selectedItem?.representedObject as? String {
|
||||
if !fm.fileExists(atPath: path) {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
loadPlist(at: path)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction func installClover(_ sender: NSButton!) {
|
||||
|
||||
let myPath = Bundle.main.bundlePath.lowercased()
|
||||
@ -658,7 +832,7 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
|
||||
AppSD.installerOutWC?.showWindow(self)
|
||||
}
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
//NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
}
|
||||
|
||||
@ -758,20 +932,26 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
|
||||
// MARK: NVRAM editing
|
||||
@IBAction func themeBoxSelected(_ sender: NSComboBox!) {
|
||||
func themeBoxSelected(_ sender: NSComboBox!) {
|
||||
//sender.window?.makeFirstResponder(nil)
|
||||
let key = "Clover.Theme"
|
||||
if let old = sender.cell?.representedObject as? String {
|
||||
let theme = sender.stringValue
|
||||
if old != theme {
|
||||
if theme.count == 0 {
|
||||
deleteNVRAM(key: key)
|
||||
} else {
|
||||
setNVRAM(key: key, stringValue: theme)
|
||||
}
|
||||
}
|
||||
let theme = sender.stringValue
|
||||
var nvramValue : String = ""
|
||||
if let data = getNVRAM()?.object(forKey: key) as? Data {
|
||||
nvramValue = String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) {
|
||||
if nvramValue != theme {
|
||||
if theme.count == 0 {
|
||||
deleteNVRAM(key: key)
|
||||
} else {
|
||||
setNVRAM(key: key, stringValue: theme)
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
|
||||
var value = ""
|
||||
if let nvram = getNVRAM() {
|
||||
let nvdata = nvram.object(forKey: key) as? Data
|
||||
@ -971,11 +1151,21 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
let installerRev = findCloverRevision(at: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI"))
|
||||
let installerRevNum = Int(installerRev ?? "0") ?? 0
|
||||
|
||||
if (self.lastReleaseLink != nil && self.lastReleaseRev != nil) {
|
||||
if !fm.fileExists(atPath: Bundle.main.sharedSupportPath!.addPath("CloverV2/EFI")) {
|
||||
DispatchQueue.main.async {
|
||||
self.updateClover(self.updateCloverButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (self.lastReleaseLink != nil && self.lastReleaseRev != nil)
|
||||
&& lastRevNum > 0
|
||||
&& (lastRevNum > currRevNum) {
|
||||
UDs.set(self.lastReleaseLink!, forKey: kLastUpdateLink)
|
||||
UDs.set(self.lastReleaseRev!, forKey: kLastUpdateRevision)
|
||||
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if #available(OSX 10.10, *) {
|
||||
AppSD.statusItem.button?.title = "\(lastRevNum)"
|
||||
@ -1049,7 +1239,7 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
alert.addButton(withTitle: "Download".locale)
|
||||
alert.addButton(withTitle: "Close".locale)
|
||||
if alert.runModal() == .alertFirstButtonReturn {
|
||||
NSWorkspace.shared.open(url)
|
||||
self.updateCloverApp(at: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1075,8 +1265,6 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
let b = URLSessionConfiguration.default
|
||||
let session = Foundation.URLSession(configuration: b, delegate: self, delegateQueue: nil)
|
||||
|
||||
//let session = URLSession(configuration: .default)
|
||||
|
||||
if (url != nil) {
|
||||
self.installCloverButton.isEnabled = false
|
||||
self.downloadTask = session.downloadTask(with: url!)
|
||||
@ -1084,6 +1272,25 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
}
|
||||
|
||||
func updateCloverApp(at url: URL) {
|
||||
if AppSD.isInstalling ||
|
||||
AppSD.isInstallerOpen ||
|
||||
self.lastReleaseLink == nil ||
|
||||
self.lastReleaseRev == nil {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
self.progressBar.isHidden = false
|
||||
self.progressBar.doubleValue = 0.0
|
||||
|
||||
let b = URLSessionConfiguration.default
|
||||
let session = Foundation.URLSession(configuration: b, delegate: self, delegateQueue: nil)
|
||||
|
||||
self.installCloverButton.isEnabled = false
|
||||
self.downloadTask = session.downloadTask(with: url)
|
||||
self.downloadTask?.resume()
|
||||
}
|
||||
|
||||
func urlSession(_ session: URLSession,
|
||||
downloadTask: URLSessionDownloadTask,
|
||||
didFinishDownloadingTo location: URL) {
|
||||
@ -1098,8 +1305,9 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
let lastPath = downloadTask.originalRequest!.url!.lastPathComponent
|
||||
let data = try Data(contentsOf: location)
|
||||
|
||||
if lastPath.fileExtension == "zip" && lastPath.hasPrefix("CloverV2") {
|
||||
// ok, We have the download completed: replace CloverV2 inside SharedSupport directory!
|
||||
if lastPath.fileExtension == "zip" &&
|
||||
(lastPath.hasPrefix("CloverV2") || lastPath.hasPrefix("Clover.app")) {
|
||||
let isApp = lastPath.hasPrefix("Clover.app")
|
||||
|
||||
// Decompress the zip archive
|
||||
// NSUserName() ensure the user have read write permissions
|
||||
@ -1115,27 +1323,52 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
let file = tempDir.addPath(lastPath)
|
||||
try data.write(to: URL(fileURLWithPath: file))
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let task : Process = Process()
|
||||
task.environment = ProcessInfo().environment
|
||||
let bash = "/bin/bash"
|
||||
// unzip -d output_dir/ zipfiles.zip
|
||||
let cmd = "/usr/bin/unzip -qq -d \(tempDir) \(file)"
|
||||
if #available(OSX 10.13, *) {
|
||||
task.executableURL = URL(fileURLWithPath: bash)
|
||||
} else {
|
||||
task.launchPath = bash
|
||||
}
|
||||
|
||||
task.arguments = ["-c", cmd]
|
||||
task.terminationHandler = { t in
|
||||
if t.terminationStatus == 0 {
|
||||
self.replaceCloverV2(with: tempDir.addPath("CloverV2"))
|
||||
if isApp {
|
||||
DispatchQueue.main.async {
|
||||
let task : Process = Process()
|
||||
task.environment = ProcessInfo().environment
|
||||
let bash = "/bin/bash"
|
||||
// unzip -d output_dir/ zipfiles.zip
|
||||
let cmd = "/usr/bin/unzip -qq -d \(tempDir) \(file)"
|
||||
if #available(OSX 10.13, *) {
|
||||
task.executableURL = URL(fileURLWithPath: bash)
|
||||
} else {
|
||||
task.launchPath = bash
|
||||
}
|
||||
|
||||
task.arguments = ["-c", cmd]
|
||||
task.terminationHandler = { t in
|
||||
if t.terminationStatus == 0 {
|
||||
self.moveCloverApp(at: file.deletingFileExtension)
|
||||
}
|
||||
}
|
||||
|
||||
task.launch()
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
let task : Process = Process()
|
||||
task.environment = ProcessInfo().environment
|
||||
let bash = "/bin/bash"
|
||||
// unzip -d output_dir/ zipfiles.zip
|
||||
let cmd = "/usr/bin/unzip -qq -d \(tempDir) \(file)"
|
||||
if #available(OSX 10.13, *) {
|
||||
task.executableURL = URL(fileURLWithPath: bash)
|
||||
} else {
|
||||
task.launchPath = bash
|
||||
}
|
||||
|
||||
task.arguments = ["-c", cmd]
|
||||
task.terminationHandler = { t in
|
||||
if t.terminationStatus == 0 {
|
||||
self.replaceCloverV2(with: tempDir.addPath("CloverV2"))
|
||||
}
|
||||
}
|
||||
|
||||
task.launch()
|
||||
}
|
||||
|
||||
task.launch()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch {
|
||||
@ -1180,8 +1413,28 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
var isDir : ObjCBool = false
|
||||
if fm.fileExists(atPath: newOne, isDirectory: &isDir) {
|
||||
if isDir.boolValue {
|
||||
// clean some unused stuff
|
||||
if fm.fileExists(atPath: newOne.addPath("rcScripts")) {
|
||||
try? fm.removeItem(atPath: newOne.addPath("rcScripts"))
|
||||
}
|
||||
if fm.fileExists(atPath: newOne.addPath("ThirdParty")) {
|
||||
try? fm.removeItem(atPath: newOne.addPath("ThirdParty"))
|
||||
}
|
||||
// let only one theme (Clovy) as we have a themes manager
|
||||
if fm.fileExists(atPath: newOne.addPath("themespkg/Clovy")) {
|
||||
if let themes = try? fm.contentsOfDirectory(atPath: newOne.addPath("themespkg")) {
|
||||
for file in themes {
|
||||
if file != "Clovy" {
|
||||
try? fm.removeItem(atPath: newOne.addPath("themespkg").addPath(file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try fm.removeItem(atPath: Cloverv2Path)
|
||||
if fm.fileExists(atPath: Cloverv2Path) {
|
||||
try fm.removeItem(atPath: Cloverv2Path)
|
||||
}
|
||||
try fm.copyItem(atPath: newOne, toPath: Cloverv2Path)
|
||||
DispatchQueue.main.async {
|
||||
self.lastReleaseRev = nil
|
||||
@ -1191,11 +1444,60 @@ final class SettingsViewController: NSViewController, NSTextFieldDelegate, URLSe
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func moveCloverApp(at path: String) {
|
||||
// move to ~/Desktop/Clover_app_new
|
||||
let new = NSHomeDirectory().addPath("Desktop/Clover_app_download")
|
||||
if fm.fileExists(atPath: new) {
|
||||
try? fm.removeItem(atPath: new)
|
||||
}
|
||||
|
||||
do {
|
||||
if fm.fileExists(atPath: new) {
|
||||
try fm.removeItem(atPath: new)
|
||||
}
|
||||
try fm.createDirectory(atPath: new, withIntermediateDirectories: false, attributes: nil)
|
||||
try fm.copyItem(atPath: path, toPath: new.addPath(path.lastPath))
|
||||
DispatchQueue.main.async {
|
||||
self.lastReleaseRev = nil
|
||||
self.lastReleaseLink = nil
|
||||
self.setUpdateInformations()
|
||||
self.setUpdateButton()
|
||||
}
|
||||
self.removeAttributes(at: new.addPath(path.lastPath))
|
||||
NSWorkspace.shared.openFile(new)
|
||||
} catch {
|
||||
print(error)
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
|
||||
private func removeAttributes(at path: String) {
|
||||
let task : Process = Process()
|
||||
task.environment = ProcessInfo().environment
|
||||
let bash = "/bin/bash"
|
||||
let cmd = "/usr/bin/xattr -rc \(path)"
|
||||
if #available(OSX 10.13, *) {
|
||||
task.executableURL = URL(fileURLWithPath: bash)
|
||||
} else {
|
||||
task.launchPath = bash
|
||||
}
|
||||
|
||||
task.arguments = ["-c", cmd]
|
||||
task.terminationHandler = { t in
|
||||
if t.terminationStatus != 0 {
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
|
||||
task.launch()
|
||||
}
|
||||
|
||||
// MARK: Close
|
||||
@IBAction func closeApp(_ sender: NSButton?) {
|
||||
NSApp.terminate(nil)
|
||||
|
@ -92,7 +92,7 @@ func findCloverHashCommit(at EFIdir: String) -> String? {
|
||||
|
||||
if (rev != nil), let revision = String(cString: (rev?.utf8String)!,
|
||||
encoding: String.Encoding.utf8) {
|
||||
if revision.count == 8 {
|
||||
if revision.count >= 4 && revision.count <= 40 {
|
||||
return revision
|
||||
}
|
||||
}
|
||||
@ -114,3 +114,4 @@ func getCoreTypeImage(named: String, isTemplate: Bool) -> NSImage? {
|
||||
image?.isTemplate = isTemplate
|
||||
return image
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// PNG8Image.h
|
||||
// ThemeImage.h
|
||||
// Clover
|
||||
//
|
||||
// Created by vector sigma on 07/03/2020.
|
||||
@ -12,10 +12,9 @@
|
||||
|
||||
//NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface PNG8Image : NSImage
|
||||
- (nullable NSData *)png8ImageDataAtPath:(NSString *_Nonnull)imagePath
|
||||
error:(NSError *_Nullable*_Nullable)errorPtr;
|
||||
|
||||
@interface ThemeImage : NSImage
|
||||
@property (nonatomic, strong) NSData * _Nonnull pngData;
|
||||
- (id _Nullable)initWithThemeImageAtPath:(nonnull NSString *)path error:(NSError *_Nullable*_Nullable)errorPtr;
|
||||
@end
|
||||
|
||||
//NS_ASSUME_NONNULL_END
|
163
CloverApp/Clover/ThemeImage.m
Normal file
163
CloverApp/Clover/ThemeImage.m
Normal file
@ -0,0 +1,163 @@
|
||||
//
|
||||
// PNG8Image.m
|
||||
// Clover
|
||||
//
|
||||
// Created by vector sigma on 07/03/2020.
|
||||
// Copyright © 2020 CloverHackyColor. All rights reserved.
|
||||
//
|
||||
|
||||
#import "ThemeImage.h"
|
||||
|
||||
@implementation ThemeImage
|
||||
|
||||
- (id _Nullable)initWithThemeImageAtPath:(nonnull NSString *)path
|
||||
error:(NSError *_Nullable*_Nullable)errorPtr {
|
||||
if (!(self = [super init])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *mainData = [NSData dataWithContentsOfFile: path];
|
||||
NSString *domain = @"org.slice.Clover.PNG8Image.Error";
|
||||
if (!mainData || [mainData length] < 4) {
|
||||
NSString *desc = [NSString stringWithFormat:@"Size of %@ is too small to be an image\n", path];
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:1
|
||||
userInfo:userInfo];
|
||||
return nil;
|
||||
}
|
||||
|
||||
UInt8 * bytes = (UInt8 *)[mainData bytes];
|
||||
if (bytes[0] != 0x89 || bytes[0] != 0x50 || bytes[0] != 0x4E || bytes[0] != 0x47) {
|
||||
NSBitmapImageRep *bir = [[NSBitmapImageRep alloc] initWithData:mainData];
|
||||
if (bir) {
|
||||
mainData = [bir representationUsingType:NSPNGFileType properties:[NSDictionary new]];
|
||||
if (mainData == nil) {
|
||||
NSString *desc = [NSString stringWithFormat:@"Can't convert %@ to png\n", path];
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:2
|
||||
userInfo:userInfo];
|
||||
return nil;
|
||||
} else {
|
||||
[self addRepresentation: [[NSBitmapImageRep alloc] initWithData:mainData]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int width, height;
|
||||
unsigned char *raw_rgba_pixels;
|
||||
|
||||
unsigned int status = lodepng_decode32(&raw_rgba_pixels,
|
||||
&width,
|
||||
&height,
|
||||
[mainData bytes],
|
||||
[mainData length]);
|
||||
|
||||
if (status) {
|
||||
NSString *desc = [NSString stringWithFormat:@"%@, %s\n",
|
||||
path,
|
||||
lodepng_error_text(status)];
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:3
|
||||
userInfo:userInfo];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Use libimagequant to make a palette for the RGBA pixels
|
||||
liq_attr *handle = liq_attr_create();
|
||||
liq_image *input_image = liq_image_create_rgba(handle,
|
||||
raw_rgba_pixels,
|
||||
width,
|
||||
height,
|
||||
0);
|
||||
// You could set more options here, like liq_set_quality
|
||||
liq_result *quantization_result;
|
||||
if (liq_image_quantize(input_image, handle, &quantization_result) != LIQ_OK) {
|
||||
NSString *desc = [NSString stringWithFormat:@"Quantization failed for %@", path];
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:4
|
||||
userInfo:userInfo];
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Use libimagequant to make new image pixels from the palette
|
||||
size_t pixels_size = width * height;
|
||||
unsigned char *raw_8bit_pixels = malloc(pixels_size);
|
||||
liq_set_dithering_level(quantization_result, 1.0);
|
||||
|
||||
liq_write_remapped_image(quantization_result,
|
||||
input_image,
|
||||
raw_8bit_pixels,
|
||||
pixels_size);
|
||||
const liq_palette *palette = liq_get_palette(quantization_result);
|
||||
|
||||
// Save converted pixels as a PNG file
|
||||
// This uses lodepng library for PNG writing (not part of libimagequant)
|
||||
LodePNGState state;
|
||||
lodepng_state_init(&state);
|
||||
state.info_raw.colortype = LCT_PALETTE;
|
||||
state.info_raw.bitdepth = 8;
|
||||
state.info_png.color.colortype = LCT_PALETTE;
|
||||
state.info_png.color.bitdepth = 8;
|
||||
|
||||
for(int i = 0; i < palette->count; i++) {
|
||||
lodepng_palette_add(&state.info_png.color,
|
||||
palette->entries[i].r,
|
||||
palette->entries[i].g,
|
||||
palette->entries[i].b,
|
||||
palette->entries[i].a);
|
||||
|
||||
lodepng_palette_add(&state.info_raw,
|
||||
palette->entries[i].r,
|
||||
palette->entries[i].g,
|
||||
palette->entries[i].b,
|
||||
palette->entries[i].a);
|
||||
}
|
||||
|
||||
unsigned char *output_file_data;
|
||||
size_t output_file_size;
|
||||
unsigned int out_status = lodepng_encode(&output_file_data,
|
||||
&output_file_size,
|
||||
raw_8bit_pixels,
|
||||
width,
|
||||
height,
|
||||
&state);
|
||||
if (out_status) {
|
||||
NSString *desc = [NSString stringWithFormat:@"Can't encode %@: %s\n",
|
||||
path,
|
||||
lodepng_error_text(out_status)];
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:5
|
||||
userInfo:userInfo];
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Prove the conversion
|
||||
self.pngData = [NSData dataWithBytes: output_file_data length: output_file_size];
|
||||
NSImage *convertedImage = [[NSImage alloc] initWithData:self.pngData];
|
||||
if (convertedImage == nil) {
|
||||
NSString *desc = [NSString stringWithFormat:@"Can't convert data to NSImage (%@)", path];
|
||||
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : desc };
|
||||
*errorPtr = [NSError errorWithDomain:domain
|
||||
code:6
|
||||
userInfo:userInfo];
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
liq_result_destroy(quantization_result); // Must be freed only after you're done using the palette
|
||||
liq_image_destroy(input_image);
|
||||
liq_attr_destroy(handle);
|
||||
|
||||
free(raw_8bit_pixels);
|
||||
lodepng_state_cleanup(&state);
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
82
CloverApp/Clover/ThemeManager/HTTPErrors.swift
Normal file
82
CloverApp/Clover/ThemeManager/HTTPErrors.swift
Normal file
@ -0,0 +1,82 @@
|
||||
//
|
||||
// HTTPErrors.swift
|
||||
// Clover
|
||||
//
|
||||
// Created by vector sigma on 27/03/2020.
|
||||
// Copyright © 2020 CloverHackyColor. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func gHTTPInfo(for statusCode: Int) -> String {
|
||||
var info = "Unknown"
|
||||
switch statusCode {
|
||||
case 100: info = "Continue"
|
||||
case 101: info = "Switching Protocols"
|
||||
case 102: info = "Processing"
|
||||
case 200: info = "OK"
|
||||
case 201: info = "Created"
|
||||
case 202: info = "Accepted"
|
||||
case 203: info = "Non-authoritative Information"
|
||||
case 204: info = "No Content"
|
||||
case 205: info = "Reset Content"
|
||||
case 206: info = "Partial Content"
|
||||
case 207: info = "Multi-Status"
|
||||
case 208: info = "Already Reported"
|
||||
case 226: info = "IM Used"
|
||||
case 300: info = "Multiple Choices"
|
||||
case 301: info = "Moved Permanently"
|
||||
case 302: info = "Found"
|
||||
case 303: info = "See Other"
|
||||
case 304: info = "Not Modified"
|
||||
case 305: info = "Use Proxy"
|
||||
case 307: info = "Temporary Redirect"
|
||||
case 308: info = "Permanent Redirect"
|
||||
case 400: info = "Bad Request"
|
||||
case 401: info = "Unauthorized"
|
||||
case 402: info = "Payment Required"
|
||||
case 403: info = "Forbidden"
|
||||
case 404: info = "Not Found"
|
||||
case 405: info = "Method Not Allowed"
|
||||
case 406: info = "Not Acceptable"
|
||||
case 407: info = "Proxy Authentication Required"
|
||||
case 408: info = "Request Timeout"
|
||||
case 409: info = "Conflict"
|
||||
case 410: info = "Gone"
|
||||
case 411: info = "Length Required"
|
||||
case 412: info = "Precondition Failed"
|
||||
case 413: info = "Payload Too Large"
|
||||
case 414: info = "Request-URI Too Long"
|
||||
case 415: info = "Unsupported Media Type"
|
||||
case 416: info = "Requested Range Not Satisfiable"
|
||||
case 417: info = "Expectation Failed"
|
||||
case 418: info = "I'm a teapot"
|
||||
case 421: info = "Misdirected Request"
|
||||
case 422: info = "Unprocessable Entity"
|
||||
case 423: info = "Locked"
|
||||
case 424: info = "Failed Dependency"
|
||||
case 426: info = "Upgrade Required"
|
||||
case 428: info = "Precondition Required"
|
||||
case 429: info = "Too Many Requests"
|
||||
case 431: info = "Request Header Fields Too Large"
|
||||
case 444: info = "Connection Closed Without Response"
|
||||
case 451: info = "Unavailable For Legal Reasons"
|
||||
case 499: info = "Client Closed Request"
|
||||
case 500: info = "Internal Server Error"
|
||||
case 501: info = "Not Implemented"
|
||||
case 502: info = "Bad Gateway"
|
||||
case 503: info = "Service Unavailable"
|
||||
case 504: info = "Gateway Timeout"
|
||||
case 505: info = "HTTP Version Not Supported"
|
||||
case 506: info = "Variant Also Negotiates"
|
||||
case 507: info = "Insufficient Storage"
|
||||
case 508: info = "Loop Detected"
|
||||
case 510: info = "Not Extended"
|
||||
case 511: info = "Network Authentication Required"
|
||||
case 599: info = "Network Connect Timeout Error"
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
@ -20,15 +20,24 @@ enum ThemeDownload {
|
||||
case complete
|
||||
}
|
||||
|
||||
enum GitProtocol : String {
|
||||
case https = "https"
|
||||
case git = "git"
|
||||
}
|
||||
|
||||
let kCloverThemeAttributeKey = "org.cloverTheme.sha"
|
||||
|
||||
final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
private let errorDomain : String = "org.slice.Clover.ThemeManager.Error"
|
||||
var statusError : Error? = nil
|
||||
var delegate : ThemeManagerVC?
|
||||
private var user : String
|
||||
private var repo : String
|
||||
var basePath : String
|
||||
private var urlBaseStr : String
|
||||
private var themeManagerIndexDir : String
|
||||
|
||||
private var gitInitCount : Int32 = 0
|
||||
|
||||
let userAgent = "Clover"
|
||||
|
||||
@ -39,7 +48,7 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
self.user = user
|
||||
self.repo = repo
|
||||
self.basePath = basePath
|
||||
self.urlBaseStr = "https://api.github.com/repos/\(user)/\(repo)/git/trees/master?recursive=1"
|
||||
self.urlBaseStr = "\(GitProtocol.https.rawValue)://api.github.com/repos/\(user)/\(repo)/git/trees/master?recursive=1"
|
||||
self.themeManagerIndexDir = indexDir
|
||||
self.delegate = delegate
|
||||
if !fm.fileExists(atPath: self.themeManagerIndexDir) {
|
||||
@ -48,8 +57,9 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
attributes: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func getIndexedThemes() -> [String] {
|
||||
self.statusError = nil
|
||||
var themes = [String]()
|
||||
if self.getSha() != nil {
|
||||
let themesIndexPath = self.themeManagerIndexDir.addPath("Themes")
|
||||
@ -67,10 +77,11 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
}
|
||||
|
||||
public func getThemes(completion: @escaping ([String]) -> ()) {
|
||||
self.statusError = nil
|
||||
var themes : [String] = [String]()
|
||||
let themesIndexPath : String = self.themeManagerIndexDir.addPath("Themes")
|
||||
|
||||
self.getInfo(urlString: urlBaseStr) { (success) in
|
||||
self.getInfo(urlString: self.urlBaseStr) { (success) in
|
||||
|
||||
do {
|
||||
let files : [String] = try fm.contentsOfDirectory(atPath: themesIndexPath)
|
||||
@ -114,8 +125,9 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
return url
|
||||
}
|
||||
|
||||
private func downloadloadFile(at url: String, dst: String,
|
||||
completion: @escaping (Bool) -> ()) {
|
||||
private func downloadFile(at url: String, dst: String,
|
||||
completion: @escaping (Bool) -> ()) {
|
||||
|
||||
if let validURL : URL = URL(string: self.normalize(url)) {
|
||||
let upperDir : String = dst.deletingLastPath
|
||||
if !fm.fileExists(atPath: upperDir) {
|
||||
@ -124,26 +136,62 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil)
|
||||
} catch {
|
||||
print("DF1, \(error)")
|
||||
let errStr = "DF0, \(error.localizedDescription)."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 1000,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var request : URLRequest = URLRequest(url: validURL)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
|
||||
request.setValue(self.userAgent, forHTTPHeaderField: "User-Agent")
|
||||
|
||||
let config = URLSessionConfiguration.ephemeral
|
||||
let session = URLSession(configuration: config)
|
||||
|
||||
let task = session.dataTask(with: request, completionHandler: {(d, r, e) in
|
||||
if let response = r as? HTTPURLResponse {
|
||||
switch response.statusCode {
|
||||
case 200:
|
||||
break
|
||||
default:
|
||||
let errStr = "DF1, Error: request for '\(validURL)' response with status code \(response.statusCode) (\(gHTTPInfo(for: response.statusCode)))."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 1001,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
let errStr = "DF2, Error: empty response for '\(validURL)'."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 1002,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
if (e != nil) {
|
||||
print("DF2, \(e!)")
|
||||
let errStr = "DF3, \(e!.localizedDescription)."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 1003,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
guard let data = d else {
|
||||
print("DF3, Error: no datas.")
|
||||
let errStr = "DF4, Error: no datas."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 1004,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
@ -151,15 +199,23 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
try data.write(to: URL(fileURLWithPath: dst))
|
||||
completion(true)
|
||||
} catch {
|
||||
print("DF4, \(error)")
|
||||
let errStr = "DF5, \(error.localizedDescription)"
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 1005,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
}
|
||||
})
|
||||
|
||||
task.resume()
|
||||
|
||||
} else {
|
||||
print("DF5, Error: invalid url '\(url)'.")
|
||||
let errStr = "DF6, Error: invalid url '\(url)'."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 1006,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
@ -169,25 +225,52 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
var request : URLRequest = URLRequest(url: url)
|
||||
request.httpMethod = "GET"
|
||||
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
|
||||
request.setValue(self.userAgent, forHTTPHeaderField: "User-Agent")
|
||||
let config = URLSessionConfiguration.ephemeral
|
||||
let session = URLSession(configuration: config)
|
||||
|
||||
let task = session.dataTask(with: request, completionHandler: {(d, r, e) in
|
||||
if let reponse = r as? HTTPURLResponse {
|
||||
if reponse.statusCode != 200 {
|
||||
let errStr = "GI0, Error: request for '\(url)' reponse with status code \(reponse.statusCode) (\(gHTTPInfo(for: reponse.statusCode)))."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2000,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (e != nil) {
|
||||
print("GI1, \(e!)")
|
||||
let errStr = "GI1, \(e!.localizedDescription)"
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2001,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = d else {
|
||||
print("GI2, no data.")
|
||||
let errStr = "GI2, no data"
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2002,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
guard let utf8 = String(decoding: data, as: UTF8.self).data(using: .utf8) else {
|
||||
print("GI3, data is not utf8.")
|
||||
let errStr = "GI3, data is not utf8."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2003,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
@ -197,19 +280,34 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
|
||||
if let jdict = json as? [String: Any] {
|
||||
guard let truncated = jdict["truncated"] as? Bool else {
|
||||
print("GI4, Error: 'truncated' key not found")
|
||||
let errStr = "GI4, Error: 'truncated' key not found"
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2004,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
if truncated == true {
|
||||
print("GI4, Error: json has truncated list.")
|
||||
let errStr = "GI5, Error: json has truncated list."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2005,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
guard let sha = jdict["sha"] as? String else {
|
||||
print("GI4, Error: 'sha' key not found")
|
||||
let errStr = "GI6, Error: 'sha' key not found"
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2006,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
@ -221,7 +319,12 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
}
|
||||
|
||||
guard let tree = jdict["tree"] as? [[String: Any]] else {
|
||||
print("GI4, Error: 'tree' key not found, or not an array.")
|
||||
let errStr = "GI7, Error: 'tree' key not found, or not an array."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2007,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
@ -234,7 +337,12 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("GI5, Error: can't write sha commit.")
|
||||
let errStr = "GI8, Error: can't write sha commit."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2008,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
@ -250,7 +358,12 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
*/
|
||||
|
||||
guard let type = obj["type"] as? String else {
|
||||
print("GI6, Error: 'type' key not found")
|
||||
let errStr = "GI9, Error: 'type' key not found."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2009,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
break
|
||||
}
|
||||
@ -267,13 +380,23 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
|
||||
|
||||
if !theme.write(toFile: plistPath, atomically: false) {
|
||||
print("GI7, Error: 'path' key not found.")
|
||||
let errStr = "GI10, Error: can't write \(plistPath)"
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2010,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print("GI8, Error: 'path' key not found.")
|
||||
let errStr = "GI11, Error: 'path' key not found."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2011,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
break
|
||||
}
|
||||
@ -289,26 +412,46 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
self.removeOld(new: sha)
|
||||
completion(true)
|
||||
} catch {
|
||||
print("GI9, \(error)")
|
||||
let errStr = "GI12, \(error.localizedDescription)"
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2012,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
||||
//remove old themes (old sha)
|
||||
} else {
|
||||
print("GI10, json is not a dictionary (API change?).")
|
||||
let errStr = "GI13, json is not a dictionary (API change?)."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2013,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
}
|
||||
|
||||
} catch {
|
||||
print("GI11, \(error)")
|
||||
let errStr = "GI14, \(error.localizedDescription)"
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2014,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
}
|
||||
})
|
||||
|
||||
task.resume()
|
||||
} else {
|
||||
print("GI12, \(urlString) is invalid.")
|
||||
let errStr = "GI15, \(urlString) is invalid."
|
||||
let se : Error = NSError(domain: self.errorDomain,
|
||||
code: 2015,
|
||||
userInfo: [NSLocalizedDescriptionKey: errStr])
|
||||
|
||||
self.statusError = se
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
@ -338,6 +481,7 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
}
|
||||
/// Return the path for a given theme, if the download succeded
|
||||
public func download(theme: String, down: ThemeDownload, completion: @escaping (String?) -> ()) {
|
||||
self.statusError = nil
|
||||
if let sha = self.getSha() {
|
||||
let shaPath : String = self.basePath.addPath(sha)
|
||||
let themeDest : String = (down == .complete)
|
||||
@ -357,52 +501,47 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
let plistPath : String = "\(themeManagerIndexDir)/Themes/\(theme).plist"
|
||||
|
||||
if let files : [String] = NSArray(contentsOfFile: plistPath) as? [String] {
|
||||
// ---------------------------------------
|
||||
let fc : Int = files.count
|
||||
if fc > 0 {
|
||||
var broken : Bool = false
|
||||
let dg = DispatchGroup()
|
||||
var succeded : Bool = true
|
||||
let dispatchGroup = DispatchGroup()
|
||||
for i in 0..<fc {
|
||||
dg.enter()
|
||||
if broken {
|
||||
dg.leave()
|
||||
break
|
||||
} else {
|
||||
let file : String = files[i]
|
||||
// build the url
|
||||
let furl : String = "https://github.com/\(self.user)/\(self.repo)/raw/master/\(file)"
|
||||
|
||||
if down == .thumbnail {
|
||||
if file != theme.addPath("screenshot.png")
|
||||
&& file != theme.addPath("theme.svg")
|
||||
&& file != theme.addPath("theme.plist") {
|
||||
dg.leave()
|
||||
continue
|
||||
}
|
||||
let file : String = files[i]
|
||||
// build the url
|
||||
let furl : String = "\(GitProtocol.https.rawValue)://raw.githubusercontent.com/CloverHackyColor/CloverThemes/master/\(file)"
|
||||
|
||||
if down == .thumbnail {
|
||||
if file != theme.addPath("screenshot.png")
|
||||
&& file != theme.addPath("theme.svg")
|
||||
&& file != theme.addPath("theme.plist") {
|
||||
continue
|
||||
}
|
||||
|
||||
let filedest : String = (down == .complete)
|
||||
? themeDest.deletingLastPath.addPath(file)
|
||||
: shaPath.addPath(file)
|
||||
|
||||
if !fm.fileExists(atPath: filedest) {
|
||||
self.downloadloadFile(at: self.normalize(furl), dst: filedest) { (success) in
|
||||
broken = (success == false)
|
||||
dg.leave()
|
||||
}
|
||||
} else {
|
||||
dg.leave()
|
||||
}
|
||||
|
||||
let filedest : String = (down == .complete)
|
||||
? themeDest.deletingLastPath.addPath(file)
|
||||
: shaPath.addPath(file)
|
||||
|
||||
if !fm.fileExists(atPath: filedest) {
|
||||
dispatchGroup.enter()
|
||||
self.downloadFile(at: self.normalize(furl), dst: filedest) { (success) in
|
||||
succeded = success
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
if !succeded {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dg.notify(queue: .main) {
|
||||
if broken {
|
||||
completion(nil)
|
||||
} else {
|
||||
dispatchGroup.notify(queue: DispatchQueue.main, execute: {
|
||||
if succeded {
|
||||
completion(themeDest)
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
@ -413,7 +552,6 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func getImageUrl(for theme: String, completion: @escaping (String?) -> ()) {
|
||||
@ -434,8 +572,7 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
print("downloading thumbnail for \(theme)")
|
||||
|
||||
// theme not found?? Downloading...
|
||||
self.download(theme: theme, down: .thumbnail) { (path) in
|
||||
if let localTheme : String = path {
|
||||
@ -484,33 +621,82 @@ final class ThemeManager: NSObject, URLSessionDataDelegate {
|
||||
return fm.fileExists(atPath: "\(self.themeManagerIndexDir)/Themes/\(theme).plist")
|
||||
}
|
||||
|
||||
public func optimizeTheme(at path: String, err: inout Error?){
|
||||
let enumerator = fm.enumerator(atPath: path)
|
||||
while let file = enumerator?.nextObject() as? String {
|
||||
let fullPath = path.addPath(file)
|
||||
if file.fileExtension == "png" || file.fileExtension == "icns" {
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: fullPath)) {
|
||||
if data[0] != 0x89 || data[0] != 0x50 || data[0] != 0x4E || data[0] != 0x47 {
|
||||
// convert it to png
|
||||
if let png = NSBitmapImageRep(data: data)?.png {
|
||||
do {
|
||||
try png.write(to: URL(fileURLWithPath: fullPath))
|
||||
} catch {
|
||||
err = error
|
||||
return
|
||||
public func optimizeTheme(at path: String, completion: @escaping (Error?) -> ()) {
|
||||
|
||||
DispatchQueue.global(priority: .background).async(execute: { () -> Void in
|
||||
let plist = NSDictionary(contentsOfFile: path.addPath("theme.plist")) as? [String : Any]
|
||||
let theme = plist?["Theme"] as? [String : Any]
|
||||
let Selection = plist?["Selection"] as? [String : Any]
|
||||
|
||||
//logo 128x128 pixels Theme->Banner
|
||||
let logo : String = (theme?["Banner"] as? String) ?? "logo.png"
|
||||
|
||||
// selection_big 64x64 pixels Theme->Selection->Big
|
||||
let Selection_big : String = (Selection?["Big"] as? String) ?? "Selection_big.png"
|
||||
|
||||
// selection_small 144x144 pixels Theme->Selection->Small
|
||||
let Selection_small : String = (Selection?["Small"] as? String) ?? "Selection_small.png"
|
||||
|
||||
var images : [String] = [String]()
|
||||
let enumerator = fm.enumerator(atPath: path)
|
||||
|
||||
while let file = enumerator?.nextObject() as? String {
|
||||
if file.fileExtension == "png" || file.fileExtension == "icns" {
|
||||
images.append(file)
|
||||
}
|
||||
}
|
||||
|
||||
for file in images {
|
||||
let fullPath = path.addPath(file)
|
||||
do {
|
||||
let image = try ThemeImage(themeImageAtPath: fullPath)
|
||||
let size = image.size
|
||||
let fileName = fullPath.lastPath
|
||||
if file.hasPrefix("icons/") || file.hasPrefix("alternative_icons/") {
|
||||
if (fileName.hasPrefix("os_") || fileName.hasPrefix("vol_")) { // 128x128 pixels
|
||||
if size.width != 128 || size.height != 128 {
|
||||
image.size = NSMakeSize(128, 128)
|
||||
}
|
||||
} else if (fileName.hasPrefix("func_") ||
|
||||
fileName.hasPrefix("tool_") ||
|
||||
fileName == "pointer.png") { // 32x32 pixels
|
||||
if size.width != 32 || size.height != 32 {
|
||||
image.size = NSMakeSize(32, 32)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if file == logo { // logo 128x128 pixels
|
||||
if size.width != 128 || size.height != 128 {
|
||||
image.size = NSMakeSize(128, 128)
|
||||
}
|
||||
} else if file == Selection_big { // selection_big 144x144 pixels
|
||||
if size.width != 144 || size.height != 144 {
|
||||
image.size = NSMakeSize(144, 144)
|
||||
}
|
||||
} else if file == Selection_small { // selection_small 64x64 pixels
|
||||
if size.width != 64 || size.height != 64 {
|
||||
image.size = NSMakeSize(64, 64)
|
||||
}
|
||||
} else if (file == "radio_button" ||
|
||||
file == "radio_button_selected" ||
|
||||
file == "checkbox" ||
|
||||
file == "checkbox_checked") { // 15x15 pixels
|
||||
if size.width != 15 || size.height != 15 {
|
||||
image.size = NSMakeSize(15, 15)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
let data = try PNG8Image().png8ImageData(atPath: fullPath)
|
||||
try data.write(to: URL(fileURLWithPath: fullPath))
|
||||
try image.pngData.write(to: URL(fileURLWithPath: fullPath))
|
||||
} catch {
|
||||
err = error
|
||||
completion(error)
|
||||
break
|
||||
}
|
||||
|
||||
if file == images.last {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,8 @@ NSTableViewDelegate, NSTableViewDataSource, WebFrameLoadDelegate, WebUIDelegate
|
||||
var loaded : Bool = false
|
||||
var showInstalled : Bool = false
|
||||
|
||||
var isBusy : Bool = false
|
||||
|
||||
override func awakeFromNib() {
|
||||
super.awakeFromNib()
|
||||
if !self.loaded {
|
||||
@ -213,8 +215,8 @@ NSTableViewDelegate, NSTableViewDataSource, WebFrameLoadDelegate, WebUIDelegate
|
||||
}
|
||||
|
||||
@IBAction func optimizeThemePressed(_ sender: NSButton!) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
var success : Bool = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
var beep : Bool = true
|
||||
let sr = self.sidebar.selectedRow
|
||||
if sr >= 0 {
|
||||
if let v = self.sidebar.view(atColumn: 0, row: sr, makeIfNecessary: false) as? ThemeView {
|
||||
@ -224,30 +226,54 @@ NSTableViewDelegate, NSTableViewDataSource, WebFrameLoadDelegate, WebUIDelegate
|
||||
|| fm.fileExists(atPath: self.targetVolume!) {
|
||||
let themePath = self.targetVolume!.addPath("EFI/CLOVER/themes").addPath(v.name)
|
||||
if fm.fileExists(atPath: themePath) {
|
||||
beep = false
|
||||
self.isBusy = true
|
||||
self.spinner.startAnimation(nil)
|
||||
var error : Error? = nil
|
||||
self.manager?.optimizeTheme(at: themePath, err: &error)
|
||||
if (error != nil) {
|
||||
NSSound(named: "Basso")?.play()
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "😱"
|
||||
alert.informativeText = error!.localizedDescription
|
||||
alert.alertStyle = .critical
|
||||
alert.addButton(withTitle: "Ok".locale)
|
||||
|
||||
alert.beginSheetModal(for: self.view.window!) { (reponse) in
|
||||
self.optimizeButton.isEnabled = false
|
||||
self.installButton.isEnabled = false
|
||||
self.unistallButton.isEnabled = false
|
||||
self.targetPop.isEnabled = false
|
||||
self.nameBox.isEnabled = false
|
||||
self.installedThemesCheckBox.isEnabled = false
|
||||
//return
|
||||
self.manager?.optimizeTheme(at: themePath, completion: { (error) in
|
||||
DispatchQueue.main.async {
|
||||
self.isBusy = false
|
||||
self.spinner.stopAnimation(nil)
|
||||
self.optimizeButton.isEnabled = true
|
||||
self.installButton.isEnabled = true
|
||||
self.unistallButton.isEnabled = true
|
||||
self.targetPop.isEnabled = true
|
||||
self.nameBox.isEnabled = true
|
||||
self.installedThemesCheckBox.isEnabled = true
|
||||
}
|
||||
return
|
||||
}
|
||||
self.spinner.stopAnimation(nil)
|
||||
success = true
|
||||
if (error != nil) {
|
||||
DispatchQueue.main.async {
|
||||
NSSound(named: "Basso")?.play()
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "😱"
|
||||
alert.informativeText = error!.localizedDescription
|
||||
alert.alertStyle = .critical
|
||||
alert.addButton(withTitle: "Ok".locale)
|
||||
|
||||
alert.beginSheetModal(for: self.view.window!) { (reponse) in
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
NSSound(named: "Glass")?.play()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// this sound is only when succeded and or it fail when theme is not found
|
||||
NSSound(named: success ? "Glass" : "Basso")?.play()
|
||||
|
||||
if beep {
|
||||
NSSound.beep()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -340,7 +366,7 @@ NSTableViewDelegate, NSTableViewDataSource, WebFrameLoadDelegate, WebUIDelegate
|
||||
}
|
||||
|
||||
@IBAction func unInstallPressed(_ sender: NSButton!) {
|
||||
if AppSD.isInstalling {
|
||||
if AppSD.isInstalling || self.isBusy {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
@ -380,7 +406,7 @@ NSTableViewDelegate, NSTableViewDataSource, WebFrameLoadDelegate, WebUIDelegate
|
||||
}
|
||||
|
||||
@IBAction func InstallPressed(_ sender: NSButton!) {
|
||||
if AppSD.isInstalling {
|
||||
if AppSD.isInstalling || self.isBusy {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
@ -396,42 +422,68 @@ NSTableViewDelegate, NSTableViewDataSource, WebFrameLoadDelegate, WebUIDelegate
|
||||
}
|
||||
let theme = self.nameBox.stringValue
|
||||
let themeDest = self.targetVolume!.addPath("EFI/CLOVER/themes").addPath(theme)
|
||||
self.spinner.startAnimation(nil)
|
||||
|
||||
let sr = self.sidebar.selectedRow
|
||||
if sr >= 0 && sr < self.dataSource().count {
|
||||
// ---------
|
||||
self.isBusy = true
|
||||
self.spinner.startAnimation(nil)
|
||||
self.optimizeButton.isEnabled = false
|
||||
self.installButton.isEnabled = false
|
||||
self.unistallButton.isEnabled = false
|
||||
self.targetPop.isEnabled = false
|
||||
self.nameBox.isEnabled = false
|
||||
self.installedThemesCheckBox.isEnabled = false
|
||||
// ---------
|
||||
self.manager?.download(theme: theme,
|
||||
down: .complete,
|
||||
completion: { (path) in
|
||||
|
||||
if let themePath = path {
|
||||
try? fm.removeItem(atPath: themeDest)
|
||||
do {
|
||||
try fm.moveItem(atPath: themePath, toPath: themeDest)
|
||||
NSSound(named: "Glass")?.play()
|
||||
} catch {
|
||||
NSSound(named: "Basso")?.play()
|
||||
DispatchQueue.main.async {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Installation failed".locale
|
||||
alert.informativeText = "\(error.localizedDescription)"
|
||||
alert.addButton(withTitle: "OK")
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if self.manager?.statusError != nil {
|
||||
NSSound(named: "Basso")?.play()
|
||||
DispatchQueue.main.async {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Installation failed".locale
|
||||
alert.informativeText = "Theme \"\(theme)\" not found."
|
||||
alert.informativeText = self.manager!.statusError!.localizedDescription
|
||||
alert.addButton(withTitle: "OK")
|
||||
alert.runModal()
|
||||
}
|
||||
} else {
|
||||
if let themePath = path {
|
||||
try? fm.removeItem(atPath: themeDest)
|
||||
do {
|
||||
try fm.moveItem(atPath: themePath, toPath: themeDest)
|
||||
NSSound(named: "Glass")?.play()
|
||||
} catch {
|
||||
NSSound(named: "Basso")?.play()
|
||||
DispatchQueue.main.async {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Installation failed".locale
|
||||
alert.informativeText = "\(error.localizedDescription)"
|
||||
alert.addButton(withTitle: "OK")
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NSSound(named: "Basso")?.play()
|
||||
DispatchQueue.main.async {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Installation failed".locale
|
||||
alert.informativeText = "Theme \"\(theme)\" not found."
|
||||
alert.addButton(withTitle: "OK")
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.isBusy = false
|
||||
AppSD.isInstalling = false
|
||||
self.spinner.stopAnimation(nil)
|
||||
self.targetPop.isEnabled = true
|
||||
self.nameBox.isEnabled = true
|
||||
self.installedThemesCheckBox.isEnabled = true
|
||||
self.onSelection()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -498,6 +550,14 @@ NSTableViewDelegate, NSTableViewDataSource, WebFrameLoadDelegate, WebUIDelegate
|
||||
}
|
||||
|
||||
func tableViewSelectionDidChange(_ notification: Notification) {
|
||||
if self.isBusy {
|
||||
NSSound.beep()
|
||||
return
|
||||
}
|
||||
self.onSelection()
|
||||
}
|
||||
|
||||
func onSelection() {
|
||||
self.isPngTheme = false
|
||||
self.optimizeButton.isEnabled = false
|
||||
self.optimizeButton.state = .off
|
||||
@ -517,7 +577,6 @@ NSTableViewDelegate, NSTableViewDataSource, WebFrameLoadDelegate, WebUIDelegate
|
||||
} else {
|
||||
self.installButton.isEnabled = false
|
||||
}
|
||||
|
||||
|
||||
if let path = v.imagePath {
|
||||
self.nameBox.stringValue = v.name
|
||||
|
@ -126,33 +126,50 @@ typedef enum LodePNGColorType {
|
||||
bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types.
|
||||
Return value: LodePNG error code (0 means no error).
|
||||
*/
|
||||
unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h,
|
||||
const unsigned char* in, size_t insize,
|
||||
LodePNGColorType colortype, unsigned bitdepth);
|
||||
unsigned lodepng_decode_memory(unsigned char** out,
|
||||
unsigned* w,
|
||||
unsigned* h,
|
||||
const unsigned char* in,
|
||||
size_t insize,
|
||||
LodePNGColorType colortype,
|
||||
unsigned bitdepth);
|
||||
|
||||
/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/
|
||||
unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h,
|
||||
const unsigned char* in, size_t insize);
|
||||
unsigned lodepng_decode32(unsigned char** out,
|
||||
unsigned* w,
|
||||
unsigned* h,
|
||||
const unsigned char* in,
|
||||
size_t insize);
|
||||
|
||||
/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/
|
||||
unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h,
|
||||
const unsigned char* in, size_t insize);
|
||||
unsigned lodepng_decode24(unsigned char** out,
|
||||
unsigned* w,
|
||||
unsigned* h,
|
||||
const unsigned char* in,
|
||||
size_t insize);
|
||||
|
||||
#ifdef LODEPNG_COMPILE_DISK
|
||||
/*
|
||||
Load PNG from disk, from file with given name.
|
||||
Same as the other decode functions, but instead takes a filename as input.
|
||||
*/
|
||||
unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h,
|
||||
unsigned lodepng_decode_file(unsigned char** out,
|
||||
unsigned* w,
|
||||
unsigned* h,
|
||||
const char* filename,
|
||||
LodePNGColorType colortype, unsigned bitdepth);
|
||||
LodePNGColorType colortype,
|
||||
unsigned bitdepth);
|
||||
|
||||
/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/
|
||||
unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h,
|
||||
unsigned lodepng_decode32_file(unsigned char** out,
|
||||
unsigned* w,
|
||||
unsigned* h,
|
||||
const char* filename);
|
||||
|
||||
/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/
|
||||
unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h,
|
||||
unsigned lodepng_decode24_file(unsigned char** out,
|
||||
unsigned* w,
|
||||
unsigned* h,
|
||||
const char* filename);
|
||||
#endif /*LODEPNG_COMPILE_DISK*/
|
||||
#endif /*LODEPNG_COMPILE_DECODER*/
|
||||
@ -175,17 +192,27 @@ unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h,
|
||||
bitdepth: the bit depth of the raw input image. See explanation on PNG color types.
|
||||
Return value: LodePNG error code (0 means no error).
|
||||
*/
|
||||
unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize,
|
||||
const unsigned char* image, unsigned w, unsigned h,
|
||||
LodePNGColorType colortype, unsigned bitdepth);
|
||||
unsigned lodepng_encode_memory(unsigned char** out,
|
||||
size_t* outsize,
|
||||
const unsigned char* image,
|
||||
unsigned w,
|
||||
unsigned h,
|
||||
LodePNGColorType colortype,
|
||||
unsigned bitdepth);
|
||||
|
||||
/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/
|
||||
unsigned lodepng_encode32(unsigned char** out, size_t* outsize,
|
||||
const unsigned char* image, unsigned w, unsigned h);
|
||||
unsigned lodepng_encode32(unsigned char** out,
|
||||
size_t* outsize,
|
||||
const unsigned char* image,
|
||||
unsigned w,
|
||||
unsigned h);
|
||||
|
||||
/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/
|
||||
unsigned lodepng_encode24(unsigned char** out, size_t* outsize,
|
||||
const unsigned char* image, unsigned w, unsigned h);
|
||||
unsigned lodepng_encode24(unsigned char** out,
|
||||
size_t* outsize,
|
||||
const unsigned char* image,
|
||||
unsigned w,
|
||||
unsigned h);
|
||||
|
||||
#ifdef LODEPNG_COMPILE_DISK
|
||||
/*
|
||||
@ -194,16 +221,23 @@ unsigned lodepng_encode24(unsigned char** out, size_t* outsize,
|
||||
NOTE: This overwrites existing files without warning!
|
||||
*/
|
||||
unsigned lodepng_encode_file(const char* filename,
|
||||
const unsigned char* image, unsigned w, unsigned h,
|
||||
LodePNGColorType colortype, unsigned bitdepth);
|
||||
const unsigned char* image,
|
||||
unsigned w,
|
||||
unsigned h,
|
||||
LodePNGColorType colortype,
|
||||
unsigned bitdepth);
|
||||
|
||||
/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/
|
||||
unsigned lodepng_encode32_file(const char* filename,
|
||||
const unsigned char* image, unsigned w, unsigned h);
|
||||
const unsigned char* image,
|
||||
unsigned w,
|
||||
unsigned h);
|
||||
|
||||
/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/
|
||||
unsigned lodepng_encode24_file(const char* filename,
|
||||
const unsigned char* image, unsigned w, unsigned h);
|
||||
const unsigned char* image,
|
||||
unsigned w,
|
||||
unsigned h);
|
||||
#endif /*LODEPNG_COMPILE_DISK*/
|
||||
#endif /*LODEPNG_COMPILE_ENCODER*/
|
||||
|
BIN
CloverApp/Clover/images/Find.png
Normal file
BIN
CloverApp/Clover/images/Find.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
CloverApp/Clover/images/Replace.png
Normal file
BIN
CloverApp/Clover/images/Replace.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
CloverApp/Clover/images/add.png
Normal file
BIN
CloverApp/Clover/images/add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
CloverApp/Clover/images/remove.png
Normal file
BIN
CloverApp/Clover/images/remove.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
342
CloverApp/Lang.bundle/Contents/Resources/ar.strings
Normal file
342
CloverApp/Lang.bundle/Contents/Resources/ar.strings
Normal file
@ -0,0 +1,342 @@
|
||||
/*
|
||||
Clover.app
|
||||
language code: ar
|
||||
|
||||
Copyright © 2019-2020 CloverHackyColor. All rights reserved.
|
||||
*/
|
||||
// Globals
|
||||
"Unsupported" = "Unsupported";
|
||||
"N/A" = "N/A"; // not available (please be short)
|
||||
|
||||
// Mount / unmount
|
||||
"Clover wants to mount %@" = "Clover wants to mount %@";
|
||||
"Clover wants to umount %@" = "Clover wants to umount %@";
|
||||
"Mount" = "Mount";
|
||||
"umount" = "umount";
|
||||
"mount point" = "mount point";
|
||||
"*auto mount" = "*auto mount";
|
||||
|
||||
// Info
|
||||
"System Serial Number:" = "System Serial Number:";
|
||||
"Model:" = "Model:";
|
||||
"board-id:" = "board-id:";
|
||||
"OEM Vendor:" = "OEM Vendor:";
|
||||
"OEM Product:" = "OEM Product:";
|
||||
"OEM Board:" = "OEM Board:";
|
||||
"NVRAM is native:" = "NVRAM is native:";
|
||||
"unknown" = "unknown";
|
||||
"Yes" = "Yes"; // first char upper case
|
||||
"No" = "No"; // first char upper case
|
||||
|
||||
// Sound
|
||||
"Startup Sound" = "Startup Sound";
|
||||
"Device:" = "Device:";
|
||||
"Volume level:" = "Volume level:";
|
||||
"LineOut" = "Line Out";
|
||||
"Speaker" = "Speaker";
|
||||
"Headphones" = "Headphones";
|
||||
"Garniture" = "Garniture";
|
||||
"Other" = "Other";
|
||||
|
||||
"true" = "true";
|
||||
"false" = "false";
|
||||
|
||||
// Theme
|
||||
"Theme:" = "Theme:";
|
||||
"Themes" = "Themes"; // window title
|
||||
"No themes found" = "No themes found";
|
||||
"Manager" = "Manager";
|
||||
"Can't remove the theme" = "Can't remove the theme";
|
||||
"Show installed" = "Show installed";
|
||||
"Optimize" = "Optimize";
|
||||
"Sound:" = "Sound:";
|
||||
// Main view (pop over)
|
||||
"*Make filesystem read-write" = "*Make filesystem read-write";
|
||||
"*Disable Sleep Proxy Client" = "*Disable Sleep Proxy Client";
|
||||
"*Require CloverDaemon" = "*Require CloverDaemon";
|
||||
"Read daemon log" = "Read daemon log";
|
||||
"Read bdmesg" = "Read boot log";
|
||||
|
||||
"Install CloverDaemonNew" = "Install CloverDaemonNew";
|
||||
"Uninstall CloverDaemonNew" = "Uninstall CloverDaemonNew";
|
||||
|
||||
"Update" = "Update";
|
||||
"Download" = "Download";
|
||||
"Update to r%d" = "Update to r%d"; // Update to r5101
|
||||
"Check update:" = "Check update:";
|
||||
"Check now" = "Check now";
|
||||
|
||||
"never" = "never";
|
||||
"daily" = "daily";
|
||||
"weekly" = "weekly";
|
||||
"monthly" = "monthly";
|
||||
"last checked:" = "last checked:"; // last date update was checked
|
||||
|
||||
"Run at login" = "Run at login";
|
||||
"Close" = "إغلاق";
|
||||
|
||||
// Installer
|
||||
"Install Clover" = "Install Clover";
|
||||
"Current Clover revision" = "Current Clover revision";
|
||||
"Boot Device:" = "Boot Device:";
|
||||
"config path:" = "config path:";
|
||||
"Installation succeded" = "Installation succeded";
|
||||
"Installation failed" = "Installation failed";
|
||||
"Clover Installer" = "Clover Installer";
|
||||
"Select a disk.." = "Select a disk..";
|
||||
"Install" = "Install";
|
||||
"Uninstall" = "Uninstall";
|
||||
"AltBoot" = "Alternative boot";
|
||||
|
||||
// Clover Bootloader and drivers
|
||||
"UEFI only" = "Install Clover to be used with UEFI motherboards only.";
|
||||
|
||||
"Install alternative booting PBR" = "Install alternative booting PBR with choice of boot with a key pressed.
|
||||
File to boot = boot{keypressed}";
|
||||
|
||||
"Don't install any bootloader (boot0X, boot1X)" = "Don't install any bootloader (boot0X, boot1X).
|
||||
Usefull for UEFI motherboards that don't need bootloader files.
|
||||
Can also be use if you don't want to upgrade MBR or PBR sectors.";
|
||||
|
||||
"Clover legacy BIOS boot sectors" = "Clover EFI requires three essential files. (in simple terms)
|
||||
boot0 (On the drive's MBR) responsible for loading boot1.
|
||||
boot1 (On the partition's boot-sector) to finding boot2.
|
||||
boot2 (On the partition's root directory) for loading CLOVERX64.efi, and kernel etc.";
|
||||
|
||||
"boot0af" = "Used for BIOS booting on BIOS motherboards.
|
||||
boot0af (boot0 Active First) bootloader try to boot the active partition defined in MBR. If there is no active partition, it will try to boot the first EFI/FAT32/HFS partition (defined in the MBR and then the GPT) with a valid PBR signature.
|
||||
This choice will setup selected HFS/Fat32 partition to be active.";
|
||||
|
||||
"boot0ss" = "Used for BIOS booting on BIOS motherboards.
|
||||
boot0ss (boot0 Signature Scanning) bootloader try to boot the first EFI/FAT32/HFS partition (defined in the MBR and then the GPT) with a valid PBR signature. If no partition is found it will try to boot the active partition defined in MBR.
|
||||
This bootloader is a good choice when you have Windows installed on the same disk because Windows wants to have its partition active.
|
||||
This choice will not activate any partition in MBR.";
|
||||
|
||||
"boot6" = "Clover EFI 64-bits using SATA to access drives.";
|
||||
"boot7" = "Clover EFI 64-bits BiosBlockIO";
|
||||
|
||||
"UEFI mandatory" = "UEFI mandatory";
|
||||
"BIOS mandatory" = "BIOS mandatory";
|
||||
|
||||
"UEFI/Other" = "UEFI/Other";
|
||||
"BIOS/Other" = "BIOS/Other";
|
||||
|
||||
"UEFI, but not from this installer" = "UEFI, but not from this installer";
|
||||
"BIOS, but not from this installer" = "BIOS, but not from this installer";
|
||||
|
||||
"UEFI/FileSystem" = "UEFI, filesystem drivers";
|
||||
"BIOS/FileSystem" = "BIOS, filesystem drivers";
|
||||
|
||||
"UEFI/HID" = "UEFI, Human Interface Devices";
|
||||
"BIOS/HID" = "BIOS, Human Interface Devices";
|
||||
|
||||
"UEFI/FileVault2" = "UEFI, FileVault 2 drivers";
|
||||
"BIOS/FileVault2" = "BIOS, FileVault 2 drivers";
|
||||
|
||||
"UEFI/MemoryFix" = "UEFI, memory fix drivers";
|
||||
|
||||
"ApfsDriverLoader.efi" = "Supports APFS filesystem driver from container for macOS 10.13 and newers";
|
||||
|
||||
"AppleImageCodec.efi" = "Decode PNG and BMP for FileVault2.";
|
||||
|
||||
"AppleImageLoader.efi" = "Secure AppleEfiFat binary driver with implementation of AppleLoadImage protocol with EfiBinary signature verification.";
|
||||
|
||||
"AppleKeyAggregator.efi" = "Support for boot UI dialog for FileVault2.";
|
||||
|
||||
"AppleKeyFeeder.efi" = "Support for PS/2 keyboard to use with FileVault 2.";
|
||||
|
||||
"AppleUISupport.efi" = "Set of protocols for support EfiLoginUi for FileVault.";
|
||||
|
||||
"AppleUITheme.efi" = "Create boot UI Themes support for FileVault2.";
|
||||
|
||||
"AptioInputFix.efi" = "Driver to fix input problems on UEFI firmware such as AMI Aptio.";
|
||||
|
||||
"AptioMemoryFix.efi" = "Preferred driver to fix Memory problems on UEFI firmware such as AMI Aptio. Do not use with other AptioFix together.";
|
||||
|
||||
"AudioDxe.efi" = "HDA driver to play Startup Sound during Clover boot.
|
||||
Configure it via Startup sound output from Option menu, including:
|
||||
Volume and Audio device (for supported IOAudio devices only).";
|
||||
|
||||
"FirmwareVolume.efi" = "Create FirmwareVolume with cursor images for FileVault2.";
|
||||
|
||||
"FSInject.efi" = "Provide injection of kernel extensions from Clover folder and allow to force load them from both /System/Library/Extensions and /Library/Extensions.";
|
||||
|
||||
"GrubEXFAT.efi" = "ExFAT filesystem driver from GRUB.";
|
||||
|
||||
"GrubISO9660.efi" = "ISO 9600 filesystem driver from GRUB.";
|
||||
|
||||
"GrubNTFS.efi" = "NTFS filesystem driver from GRUB.";
|
||||
|
||||
"GrubUDF.efi" = "UDF filesystem driver from GRUB.";
|
||||
|
||||
"SMCHelper.efi" = "Restore SMC keys left in NVRAM by FakeSMC.";
|
||||
|
||||
"XhciDxe.efi" = "USB 3.0 driver";
|
||||
|
||||
"AppleEvent.uefi" = "Create AppleEvent protocol for FileVault2.";
|
||||
|
||||
"AppleGraphicsConfig.uefi" = "Create optional AppleGraphicsConfig protocol.";
|
||||
|
||||
"CsmVideoDxe.efi" = "Video Driver for Clover GUI allowing to choose more resolutions. It is based on CSM module in UEFI BIOS and required CSM will be enabled.
|
||||
Clover may not started with it and you may have wake problem in system. Use with precautions.";
|
||||
|
||||
"DataHubDxe.efi" = "This is DataHub protocol support mandatory for macOS.
|
||||
Usually it is already present but sometime it may be missed. In this case you should see warning on screen.";
|
||||
|
||||
"EmuVariableUefi.efi" = "Workaround for store NVRAM variables for systems without UEFI hardware.
|
||||
Mostly UEFI boot uses hardware NVRAM but in some rare cases this driver is needed. Use it only if you have a problem without it";
|
||||
|
||||
"EnglishDxe.efi" = "Support for UnicodeCollation protocol used by EFI Shell if it missed in UEFI.";
|
||||
|
||||
"Fat.efi" = "FAT filesystem driver.";
|
||||
|
||||
"HashServiceFix.efi" = "Fix Hash support if absent in native UEFI BIOS.";
|
||||
|
||||
"HFSPlus.efi" = "Alternate HFS+ filesystem driver.";
|
||||
|
||||
"NvmExpressDxe.efi" = "Driver for support NVM Express devices.";
|
||||
|
||||
"OsxAptioFix3Drv.efi" = "Alternate driver (v3) to fix Memory problems on UEFI firmware. Do not use with other AptioFix together.";
|
||||
|
||||
"OsxAptioFixDrv.efi" = "Old Driver to fix Memory problems on UEFI firmware such as AMI Aptio. Do not use with other AptioFix together.";
|
||||
|
||||
"OsxFatBinaryDrv.efi" = "Driver for support FAT Binary executables for OS X 10.9 and older.";
|
||||
|
||||
"OsxLowMemFixDrv.efi" = "Simplified variant of OsxAptioFixDrv. Do not use with other AptioFix together.";
|
||||
|
||||
"PartitionDxe.efi" = "Driver to support non-usual partition maps such as: hybrid GPT/MBR or Apple Partition Map.";
|
||||
|
||||
"Ps2MouseDxe.efi" = "PS/2 mouse driver";
|
||||
|
||||
"UsbKbDxe.efi" = "Keyboard driver for boot UI support.";
|
||||
|
||||
"UsbMouseDxe.efi" = "USB mouse driver";
|
||||
|
||||
"VBoxExt2.efi" = "EXT2/3 filesystem driver from VirtualBox.";
|
||||
|
||||
"VBoxExt4.efi" = "EXT4 filesystem driver from VirtualBox.";
|
||||
|
||||
"VBoxHfs.efi" = "HFS+ filesystem driver.";
|
||||
|
||||
"VBoxIso9600.efi" = "ISO 9600 filesystem driver.";
|
||||
|
||||
// Plist Editor Menu
|
||||
// Clover.app Menu
|
||||
"About Clover" = "حول Clover";
|
||||
"Preferences…" = "تفضيلات…";
|
||||
"Services" = "خدمات";
|
||||
"Hide Clover" = "إخفاء Clover";
|
||||
"Hide Others" = "إخفاء الأخرى";
|
||||
"Show All" = "إظهار الكل";
|
||||
"Quit Clover" = "إنهاء Clover";
|
||||
// File
|
||||
"New" = "New";
|
||||
"Open…" = "فتح…";
|
||||
"Open Recent" = "Open Recent";
|
||||
"Close" = "إغلاق";
|
||||
"Page Setup…" = "Page Setup…";
|
||||
"Print…" = "Print…";
|
||||
// Edit
|
||||
"Undo" = "تراجع";
|
||||
"Redo" = "إعادة";
|
||||
"Cut" = "قص";
|
||||
"Copy" = "نسخ";
|
||||
"Paste" = "لصق";
|
||||
"Paste and Match Style" = "لصق ومطابقة النمط";
|
||||
"Delete" = "حذف";
|
||||
"Select All" = "تحديد الكل";
|
||||
// Edit->Find
|
||||
"Edit" = "تحرير";
|
||||
"File" = "ملف";
|
||||
"Find" = "بحث";
|
||||
"Find…" = "بحث…";
|
||||
"Find and Replace…" = "بحث واستبدال...";
|
||||
"Find Next" = "بحث عن التالي";
|
||||
"Find Previous" = "بحث عن السابق";
|
||||
"Use Selection for Find" = "استخدام التحديد للبحث";
|
||||
"Use Selection for Replace" = "استخدام التحديد للاستبدال";
|
||||
"Jump to Selection" = "انتقال سريع إلى التحديد";
|
||||
// Edit->Spelling and Grammar
|
||||
"Spelling" = "تدقيق إملائي";
|
||||
"Spelling and Grammar" = "التدقيق الإملائي والتدقيق النحوي";
|
||||
"Show Spelling and Grammar" = "إظهار التدقيق الإملائي والتدقيق النحوي";
|
||||
"Check Document Now" = "تدقيق المستند الآن";
|
||||
"Check Spelling While Typing" = "تحقق من التدقيق الإملائي أثناء الكتابة";
|
||||
"Check Grammar With Spelling" = "تحقق من التدقيق النحوي مع التدقيق الإملائي";
|
||||
"Correct Spelling Automatically" = "تصحيح التدقيق الإملائي تلقائيًا";
|
||||
// Edit->Substitutions
|
||||
"Substitutions" = "البدائل";
|
||||
"Show Substitutions" = "إظهار البدائل";
|
||||
"Smart Copy/Paste" = "النسخ/اللصق الذكي";
|
||||
"Smart Quotes" = "علامات الاقتباس الذكية";
|
||||
"Smart Dashes" = "شرط ذكية";
|
||||
"Smart Links" = "الروابط الذكية";
|
||||
"Text Replacement" = "استبدال النص";
|
||||
// Edit->Transformations
|
||||
"Transformations" = "التحويلات";
|
||||
"Make Upper Case" = "استخدام الأحرف الكبيرة";
|
||||
"Make Lower Case" = "تحويل إلى أحرف صغيرة";
|
||||
"Capitalize" = "البدء بأحرف كبيرة";
|
||||
// Edit->Speech
|
||||
"Speech" = "محادثة";
|
||||
"Start Speaking" = "بدء التكلم";
|
||||
"Stop Speaking" = "إيقاف التكلم";
|
||||
// View
|
||||
"View" = "View";
|
||||
"Show Toolbar" = "Show Toolbar";
|
||||
"Customize Toolbar…" = "Customize Toolbar…";
|
||||
"Show Sidebar" = "Show Sidebar";
|
||||
// Window
|
||||
"Window" = "نافذة";
|
||||
"Minimize" = "تصغير";
|
||||
"Zoom" = "تكبير/تصغير";
|
||||
"Bring Clover Window to Front" = "<null>";
|
||||
"Bring All to Front" = "إحضار الكل إلى الأمام";
|
||||
// Help
|
||||
"Clover Help" = "Clover Help";
|
||||
|
||||
// Plist Editor document
|
||||
"Search" = "Search";
|
||||
"Replace" = "Replace";
|
||||
"All" = "All";
|
||||
"Item" = "Item";
|
||||
"Items" = "Items";
|
||||
"Untiteled" = "Untiteled";
|
||||
"New Item" = "New Item";
|
||||
"bytes" = "bytes";
|
||||
"typing" = "typing";
|
||||
"change type" = "change type";
|
||||
"change bool value" = "change bool value";
|
||||
"replace duplicate key" = "replace duplicate key";
|
||||
"move item" = "move item";
|
||||
"paste Item" = "paste Item";
|
||||
"remove Item" = "remove Item";
|
||||
"cut Item" = "cut Item";
|
||||
"add new Item" = "add new Item";
|
||||
"No data" = "No data";
|
||||
"missing '<' at the beginning" = "missing '<' at the beginning";
|
||||
"missing '>' at the end" = "missing '>' at the end";
|
||||
"Your data contains illegal characters" = "Your data contains illegal characters";
|
||||
"bytes count is odd, must be even" = "bytes count is odd, must be even";
|
||||
"Keep editing" = "Keep editing";
|
||||
"Duplicate key in the Dictionary!" = "Duplicate key in the Dictionary!";
|
||||
"'%@' is already present in the Dictionary. Do you want to undo the editing or replace the existing key?" = "'%@' is already present in the Dictionary. Do you want to undo the editing or replace the existing key?";
|
||||
"Invalid value detected!" = "Invalid value detected!";
|
||||
"Your edit is not valid. Do you want to restore last valid value or keep editing?" = "Your edit is not valid. Do you want to restore last valid value or keep editing?";
|
||||
|
||||
// Plist Editor header
|
||||
"Key" = "Key";
|
||||
"Type" = "Type";
|
||||
"Value" = "Value";
|
||||
// Plist Editor tags
|
||||
"Dictionary" = "Dictionary";
|
||||
"Array" = "Array";
|
||||
"String" = "String";
|
||||
"Number" = "Number";
|
||||
"Bool" = "Bool";
|
||||
"Date" = "Date";
|
||||
"Data" = "Data";
|
||||
|
||||
// Plist Editor Boolean values
|
||||
"YES" = "YES";
|
||||
"NO" = "NO";
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user