From bbb5db46a606ddf436b0803aed418590c225c40c Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Sun, 1 Mar 2026 08:55:04 +0200 Subject: [PATCH 1/2] feat: eliminate Editor.xib with programmatic NSWindow + SwiftUI root (#237) Replace the 515-line Editor.xib and ~28 legacy ObjC files (~2800 LOC) with a programmatic NSWindow containing a single SwiftUI EditorView root. This is the final step (PR 3 of 3) in the editor modernization series, following the sidebar (#234) and content area (#236) SwiftUI migrations. New SwiftUI components: - EditorView: root view with NavigationSplitView, owns @StateObject HostsDataStore - EditorToolbar: 4 toolbar items using SF Symbols (plus, minus, square.and.arrow.down, power) - StatusBarView: files count label, read-only lock icon, busy spinner - EditorWindowPresenter: @objc bridge creating NSWindow programmatically Key changes: - HostsDataStore converted from singleton to @StateObject-owned instance - Busy indicator moved from ApplicationController to HostsDataStore with thread-safe notification observers (queue: .main) - Toolbar Remove now uses moveToTrash:true (reversible) matching sidebar - Window lifecycle (open/close/reopen, dock show/hide) preserved --- Editor.xib | 515 ------------------ Gas Mask.xcodeproj/project.pbxproj | 124 +---- Source/AlertBadge.h | 34 -- Source/AlertBadge.m | 58 -- Source/ApplicationController.h | 2 - Source/ApplicationController.m | 44 +- Source/Badge.h | 29 - Source/Badge.m | 51 -- Source/BadgeManager.h | 36 -- Source/BadgeManager.m | 78 --- Source/Cell.h | 45 -- Source/Cell.m | 400 -------------- Source/CombinedHostsPredicateController.h | 38 -- Source/CombinedHostsPredicateController.m | 197 ------- .../CombinedHostsPredicateEditorRowTemplate.h | 27 - .../CombinedHostsPredicateEditorRowTemplate.m | 152 ------ Source/EditorController.h | 29 - Source/EditorController.m | 100 ---- Source/ExtendedNSSplitView.h | 23 - Source/ExtendedNSSplitView.m | 39 -- Source/FilesCountTransformer.h | 22 - Source/FilesCountTransformer.m | 49 -- Source/HostsListView.h | 33 -- Source/HostsListView.m | 151 ----- Source/HostsListViewMenu.h | 27 - Source/HostsListViewMenu.m | 103 ---- Source/HostsTextController.h | 28 - Source/HostsTextController.m | 58 -- Source/ListController.h | 36 -- Source/ListController.m | 437 --------------- Source/NSToolbarPoofAnimator.h | 27 - Source/OfflineBadge.h | 34 -- Source/OfflineBadge.m | 55 -- Source/Swift/ContentInstaller.swift | 16 - Source/Swift/EditorToolbar.swift | 75 +++ Source/Swift/EditorView.swift | 16 + Source/Swift/EditorWindowPresenter.swift | 44 ++ Source/Swift/GasMask-Bridging-Header.h | 1 - Source/Swift/HostsDataStore.swift | 37 +- Source/Swift/SidebarInstaller.swift | 18 - Source/Swift/StatusBarView.swift | 44 ++ Source/SyncingArrowsBadge.h | 32 -- Source/SyncingArrowsBadge.m | 58 -- .../GasMaskTests/ApplicationControllerTests.m | 43 +- .../EditorWindowPresenterTests.swift | 54 ++ Tests/GasMaskTests/HostsDataStoreTests.swift | 41 +- 46 files changed, 348 insertions(+), 3212 deletions(-) delete mode 100644 Editor.xib delete mode 100644 Source/AlertBadge.h delete mode 100644 Source/AlertBadge.m delete mode 100644 Source/Badge.h delete mode 100644 Source/Badge.m delete mode 100644 Source/BadgeManager.h delete mode 100644 Source/BadgeManager.m delete mode 100644 Source/Cell.h delete mode 100644 Source/Cell.m delete mode 100644 Source/CombinedHostsPredicateController.h delete mode 100644 Source/CombinedHostsPredicateController.m delete mode 100644 Source/CombinedHostsPredicateEditorRowTemplate.h delete mode 100644 Source/CombinedHostsPredicateEditorRowTemplate.m delete mode 100644 Source/EditorController.h delete mode 100644 Source/EditorController.m delete mode 100644 Source/ExtendedNSSplitView.h delete mode 100644 Source/ExtendedNSSplitView.m delete mode 100644 Source/FilesCountTransformer.h delete mode 100644 Source/FilesCountTransformer.m delete mode 100644 Source/HostsListView.h delete mode 100644 Source/HostsListView.m delete mode 100644 Source/HostsListViewMenu.h delete mode 100644 Source/HostsListViewMenu.m delete mode 100644 Source/HostsTextController.h delete mode 100644 Source/HostsTextController.m delete mode 100644 Source/ListController.h delete mode 100644 Source/ListController.m delete mode 100644 Source/NSToolbarPoofAnimator.h delete mode 100644 Source/OfflineBadge.h delete mode 100644 Source/OfflineBadge.m delete mode 100644 Source/Swift/ContentInstaller.swift create mode 100644 Source/Swift/EditorToolbar.swift create mode 100644 Source/Swift/EditorView.swift create mode 100644 Source/Swift/EditorWindowPresenter.swift delete mode 100644 Source/Swift/SidebarInstaller.swift create mode 100644 Source/Swift/StatusBarView.swift delete mode 100644 Source/SyncingArrowsBadge.h delete mode 100644 Source/SyncingArrowsBadge.m create mode 100644 Tests/GasMaskTests/EditorWindowPresenterTests.swift diff --git a/Editor.xib b/Editor.xib deleted file mode 100644 index 724fcb6..0000000 --- a/Editor.xib +++ /dev/null @@ -1,515 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FilesCountTransformer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - name - - - address - - - sign - - - - - - - - - name - - - - - address - - - - - sign - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NSNegateBoolean - - - - - - - - - - - - - - NSNegateBoolean - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - title - contents - saved - active - name - editable - exists - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - name - - - address - - - sign - - - - - - - - - name - - - - - address - - - - - sign - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Gas Mask.xcodeproj/project.pbxproj b/Gas Mask.xcodeproj/project.pbxproj index 51ddacc..31665fc 100644 --- a/Gas Mask.xcodeproj/project.pbxproj +++ b/Gas Mask.xcodeproj/project.pbxproj @@ -13,18 +13,13 @@ 350920C51226EDD600ACA0F4 /* AbstractHostsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 350920C41226EDD600ACA0F4 /* AbstractHostsManager.m */; }; 350C7F391A3C55B000B46B09 /* Create@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 350C7F381A3C55B000B46B09 /* Create@2x.png */; }; 350C7F3B1A3C56BB00B46B09 /* Read Only@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 350C7F3A1A3C56BB00B46B09 /* Read Only@2x.png */; }; - 350E7D3E121093E400D2F5F5 /* AlertBadge.m in Sources */ = {isa = PBXBuildFile; fileRef = 350E7D3D121093E400D2F5F5 /* AlertBadge.m */; }; - 350E7D7E12111B2300D2F5F5 /* Badge.m in Sources */ = {isa = PBXBuildFile; fileRef = 350E7D7D12111B2300D2F5F5 /* Badge.m */; }; - 350E7D941211A7AE00D2F5F5 /* BadgeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 350E7D931211A7AE00D2F5F5 /* BadgeManager.m */; }; 351167C010D6346000A5FAA1 /* HostsGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 351167BF10D6346000A5FAA1 /* HostsGroup.m */; }; - 3511697810D68B1000A5FAA1 /* FilesCountTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3511697710D68B1000A5FAA1 /* FilesCountTransformer.m */; }; 35116A7010D6AAD200A5FAA1 /* Menulet.m in Sources */ = {isa = PBXBuildFile; fileRef = 35116A6F10D6AAD200A5FAA1 /* Menulet.m */; }; 35116AC010D6AF0500A5FAA1 /* LoginItem.m in Sources */ = {isa = PBXBuildFile; fileRef = 35116ABF10D6AF0500A5FAA1 /* LoginItem.m */; }; 3513A600113908A900AD789D /* Read Only.png in Resources */ = {isa = PBXBuildFile; fileRef = 3513A5FF113908A900AD789D /* Read Only.png */; }; 3513A61C11390B7900AD789D /* Error.m in Sources */ = {isa = PBXBuildFile; fileRef = 3513A61B11390B7900AD789D /* Error.m */; }; 351416CF28C3A7B80093A452 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 351416CE28C3A7B80093A452 /* Sparkle */; }; AA1B2C3D4E5F000100000001 /* MASShortcut in Frameworks */ = {isa = PBXBuildFile; productRef = AA1B2C3D4E5F000200000001 /* MASShortcut */; }; - 3516864B11187EDE00314609 /* HostsTextController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3516864A11187EDE00314609 /* HostsTextController.m */; }; 351D904510E76F7100CA6B5E /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 351D904110E76F7100CA6B5E /* Help */; }; 3522BEE0153316AA00035B90 /* ExtendedNSArray.m in Sources */ = {isa = PBXBuildFile; fileRef = 3522BEDF153316AA00035B90 /* ExtendedNSArray.m */; }; 3522BEE31533552200035B90 /* ExtendedNSPredicate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3522BEE21533552200035B90 /* ExtendedNSPredicate.m */; }; @@ -34,14 +29,10 @@ 3528ECEC10E3F87E003C8CB9 /* Syncing_arrows4.png in Resources */ = {isa = PBXBuildFile; fileRef = 3528ECE610E3F87E003C8CB9 /* Syncing_arrows4.png */; }; 3528ECED10E3F87E003C8CB9 /* Syncing_arrows5.png in Resources */ = {isa = PBXBuildFile; fileRef = 3528ECE710E3F87E003C8CB9 /* Syncing_arrows5.png */; }; 3528ECEE10E3F87E003C8CB9 /* Syncing_arrows6.png in Resources */ = {isa = PBXBuildFile; fileRef = 3528ECE810E3F87E003C8CB9 /* Syncing_arrows6.png */; }; - 352E048D106279F50071E25B /* ListController.m in Sources */ = {isa = PBXBuildFile; fileRef = 352E048C106279F50071E25B /* ListController.m */; }; - 352E049F10627A830071E25B /* Editor.xib in Resources */ = {isa = PBXBuildFile; fileRef = 352E049E10627A830071E25B /* Editor.xib */; }; 352E04BA10627C1D0071E25B /* ApplicationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 352E04B910627C1D0071E25B /* ApplicationController.m */; }; 352E04FD106281940071E25B /* Create.png in Resources */ = {isa = PBXBuildFile; fileRef = 352E04F9106281940071E25B /* Create.png */; }; 352E04FF106281940071E25B /* Save.png in Resources */ = {isa = PBXBuildFile; fileRef = 352E04FB106281940071E25B /* Save.png */; }; - 352E050E106283630071E25B /* EditorController.m in Sources */ = {isa = PBXBuildFile; fileRef = 352E050D106283630071E25B /* EditorController.m */; }; 352E0556106288400071E25B /* Node.m in Sources */ = {isa = PBXBuildFile; fileRef = 352E0555106288400071E25B /* Node.m */; }; - 352E059310628A2F0071E25B /* Cell.m in Sources */ = {isa = PBXBuildFile; fileRef = 352E059210628A2F0071E25B /* Cell.m */; }; 352E05B610628D790071E25B /* Activated.png in Resources */ = {isa = PBXBuildFile; fileRef = 352E05B310628D790071E25B /* Activated.png */; }; 352E05B710628D790071E25B /* Blue Dot.png in Resources */ = {isa = PBXBuildFile; fileRef = 352E05B410628D790071E25B /* Blue Dot.png */; }; 352E05DA10628E400071E25B /* Local File.png in Resources */ = {isa = PBXBuildFile; fileRef = 352E05D910628E400071E25B /* Local File.png */; }; @@ -54,16 +45,12 @@ 353A80BB10B01C050005CAD1 /* Preferences.m in Sources */ = {isa = PBXBuildFile; fileRef = 353A80BA10B01C050005CAD1 /* Preferences.m */; }; 353A80CA10B01F320005CAD1 /* ExtendedNSString.m in Sources */ = {isa = PBXBuildFile; fileRef = 353A80C910B01F320005CAD1 /* ExtendedNSString.m */; }; 353A80FC10B020B10005CAD1 /* UserDefaults.plist in Resources */ = {isa = PBXBuildFile; fileRef = 353A80FB10B020B10005CAD1 /* UserDefaults.plist */; }; - 3541CE0510E4AF9B00FA00CB /* SyncingArrowsBadge.m in Sources */ = {isa = PBXBuildFile; fileRef = 3541CE0410E4AF9B00FA00CB /* SyncingArrowsBadge.m */; }; 3544E2D310E4CCB1009AC525 /* Map.m in Sources */ = {isa = PBXBuildFile; fileRef = 3544E2D210E4CCB1009AC525 /* Map.m */; }; 3545DC7310E38DDE00EBA66D /* Offline.png in Resources */ = {isa = PBXBuildFile; fileRef = 3545DC7210E38DDE00EBA66D /* Offline.png */; }; 3545DCA910E396CF00EBA66D /* Network.m in Sources */ = {isa = PBXBuildFile; fileRef = 3545DCA810E396CF00EBA66D /* Network.m */; }; 3545DCE410E3997200EBA66D /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3545DCE310E3997200EBA66D /* SystemConfiguration.framework */; }; - 3546AF7815318F48006B293B /* CombinedHostsPredicateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3546AF7715318F48006B293B /* CombinedHostsPredicateController.m */; }; - 3546AF7F1531B7C2006B293B /* CombinedHostsPredicateEditorRowTemplate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3546AF7E1531B7C2006B293B /* CombinedHostsPredicateEditorRowTemplate.m */; }; 35498EDB12187F5D00E46952 /* Offline_rollover.png in Resources */ = {isa = PBXBuildFile; fileRef = 35498EDA12187F5D00E46952 /* Offline_rollover.png */; }; 354C0BC510E782A5005B9A33 /* hosts.icns in Resources */ = {isa = PBXBuildFile; fileRef = 354C0BC410E782A5005B9A33 /* hosts.icns */; }; - 354CCF811117285300EB6948 /* HostsListViewMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 354CCF801117285300EB6948 /* HostsListViewMenu.m */; }; 354E7E4010AEB09D00FC4757 /* HostsMainController.m in Sources */ = {isa = PBXBuildFile; fileRef = 354E7E3F10AEB09D00FC4757 /* HostsMainController.m */; }; 354E7E7410AEB25100FC4757 /* Hosts.m in Sources */ = {isa = PBXBuildFile; fileRef = 354E7E7310AEB25100FC4757 /* Hosts.m */; }; 354E7EB110AEEF3D00FC4757 /* StructureConverter.m in Sources */ = {isa = PBXBuildFile; fileRef = 354E7EB010AEEF3D00FC4757 /* StructureConverter.m */; }; @@ -78,11 +65,9 @@ 35621A3110E52F6C00772473 /* AboutBoxController.m in Sources */ = {isa = PBXBuildFile; fileRef = 35621A3010E52F6C00772473 /* AboutBoxController.m */; }; 356712201132E2A700DF30E0 /* NetworkStatus.m in Sources */ = {isa = PBXBuildFile; fileRef = 3567121F1132E2A700DF30E0 /* NetworkStatus.m */; }; 3569AEBF12303479007DF95F /* Preferences+Remote.m in Sources */ = {isa = PBXBuildFile; fileRef = 3569AEBE12303479007DF95F /* Preferences+Remote.m */; }; - 356DB76A1824EAFD0020CEA0 /* ExtendedNSSplitView.m in Sources */ = {isa = PBXBuildFile; fileRef = 356DB7691824EAFD0020CEA0 /* ExtendedNSSplitView.m */; }; 35730FCF113424C100066B68 /* RemoteHosts.m in Sources */ = {isa = PBXBuildFile; fileRef = 35730FCE113424C100066B68 /* RemoteHosts.m */; }; 35730FDD1134291300066B68 /* Remote old.png in Resources */ = {isa = PBXBuildFile; fileRef = 35730FDC1134291300066B68 /* Remote old.png */; }; 35730FE911342C6E00066B68 /* Remote_disabled.png in Resources */ = {isa = PBXBuildFile; fileRef = 35730FE811342C6E00066B68 /* Remote_disabled.png */; }; - 35738409110209AB00E8DB54 /* HostsListView.m in Sources */ = {isa = PBXBuildFile; fileRef = 35738408110209AB00E8DB54 /* HostsListView.m */; }; 3579F0EA1114B7E700123416 /* launcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 35FC65991114B12600BD18C3 /* launcher.m */; }; 3579F0EB1114B7EF00123416 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 3579F1181114B95300123416 /* Launcher.app in Resources */ = {isa = PBXBuildFile; fileRef = 3579F0CD1114B7B900123416 /* Launcher.app */; }; @@ -111,7 +96,6 @@ 35D4CBFB10E6934700B9F63A /* ExtendedNSTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 35D4CBFA10E6934700B9F63A /* ExtendedNSTextView.m */; }; 35D83BEF20DD97CF00169358 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 35D83BEE20DD97CF00169358 /* Assets.xcassets */; }; 35DA4DF6123BF4F30043CA87 /* sparkle_dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 35DA4DF5123BF4F30043CA87 /* sparkle_dsa_pub.pem */; }; - 35E32D841211D52F00B2C631 /* OfflineBadge.m in Sources */ = {isa = PBXBuildFile; fileRef = 35E32D831211D52F00B2C631 /* OfflineBadge.m */; }; 35E32EDC12145CF900B2C631 /* Logger.m in Sources */ = {isa = PBXBuildFile; fileRef = 35E32EDB12145CF900B2C631 /* Logger.m */; }; 35E33075121490B100B2C631 /* ExtendedNSThread.m in Sources */ = {isa = PBXBuildFile; fileRef = 35E33074121490B100B2C631 /* ExtendedNSThread.m */; }; 35E9008A1147F42900851A25 /* MAAttachedWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = 35E900891147F42900851A25 /* MAAttachedWindow.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; @@ -137,14 +121,17 @@ AA00000E000000000000AAAA /* URLSheetPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA00000D000000000000AAAA /* URLSheetPresenterTests.swift */; }; AA000010000000000000AAAA /* URLSheetViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA00000F000000000000AAAA /* URLSheetViewTests.swift */; }; AA000012000000000000AAAA /* HostsDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000011000000000000AAAA /* HostsDataStore.swift */; }; + 4367D8248BF62656D4AC86D1 /* EditorToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD2C7D6281D5A0139260D12A /* EditorToolbar.swift */; }; + DD2A255B82569B83ED993DC2 /* StatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D979CFF69FCB07AB746051E /* StatusBarView.swift */; }; + 7189711AD9756F2CBD4BCF22 /* EditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C64FE428AF00EB0DEEAAEBD /* EditorView.swift */; }; + 3CC80FFF3B4950D6CB6B71D1 /* EditorWindowPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 682E8D22B3228ABF7EBFDC08 /* EditorWindowPresenter.swift */; }; AA000014000000000000AAAA /* HostsRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000013000000000000AAAA /* HostsRowView.swift */; }; AA000016000000000000AAAA /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000015000000000000AAAA /* SidebarView.swift */; }; - AA000018000000000000AAAA /* SidebarInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000017000000000000AAAA /* SidebarInstaller.swift */; }; AA00001A000000000000AAAA /* HostsDataStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000019000000000000AAAA /* HostsDataStoreTests.swift */; }; + EE000002000000000000EE01 /* EditorWindowPresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE000001000000000000EE01 /* EditorWindowPresenterTests.swift */; }; AA00001C000000000000AAAA /* HostsTextViewRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA00001B000000000000AAAA /* HostsTextViewRepresentable.swift */; }; AA00001E000000000000AAAA /* CombinedHostsPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA00001D000000000000AAAA /* CombinedHostsPickerView.swift */; }; AA000020000000000000AAAA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA00001F000000000000AAAA /* ContentView.swift */; }; - AA000022000000000000AAAA /* ContentInstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA000021000000000000AAAA /* ContentInstaller.swift */; }; BB000002000000000000BBBB /* RemoteIntervalMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB000001000000000000BBBB /* RemoteIntervalMapper.swift */; }; BB000004000000000000BBBB /* ShortcutRecorderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB000003000000000000BBBB /* ShortcutRecorderView.swift */; }; CC000002000000000000CC01 /* GlobalShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC000001000000000000CC01 /* GlobalShortcuts.swift */; }; @@ -203,16 +190,8 @@ 350920C41226EDD600ACA0F4 /* AbstractHostsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AbstractHostsManager.m; path = Source/AbstractHostsManager.m; sourceTree = ""; }; 350C7F381A3C55B000B46B09 /* Create@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Create@2x.png"; path = "Resources/Images/Create@2x.png"; sourceTree = ""; }; 350C7F3A1A3C56BB00B46B09 /* Read Only@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Read Only@2x.png"; path = "Resources/Images/Read Only@2x.png"; sourceTree = ""; }; - 350E7D3C121093E400D2F5F5 /* AlertBadge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AlertBadge.h; path = Source/AlertBadge.h; sourceTree = ""; }; - 350E7D3D121093E400D2F5F5 /* AlertBadge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AlertBadge.m; path = Source/AlertBadge.m; sourceTree = ""; }; - 350E7D7C12111B2300D2F5F5 /* Badge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Badge.h; path = Source/Badge.h; sourceTree = ""; }; - 350E7D7D12111B2300D2F5F5 /* Badge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Badge.m; path = Source/Badge.m; sourceTree = ""; }; - 350E7D921211A7AE00D2F5F5 /* BadgeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BadgeManager.h; path = Source/BadgeManager.h; sourceTree = ""; }; - 350E7D931211A7AE00D2F5F5 /* BadgeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BadgeManager.m; path = Source/BadgeManager.m; sourceTree = ""; }; 351167BE10D6346000A5FAA1 /* HostsGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HostsGroup.h; path = Source/HostsGroup.h; sourceTree = ""; }; 351167BF10D6346000A5FAA1 /* HostsGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HostsGroup.m; path = Source/HostsGroup.m; sourceTree = ""; }; - 3511697610D68B1000A5FAA1 /* FilesCountTransformer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FilesCountTransformer.h; path = Source/FilesCountTransformer.h; sourceTree = ""; }; - 3511697710D68B1000A5FAA1 /* FilesCountTransformer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FilesCountTransformer.m; path = Source/FilesCountTransformer.m; sourceTree = ""; }; 35116A6E10D6AAD200A5FAA1 /* Menulet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Menulet.h; path = Source/Menulet.h; sourceTree = ""; }; 35116A6F10D6AAD200A5FAA1 /* Menulet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Menulet.m; path = Source/Menulet.m; sourceTree = ""; }; 35116ABE10D6AF0500A5FAA1 /* LoginItem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LoginItem.h; path = Source/LoginItem.h; sourceTree = ""; }; @@ -220,8 +199,6 @@ 3513A5FF113908A900AD789D /* Read Only.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Read Only.png"; path = "Resources/Images/Read Only.png"; sourceTree = ""; }; 3513A61A11390B7900AD789D /* Error.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Error.h; path = Source/Error.h; sourceTree = ""; }; 3513A61B11390B7900AD789D /* Error.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Error.m; path = Source/Error.m; sourceTree = ""; }; - 3516864911187EDE00314609 /* HostsTextController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HostsTextController.h; path = Source/HostsTextController.h; sourceTree = ""; }; - 3516864A11187EDE00314609 /* HostsTextController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HostsTextController.m; path = Source/HostsTextController.m; sourceTree = ""; }; 351D904110E76F7100CA6B5E /* Help */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Help; sourceTree = ""; }; 3522BEDE153316AA00035B90 /* ExtendedNSArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ExtendedNSArray.h; path = Source/ExtendedNSArray.h; sourceTree = ""; }; 3522BEDF153316AA00035B90 /* ExtendedNSArray.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ExtendedNSArray.m; path = Source/ExtendedNSArray.m; sourceTree = ""; }; @@ -233,19 +210,12 @@ 3528ECE610E3F87E003C8CB9 /* Syncing_arrows4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Syncing_arrows4.png; path = Resources/Images/Syncing_arrows4.png; sourceTree = ""; }; 3528ECE710E3F87E003C8CB9 /* Syncing_arrows5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Syncing_arrows5.png; path = Resources/Images/Syncing_arrows5.png; sourceTree = ""; }; 3528ECE810E3F87E003C8CB9 /* Syncing_arrows6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Syncing_arrows6.png; path = Resources/Images/Syncing_arrows6.png; sourceTree = ""; }; - 352E048B106279F50071E25B /* ListController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ListController.h; path = Source/ListController.h; sourceTree = ""; }; - 352E048C106279F50071E25B /* ListController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ListController.m; path = Source/ListController.m; sourceTree = ""; }; - 352E049E10627A830071E25B /* Editor.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Editor.xib; sourceTree = ""; }; 352E04B810627C1D0071E25B /* ApplicationController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ApplicationController.h; path = Source/ApplicationController.h; sourceTree = ""; }; 352E04B910627C1D0071E25B /* ApplicationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ApplicationController.m; path = Source/ApplicationController.m; sourceTree = ""; }; 352E04F9106281940071E25B /* Create.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Create.png; path = Resources/Images/Create.png; sourceTree = ""; }; 352E04FB106281940071E25B /* Save.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Save.png; path = Resources/Images/Save.png; sourceTree = ""; }; - 352E050C106283630071E25B /* EditorController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EditorController.h; path = Source/EditorController.h; sourceTree = ""; }; - 352E050D106283630071E25B /* EditorController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = EditorController.m; path = Source/EditorController.m; sourceTree = ""; }; 352E0554106288400071E25B /* Node.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Node.h; path = Source/Node.h; sourceTree = ""; }; 352E0555106288400071E25B /* Node.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Node.m; path = Source/Node.m; sourceTree = ""; }; - 352E059110628A2F0071E25B /* Cell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Cell.h; path = Source/Cell.h; sourceTree = ""; }; - 352E059210628A2F0071E25B /* Cell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Cell.m; path = Source/Cell.m; sourceTree = ""; }; 352E05B310628D790071E25B /* Activated.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Activated.png; path = Resources/Images/Activated.png; sourceTree = ""; }; 352E05B410628D790071E25B /* Blue Dot.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Blue Dot.png"; path = "Resources/Images/Blue Dot.png"; sourceTree = ""; }; 352E05D910628E400071E25B /* Local File.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Local File.png"; path = "Resources/Images/Local File.png"; sourceTree = ""; }; @@ -262,23 +232,14 @@ 353A80C810B01F320005CAD1 /* ExtendedNSString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ExtendedNSString.h; path = Source/ExtendedNSString.h; sourceTree = ""; }; 353A80C910B01F320005CAD1 /* ExtendedNSString.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ExtendedNSString.m; path = Source/ExtendedNSString.m; sourceTree = ""; }; 353A80FB10B020B10005CAD1 /* UserDefaults.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = UserDefaults.plist; path = Resources/UserDefaults.plist; sourceTree = ""; }; - 3541CE0310E4AF9B00FA00CB /* SyncingArrowsBadge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SyncingArrowsBadge.h; path = Source/SyncingArrowsBadge.h; sourceTree = ""; }; - 3541CE0410E4AF9B00FA00CB /* SyncingArrowsBadge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SyncingArrowsBadge.m; path = Source/SyncingArrowsBadge.m; sourceTree = ""; }; 3544E2D110E4CCB1009AC525 /* Map.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Map.h; path = Source/Map.h; sourceTree = ""; }; 3544E2D210E4CCB1009AC525 /* Map.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Map.m; path = Source/Map.m; sourceTree = ""; }; 3545DC7210E38DDE00EBA66D /* Offline.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Offline.png; path = Resources/Images/Offline.png; sourceTree = ""; }; 3545DCA710E396CF00EBA66D /* Network.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Network.h; path = Source/Network.h; sourceTree = ""; }; 3545DCA810E396CF00EBA66D /* Network.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Network.m; path = Source/Network.m; sourceTree = ""; }; 3545DCE310E3997200EBA66D /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = /System/Library/Frameworks/SystemConfiguration.framework; sourceTree = ""; }; - 3546AF7615318F48006B293B /* CombinedHostsPredicateController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CombinedHostsPredicateController.h; path = Source/CombinedHostsPredicateController.h; sourceTree = ""; }; - 3546AF7715318F48006B293B /* CombinedHostsPredicateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CombinedHostsPredicateController.m; path = Source/CombinedHostsPredicateController.m; sourceTree = ""; }; - 3546AF7D1531B7C2006B293B /* CombinedHostsPredicateEditorRowTemplate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CombinedHostsPredicateEditorRowTemplate.h; path = Source/CombinedHostsPredicateEditorRowTemplate.h; sourceTree = ""; }; - 3546AF7E1531B7C2006B293B /* CombinedHostsPredicateEditorRowTemplate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CombinedHostsPredicateEditorRowTemplate.m; path = Source/CombinedHostsPredicateEditorRowTemplate.m; sourceTree = ""; }; 35498EDA12187F5D00E46952 /* Offline_rollover.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Offline_rollover.png; path = Resources/Images/Offline_rollover.png; sourceTree = ""; }; 354C0BC410E782A5005B9A33 /* hosts.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = hosts.icns; path = Resources/hosts.icns; sourceTree = ""; }; - 354CCF7F1117285300EB6948 /* HostsListViewMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HostsListViewMenu.h; path = Source/HostsListViewMenu.h; sourceTree = ""; }; - 354CCF801117285300EB6948 /* HostsListViewMenu.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HostsListViewMenu.m; path = Source/HostsListViewMenu.m; sourceTree = ""; }; - 354DDCED114EAC5000DB76D7 /* NSToolbarPoofAnimator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NSToolbarPoofAnimator.h; path = Source/NSToolbarPoofAnimator.h; sourceTree = ""; }; 354E7E3E10AEB09D00FC4757 /* HostsMainController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HostsMainController.h; path = Source/HostsMainController.h; sourceTree = ""; }; 354E7E3F10AEB09D00FC4757 /* HostsMainController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HostsMainController.m; path = Source/HostsMainController.m; sourceTree = ""; }; 354E7E7210AEB25100FC4757 /* Hosts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Hosts.h; path = Source/Hosts.h; sourceTree = ""; }; @@ -304,14 +265,10 @@ 3567121F1132E2A700DF30E0 /* NetworkStatus.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NetworkStatus.m; path = Source/NetworkStatus.m; sourceTree = ""; }; 3569AEBD12303479007DF95F /* Preferences+Remote.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "Preferences+Remote.h"; path = "Source/Preferences+Remote.h"; sourceTree = ""; }; 3569AEBE12303479007DF95F /* Preferences+Remote.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "Preferences+Remote.m"; path = "Source/Preferences+Remote.m"; sourceTree = ""; }; - 356DB7681824EAFD0020CEA0 /* ExtendedNSSplitView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ExtendedNSSplitView.h; path = Source/ExtendedNSSplitView.h; sourceTree = ""; }; - 356DB7691824EAFD0020CEA0 /* ExtendedNSSplitView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ExtendedNSSplitView.m; path = Source/ExtendedNSSplitView.m; sourceTree = ""; }; 35730FCD113424C100066B68 /* RemoteHosts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RemoteHosts.h; path = Source/RemoteHosts.h; sourceTree = ""; }; 35730FCE113424C100066B68 /* RemoteHosts.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RemoteHosts.m; path = Source/RemoteHosts.m; sourceTree = ""; }; 35730FDC1134291300066B68 /* Remote old.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Remote old.png"; path = "Resources/Images/Remote old.png"; sourceTree = ""; }; 35730FE811342C6E00066B68 /* Remote_disabled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = Remote_disabled.png; path = Resources/Images/Remote_disabled.png; sourceTree = ""; }; - 35738407110209AB00E8DB54 /* HostsListView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HostsListView.h; path = Source/HostsListView.h; sourceTree = ""; }; - 35738408110209AB00E8DB54 /* HostsListView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HostsListView.m; path = Source/HostsListView.m; sourceTree = ""; }; 3579F0CD1114B7B900123416 /* Launcher.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Launcher.app; sourceTree = BUILT_PRODUCTS_DIR; }; 3579F0CF1114B7B900123416 /* Launcher-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Launcher-Info.plist"; sourceTree = ""; }; 358448B110DFE283002E6A9B /* Util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Util.h; path = Source/Util.h; sourceTree = ""; }; @@ -339,14 +296,17 @@ AA00000D000000000000AAAA /* URLSheetPresenterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSheetPresenterTests.swift; sourceTree = ""; }; AA00000F000000000000AAAA /* URLSheetViewTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSheetViewTests.swift; sourceTree = ""; }; AA000011000000000000AAAA /* HostsDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HostsDataStore.swift; path = "Source/Swift/HostsDataStore.swift"; sourceTree = ""; }; + CD2C7D6281D5A0139260D12A /* EditorToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EditorToolbar.swift; path = "Source/Swift/EditorToolbar.swift"; sourceTree = ""; }; + 3D979CFF69FCB07AB746051E /* StatusBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StatusBarView.swift; path = "Source/Swift/StatusBarView.swift"; sourceTree = ""; }; + 7C64FE428AF00EB0DEEAAEBD /* EditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EditorView.swift; path = "Source/Swift/EditorView.swift"; sourceTree = ""; }; + 682E8D22B3228ABF7EBFDC08 /* EditorWindowPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = EditorWindowPresenter.swift; path = "Source/Swift/EditorWindowPresenter.swift"; sourceTree = ""; }; AA000013000000000000AAAA /* HostsRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HostsRowView.swift; path = "Source/Swift/HostsRowView.swift"; sourceTree = ""; }; AA000015000000000000AAAA /* SidebarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SidebarView.swift; path = "Source/Swift/SidebarView.swift"; sourceTree = ""; }; - AA000017000000000000AAAA /* SidebarInstaller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SidebarInstaller.swift; path = "Source/Swift/SidebarInstaller.swift"; sourceTree = ""; }; AA000019000000000000AAAA /* HostsDataStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HostsDataStoreTests.swift; sourceTree = ""; }; + EE000001000000000000EE01 /* EditorWindowPresenterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorWindowPresenterTests.swift; sourceTree = ""; }; AA00001B000000000000AAAA /* HostsTextViewRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HostsTextViewRepresentable.swift; path = "Source/Swift/HostsTextViewRepresentable.swift"; sourceTree = ""; }; AA00001D000000000000AAAA /* CombinedHostsPickerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CombinedHostsPickerView.swift; path = "Source/Swift/CombinedHostsPickerView.swift"; sourceTree = ""; }; AA00001F000000000000AAAA /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContentView.swift; path = "Source/Swift/ContentView.swift"; sourceTree = ""; }; - AA000021000000000000AAAA /* ContentInstaller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ContentInstaller.swift; path = "Source/Swift/ContentInstaller.swift"; sourceTree = ""; }; 35A183A71A0ACF37002D6289 /* menuIcon@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = "menuIcon@2x.tiff"; path = "Resources/Images/menuIcon@2x.tiff"; sourceTree = ""; }; BB000001000000000000BBBB /* RemoteIntervalMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RemoteIntervalMapper.swift; path = "Source/Swift/RemoteIntervalMapper.swift"; sourceTree = ""; }; BB000003000000000000BBBB /* ShortcutRecorderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ShortcutRecorderView.swift; path = "Source/Swift/ShortcutRecorderView.swift"; sourceTree = ""; }; @@ -379,8 +339,6 @@ 35D4CBFA10E6934700B9F63A /* ExtendedNSTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ExtendedNSTextView.m; path = Source/ExtendedNSTextView.m; sourceTree = ""; }; 35D83BEE20DD97CF00169358 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 35DA4DF5123BF4F30043CA87 /* sparkle_dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = sparkle_dsa_pub.pem; path = Resources/sparkle_dsa_pub.pem; sourceTree = ""; }; - 35E32D821211D52F00B2C631 /* OfflineBadge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = OfflineBadge.h; path = Source/OfflineBadge.h; sourceTree = ""; }; - 35E32D831211D52F00B2C631 /* OfflineBadge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OfflineBadge.m; path = Source/OfflineBadge.m; sourceTree = ""; }; 35E32EDA12145CF900B2C631 /* Logger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Logger.h; path = Source/Logger.h; sourceTree = ""; }; 35E32EDB12145CF900B2C631 /* Logger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Logger.m; path = Source/Logger.m; sourceTree = ""; }; 35E33073121490B100B2C631 /* ExtendedNSThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ExtendedNSThread.h; path = Source/ExtendedNSThread.h; sourceTree = ""; }; @@ -445,8 +403,6 @@ 3511696F10D68AE800A5FAA1 /* GUI */, 3513A61A11390B7900AD789D /* Error.h */, 3513A61B11390B7900AD789D /* Error.m */, - 352E050C106283630071E25B /* EditorController.h */, - 352E050D106283630071E25B /* EditorController.m */, 35116ABE10D6AF0500A5FAA1 /* LoginItem.h */, 35116ABF10D6AF0500A5FAA1 /* LoginItem.m */, ); @@ -535,7 +491,6 @@ 3579F0CF1114B7B900123416 /* Launcher-Info.plist */, 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, 1DDD58140DA1D0A300B32029 /* MainMenu.xib */, - 352E049E10627A830071E25B /* Editor.xib */, 359289AA1659222E00492494 /* CHANGELOG.txt */, ); name = Resources; @@ -553,16 +508,6 @@ 350E7D3A121093B400D2F5F5 /* Cell Badges */ = { isa = PBXGroup; children = ( - 350E7D921211A7AE00D2F5F5 /* BadgeManager.h */, - 350E7D931211A7AE00D2F5F5 /* BadgeManager.m */, - 350E7D7C12111B2300D2F5F5 /* Badge.h */, - 350E7D7D12111B2300D2F5F5 /* Badge.m */, - 3541CE0310E4AF9B00FA00CB /* SyncingArrowsBadge.h */, - 3541CE0410E4AF9B00FA00CB /* SyncingArrowsBadge.m */, - 350E7D3C121093E400D2F5F5 /* AlertBadge.h */, - 350E7D3D121093E400D2F5F5 /* AlertBadge.m */, - 35E32D821211D52F00B2C631 /* OfflineBadge.h */, - 35E32D831211D52F00B2C631 /* OfflineBadge.m */, ); name = "Cell Badges"; sourceTree = ""; @@ -622,8 +567,6 @@ children = ( 3513F353115153560046FFF7 /* Hosts Text View */, 3536E03B114BB4B700972A49 /* Hosts List */, - 3511697610D68B1000A5FAA1 /* FilesCountTransformer.h */, - 3511697710D68B1000A5FAA1 /* FilesCountTransformer.m */, ); name = Editor; sourceTree = ""; @@ -633,8 +576,6 @@ children = ( 353A804510B01AEF0005CAD1 /* HostsTextView.h */, 353A804610B01AEF0005CAD1 /* HostsTextView.m */, - 3516864911187EDE00314609 /* HostsTextController.h */, - 3516864A11187EDE00314609 /* HostsTextController.m */, ); name = "Hosts Text View"; sourceTree = ""; @@ -652,14 +593,6 @@ isa = PBXGroup; children = ( 350E7D3A121093B400D2F5F5 /* Cell Badges */, - 352E048B106279F50071E25B /* ListController.h */, - 352E048C106279F50071E25B /* ListController.m */, - 35738407110209AB00E8DB54 /* HostsListView.h */, - 35738408110209AB00E8DB54 /* HostsListView.m */, - 354CCF7F1117285300EB6948 /* HostsListViewMenu.h */, - 354CCF801117285300EB6948 /* HostsListViewMenu.m */, - 352E059110628A2F0071E25B /* Cell.h */, - 352E059210628A2F0071E25B /* Cell.m */, 352E0554106288400071E25B /* Node.h */, 352E0555106288400071E25B /* Node.m */, 351167BE10D6346000A5FAA1 /* HostsGroup.h */, @@ -787,13 +720,15 @@ AA000007000000000000AAAA /* URLSheetView.swift */, AA000009000000000000AAAA /* URLSheetPresenter.swift */, AA000011000000000000AAAA /* HostsDataStore.swift */, + CD2C7D6281D5A0139260D12A /* EditorToolbar.swift */, + 3D979CFF69FCB07AB746051E /* StatusBarView.swift */, + 7C64FE428AF00EB0DEEAAEBD /* EditorView.swift */, + 682E8D22B3228ABF7EBFDC08 /* EditorWindowPresenter.swift */, AA000013000000000000AAAA /* HostsRowView.swift */, AA000015000000000000AAAA /* SidebarView.swift */, - AA000017000000000000AAAA /* SidebarInstaller.swift */, AA00001B000000000000AAAA /* HostsTextViewRepresentable.swift */, AA00001D000000000000AAAA /* CombinedHostsPickerView.swift */, AA00001F000000000000AAAA /* ContentView.swift */, - AA000021000000000000AAAA /* ContentInstaller.swift */, BB000020000000000000BBBB /* Preferences */, ); name = Swift; @@ -819,10 +754,6 @@ 35D3328D152F5A87001DA824 /* CombinedHosts.m */, 35D3328F152F5B7B001DA824 /* CombinedHostsController.h */, 35D33290152F5B7B001DA824 /* CombinedHostsController.m */, - 3546AF7615318F48006B293B /* CombinedHostsPredicateController.h */, - 3546AF7715318F48006B293B /* CombinedHostsPredicateController.m */, - 3546AF7D1531B7C2006B293B /* CombinedHostsPredicateEditorRowTemplate.h */, - 3546AF7E1531B7C2006B293B /* CombinedHostsPredicateEditorRowTemplate.m */, ); name = "Combined Hosts"; sourceTree = ""; @@ -830,7 +761,6 @@ 35D4C98210E64D8800B9F63A /* Extended Next Step */ = { isa = PBXGroup; children = ( - 354DDCED114EAC5000DB76D7 /* NSToolbarPoofAnimator.h */, 35D4CBF910E6934700B9F63A /* ExtendedNSTextView.h */, 35D4CBFA10E6934700B9F63A /* ExtendedNSTextView.m */, 353A80C810B01F320005CAD1 /* ExtendedNSString.h */, @@ -843,8 +773,6 @@ 3522BEDF153316AA00035B90 /* ExtendedNSArray.m */, 3522BEE11533552200035B90 /* ExtendedNSPredicate.h */, 3522BEE21533552200035B90 /* ExtendedNSPredicate.m */, - 356DB7681824EAFD0020CEA0 /* ExtendedNSSplitView.h */, - 356DB7691824EAFD0020CEA0 /* ExtendedNSSplitView.m */, ); name = "Extended Next Step"; sourceTree = ""; @@ -878,6 +806,7 @@ AA00000D000000000000AAAA /* URLSheetPresenterTests.swift */, AA00000F000000000000AAAA /* URLSheetViewTests.swift */, AA000019000000000000AAAA /* HostsDataStoreTests.swift */, + EE000001000000000000EE01 /* EditorWindowPresenterTests.swift */, BB00000D000000000000BBBB /* RemoteIntervalMapperTests.swift */, BB00000F000000000000BBBB /* SparkleObserverTests.swift */, BB000011000000000000BBBB /* PreferencesPresenterTests.swift */, @@ -1014,7 +943,6 @@ 3579F1181114B95300123416 /* Launcher.app in Resources */, 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, 1DDD58160DA1D0A300B32029 /* MainMenu.xib in Resources */, - 352E049F10627A830071E25B /* Editor.xib in Resources */, 352E04FD106281940071E25B /* Create.png in Resources */, 352E04FF106281940071E25B /* Save.png in Resources */, 358E0A891A3A1E1A004521D1 /* Remote yosemite@2x.tiff in Resources */, @@ -1081,11 +1009,8 @@ buildActionMask = 2147483647; files = ( 8D11072D0486CEB800E47090 /* main.m in Sources */, - 352E048D106279F50071E25B /* ListController.m in Sources */, 352E04BA10627C1D0071E25B /* ApplicationController.m in Sources */, - 352E050E106283630071E25B /* EditorController.m in Sources */, 352E0556106288400071E25B /* Node.m in Sources */, - 352E059310628A2F0071E25B /* Cell.m in Sources */, 354E7E4010AEB09D00FC4757 /* HostsMainController.m in Sources */, 354E7E7410AEB25100FC4757 /* Hosts.m in Sources */, 354E7EB110AEEF3D00FC4757 /* StructureConverter.m in Sources */, @@ -1096,20 +1021,15 @@ 353A80BB10B01C050005CAD1 /* Preferences.m in Sources */, 353A80CA10B01F320005CAD1 /* ExtendedNSString.m in Sources */, 351167C010D6346000A5FAA1 /* HostsGroup.m in Sources */, - 3511697810D68B1000A5FAA1 /* FilesCountTransformer.m in Sources */, 35116A7010D6AAD200A5FAA1 /* Menulet.m in Sources */, 35116AC010D6AF0500A5FAA1 /* LoginItem.m in Sources */, 358448B310DFE283002E6A9B /* Util.m in Sources */, 3545DCA910E396CF00EBA66D /* Network.m in Sources */, - 3541CE0510E4AF9B00FA00CB /* SyncingArrowsBadge.m in Sources */, 3544E2D310E4CCB1009AC525 /* Map.m in Sources */, 35621A3110E52F6C00772473 /* AboutBoxController.m in Sources */, 35D4CACB10E64DDD00B9F63A /* ExtendedNSApplication.m in Sources */, 35D4CBFB10E6934700B9F63A /* ExtendedNSTextView.m in Sources */, - 35738409110209AB00E8DB54 /* HostsListView.m in Sources */, 3597135D110DED0F00C7ECAF /* HostsMenu.m in Sources */, - 354CCF811117285300EB6948 /* HostsListViewMenu.m in Sources */, - 3516864B11187EDE00314609 /* HostsTextController.m in Sources */, 356712201132E2A700DF30E0 /* NetworkStatus.m in Sources */, 35730FCF113424C100066B68 /* RemoteHosts.m in Sources */, 35D2C253113507A6007C8037 /* RemoteHostsManager.m in Sources */, @@ -1119,13 +1039,15 @@ AA000008000000000000AAAA /* URLSheetView.swift in Sources */, AA00000A000000000000AAAA /* URLSheetPresenter.swift in Sources */, AA000012000000000000AAAA /* HostsDataStore.swift in Sources */, + 4367D8248BF62656D4AC86D1 /* EditorToolbar.swift in Sources */, + DD2A255B82569B83ED993DC2 /* StatusBarView.swift in Sources */, + 7189711AD9756F2CBD4BCF22 /* EditorView.swift in Sources */, + 3CC80FFF3B4950D6CB6B71D1 /* EditorWindowPresenter.swift in Sources */, AA000014000000000000AAAA /* HostsRowView.swift in Sources */, AA000016000000000000AAAA /* SidebarView.swift in Sources */, - AA000018000000000000AAAA /* SidebarInstaller.swift in Sources */, AA00001C000000000000AAAA /* HostsTextViewRepresentable.swift in Sources */, AA00001E000000000000AAAA /* CombinedHostsPickerView.swift in Sources */, AA000020000000000000AAAA /* ContentView.swift in Sources */, - AA000022000000000000AAAA /* ContentInstaller.swift in Sources */, BB000002000000000000BBBB /* RemoteIntervalMapper.swift in Sources */, BB000004000000000000BBBB /* ShortcutRecorderView.swift in Sources */, CC000002000000000000CC01 /* GlobalShortcuts.swift in Sources */, @@ -1133,12 +1055,7 @@ BB000008000000000000BBBB /* LoginItemObserver.swift in Sources */, BB00000A000000000000BBBB /* PreferencesView.swift in Sources */, BB00000C000000000000BBBB /* PreferencesPresenter.swift in Sources */, - 356DB76A1824EAFD0020CEA0 /* ExtendedNSSplitView.m in Sources */, 35E9008A1147F42900851A25 /* MAAttachedWindow.m in Sources */, - 350E7D3E121093E400D2F5F5 /* AlertBadge.m in Sources */, - 350E7D7E12111B2300D2F5F5 /* Badge.m in Sources */, - 350E7D941211A7AE00D2F5F5 /* BadgeManager.m in Sources */, - 35E32D841211D52F00B2C631 /* OfflineBadge.m in Sources */, 35E32EDC12145CF900B2C631 /* Logger.m in Sources */, 35E33075121490B100B2C631 /* ExtendedNSThread.m in Sources */, 35FBCA3B1223172300860FDA /* RegexKitLite.m in Sources */, @@ -1154,8 +1071,6 @@ 35F414F4152E1B7800B99583 /* VDKQueue.m in Sources */, 35D3328E152F5A87001DA824 /* CombinedHosts.m in Sources */, 35D33291152F5B7B001DA824 /* CombinedHostsController.m in Sources */, - 3546AF7815318F48006B293B /* CombinedHostsPredicateController.m in Sources */, - 3546AF7F1531B7C2006B293B /* CombinedHostsPredicateEditorRowTemplate.m in Sources */, 3522BEE0153316AA00035B90 /* ExtendedNSArray.m in Sources */, 3522BEE31533552200035B90 /* ExtendedNSPredicate.m in Sources */, 359967541656B52500BCF16D /* NotificationHelper.m in Sources */, @@ -1175,6 +1090,7 @@ AA00000E000000000000AAAA /* URLSheetPresenterTests.swift in Sources */, AA000010000000000000AAAA /* URLSheetViewTests.swift in Sources */, AA00001A000000000000AAAA /* HostsDataStoreTests.swift in Sources */, + EE000002000000000000EE01 /* EditorWindowPresenterTests.swift in Sources */, BB00000E000000000000BBBB /* RemoteIntervalMapperTests.swift in Sources */, BB000010000000000000BBBB /* SparkleObserverTests.swift in Sources */, BB000012000000000000BBBB /* PreferencesPresenterTests.swift in Sources */, diff --git a/Source/AlertBadge.h b/Source/AlertBadge.h deleted file mode 100644 index dc3b84c..0000000 --- a/Source/AlertBadge.h +++ /dev/null @@ -1,34 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "Badge.h" - -@class RemoteHosts; - -@interface AlertBadge : Badge { - @private - NSImage *icon; - NSImage *rolloverIcon; - RemoteHosts *hosts; -} - -- (id) initWithRemoteHosts:(RemoteHosts*)remoteHosts; - -@end diff --git a/Source/AlertBadge.m b/Source/AlertBadge.m deleted file mode 100644 index 028bd13..0000000 --- a/Source/AlertBadge.m +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "AlertBadge.h" -#import "Error.h" -#import "RemoteHosts.h" -#import "ListController.h" - - -@implementation AlertBadge - -- (id) initWithRemoteHosts:(RemoteHosts*)remoteHosts -{ - self = [super init]; - - [self addTrackingArea:[[NSTrackingArea alloc] initWithRect:[self frame] - options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways - owner:self - userInfo:nil]]; - - icon = [NSImage imageNamed: @"Alert.png"]; - rolloverIcon = [NSImage imageNamed: @"Alert_rollover.png"]; - - [self setActiveIcon:icon]; - - hosts = remoteHosts; - - return self; -} - -- (void)mouseEntered:(NSEvent *)theEvent -{ - [self setActiveIcon:rolloverIcon]; -} - -- (void)mouseExited:(NSEvent *)theEvent -{ - [self setActiveIcon:icon]; -} - -@end diff --git a/Source/ApplicationController.h b/Source/ApplicationController.h index 48d2f41..2703f17 100644 --- a/Source/ApplicationController.h +++ b/Source/ApplicationController.h @@ -26,10 +26,8 @@ @interface ApplicationController : NSObject { @private - IBOutlet NSProgressIndicator *busyIndicator; IBOutlet HostsMainController *hostsController; IBOutlet NSMenuItem *checkForUpdatesMenuItem; - int busyThreads; BOOL shouldQuit; BOOL editorWindowOpened; AboutBoxController *aboutBoxController; diff --git a/Source/ApplicationController.m b/Source/ApplicationController.m index fd830ea..83e82b6 100644 --- a/Source/ApplicationController.m +++ b/Source/ApplicationController.m @@ -32,7 +32,6 @@ @interface ApplicationController () { __weak NSWindow *_editorWindow; - NSArray *_editorNibTopLevelObjects; // retains top-level NIB objects (e.g. EditorController) } @end @@ -63,7 +62,6 @@ - (id)init return sharedInstance; } if (self = [super init]) { - busyThreads = 0; shouldQuit = YES; BOOL isTesting = NSClassFromString(@"XCTestCase") != nil; @@ -122,7 +120,7 @@ -(IBAction)quit:(id)sender - (IBAction)openEditorWindow:(id)sender { if (!editorWindowOpened) { - if (_editorNibTopLevelObjects) { + if (_editorWindow) { editorWindowOpened = YES; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(editorWindowWillClose:) @@ -171,22 +169,6 @@ - (BOOL)editorWindowOpened return editorWindowOpened; } -- (void)increaseBusyThreadsCount:(NSNotification *)notification -{ - busyThreads++; - [busyIndicator startAnimation:self]; -} - -- (void)decreaseBusyThreadsCount:(NSNotification *)notification -{ - if (busyThreads > 0) { - busyThreads--; - } - if (busyThreads == 0) { - [busyIndicator stopAnimation:self]; - } -} - #pragma mark - Application Delegate - (void)applicationWillFinishLaunching:(NSNotification *)aNotification @@ -199,11 +181,7 @@ - (void)applicationWillFinishLaunching:(NSNotification *)aNotification [NSApp setServicesProvider:self]; [self initStructure]; - - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc addObserver:self selector:@selector(increaseBusyThreadsCount:) name:ThreadBusyNotification object:nil]; - [nc addObserver:self selector:@selector(decreaseBusyThreadsCount:) name:ThreadNotBusyNotification object:nil]; - + [hostsController load]; if (!openedAtLogin() && [Preferences showEditorWindow]) { @@ -290,20 +268,12 @@ - (void)initStructure - (void)initEditorWindow { logDebug(@"Initializing editor window"); - NSArray *topLevelObjects = nil; - [[NSBundle mainBundle] loadNibNamed:@"Editor" owner:self topLevelObjects:&topLevelObjects]; - _editorNibTopLevelObjects = topLevelObjects; + _editorWindow = [EditorWindowPresenter createEditorWindow]; editorWindowOpened = YES; - for (id obj in topLevelObjects) { - if ([obj isKindOfClass:[NSWindow class]]) { - _editorWindow = obj; - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(editorWindowWillClose:) - name:NSWindowWillCloseNotification - object:obj]; - break; - } - } + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(editorWindowWillClose:) + name:NSWindowWillCloseNotification + object:_editorWindow]; } - (void)activatePreviousFile:(NSNotification *)note diff --git a/Source/Badge.h b/Source/Badge.h deleted file mode 100644 index 69cbcd3..0000000 --- a/Source/Badge.h +++ /dev/null @@ -1,29 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - - -@interface Badge : NSView { - @private - NSImage *activeIcon; -} - -- (void)setActiveIcon:(NSImage*)icon; - -@end diff --git a/Source/Badge.m b/Source/Badge.m deleted file mode 100644 index 7c86b33..0000000 --- a/Source/Badge.m +++ /dev/null @@ -1,51 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "Badge.h" - - -@implementation Badge - -- (id) init -{ - NSRect frame = NSMakeRect(0.0f, 0.0f, 16.0f, 16.0f); - self = [super initWithFrame:frame]; - - return self; -} - -- (void)setActiveIcon:(NSImage*)icon -{ - if (activeIcon == icon) { - return; - } - activeIcon = icon; - - [self setNeedsDisplay:YES]; -} - -- (void)drawRect:(NSRect)dirtyRect -{ - if (activeIcon != nil) { - [activeIcon drawAtPoint:[self bounds].origin fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0]; - } -} - -@end diff --git a/Source/BadgeManager.h b/Source/BadgeManager.h deleted file mode 100644 index 950d133..0000000 --- a/Source/BadgeManager.h +++ /dev/null @@ -1,36 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@class Map; -@class Badge; - -@interface BadgeManager : NSObject { - @private - Map *badges; - id target; - SEL action; -} - -+ (id)badgeManagerWithCreator:(SEL)creator target:(id)t; - -- (Badge*)getBadgeForObject:(id)object; -- (void)removeBadgeForObject:(id)object; - -@end diff --git a/Source/BadgeManager.m b/Source/BadgeManager.m deleted file mode 100644 index 740fb64..0000000 --- a/Source/BadgeManager.m +++ /dev/null @@ -1,78 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "BadgeManager.h" -#import "Badge.h" -#import "Map.h" - -@interface BadgeManager (Private) - -- (id)initWithCreator:(SEL)creator target:(id)t; - -@end - - -@implementation BadgeManager - -+ (id)badgeManagerWithCreator:(SEL)creator target:(id)t -{ - return [[self alloc] initWithCreator:creator target:t]; -} - -- (Badge*)getBadgeForObject:(id)object -{ - Badge *badge; - if ([badges haveObjectForKey:object]) { - badge = (Badge*)[badges objectForKey:object]; - } - else { - SuppressPerformSelectorLeakWarning(badge = [target performSelector:action]); - [badges addObject:badge forKey:object]; - } - - return badge; -} - -- (void)removeBadgeForObject:(id)object -{ - if ([badges haveObjectForKey:object]) { - Badge *badge = (Badge*)[badges objectForKey:object]; - [badge removeFromSuperview]; - [badges removeObjectForKey:object]; - } -} - -@end - -@implementation BadgeManager (Private) - -- (id)initWithCreator:(SEL)creator target:(id)t -{ - self = [super init]; - action = creator; - target = t; - badges = [Map new]; - - return self; - -} - -@end - diff --git a/Source/Cell.h b/Source/Cell.h deleted file mode 100644 index 2f0064f..0000000 --- a/Source/Cell.h +++ /dev/null @@ -1,45 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@class Hosts; -@class BadgeManager; - -@interface Cell : NSTextFieldCell { - @private - Hosts *item; - - NSImage *localFileIcon; - NSImage *remoteFileIcon; - NSImage *remoteDisabledFileIcon; - NSImage *combinedFileIcon; - NSImage *activeIcon; - NSImage *unsavedIcon; - - BadgeManager *syncingArrowsBadgeManager; - BadgeManager *alertBadgeManager; - BadgeManager *offlineBadgeManager; - - BOOL offline; -} - -- (void)setItem:(Hosts*)i; -- (void)removeAllBadges; - -@end diff --git a/Source/Cell.m b/Source/Cell.m deleted file mode 100644 index 41d29a2..0000000 --- a/Source/Cell.m +++ /dev/null @@ -1,400 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "Cell.h" -#import "Hosts.h" -#import "RemoteHosts.h" -#import "CombinedHosts.h" -#import "HostsGroup.h" -#import "SyncingArrowsBadge.h" -#import "AlertBadge.h" -#import "OfflineBadge.h" -#import "BadgeManager.h" -#import "ListController.h" -#import "HostsListView.h" - -#define kIconImageSize 16.0 -#define kImageOriginXOffset 3 -#define kImageOriginYOffset 1 - -#define kActiveImageXOffset 32 - -#define kTextOriginXOffset 4 -#define kTextOriginYOffset 2 -#define kTextHeightAdjust 4 - -#define kImageSize 13 -#define kRightImageArea 22 - -CGFloat const kWidthOfProgressIndicator = 16.0f; - -@interface Cell(Private) - -- (void)drawActiveIconWithFrame:(NSRect)cellFrame; -- (void)drawUnsavedIconWithFrame:(NSRect)cellFrame; - -- (void)drawIconRight:(NSImage*)icon withFrame:(NSRect)cellFrame; - -- (NSRect)textFrame:(NSRect)cellFrame; -- (NSRect)fileIconFrame:(NSRect)cellFrame icon:(NSImage*)icon; -- (NSRect)rightIconFrame:(NSRect)cellFrame; - -- (void)cleanUpForHosts:(NSNotification *)notification; -@end - -@interface Cell(Badge) -- (void)placeBadge:(Badge*)badge inFrame:(NSRect)cellFrame view:(NSView *)controlView; - -- (AlertBadge*)createAlertBadge; -- (void)placeAlertBadgeInFrame:(NSRect)cellFrame view:(NSView *)controlView; -- (void)removeAlertBadge; - -- (SyncingArrowsBadge*)createSyncArrowsBadge; -- (void)placeSyncArrowsBadgeInFrame:(NSRect)cellFrame view:(NSView *)controlView; -- (void)removeSyncArrowsBadge; - -- (void)placeOfflineBadgeInFrame:(NSRect)cellFrame view:(NSView *)controlView; -- (OfflineBadge*)createOfflineBadge; -- (void)removeOfflineBadge; - -@end - - -@implementation Cell - -- (id)init -{ - self = [super init]; - - [self setTruncatesLastVisibleLine:YES]; - [self setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]]; - - if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9) { - localFileIcon = [NSImage imageNamed: @"Local File.png"]; - remoteFileIcon = [NSImage imageNamed: @"Remote old.png"]; - remoteDisabledFileIcon = [NSImage imageNamed: @"Remote_disabled.png"]; - combinedFileIcon = [NSImage imageNamed: @"Combined_File.png"]; - } - else { - localFileIcon = [NSImage imageNamed: @"Local File yosemite.tiff"]; - remoteFileIcon = [NSImage imageNamed: @"Remote yosemite.tiff"]; - remoteDisabledFileIcon = [NSImage imageNamed: @"Remote yosemite.tiff"]; - combinedFileIcon = [NSImage imageNamed: @"Combined_File_yosemite.tiff"]; - } - - activeIcon = [NSImage imageNamed: @"Activated"]; - unsavedIcon = [NSImage imageNamed: @"Blue Dot"]; - - syncingArrowsBadgeManager = [BadgeManager badgeManagerWithCreator:@selector(createSyncArrowsBadge) target:self]; - alertBadgeManager = [BadgeManager badgeManagerWithCreator:@selector(createAlertBadge) target:self]; - offlineBadgeManager = [BadgeManager badgeManagerWithCreator:@selector(createOfflineBadge) target:self]; - - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc addObserver:self selector:@selector(cleanUpForHosts:) name:HostsFileRemovedNotification object:nil]; - - return self; -} - -- (void)setItem:(Hosts*)i -{ - item = i; - offline = [item isKindOfClass:[HostsGroup class]] && ![(HostsGroup*)item online]; -} - -- (void)removeAllBadges -{ - [self removeAlertBadge]; - [self removeOfflineBadge]; - [self removeSyncArrowsBadge]; -} - -- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView -{ - if (!item) { - return; - } - if ([item isGroup]) { - HostsGroup *group = (HostsGroup*)item; - if ([[group children] count] == 0) { - return; - } - - if (offline) { - [self placeOfflineBadgeInFrame:cellFrame view:controlView]; - } - else { - [self removeOfflineBadge]; - } - - if ([group synchronizing]) { - [self placeSyncArrowsBadgeInFrame:cellFrame view:controlView]; - } - else { - [self removeSyncArrowsBadge]; - } - - if ([item error] != nil) { - [self placeAlertBadgeInFrame:cellFrame view:controlView]; - } - else { - [self removeAlertBadge]; - } - - [super drawWithFrame:cellFrame inView:controlView]; - return; - } - - NSImage *image; - [self setEnabled:YES]; - - if (![item enabled]) { - [self setEnabled:NO]; - } - - if ([item isKindOfClass:[RemoteHosts class]]) { - image = [item enabled] ? remoteFileIcon : remoteDisabledFileIcon; - } - else if ([item isKindOfClass:[CombinedHosts class]]) { - image = combinedFileIcon; - } - else { - image = localFileIcon; - } - - NSRect frame = [self fileIconFrame:cellFrame icon:image]; - [image drawInRect:frame fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES hints:nil]; - - [super drawWithFrame:[self textFrame:cellFrame] inView:controlView]; - - if ([item active]) { - [self drawActiveIconWithFrame:cellFrame]; - } - - if (![item saved]) { - [self drawUnsavedIconWithFrame:cellFrame]; - } - - if ([item error] != nil) { - [self placeAlertBadgeInFrame:cellFrame view:controlView]; - } - else { - [self removeAlertBadge]; - } -} - -- (NSRect)titleRectForBounds:(NSRect)cellRect -{ - NSRect newFrame = cellRect; - - int xOffset = kImageOriginXOffset + kIconImageSize + 4; - newFrame.origin.x += xOffset; - newFrame.origin.y += kTextOriginYOffset; - newFrame.size.height -= kTextHeightAdjust; - newFrame.size.width -= xOffset; - - if (![item saved] || [item error] != nil) { - newFrame.size.width -= kRightImageArea; - } - - return newFrame; -} - -- (NSCellHitResult) hitTestForEvent: (NSEvent *) event inRect: (NSRect) cellFrame ofView: (NSView *) controlView -{ - NSPoint point = [controlView convertPoint:[event locationInWindow] fromView:nil]; - - if (([item error] != nil || offline) && NSMouseInRect(point, [self rightIconFrame:cellFrame], [controlView isFlipped])) { - return NSCellHitTrackableArea; - } - else if (NSMouseInRect(point, [self textFrame:cellFrame], [controlView isFlipped])) { - return NSCellHitEditableTextArea; - } - - return NSCellHitContentArea; -} - -- (BOOL) trackMouse: (NSEvent *) event inRect: (NSRect) cellFrame ofView: (NSView *) controlView untilMouseUp: (BOOL) flag -{ - return YES; -} - - -- (void)editWithFrame:(NSRect)aRect inView:(NSView*)controlView editor:(NSText*)textObj delegate:(id)anObject event:(NSEvent*)theEvent -{ - NSRect textFrame = [self titleRectForBounds:aRect]; - [super editWithFrame:textFrame inView:controlView editor:textObj delegate:anObject event:theEvent]; -} - -- (void)selectWithFrame:(NSRect)aRect inView:(NSView*)controlView editor:(NSText*)textObj delegate:(id)anObject start:(long)selStart length:(NSInteger)selLength -{ - NSRect textFrame = [self titleRectForBounds:aRect]; - [super selectWithFrame:textFrame inView:controlView editor:textObj delegate:anObject start:selStart length:selLength]; -} - -@end - -@implementation Cell(Private) - -- (void)drawActiveIconWithFrame:(NSRect)cellFrame -{ - NSSize imageSize = [activeIcon size]; - NSRect frame; - NSDivideRect(cellFrame, &frame, &cellFrame, imageSize.width, NSMinXEdge); - - frame.size = imageSize; - frame.origin.x -= imageSize.width + 1; - frame.origin.y += ceil((cellFrame.size.height - imageSize.height) / 2); - - [activeIcon drawInRect:frame fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES hints:nil]; -} - -- (void)drawUnsavedIconWithFrame:(NSRect)cellFrame -{ - [self drawIconRight:unsavedIcon withFrame:cellFrame]; -} - -- (void)drawIconRight:(NSImage*)icon withFrame:(NSRect)cellFrame -{ - NSRect frame = [self rightIconFrame:cellFrame]; - [icon drawInRect:frame fromRect:NSZeroRect operation:NSCompositingOperationSourceOver fraction:1.0 respectFlipped:YES hints:nil]; -} - -- (NSRect)textFrame:(NSRect)cellFrame -{ - NSRect frame = cellFrame; - frame.origin.x += kTextOriginXOffset + kIconImageSize; - frame.origin.y += kTextOriginYOffset; - frame.size.height -= kTextHeightAdjust; - frame.size.width -= frame.origin.x; - - return frame; -} - -- (NSRect)fileIconFrame:(NSRect)cellFrame icon:(NSImage*)icon -{ - NSSize imageSize = [icon size]; - NSRect frame; - NSDivideRect(cellFrame, &frame, &cellFrame, imageSize.width, NSMinXEdge); - - frame.size = imageSize; - frame.origin.x += 2; - frame.origin.y += ceil((cellFrame.size.height - imageSize.height) / 2); - - return frame; -} - -- (NSRect)rightIconFrame:(NSRect)cellFrame -{ - NSSize iconSize = NSMakeSize(kImageSize, kImageSize); - NSRect frame; - NSDivideRect(cellFrame, &frame, &cellFrame, iconSize.width, NSMaxXEdge); - frame.size = iconSize; - frame.origin.y += ceil((cellFrame.size.height - iconSize.height) / 2); - - return frame; -} - -- (void)cleanUpForHosts:(NSNotification *)notification -{ - Hosts *hosts = item; - item = [notification object]; - - [self removeSyncArrowsBadge]; - [self removeAlertBadge]; - - item = hosts; -} - -@end - -@implementation Cell (Badge) - -- (void)placeBadge:(Badge*)badge inFrame:(NSRect)cellFrame view:(NSView *)controlView -{ - NSSize size; - NSRect frame; - - size = [badge frame].size; - NSDivideRect(cellFrame, &frame, &cellFrame, size.width, NSMaxXEdge); - frame.size = size; - - [badge setFrame:frame]; - [controlView addSubview:badge]; -} - -#pragma mark Alert - -- (AlertBadge*)createAlertBadge -{ - return [[AlertBadge alloc] initWithRemoteHosts:(RemoteHosts*)item]; -} - -- (void)placeAlertBadgeInFrame:(NSRect)cellFrame view:(NSView *)controlView -{ - Badge *badge = [alertBadgeManager getBadgeForObject:item]; - [self placeBadge:badge inFrame:cellFrame view:controlView]; -} - -- (void)removeAlertBadge -{ - [alertBadgeManager removeBadgeForObject:item]; -} - -#pragma mark Syncing Arrows - -- (SyncingArrowsBadge*)createSyncArrowsBadge -{ - SyncingArrowsBadge *badge = [SyncingArrowsBadge new]; - [badge start]; - return badge; -} - -- (void)placeSyncArrowsBadgeInFrame:(NSRect)cellFrame view:(NSView *)controlView -{ - Badge *badge = [syncingArrowsBadgeManager getBadgeForObject:item]; - [self placeBadge:badge inFrame:cellFrame view:controlView]; -} - -- (void)removeSyncArrowsBadge -{ - [syncingArrowsBadgeManager removeBadgeForObject:item]; -} - -#pragma mark Offline - -- (void)placeOfflineBadgeInFrame:(NSRect)cellFrame view:(NSView *)controlView -{ - Badge *badge = [offlineBadgeManager getBadgeForObject:item]; - [self placeBadge:badge inFrame:cellFrame view:controlView]; - -} - -- (OfflineBadge*)createOfflineBadge -{ - return [[OfflineBadge alloc] initWithHosts:item]; -} - -- (void)removeOfflineBadge -{ - [offlineBadgeManager removeBadgeForObject:item]; -} - -@end - diff --git a/Source/CombinedHostsPredicateController.h b/Source/CombinedHostsPredicateController.h deleted file mode 100644 index 31ab200..0000000 --- a/Source/CombinedHostsPredicateController.h +++ /dev/null @@ -1,38 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@class Hosts; -@class CombinedHosts; - -@interface CombinedHostsPredicateController : NSObject { - IBOutlet NSPredicateEditor *predicateEditor; - IBOutlet NSScrollView *lowerScrollView; - IBOutlet NSView *parentView; - @private - int rowCount; - CombinedHosts *selectedHostsFile; - NSImageView *hintView; -} - -- (IBAction)predicateEditorChanged:(id)sender; -- (void)setSelectedFile:(Hosts*)value; -- (Hosts*)selectedFile; - -@end diff --git a/Source/CombinedHostsPredicateController.m b/Source/CombinedHostsPredicateController.m deleted file mode 100644 index ec9a3d0..0000000 --- a/Source/CombinedHostsPredicateController.m +++ /dev/null @@ -1,197 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "CombinedHostsPredicateController.h" -#import "CombinedHostsPredicateEditorRowTemplate.h" -#import "HostsMainController.h" -#import "CombinedHosts.h" -#import "Hosts.h" -#import "ExtendedNSArray.h" -#import "ExtendedNSPredicate.h" - -@interface CombinedHostsPredicateController (Private) -- (void)updateHostsFileContents; -- (void)fillTemplate; -@end - -@implementation CombinedHostsPredicateController - -- (id)init -{ - self = [super init]; - rowCount = 1; - - return self; -} - -- (void)awakeFromNib -{ - [self bind:@"selectedFile" toObject:[HostsMainController defaultInstance] withKeyPath:@"selection" options:nil]; - - [predicateEditor setRowHeight:25]; - - [predicateEditor setObjectValue:[NSPredicate predicateWithFormat:@"name = ''"]]; - - [self setSelectedFile:[[HostsMainController defaultInstance] activeHostsFile]]; -} - -- (IBAction)predicateEditorChanged:(id)sender -{ - [self updateHostsFileContents]; - - NSInteger newRowCount = [predicateEditor numberOfRows]; - - if (newRowCount == rowCount) { - return; - } - - BOOL growing = (newRowCount > rowCount); - - CGFloat heightDifference = fabs([predicateEditor rowHeight] * (newRowCount - rowCount)); - NSSize sizeChange = [predicateEditor convertSize:NSMakeSize(0, heightDifference) toView:nil]; - - NSScrollView *predicateEditorScrollView = [predicateEditor enclosingScrollView]; - - NSRect frame = [predicateEditorScrollView frame]; - frame.size.height += growing? sizeChange.height : -sizeChange.height; - frame.origin.y += growing? -sizeChange.height : sizeChange.height; - [predicateEditorScrollView setFrame:frame]; - - frame = [lowerScrollView frame]; - frame.origin.y += growing? -sizeChange.height : sizeChange.height; - [lowerScrollView setFrame:frame]; - - rowCount = newRowCount; - - [selectedHostsFile setSaved:NO]; -} - -- (void)setSelectedFile:(Hosts*)value -{ - Hosts *hosts = [[HostsMainController defaultInstance] selectedHosts]; - NSScrollView *scrollView = [predicateEditor enclosingScrollView]; - - if ([hosts isMemberOfClass:[CombinedHosts class]]) { - int previousRowCount = rowCount; - [scrollView setHidden:NO]; - [predicateEditor reloadCriteria]; - - selectedHostsFile = (CombinedHosts*)hosts; - [self fillTemplate]; - - int rowHeight = [predicateEditor rowHeight]; - rowCount = [predicateEditor numberOfRows]; - int height = rowCount * rowHeight; - int difference = (rowCount - previousRowCount) * rowHeight; - - NSRect frame = [lowerScrollView frame]; - frame.origin.y = 0; - frame.size.height = [parentView frame].size.height - height; - [lowerScrollView setFrame:frame]; - - NSRect frame2 = [scrollView frame]; - frame2.size.height = height; - frame2.origin.y -= difference; - [scrollView setFrame:frame2]; - - if ([[selectedHostsFile hostsFiles] count] == 0 && hintView == nil) { - hintView = [[NSImageView alloc] initWithFrame:NSMakeRect(140, -20, 210, 99)]; - [hintView setImage:[NSImage imageNamed: @"Combined Hosts Hint.png"]]; - [lowerScrollView addSubview:hintView]; - } - else if (hintView != nil) { - [hintView removeFromSuperview]; - hintView = nil; - } - - } else { - [scrollView setHidden:YES]; - NSRect frame = [lowerScrollView frame]; - frame.size.height = [parentView frame].size.height; - frame.origin.y = 0; - [lowerScrollView setFrame:frame]; - - if (hintView != nil) { - [hintView removeFromSuperview]; - hintView = nil; - } - } -} -- (Hosts*)selectedFile -{ - return selectedHostsFile; -} - -@end - -@implementation CombinedHostsPredicateController (Private) - -- (void)updateHostsFileContents -{ - if (selectedHostsFile == nil) { - return; - } - - logDebug(@"Updating hosts file: \"%@\"", [selectedHostsFile name]); - - NSPredicate *predicate = [predicateEditor predicate]; - if (predicate == nil) { - return; - } - - NSArray *predicates; - - if ([predicateEditor numberOfRows] > 1 && [predicate containsNestedPredicates]) { - NSCompoundPredicate * compound = (NSCompoundPredicate*)predicate; - predicates = [compound subpredicates]; - } - else { - predicates = [NSArray arrayWithObject:predicate]; - } - - NSArray *files = [[[HostsMainController defaultInstance] allHostsFiles] filteredOrderedArrayUsingPredicates:predicates]; - - [selectedHostsFile setHostsFiles:files]; - - if (hintView != nil) { - [hintView removeFromSuperview]; - hintView = nil; - } -} - -- (void)fillTemplate -{ - BOOL emptyPredicate = YES; - NSMutableArray *predicates = [NSMutableArray new]; - for (Hosts *hosts in [selectedHostsFile hostsFiles]) { - NSPredicate *predicate = [NSPredicate predicateWithFormat:@"type = %@ AND name = %@" argumentArray:[NSArray arrayWithObjects:[hosts type], [hosts name], nil]]; - [predicates addObject:predicate]; - emptyPredicate = NO; - } - - if (emptyPredicate) { - [predicates addObject:[NSPredicate predicateWithFormat:@"name = ''"]]; - } - - NSPredicate *compound = [NSCompoundPredicate orPredicateWithSubpredicates:predicates]; - [predicateEditor setObjectValue:compound]; -} - -@end diff --git a/Source/CombinedHostsPredicateEditorRowTemplate.h b/Source/CombinedHostsPredicateEditorRowTemplate.h deleted file mode 100644 index c108211..0000000 --- a/Source/CombinedHostsPredicateEditorRowTemplate.h +++ /dev/null @@ -1,27 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@interface CombinedHostsPredicateEditorRowTemplate : NSPredicateEditorRowTemplate { - @private - NSTextField * textField; - NSPopUpButton *select; -} - -@end diff --git a/Source/CombinedHostsPredicateEditorRowTemplate.m b/Source/CombinedHostsPredicateEditorRowTemplate.m deleted file mode 100644 index b239101..0000000 --- a/Source/CombinedHostsPredicateEditorRowTemplate.m +++ /dev/null @@ -1,152 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "CombinedHostsPredicateEditorRowTemplate.h" -#import "HostsMainController.h" -#import "RemoteHosts.h" -#import "Pair.h" -#import "CombinedHosts.h" - -@interface CombinedHostsPredicateEditorRowTemplate (Private) -- (NSTextField*)label; -- (NSPopUpButton*)select; -- (void)populateSelectMenu; -- (IBAction)hostsFileRemoved:(NSNotification *)notification; -- (IBAction)hostsFileAdded:(NSNotification *)notification; -- (IBAction)hostsFileRenamed:(NSNotification *)notification; -@end - -@implementation CombinedHostsPredicateEditorRowTemplate - -- (void)awakeFromNib -{ - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc addObserver:self selector:@selector(hostsFileRemoved:) name:HostsFileRemovedNotification object:nil]; - [nc addObserver:self selector:@selector(hostsFileAdded:) name:HostsFileCreatedNotification object:nil]; - [nc addObserver:self selector:@selector(hostsFileRenamed:) name:HostsFileRenamedNotification object:nil]; -} - -- (NSArray *) templateViews -{ - return [NSArray arrayWithObjects:[self label], [self select], nil]; -} - -- (double)matchForPredicate:(NSPredicate *)predicate -{ - return 1; -} - -- (NSPredicate *)predicateWithSubpredicates:(NSArray *)subpredicates -{ - - NSMenuItem * item = [[self select] selectedItem]; - Hosts *hosts = (Hosts*)[item representedObject]; - if (hosts == nil) { - return nil; - } - - return [NSPredicate predicateWithFormat:@"type = %@ AND name = %@" argumentArray:[NSArray arrayWithObjects:[hosts type], [hosts name], nil]]; -} - -- (void)setPredicate:(NSPredicate *)predicate -{ - NSMenu *menu = [[self select] menu]; - for (NSMenuItem * item in [menu itemArray]) { - Hosts *hosts = [item representedObject]; - if ([predicate evaluateWithObject:hosts]) { - [[self select] selectItem:item]; - return; - } - } - - [[self select] selectItemAtIndex:0]; -} - -@end - -@implementation CombinedHostsPredicateEditorRowTemplate (Private) - -- (NSTextField*)label -{ - if (textField == nil) { - textField = [[NSTextField alloc] init]; - [textField setFrame:NSMakeRect(0, 0, 100, 15)]; - [textField setEditable:NO]; - [textField setSelectable:NO]; - [textField setBordered:NO]; - [textField setDrawsBackground:NO]; - [textField setAlignment:NSCenterTextAlignment]; - [textField setTitleWithMnemonic:@"Hosts File:"]; - } - return textField; -} - -- (NSPopUpButton*)select -{ - if (select == nil) { - select = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(0, 0, 200, 15) pullsDown:YES]; - [self populateSelectMenu]; - } - return select; -} - -- (void)populateSelectMenu -{ - [select removeAllItems]; - [select addItemWithTitle:@" "]; - - NSMenu *menu = [select menu]; - - NSArray *allHosts = [[HostsMainController defaultInstance] allHostsFiles]; - for (Hosts *hosts in allHosts) { - if ([hosts isKindOfClass:[CombinedHosts class]]) { - continue; - } - NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:[hosts name] action:NULL keyEquivalent:@""]; - [item setRepresentedObject:hosts]; - [menu addItem:item]; - } -} - -- (IBAction)hostsFileRemoved:(NSNotification *)notification -{ - Hosts *removedHosts = [notification object]; - NSMenu *menu = [[self select] menu]; - for (NSMenuItem * item in [menu itemArray]) { - Hosts *hosts = [item representedObject]; - if ([removedHosts isEqualTo:hosts]) { - [menu removeItem:item]; - break; - } - } -} - -- (IBAction)hostsFileAdded:(NSNotification *)notification -{ - logDebug(@"Hosts file added: %@", [[notification object] name]); - [self populateSelectMenu]; -} - -- (IBAction)hostsFileRenamed:(NSNotification *)notification -{ - [self populateSelectMenu]; -} - -@end diff --git a/Source/EditorController.h b/Source/EditorController.h deleted file mode 100644 index 087845e..0000000 --- a/Source/EditorController.h +++ /dev/null @@ -1,29 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - - -@interface EditorController : NSObject { - IBOutlet NSSplitView *splitView; - IBOutlet NSWindow *editorWindow; - IBOutlet NSTextField *filesCountTextField; - IBOutlet NSImageView *readOnlyIconView; -} - -@end diff --git a/Source/EditorController.m b/Source/EditorController.m deleted file mode 100644 index 5d60de7..0000000 --- a/Source/EditorController.m +++ /dev/null @@ -1,100 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "EditorController.h" -#import "Preferences.h" -#import "ExtendedNSSplitView.h" -#import "Gas_Mask-Swift.h" - -#define SplitViewMinWidth 140 -#define SplitViewMaxWidth 300 -#define SplitViewDefaultWidth 160 - - -@implementation EditorController - -- (void)awakeFromNib -{ - [editorWindow setContentBorderThickness: [splitView frame].origin.y forEdge: NSMinYEdge]; - [[filesCountTextField cell] setBackgroundStyle: NSBackgroundStyleRaised]; - - [readOnlyIconView setToolTip:@"Hosts file can not be modified"]; - - int dividerIndex = 0; - CGFloat position = [splitView positionOfDividerAtIndex:dividerIndex]; - if (position > SplitViewMaxWidth) { - [splitView setPosition:SplitViewDefaultWidth ofDividerAtIndex:dividerIndex]; - } - - [SidebarInstaller installIn:splitView]; - [ContentInstaller installIn:splitView]; -} - -#pragma mark - Split View Delegate - -- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex; -{ - return proposedMinimumPosition + SplitViewMinWidth; -} - -- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex; -{ - return SplitViewMaxWidth; -} - -- (void)splitView:(NSSplitView*)sender resizeSubviewsWithOldSize:(NSSize)oldSize -{ - NSRect newFrame = [sender frame]; - NSView *left = [[sender subviews] objectAtIndex:0]; - NSRect leftFrame = [left frame]; - NSView *right = [[sender subviews] objectAtIndex:1]; - NSRect rightFrame = [right frame]; - - CGFloat dividerThickness = [sender dividerThickness]; - - leftFrame.size.height = newFrame.size.height; - - rightFrame.size.width = newFrame.size.width - leftFrame.size.width - dividerThickness; - rightFrame.size.height = newFrame.size.height; - rightFrame.origin.x = leftFrame.size.width + dividerThickness; - - [left setFrame:leftFrame]; - [right setFrame:rightFrame]; -} - -- (BOOL)splitView:(NSSplitView *)splitView canCollapseSubview:(NSView *)subview -{ - return NO; -} - -#pragma mark - NSWindow Delegate - -- (void)windowDidBecomeMain:(NSNotification *)notification -{ - [Preferences setShowEditorWindow:YES]; -} - -- (BOOL)windowShouldClose:(id)sender -{ - [Preferences setShowEditorWindow:NO]; - return YES; -} - -@end diff --git a/Source/ExtendedNSSplitView.h b/Source/ExtendedNSSplitView.h deleted file mode 100644 index 269ff06..0000000 --- a/Source/ExtendedNSSplitView.h +++ /dev/null @@ -1,23 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2013 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@interface NSSplitView (Extended) -- (CGFloat)positionOfDividerAtIndex:(NSInteger)dividerIndex; -@end diff --git a/Source/ExtendedNSSplitView.m b/Source/ExtendedNSSplitView.m deleted file mode 100644 index cb13d31..0000000 --- a/Source/ExtendedNSSplitView.m +++ /dev/null @@ -1,39 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2013 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "ExtendedNSSplitView.h" - -@implementation NSSplitView (Extended) - -- (CGFloat)positionOfDividerAtIndex:(NSInteger)dividerIndex -{ - while (dividerIndex >= 0 && [self isSubviewCollapsed:[[self subviews] objectAtIndex:dividerIndex]]) { - dividerIndex--; - } - - if (dividerIndex < 0) { - return 0.0f; - } - - NSRect priorViewFrame = [[[self subviews] objectAtIndex:dividerIndex] frame]; - return [self isVertical] ? NSMaxX(priorViewFrame) : NSMaxY(priorViewFrame); -} - -@end diff --git a/Source/FilesCountTransformer.h b/Source/FilesCountTransformer.h deleted file mode 100644 index 71e76f5..0000000 --- a/Source/FilesCountTransformer.h +++ /dev/null @@ -1,22 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@interface FilesCountTransformer : NSValueTransformer -@end diff --git a/Source/FilesCountTransformer.m b/Source/FilesCountTransformer.m deleted file mode 100644 index a71321d..0000000 --- a/Source/FilesCountTransformer.m +++ /dev/null @@ -1,49 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "FilesCountTransformer.h" - - -@implementation FilesCountTransformer - -+ (Class)transformedValueClass -{ - return [NSString class]; -} - -+ (BOOL)allowsReverseTransformation -{ - return NO; -} - -- (id)transformedValue:(id)count -{ - if ([count intValue] == 0) { - return @"No files"; - } - else if ([count intValue] == 1) { - return @"One file"; - } - else { - return [NSString stringWithFormat:@"%d files", [count intValue]]; - } -} - -@end \ No newline at end of file diff --git a/Source/HostsListView.h b/Source/HostsListView.h deleted file mode 100644 index 8e4af8b..0000000 --- a/Source/HostsListView.h +++ /dev/null @@ -1,33 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@class Cell; - -@interface HostsListView : NSOutlineView { - @private - BOOL showEmptyHostsGroups; - Cell *cell; -} - -@property (readonly) BOOL showEmptyHostsGroups; - -- (void)removeBadgesFromGroups; - -@end diff --git a/Source/HostsListView.m b/Source/HostsListView.m deleted file mode 100644 index a36c8da..0000000 --- a/Source/HostsListView.m +++ /dev/null @@ -1,151 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "HostsListView.h" -#import "HostsListViewMenu.h" -#import "HostsGroup.h" -#import "Hosts.h" -#import "Cell.h" - -#define kColumnIdName @"NameColumn" - -@interface HostsListView (Private) - -- (void)hideEmptyHostsGroups; - -@end - - -@implementation HostsListView - - -@synthesize showEmptyHostsGroups; - -- (void)awakeFromNib -{ - [self registerForDraggedTypes:@[NSPasteboardTypeString, NSPasteboardTypeFileURL]]; - [self setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; - [self setDraggingSourceOperationMask:NSDragOperationEvery forLocal:NO]; - - [self setSelectionHighlightStyle:NSTableViewSelectionHighlightStyleSourceList]; - - NSTableColumn *tableColumn = [self tableColumnWithIdentifier:kColumnIdName]; - cell = [[Cell alloc] init]; - [cell setEditable:YES]; - [tableColumn setDataCell:cell]; - -} - - -- (NSMenu *)menuForEvent:(NSEvent *)theEvent -{ - NSPoint point = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - int row = [self rowAtPoint:point]; - - if (row == -1) { - return nil; - } - - Hosts *hosts = [[self itemAtRow:row] representedObject]; - if (![hosts selectable]) { - return nil; - } - - [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; - - return [[HostsListViewMenu alloc] initWithHosts:hosts]; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard -{ - return YES; -} - -/* Removes all badges from HostGroup items. - It is needed because item with no height will not be redrawn and bages - would not be removed. - */ -- (void)removeBadgesFromGroups -{ - for (int i=0; i<[self numberOfRows]; i++) { - Hosts *hosts = (Hosts*)[[self itemAtRow:i] representedObject]; - if ([hosts isKindOfClass:[HostsGroup class]]) { - - [cell setItem:hosts]; - [cell removeAllBadges]; - - } - } -} - -#pragma mark - -#pragma mark NSDraggingSource - -- (void)draggedImage:(NSImage *)image endedAt:(NSPoint)screenPoint operation:(NSDragOperation)operation -{ - // Dragged item ended up in Trash - if (operation == NSDragOperationDelete) { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc postNotificationName:DraggedFileShouldBeRemovedNotification object:nil]; - } - else { - [super draggedImage:image endedAt:screenPoint operation:operation]; - } -} - -#pragma mark - -#pragma mark NSDraggingDestination - -- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender -{ - // Unhide empty hosts groups - showEmptyHostsGroups = YES; - [self reloadData]; - - return [super draggingEntered:sender]; -} - -- (void)draggingEnded:(id < NSDraggingInfo >)sender -{ - [self hideEmptyHostsGroups]; -} - -- (void)draggingExited:(id < NSDraggingInfo >)sender -{ - [self hideEmptyHostsGroups]; - [super draggingExited:sender]; -} - -@end - -@implementation HostsListView (Private) - -- (void)hideEmptyHostsGroups -{ - if (showEmptyHostsGroups) { - showEmptyHostsGroups = NO; - - [self removeBadgesFromGroups]; - [self reloadData]; - } -} - -@end - diff --git a/Source/HostsListViewMenu.h b/Source/HostsListViewMenu.h deleted file mode 100644 index 32dcdba..0000000 --- a/Source/HostsListViewMenu.h +++ /dev/null @@ -1,27 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@class Hosts; - -@interface HostsListViewMenu : NSMenu - -- (id)initWithHosts:(Hosts*)hosts; - -@end diff --git a/Source/HostsListViewMenu.m b/Source/HostsListViewMenu.m deleted file mode 100644 index b31728e..0000000 --- a/Source/HostsListViewMenu.m +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "HostsListViewMenu.h" -#import "HostsMainController.h" -#import "Hosts.h" -#import "RemoteHosts.h" -#import "LocalHostsController.h" - - -@implementation HostsListViewMenu - -- (id)initWithHosts:(Hosts*)hosts -{ - self = [self init]; - [self setAutoenablesItems:NO]; - - HostsMainController *controller = [HostsMainController defaultInstance]; - NSMenuItem *item; - - if (![hosts saved]) { - item = [[NSMenuItem alloc] initWithTitle:@"Save" action:@selector(save:) keyEquivalent:@""]; - [item setRepresentedObject:hosts]; - [item setTarget:controller]; - [self addItem:item]; - } - - if (![hosts active]) { - item = [[NSMenuItem alloc] initWithTitle:@"Activate" action:@selector(activate:) keyEquivalent:@""]; - [item setRepresentedObject:hosts]; - [item setEnabled:[hosts exists]]; - [item setTarget:controller]; - [self addItem:item]; - } - - item = [[NSMenuItem alloc] initWithTitle:@"Show In Finder" action:@selector(showInFinder:) keyEquivalent:@""]; - [item setRepresentedObject:hosts]; - [item setTarget:self]; - [item setEnabled:[hosts exists]]; - [self addItem:item]; - - if ([controller canRemoveFiles]) { - item = [[NSMenuItem alloc] initWithTitle:@"Remove" action:@selector(remove:) keyEquivalent:@""]; - [item setRepresentedObject:hosts]; - [item setTarget:controller]; - [self addItem:item]; - } - - if ([hosts isMemberOfClass:[RemoteHosts class]]) { - [self addItem:[NSMenuItem separatorItem]]; - - item = [[NSMenuItem alloc] initWithTitle:@"Move to Local" action:@selector(moveToLocal:) keyEquivalent:@""]; - [item setRepresentedObject:hosts]; - [item setTarget:self]; - [item setEnabled:[hosts exists]]; - [self addItem:item]; - - [self addItem:[NSMenuItem separatorItem]]; - - item = [[NSMenuItem alloc] initWithTitle:@"Open in Browser" action:@selector(openInBrowser:) keyEquivalent:@""]; - [item setRepresentedObject:hosts]; - [item setTarget:self]; - [self addItem:item]; - } - - return self; -} - -- (IBAction)openInBrowser:(id)sender -{ - [[NSWorkspace sharedWorkspace] openURL:[[sender representedObject] url]]; -} - -- (IBAction)showInFinder:(id)sender -{ - NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; - NSString *path = [[sender representedObject] path]; - [workspace selectFile: path inFileViewerRootedAtPath:@""]; -} - -- (IBAction)moveToLocal:(id)sender -{ - [[HostsMainController defaultInstance] move:[sender representedObject] toControllerClass:[LocalHostsController class]]; -} - -@end diff --git a/Source/HostsTextController.h b/Source/HostsTextController.h deleted file mode 100644 index 7e789dc..0000000 --- a/Source/HostsTextController.h +++ /dev/null @@ -1,28 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -@class HostsTextView; - -@interface HostsTextController : NSObject { - @private - IBOutlet HostsTextView *view; -} - -@end diff --git a/Source/HostsTextController.m b/Source/HostsTextController.m deleted file mode 100644 index 540e02a..0000000 --- a/Source/HostsTextController.m +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "HostsTextController.h" -#import "HostsTextView.h" -#import "Preferences.h" -#import "HostsMainController.h" - -@interface HostsTextController (Private) - -- (void)bindValue; - -@end - - - -@implementation HostsTextController - -- (void)awakeFromNib -{ - [self bindValue]; - [view bind:@"syntaxHighlighting" toObject:[Preferences instance] withKeyPath:@"values.syntaxHighlighting" options:nil]; -} - -- (void)bindValue:(NSNotification *)notification -{ - logDebug(@"Bind?"); - //[self bindValue]; -} -@end - -@implementation HostsTextController (Private) - -- (void)bindValue -{ - NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:NSContinuouslyUpdatesValueBindingOption]; - [view bind:@"value" toObject:[HostsMainController defaultInstance] withKeyPath:@"selection.contents" options:options]; -} - -@end - diff --git a/Source/ListController.h b/Source/ListController.h deleted file mode 100644 index 6129004..0000000 --- a/Source/ListController.h +++ /dev/null @@ -1,36 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - - -@class HostsMainController, Hosts, HostsListView; - -@interface ListController : NSObject { - @private - IBOutlet HostsListView *list; - IBOutlet HostsMainController *hostsController; - Hosts *draggedHosts; -} - -+ (ListController*)defaultInstance; - -- (Hosts*)selectedHosts; -- (void)deactivate; - -@end diff --git a/Source/ListController.m b/Source/ListController.m deleted file mode 100644 index f1b6b22..0000000 --- a/Source/ListController.m +++ /dev/null @@ -1,437 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "NSToolbarPoofAnimator.h" - -#import "ListController.h" -#import "HostsListView.h" -#import "Node.h" -#import "Cell.h" -#import "Hosts.h" -#import "HostsGroup.h" -#import "RemoteHosts.h" -#import "HostsMainController.h" - -#define kFileURLType @"public.file-url" -#define kTextType @"public.utf8-plain-text" - -#define kHostsHeight 20.0 -#define kEmptyHostsGroupHeight 0.1 - -@interface ListController (Private) -- (void)hostsFilesLoaded:(NSNotification *)notification; -- (void)selectActiveHostsFile; -- (void)expandAllItems; -- (void)showEditError:(NSString*)message; -- (NSString*)urlFromPasteBoard:(NSPasteboard*)pasteboard; -- (BOOL)allowToDropTo:(Hosts*)target; -- (int)indexOfHosts:(Hosts*)hosts; -- (NSPoint)locationOfHosts:(Hosts*)hosts; -- (NSPoint)rightCenterLocationOfHosts:(Hosts*)hosts; -- (NSPoint)centerLocationOfHostsOnScreen:(Hosts*)hosts; -@end - -@implementation ListController - -static ListController *sharedInstance = nil; - -+ (ListController*)defaultInstance -{ - return sharedInstance; -} - -- (id)init -{ - if (sharedInstance) { - return sharedInstance; - } - if (self = [super init]) { - - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc addObserver:self selector:@selector(updateItem:) name:HostsNodeNeedsUpdateNotification object:nil]; - [nc addObserver:self selector:@selector(updateItem:) name:SynchronizingStatusChangedNotification object:nil]; - [nc addObserver:self selector:@selector(renameHostsFile:) name:HostsFileShouldBeRenamedNotification object:nil]; - [nc addObserver:self selector:@selector(selectHostsFile:) name:HostsFileShouldBeSelectedNotification object:nil]; - [nc addObserver:self selector:@selector(deleteDraggedHostsFile:) name:DraggedFileShouldBeRemovedNotification object:nil]; - [nc addObserver:self selector:@selector(handleHostsFileRemoval:) name:HostsFileWillBeRemovedNotification object:nil]; - [nc addObserver:self selector:@selector(hostsFilesLoaded:) name:AllHostsFilesLoadedFromDiskNotification object:nil]; - - sharedInstance = self; - } - return sharedInstance; -} - -- (void)awakeFromNib -{ - [self expandAllItems]; - [self selectActiveHostsFile]; -} - -- (void)deactivate -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)updateItem:(NSNotification *)notification -{ - int index = [self indexOfHosts:[notification object]]; - [list reloadItem:[list itemAtRow:index]]; -} - -- (Hosts*) selectedHosts -{ - return [[list itemAtRow:[list selectedRow]] representedObject]; -} - -@end - -@implementation ListController (Private) - -- (void)hostsFilesLoaded:(NSNotification *)notification -{ - [self expandAllItems]; - [self selectActiveHostsFile]; -} - -- (void)selectActiveHostsFile -{ - for (int i=0; i<[list numberOfRows]; i++) { - Hosts *hosts = [[list itemAtRow:i] representedObject]; - if ([hosts active]) { - - logInfo(@"Selecting active item: %@", [hosts name]); - NSIndexSet *idx = [[NSIndexSet alloc] initWithIndex:i]; - [list selectRowIndexes:idx byExtendingSelection:NO]; - - return; - } - } - logWarn(@"No active item to select!"); -} - -- (void)renameHostsFile:(NSNotification *)notification -{ - [list editColumn:0 row:[self indexOfHosts:[notification object]] withEvent:nil select:YES]; -} - -- (void)selectHostsFile:(NSNotification *)notification -{ - int index = [self indexOfHosts:[notification object]]; - NSIndexSet *idx = [[NSIndexSet alloc] initWithIndex:index]; - [list selectRowIndexes:idx byExtendingSelection:NO]; -} - -- (void)deleteDraggedHostsFile:(NSNotification *)notification -{ - if ([hostsController canRemoveFiles]) { - [hostsController removeHostsFile:draggedHosts moveToTrash:YES]; - draggedHosts = nil; - } -} - -- (void)handleHostsFileRemoval:(NSNotification *)notification -{ - [list removeBadgesFromGroups]; - - // Let's have some fun :) - NSPoint point = [self centerLocationOfHostsOnScreen:[notification object]]; - [NSToolbarPoofAnimator runPoofAtPoint:point]; -} - - -- (void)expandAllItems -{ - logDebug(@"Expanding all items"); - for (int i=0; i<[list numberOfRows]; i++) { - [list expandItem:[list itemAtRow:i]]; - } -} - - -#pragma mark - -#pragma mark NSOutlineView delegate - --(BOOL)outlineView:(NSOutlineView*)outlineView isGroupItem:(id)item -{ - return [[item representedObject] isGroup]; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item; -{ - return [[item representedObject] selectable]; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView shouldShowOutlineCellForItem:(id)item -{ - return NO; -} - -- (void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell*)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - [(Cell*)cell setItem:[item representedObject]]; -} - -- (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item -{ - Hosts *hosts = [item representedObject]; - if (![list showEmptyHostsGroups] && [hosts isMemberOfClass:[HostsGroup class]] && [[hosts children] count] == 0) { - return kEmptyHostsGroupHeight; - } - - return kHostsHeight; -} - -#pragma mark - -#pragma mark Drag and Drop - -- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard -{ - Hosts *hosts = [[items objectAtIndex:0] representedObject]; - if ([hosts isKindOfClass:[HostsGroup class]]) { - return NO; - } - - [pboard setString:[hosts contents] forType:NSPasteboardTypeString]; - draggedHosts = hosts; - - return YES; -} - -/* - Proposing data for dropping. - */ -- (NSDragOperation)outlineView:(NSOutlineView *)ov validateDrop:(id )info proposedItem:(id)item proposedChildIndex:(NSInteger)childIndex -{ - Hosts *destinationHosts = [item representedObject]; - if (destinationHosts == nil) { - return NSDragOperationNone; - } - - // Data is dragged from external application - if ([info draggingSource] == nil) { - - NSString *rawURL = [self urlFromPasteBoard:[info draggingPasteboard]]; - - // URL is dragged to the list - if (rawURL) { - - id target = item; - if (![destinationHosts isMemberOfClass:[HostsGroup class]]) { - target = [list parentForItem:item]; - } - - NSURL *url = [NSURL URLWithString:rawURL]; - // Local URL - if ([url isFileURL]) { - if (![[rawURL pathExtension] isEqual:HostsFileExtension]) { - return NSDragOperationNone; - } - - if (![hostsController canCreateHostsFromLocalURL:url toGroup:(HostsGroup*)[target representedObject]]) { - return NSDragOperationNone; - } - } - // Remote URL - else { - if (![hostsController canCreateHostsFromURL:url toGroup:(HostsGroup*)[target representedObject]]) { - return NSDragOperationNone; - } - } - - [list setDropItem:target dropChildIndex:NSOutlineViewDropOnItemIndex]; - - return NSDragOperationGeneric; - } - - return NSDragOperationNone; - } - - NSDragOperation result = NSDragOperationGeneric; - - id target = item; - if ([destinationHosts isMemberOfClass:[Hosts class]]) { - target = [list parentForItem:item]; - } - - if ([info draggingSource] == list) { - if (![self allowToDropTo:destinationHosts]) { - return NSDragOperationNone; - } - } - - [list setDropItem:target dropChildIndex:NSOutlineViewDropOnItemIndex]; - - return result; -} - -/* - Actually droping data. - */ -- (BOOL)outlineView:(NSOutlineView *)ov acceptDrop:(id )info item:(id)item childIndex:(NSInteger)childIndex -{ - - if ([info draggingSource] == list) { - [hostsController move:draggedHosts to:[item representedObject]]; - draggedHosts = nil; - return YES; - } - - NSString *rawURL = [self urlFromPasteBoard:[info draggingPasteboard]]; - if (rawURL) { - NSURL *url = [NSURL URLWithString:rawURL]; - HostsGroup *group = [item representedObject]; - - // Local URL - if ([url isFileURL]) { - return [hostsController createHostsFromLocalURL:url toGroup:group]; - } - // WEB URL - else { - logDebug(@"Creating from URL \"%@\" to group \"%@\"", [url absoluteString], group); - - return [hostsController createHostsFromURL:url toGroup:group]; - } - } - - return NO; -} - - -- (NSArray *)outlineView:(NSOutlineView *)outlineView namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedItems:(NSArray *)items -{ - // TODO - logDebug(@"PROMISE!"); - return nil; -} - -- (NSString*)urlFromPasteBoard:(NSPasteboard*)pasteboard -{ - for (NSPasteboardItem *item in [pasteboard pasteboardItems]) { - if ([[item types] containsObject:kFileURLType]) { - return [item stringForType:kFileURLType]; - } - } - - for (NSPasteboardItem *item in [pasteboard pasteboardItems]) { - if ([[item types] containsObject:kTextType]) { - NSString *text = [item stringForType:kTextType]; - NSURL *url = [NSURL URLWithString:text]; - if (url && [url scheme] != nil) { - return text; - } - } - } - - return nil; -} - -- (BOOL)allowToDropTo:(Hosts*)target -{ - if ([draggedHosts isMemberOfClass:[RemoteHosts class]]) { - if ([target isMemberOfClass:[Hosts class]] && [draggedHosts exists]) { - return YES; - } - } - return NO; -} - -- (int)indexOfHosts:(Hosts*)hosts -{ - for (int i=0; i<[list numberOfRows]; i++) { - Hosts *obj = [[list itemAtRow:i] representedObject]; - if ([obj isEqual:hosts]) { - return i; - } - } - - return -1; -} - -- (NSPoint)locationOfHosts:(Hosts*)hosts -{ - NSRect frame = [list rectOfRow:[self indexOfHosts:hosts]]; - - NSPoint widgetOrigin = frame.origin; - NSPoint point = [list convertPoint:widgetOrigin toView:nil]; - - return point; -} - -- (NSPoint)rightCenterLocationOfHosts:(Hosts*)hosts -{ - NSPoint point = [self locationOfHosts:hosts]; - NSRect frame = [list rectOfRow:[list selectedRow]]; - - point.x += frame.size.width; - point.y -= frame.size.height / 2; - - return point; -} - -- (NSPoint)centerLocationOfHostsOnScreen:(Hosts*)hosts -{ - NSPoint hostsPoint = [self locationOfHosts:hosts]; - - NSRect frame = [list rectOfRow:[list selectedRow]]; - - hostsPoint.x += frame.size.width / 2; - hostsPoint.y -= frame.size.height / 2; - - NSPoint point = [[NSApp mainWindow] frame].origin; - point.x += hostsPoint.x; - point.y += hostsPoint.y; - - return point; -} - -#pragma mark - -#pragma mark NSControlTextEditingDelegate - -- (BOOL)control:(NSControl *)control textShouldEndEditing:(NSText *)fieldEditor -{ - Hosts *selectedHosts = [self selectedHosts]; - - // Nothing changed - if ([[selectedHosts name] isEqualToString:[fieldEditor string]]) { - return YES; - } - - NSRange range = [[fieldEditor string] rangeOfString:@"/"]; - if (range.location != NSNotFound) { - [self showEditError:@"File Name Can Not Contain Forward Slash."]; - [fieldEditor setString:[selectedHosts name]]; - return YES; - } - - BOOL renamed = [hostsController rename:selectedHosts to:[fieldEditor string]]; - if (renamed) { - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc postNotificationName:HostsFileRenamedNotification object:selectedHosts]; - } - else { - [self showEditError:@"File With Specified Name Already Exists."]; - [fieldEditor setString:[selectedHosts name]]; - return YES; - } - - return YES; -} - -@end diff --git a/Source/NSToolbarPoofAnimator.h b/Source/NSToolbarPoofAnimator.h deleted file mode 100644 index 9063fb3..0000000 --- a/Source/NSToolbarPoofAnimator.h +++ /dev/null @@ -1,27 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import - -@interface NSToolbarPoofAnimator:NSObject - -+ (void)runPoofAtPoint:(NSPoint)location; - -@end \ No newline at end of file diff --git a/Source/OfflineBadge.h b/Source/OfflineBadge.h deleted file mode 100644 index 290c04f..0000000 --- a/Source/OfflineBadge.h +++ /dev/null @@ -1,34 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "Badge.h" - -@class Hosts; - -@interface OfflineBadge : Badge { - @private - NSImage *icon; - NSImage *rolloverIcon; - Hosts *hosts; -} - -- (id)initWithHosts:(Hosts*)hostsValue; - -@end diff --git a/Source/OfflineBadge.m b/Source/OfflineBadge.m deleted file mode 100644 index f47e4be..0000000 --- a/Source/OfflineBadge.m +++ /dev/null @@ -1,55 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2012 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "OfflineBadge.h" -#import "ListController.h" -#import "Hosts.h" - - -@implementation OfflineBadge - -- (id)initWithHosts:(Hosts*)hostsValue -{ - self = [super init]; - icon = [NSImage imageNamed: @"Offline.png"]; - rolloverIcon = [NSImage imageNamed: @"Offline_rollover.png"]; - hosts = hostsValue; - - [self addTrackingArea:[[NSTrackingArea alloc] initWithRect:[self frame] - options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways - owner:self - userInfo:nil]]; - - [self setActiveIcon:icon]; - - return self; -} - -- (void)mouseEntered:(NSEvent *)theEvent -{ - [self setActiveIcon:rolloverIcon]; -} - -- (void)mouseExited:(NSEvent *)theEvent -{ - [self setActiveIcon:icon]; -} - -@end diff --git a/Source/Swift/ContentInstaller.swift b/Source/Swift/ContentInstaller.swift deleted file mode 100644 index d4d8531..0000000 --- a/Source/Swift/ContentInstaller.swift +++ /dev/null @@ -1,16 +0,0 @@ -import SwiftUI - -@objc final class ContentInstaller: NSObject { - @objc static func install(in splitView: NSSplitView) { - guard splitView.subviews.count >= 2 else { return } - - let store = HostsDataStore.shared - let content = ContentView(store: store) - let hostingView = NSHostingView(rootView: content) - - let rightPane = splitView.subviews[1] - hostingView.frame = rightPane.frame - hostingView.autoresizingMask = [.width, .height] - splitView.replaceSubview(rightPane, with: hostingView) - } -} diff --git a/Source/Swift/EditorToolbar.swift b/Source/Swift/EditorToolbar.swift new file mode 100644 index 0000000..f7bbee1 --- /dev/null +++ b/Source/Swift/EditorToolbar.swift @@ -0,0 +1,75 @@ +import SwiftUI + +struct EditorToolbar: ToolbarContent { + @ObservedObject var store: HostsDataStore + + @State private var hostsToRemove: Hosts? + + var body: some ToolbarContent { + ToolbarItem(id: "create", placement: .automatic) { + Menu { + Button("Local") { + HostsMainController.defaultInstance()?.createNewHostsFile(nil) + } + Button("Remote") { + URLSheetPresenter.presentInWindow(nil) + } + Button("Combined") { + HostsMainController.defaultInstance()?.createCombinedHostsFile(nil) + } + } label: { + Label("Create", systemImage: "plus") + } + } + + ToolbarItem(id: "remove", placement: .automatic) { + Button { + hostsToRemove = store.selectedHosts + } label: { + Label("Remove", systemImage: "minus") + } + .disabled(!store.canRemoveFiles || store.selectedHosts == nil) + .alert("Remove Hosts File", isPresented: Binding( + get: { hostsToRemove != nil }, + set: { if !$0 { hostsToRemove = nil } } + )) { + Button("Cancel", role: .cancel) { hostsToRemove = nil } + Button("Remove", role: .destructive) { + if let hosts = hostsToRemove { + HostsMainController.defaultInstance()?.removeHostsFile(hosts, moveToTrash: true) + } + hostsToRemove = nil + } + } message: { + Text("Are you sure you want to remove \"\(hostsToRemove?.name() ?? "")\"? The file will be moved to Trash.") + } + } + + ToolbarItem(id: "save", placement: .automatic) { + Button { + if let hosts = store.selectedHosts { + HostsMainController.defaultInstance()?.save(hosts) + } + } label: { + Label("Save", systemImage: "square.and.arrow.down") + } + .keyboardShortcut("s", modifiers: .command) + .disabled(store.selectedHosts == nil || store.selectedHosts?.saved() != false) + } + + ToolbarItem(id: "activate", placement: .automatic) { + Button { + if let hosts = store.selectedHosts { + HostsMainController.defaultInstance()?.activateHostsFile(hosts) + } + } label: { + Label("Activate", systemImage: "power") + } + .disabled( + store.selectedHosts == nil + || store.selectedHosts?.active() == true + || store.selectedHosts?.exists != true + ) + } + } +} diff --git a/Source/Swift/EditorView.swift b/Source/Swift/EditorView.swift new file mode 100644 index 0000000..74141e2 --- /dev/null +++ b/Source/Swift/EditorView.swift @@ -0,0 +1,16 @@ +import SwiftUI + +struct EditorView: View { + @StateObject private var store = HostsDataStore() + + var body: some View { + NavigationSplitView { + SidebarView(store: store) + .navigationSplitViewColumnWidth(min: 140, ideal: 160, max: 300) + } detail: { + ContentView(store: store) + } + .toolbar { EditorToolbar(store: store) } + .safeAreaInset(edge: .bottom) { StatusBarView(store: store) } + } +} diff --git a/Source/Swift/EditorWindowPresenter.swift b/Source/Swift/EditorWindowPresenter.swift new file mode 100644 index 0000000..93a3fda --- /dev/null +++ b/Source/Swift/EditorWindowPresenter.swift @@ -0,0 +1,44 @@ +import Cocoa +import SwiftUI + +@objc final class EditorWindowPresenter: NSObject, NSWindowDelegate { + + private static var instance: EditorWindowPresenter? + + private let window: NSWindow + + private init(window: NSWindow) { + self.window = window + super.init() + window.delegate = self + } + + @objc static func createEditorWindow() -> NSWindow { + let editorView = EditorView() + let hostingController = NSHostingController(rootView: editorView) + + let window = NSWindow(contentViewController: hostingController) + window.title = "Gas Mask" + window.styleMask = [.titled, .closable, .miniaturizable, .resizable] + window.setContentSize(NSSize(width: 619, height: 479)) + window.minSize = NSSize(width: 400, height: 400) + window.isReleasedWhenClosed = false + window.setFrameAutosaveName("editor_window") + + let presenter = EditorWindowPresenter(window: window) + instance = presenter + + return window + } + + // MARK: - NSWindowDelegate + + func windowDidBecomeMain(_ notification: Notification) { + Preferences.setShowEditorWindow(true) + } + + func windowShouldClose(_ sender: NSWindow) -> Bool { + Preferences.setShowEditorWindow(false) + return true + } +} diff --git a/Source/Swift/GasMask-Bridging-Header.h b/Source/Swift/GasMask-Bridging-Header.h index fbb3927..957f874 100644 --- a/Source/Swift/GasMask-Bridging-Header.h +++ b/Source/Swift/GasMask-Bridging-Header.h @@ -11,7 +11,6 @@ #import "Error.h" #import "LocalHostsController.h" #import "CombinedHostsController.h" -#import "ListController.h" #import "HostsTextView.h" #import "Preferences+Remote.h" #import "LoginItem.h" diff --git a/Source/Swift/HostsDataStore.swift b/Source/Swift/HostsDataStore.swift index 639a070..7ae509c 100644 --- a/Source/Swift/HostsDataStore.swift +++ b/Source/Swift/HostsDataStore.swift @@ -15,14 +15,14 @@ extension NSNotification.Name { static let hostsFileShouldBeSelected = NSNotification.Name("HostsFileShouldBeSelectedNotification") static let synchronizingStatusChanged = NSNotification.Name("SynchronizingStatusChangedNotification") static let allHostsFilesLoadedFromDisk = NSNotification.Name("AllHostsFilesLoadedFromDiskNotification") + static let threadBusy = NSNotification.Name("ThreadBusyNotification") + static let threadNotBusy = NSNotification.Name("ThreadNotBusyNotification") } // MARK: - HostsDataStore final class HostsDataStore: ObservableObject { - static let shared = HostsDataStore() - // MARK: Published Properties @Published var hostsGroups: [HostsGroup] = [] @@ -37,20 +37,28 @@ final class HostsDataStore: ObservableObject { @Published var filesCount: Int = 0 @Published var canRemoveFiles: Bool = false @Published var renamingHosts: Hosts? + @Published var isBusy: Bool = false // MARK: Private private var notificationObservers: [NSObjectProtocol] = [] private var isSyncingSelection = false + private var busyCount = 0 // MARK: Init - private init() { + init() { refreshGroups() refreshFilesCount() observeNotifications() } + deinit { + for observer in notificationObservers { + NotificationCenter.default.removeObserver(observer) + } + } + // MARK: Refresh func refreshGroups() { @@ -139,6 +147,29 @@ final class HostsDataStore: ObservableObject { } } notificationObservers.append(selectObserver) + + // Busy state notifications — posted from background threads, so use queue: .main + let busyObserver = nc.addObserver( + forName: .threadBusy, object: nil, queue: .main + ) { [weak self] _ in + guard let self else { return } + self.busyCount += 1 + self.isBusy = true + } + notificationObservers.append(busyObserver) + + let notBusyObserver = nc.addObserver( + forName: .threadNotBusy, object: nil, queue: .main + ) { [weak self] _ in + guard let self else { return } + if self.busyCount > 0 { + self.busyCount -= 1 + } + if self.busyCount == 0 { + self.isBusy = false + } + } + notificationObservers.append(notBusyObserver) } } diff --git a/Source/Swift/SidebarInstaller.swift b/Source/Swift/SidebarInstaller.swift deleted file mode 100644 index 07d5725..0000000 --- a/Source/Swift/SidebarInstaller.swift +++ /dev/null @@ -1,18 +0,0 @@ -import SwiftUI - -@objc final class SidebarInstaller: NSObject { - @objc static func install(in splitView: NSSplitView) { - let store = HostsDataStore.shared - let sidebar = SidebarView(store: store) - let hostingView = NSHostingView(rootView: sidebar) - - guard !splitView.subviews.isEmpty else { return } - let leftPane = splitView.subviews[0] - hostingView.frame = leftPane.frame - hostingView.autoresizingMask = [.width, .height] - splitView.replaceSubview(leftPane, with: hostingView) - - // Deactivate the old ListController to prevent duplicate notification handling - ListController.defaultInstance()?.deactivate() - } -} diff --git a/Source/Swift/StatusBarView.swift b/Source/Swift/StatusBarView.swift new file mode 100644 index 0000000..0e660e6 --- /dev/null +++ b/Source/Swift/StatusBarView.swift @@ -0,0 +1,44 @@ +import SwiftUI + +struct StatusBarView: View { + @ObservedObject var store: HostsDataStore + + var body: some View { + VStack(spacing: 0) { + Divider() + HStack { + if store.selectedHosts?.editable == false { + Image(systemName: "lock.fill") + .font(.system(size: 10)) + .foregroundStyle(.secondary) + .help("Hosts file can not be modified") + .accessibilityLabel("Read-only") + } + + Spacer() + + Text(filesCountText) + .font(.system(size: NSFont.smallSystemFontSize)) + .foregroundStyle(.secondary) + + Spacer() + + if store.isBusy { + ProgressView() + .controlSize(.small) + .accessibilityLabel("Busy") + } + } + .padding(.horizontal, 8) + .padding(.vertical, 3) + } + } + + private var filesCountText: String { + switch store.filesCount { + case 0: return "No files" + case 1: return "One file" + default: return "\(store.filesCount) files" + } + } +} diff --git a/Source/SyncingArrowsBadge.h b/Source/SyncingArrowsBadge.h deleted file mode 100644 index 143eb07..0000000 --- a/Source/SyncingArrowsBadge.h +++ /dev/null @@ -1,32 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "Badge.h" - -@interface SyncingArrowsBadge : Badge { - @private - NSArray *images; - NSTimer *timer; - int index; -} - -- (void)start; - -@end diff --git a/Source/SyncingArrowsBadge.m b/Source/SyncingArrowsBadge.m deleted file mode 100644 index e6c9fcd..0000000 --- a/Source/SyncingArrowsBadge.m +++ /dev/null @@ -1,58 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2009-2010 by Clockwise * - * copyright@clockwise.ee * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#import "SyncingArrowsBadge.h" - - -@implementation SyncingArrowsBadge - -- (id)init -{ - self = [super init]; - - images = [[NSArray alloc] initWithObjects: - [NSImage imageNamed: @"Syncing_arrows1.png"], - [NSImage imageNamed: @"Syncing_arrows2.png"], - [NSImage imageNamed: @"Syncing_arrows3.png"], - [NSImage imageNamed: @"Syncing_arrows4.png"], - [NSImage imageNamed: @"Syncing_arrows5.png"], - [NSImage imageNamed: @"Syncing_arrows6.png"], - nil]; - - return self; -} - -- (void)start -{ - timer = [NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval).09 target:self selector:@selector(updateImage) userInfo:nil repeats:YES]; -} - -- (void)updateImage -{ - if (index < 5) { - index++; - } - else { - index = 0; - } - [self setActiveIcon:[images objectAtIndex:index]]; -} - -@end diff --git a/Tests/GasMaskTests/ApplicationControllerTests.m b/Tests/GasMaskTests/ApplicationControllerTests.m index 90b5953..7cf1858 100644 --- a/Tests/GasMaskTests/ApplicationControllerTests.m +++ b/Tests/GasMaskTests/ApplicationControllerTests.m @@ -3,24 +3,25 @@ #import "ApplicationController.h" @interface ApplicationController (Testing) -- (void)loadEditorNibForTesting; +- (void)loadEditorWindowForTesting; - (void)closeEditorWindowForTesting; - (NSWindow *)editorWindowForTesting; @end @implementation ApplicationController (Testing) -- (void)loadEditorNibForTesting +- (void)loadEditorWindowForTesting { - id objects = [self valueForKey:@"_editorNibTopLevelObjects"]; - if (objects) { + NSWindow *existing = [self editorWindowForTesting]; + if (existing) { [self setValue:@YES forKey:@"editorWindowOpened"]; return; } - NSArray *topLevelObjects = nil; - [[NSBundle mainBundle] loadNibNamed:@"Editor" owner:self topLevelObjects:&topLevelObjects]; - [self setValue:topLevelObjects forKey:@"_editorNibTopLevelObjects"]; - [self setValue:@YES forKey:@"editorWindowOpened"]; + // Call the private initEditorWindow method which uses EditorWindowPresenter internally +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self performSelector:NSSelectorFromString(@"initEditorWindow")]; +#pragma clang diagnostic pop } - (void)closeEditorWindowForTesting @@ -87,26 +88,26 @@ - (void)testSingletonExists XCTAssertEqual([ApplicationController defaultInstance], self.controller); } -#pragma mark - NIB loading / open +#pragma mark - Window creation / open -- (void)testLoadEditorNib_setsEditorWindowOpenedFlag +- (void)testLoadEditorWindow_setsEditorWindowOpenedFlag { XCTAssertFalse([self.controller editorWindowOpened], @"precondition"); - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; XCTAssertTrue([self.controller editorWindowOpened]); } -- (void)testLoadEditorNib_windowExistsAfterOpen +- (void)testLoadEditorWindow_windowExistsAfterOpen { - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; NSWindow *w = [self.controller editorWindowForTesting]; XCTAssertNotNil(w); XCTAssertEqualObjects([w frameAutosaveName], @"editor_window"); } -- (void)testLoadEditorNib_windowAppearsInNSAppWindows +- (void)testLoadEditorWindow_windowAppearsInNSAppWindows { - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; NSWindow *found = nil; for (NSWindow *w in [NSApp windows]) { @@ -122,7 +123,7 @@ - (void)testLoadEditorNib_windowAppearsInNSAppWindows - (void)testCloseEditorWindow_clearsEditorWindowOpenedFlag { - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; XCTAssertTrue([self.controller editorWindowOpened], @"precondition"); [self.controller closeEditorWindowForTesting]; XCTAssertFalse([self.controller editorWindowOpened]); @@ -130,7 +131,7 @@ - (void)testCloseEditorWindow_clearsEditorWindowOpenedFlag - (void)testCloseEditorWindow_windowStillAccessibleAfterClose { - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; [self.controller closeEditorWindowForTesting]; runLoopDrain(0.05); @@ -142,22 +143,22 @@ - (void)testCloseEditorWindow_windowStillAccessibleAfterClose - (void)testReopenAfterClose_flagIsYes { - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; [self.controller closeEditorWindowForTesting]; runLoopDrain(0.05); XCTAssertFalse([self.controller editorWindowOpened], @"precondition: closed"); - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; XCTAssertTrue([self.controller editorWindowOpened]); } - (void)testReopenAfterClose_windowAccessible { - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; [self.controller closeEditorWindowForTesting]; runLoopDrain(0.05); - [self.controller loadEditorNibForTesting]; + [self.controller loadEditorWindowForTesting]; XCTAssertNotNil([self.controller editorWindowForTesting]); } diff --git a/Tests/GasMaskTests/EditorWindowPresenterTests.swift b/Tests/GasMaskTests/EditorWindowPresenterTests.swift new file mode 100644 index 0000000..ff3fee0 --- /dev/null +++ b/Tests/GasMaskTests/EditorWindowPresenterTests.swift @@ -0,0 +1,54 @@ +import XCTest +@testable import Gas_Mask + +final class EditorWindowPresenterTests: XCTestCase { + + private var window: NSWindow! + + override func setUp() { + super.setUp() + window = EditorWindowPresenter.createEditorWindow() + } + + override func tearDown() { + window.orderOut(nil) + window = nil + super.tearDown() + } + + func testWindowTitle() { + XCTAssertEqual(window.title, "Gas Mask") + } + + func testFrameAutosaveName() { + XCTAssertEqual(window.frameAutosaveName, "editor_window") + } + + func testMinSize() { + // Width should match the configured minimum. + // Height may be larger than 400 due to SwiftUI content minimum sizing. + XCTAssertEqual(window.minSize.width, 400) + XCTAssertGreaterThanOrEqual(window.minSize.height, 400) + } + + func testStyleMask_containsExpectedOptions() { + let mask = window.styleMask + XCTAssertTrue(mask.contains(.titled)) + XCTAssertTrue(mask.contains(.closable)) + XCTAssertTrue(mask.contains(.miniaturizable)) + XCTAssertTrue(mask.contains(.resizable)) + } + + func testContentViewController_exists() { + XCTAssertNotNil(window.contentViewController) + } + + func testWindowDelegate_exists() { + XCTAssertNotNil(window.delegate) + } + + func testWindowShouldClose_returnsTrue() { + let result = window.delegate?.windowShouldClose?(window) ?? false + XCTAssertTrue(result) + } +} diff --git a/Tests/GasMaskTests/HostsDataStoreTests.swift b/Tests/GasMaskTests/HostsDataStoreTests.swift index 9020831..b52e61c 100644 --- a/Tests/GasMaskTests/HostsDataStoreTests.swift +++ b/Tests/GasMaskTests/HostsDataStoreTests.swift @@ -18,19 +18,18 @@ final class HostsDataStoreTests: XCTestCase { XCTAssertEqual(NSNotification.Name.allHostsFilesLoadedFromDisk.rawValue, "AllHostsFilesLoadedFromDiskNotification") } - // MARK: - Singleton + // MARK: - Instance Creation - func testShared_returnsSameInstance() { - let a = HostsDataStore.shared - let b = HostsDataStore.shared - XCTAssertTrue(a === b) + func testInit_returnsDistinctInstances() { + let a = HostsDataStore() + let b = HostsDataStore() + XCTAssertFalse(a === b) } // MARK: - Notification Response func testRenameNotification_setsRenamingHosts() { - let store = HostsDataStore.shared - addTeardownBlock { store.renamingHosts = nil } + let store = HostsDataStore() let hosts = Hosts(path: "/tmp/test.hst")! store.renamingHosts = nil @@ -42,8 +41,7 @@ final class HostsDataStoreTests: XCTestCase { } func testSelectNotification_updatesSelectedHosts() { - let store = HostsDataStore.shared - addTeardownBlock { store.selectedHosts = nil } + let store = HostsDataStore() let hosts = Hosts(path: "/tmp/test.hst")! store.selectedHosts = nil @@ -53,4 +51,29 @@ final class HostsDataStoreTests: XCTestCase { XCTAssertTrue(store.selectedHosts === hosts) } + + // MARK: - Busy State + + func testBusyNotification_setsIsBusy() { + let store = HostsDataStore() + XCTAssertFalse(store.isBusy, "precondition") + + NotificationCenter.default.post(name: .threadBusy, object: nil) + RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1)) + + XCTAssertTrue(store.isBusy) + } + + func testNotBusyNotification_clearsIsBusy() { + let store = HostsDataStore() + + NotificationCenter.default.post(name: .threadBusy, object: nil) + RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1)) + XCTAssertTrue(store.isBusy, "precondition") + + NotificationCenter.default.post(name: .threadNotBusy, object: nil) + RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1)) + + XCTAssertFalse(store.isBusy) + } } From 7d026f82363cb909b3eda618349b15fb691177b9 Mon Sep 17 00:00:00 2001 From: Siim Raud Date: Sun, 1 Mar 2026 09:56:16 +0200 Subject: [PATCH 2/2] docs: clarify non-obvious self-assignment in HostsDataStore The self.hostsGroups = self.hostsGroups line looks like a no-op but is needed to trigger @Published willSet since HostsGroup objects are reference types whose property mutations don't change the array identity. --- Source/Swift/HostsDataStore.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Swift/HostsDataStore.swift b/Source/Swift/HostsDataStore.swift index 7ae509c..ea45e86 100644 --- a/Source/Swift/HostsDataStore.swift +++ b/Source/Swift/HostsDataStore.swift @@ -126,6 +126,8 @@ final class HostsDataStore: ObservableObject { for name in rowRefreshNames { let observer = nc.addObserver(forName: name, object: nil, queue: .main) { [weak self] _ in guard let self else { return } + // Re-assign to trigger @Published willSet — HostsGroup objects are reference types, + // so SwiftUI won't detect their property changes without this. self.hostsGroups = self.hostsGroups } notificationObservers.append(observer)