From 1fdc8eb13aca8433d39b70ab26b0bfec29218234 Mon Sep 17 00:00:00 2001 From: Roopesh Chander Date: Sun, 17 Mar 2019 16:38:07 +0530 Subject: [PATCH] macOS: Tunnel detail: Make the Activate button part of the list view --- WireGuard/WireGuard.xcodeproj/project.pbxproj | 4 + WireGuard/WireGuard/UI/TunnelViewModel.swift | 2 + .../WireGuard/UI/macOS/View/ButtonRow.swift | 67 ++++++++++++++++ .../TunnelDetailTableViewController.swift | 78 +++++++++---------- 4 files changed, 112 insertions(+), 39 deletions(-) create mode 100644 WireGuard/WireGuard/UI/macOS/View/ButtonRow.swift diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index a2e9730..5a76107 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -55,6 +55,7 @@ 6F5A2B4821AFF49A0081EDD8 /* FileManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5A2B4421AFDE020081EDD8 /* FileManager+Extension.swift */; }; 6F5D0C1D218352EF000F85AD /* PacketTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5D0C1C218352EF000F85AD /* PacketTunnelProvider.swift */; }; 6F5D0C22218352EF000F85AD /* WireGuardNetworkExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 6F5D0C1A218352EF000F85AD /* WireGuardNetworkExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5EA59A223E58A8002B380A /* ButtonRow.swift */; }; 6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */; }; 6F61F1E921B932F700483816 /* WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1E821B932F700483816 /* WireGuardAppError.swift */; }; 6F61F1EB21B937EF00483816 /* WireGuardResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */; }; @@ -270,6 +271,7 @@ 6F5D0C1F218352EF000F85AD /* WireGuardNetworkExtension_iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WireGuardNetworkExtension_iOS.entitlements; sourceTree = ""; }; 6F5D0C3421839E37000F85AD /* WireGuardNetworkExtension-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WireGuardNetworkExtension-Bridging-Header.h"; sourceTree = ""; }; 6F5D0C472183C6A3000F85AD /* PacketTunnelSettingsGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelSettingsGenerator.swift; sourceTree = ""; }; + 6F5EA59A223E58A8002B380A /* ButtonRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonRow.swift; sourceTree = ""; }; 6F613D9A21DE33B8004B217A /* KeyValueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueRow.swift; sourceTree = ""; }; 6F61F1E821B932F700483816 /* WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardAppError.swift; sourceTree = ""; }; 6F61F1EA21B937EF00483816 /* WireGuardResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireGuardResult.swift; sourceTree = ""; }; @@ -425,6 +427,7 @@ 5F52D0BC21E3785C00283CEA /* ConfTextStorage.swift */, 6F9B582721E8CD4300544D02 /* PopupRow.swift */, 6FE3661C21F64F6B00F78C7D /* ConfTextColorTheme.swift */, + 6F5EA59A223E58A8002B380A /* ButtonRow.swift */, ); path = View; sourceTree = ""; @@ -1165,6 +1168,7 @@ 6FB1BDBD21D50F0200A991BF /* ringlogger.h in Sources */, 6FBA103F21D6B6FF0051C35F /* TunnelImporter.swift in Sources */, 6F89E17A21EDEB0E00C97BB9 /* StatusItemController.swift in Sources */, + 6F5EA59B223E58A8002B380A /* ButtonRow.swift in Sources */, 6F4DD16B21DA558800690EAE /* TunnelListRow.swift in Sources */, 6FE3661D21F64F6B00F78C7D /* ConfTextColorTheme.swift in Sources */, 5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */, diff --git a/WireGuard/WireGuard/UI/TunnelViewModel.swift b/WireGuard/WireGuard/UI/TunnelViewModel.swift index 20620fc..e6b00a1 100644 --- a/WireGuard/WireGuard/UI/TunnelViewModel.swift +++ b/WireGuard/WireGuard/UI/TunnelViewModel.swift @@ -15,6 +15,7 @@ class TunnelViewModel { case mtu case dns case status + case toggleStatus var localizedUIString: String { switch self { @@ -27,6 +28,7 @@ class TunnelViewModel { case .mtu: return tr("tunnelInterfaceMTU") case .dns: return tr("tunnelInterfaceDNS") case .status: return tr("tunnelInterfaceStatus") + case .toggleStatus: return "" } } } diff --git a/WireGuard/WireGuard/UI/macOS/View/ButtonRow.swift b/WireGuard/WireGuard/UI/macOS/View/ButtonRow.swift new file mode 100644 index 0000000..4d15f5e --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/View/ButtonRow.swift @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. + +import Cocoa + +class ButtonRow: NSView { + let button: NSButton = { + let button = NSButton() + button.title = "" + button.setButtonType(.momentaryPushIn) + button.bezelStyle = .rounded + return button + }() + + var buttonTitle: String { + get { return button.title } + set(value) { button.title = value } + } + + var isButtonEnabled: Bool { + get { return button.isEnabled } + set(value) { button.isEnabled = value } + } + + var buttonToolTip: String { + get { return button.toolTip ?? "" } + set(value) { button.toolTip = value } + } + + var onButtonClicked: (() -> Void)? + var observationToken: AnyObject? + + override var intrinsicContentSize: NSSize { + return NSSize(width: NSView.noIntrinsicMetric, height: button.intrinsicContentSize.height) + } + + init() { + super.init(frame: CGRect.zero) + + button.target = self + button.action = #selector(buttonClicked) + + addSubview(button) + button.translatesAutoresizingMaskIntoConstraints = false + + NSLayoutConstraint.activate([ + button.centerYAnchor.constraint(equalTo: self.centerYAnchor), + button.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 155), + button.widthAnchor.constraint(greaterThanOrEqualToConstant: 100) + ]) + } + + required init?(coder decoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func buttonClicked() { + onButtonClicked?() + } + + override func prepareForReuse() { + buttonTitle = "" + buttonToolTip = "" + onButtonClicked = nil + observationToken = nil + } +} diff --git a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift index fc747a8..9d77542 100644 --- a/WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift +++ b/WireGuard/WireGuard/UI/macOS/ViewController/TunnelDetailTableViewController.swift @@ -32,7 +32,7 @@ class TunnelDetailTableViewController: NSViewController { static let interfaceFields: [TunnelViewModel.InterfaceField] = [ .name, .status, .publicKey, .addresses, - .listenPort, .mtu, .dns + .listenPort, .mtu, .dns, .toggleStatus ] static let peerFields: [TunnelViewModel.PeerField] = [ @@ -51,16 +51,6 @@ class TunnelDetailTableViewController: NSViewController { return tableView }() - let toggleStatusButton: NSButton = { - let button = NSButton() - button.title = "" - button.setButtonType(.momentaryPushIn) - button.bezelStyle = .rounded - button.toolTip = "Toggle status (⌘T)" - button.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true - return button - }() - let editButton: NSButton = { let button = NSButton() button.title = tr("Edit") @@ -114,9 +104,6 @@ class TunnelDetailTableViewController: NSViewController { tableView.dataSource = self tableView.delegate = self - toggleStatusButton.target = self - toggleStatusButton.action = #selector(handleToggleActiveStatusAction) - editButton.target = self editButton.action = #selector(handleEditTunnelAction) @@ -134,11 +121,9 @@ class TunnelDetailTableViewController: NSViewController { containerView.addLayoutGuide(bottomControlsContainer) containerView.addSubview(box) containerView.addSubview(scrollView) - containerView.addSubview(toggleStatusButton) containerView.addSubview(editButton) box.translatesAutoresizingMaskIntoConstraints = false scrollView.translatesAutoresizingMaskIntoConstraints = false - toggleStatusButton.translatesAutoresizingMaskIntoConstraints = false editButton.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ @@ -150,8 +135,6 @@ class TunnelDetailTableViewController: NSViewController { bottomControlsContainer.heightAnchor.constraint(equalToConstant: 32), scrollView.bottomAnchor.constraint(equalTo: bottomControlsContainer.topAnchor), bottomControlsContainer.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), - toggleStatusButton.leadingAnchor.constraint(equalTo: bottomControlsContainer.leadingAnchor), - bottomControlsContainer.bottomAnchor.constraint(equalTo: toggleStatusButton.bottomAnchor, constant: 0), editButton.trailingAnchor.constraint(equalTo: bottomControlsContainer.trailingAnchor), bottomControlsContainer.bottomAnchor.constraint(equalTo: editButton.bottomAnchor, constant: 0) ]) @@ -176,7 +159,7 @@ class TunnelDetailTableViewController: NSViewController { var interfaceSection = [(isVisible: Bool, modelRow: TableViewModelRow)]() for field in TunnelDetailTableViewController.interfaceFields { - let isStatus = field == .status + let isStatus = field == .status || field == .toggleStatus let isEmpty = tunnelViewModel.interfaceData[field].isEmpty interfaceSection.append((isVisible: isStatus || !isEmpty, modelRow: .interfaceFieldRow(field))) } @@ -204,26 +187,6 @@ class TunnelDetailTableViewController: NSViewController { } func updateStatus() { - let toggleStatusButtonText: String - switch tunnel.status { - case .waiting: - toggleStatusButtonText = tr("macToggleStatusButtonWaiting") - case .inactive: - toggleStatusButtonText = tr("macToggleStatusButtonActivate") - case .activating: - toggleStatusButtonText = tr("macToggleStatusButtonActivating") - case .active: - toggleStatusButtonText = tr("macToggleStatusButtonDeactivate") - case .deactivating: - toggleStatusButtonText = tr("macToggleStatusButtonDeactivating") - case .reasserting: - toggleStatusButtonText = tr("macToggleStatusButtonReasserting") - case .restarting: - toggleStatusButtonText = tr("macToggleStatusButtonRestarting") - } - toggleStatusButton.title = toggleStatusButtonText - let shouldBeEnabled = (tunnel.status == .active || tunnel.status == .inactive) - toggleStatusButton.isEnabled = shouldBeEnabled if tunnel.status == .active { startUpdatingRuntimeConfiguration() } else if tunnel.status == .inactive { @@ -392,6 +355,8 @@ extension TunnelDetailTableViewController: NSTableViewDelegate { case .interfaceFieldRow(let field): if field == .status { return statusCell() + } else if field == .toggleStatus { + return toggleStatusCell() } else { let cell: KeyValueRow = tableView.dequeueReusableCell() let localizedKeyString = modelRow.isTitleRow() ? modelRow.localizedSectionKeyString() : field.localizedUIString @@ -437,6 +402,22 @@ extension TunnelDetailTableViewController: NSTableViewDelegate { return cell } + func toggleStatusCell() -> NSView { + let cell: ButtonRow = tableView.dequeueReusableCell() + cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(forStatus: tunnel.status) + cell.isButtonEnabled = (tunnel.status == .active || tunnel.status == .inactive) + cell.buttonToolTip = "Toggle status (⌘T)" + cell.onButtonClicked = { [weak self] in + self?.handleToggleActiveStatusAction() + } + cell.observationToken = tunnel.observe(\.status) { [weak cell] tunnel, _ in + guard let cell = cell else { return } + cell.buttonTitle = TunnelDetailTableViewController.localizedToggleStatusActionText(forStatus: tunnel.status) + cell.isButtonEnabled = (tunnel.status == .active || tunnel.status == .inactive) + } + return cell + } + private static func localizedStatusDescription(forStatus status: TunnelStatus) -> String { switch status { case .inactive: @@ -467,6 +448,25 @@ extension TunnelDetailTableViewController: NSTableViewDelegate { return NSImage(named: NSImage.statusNoneName) } } + + private static func localizedToggleStatusActionText(forStatus status: TunnelStatus) -> String { + switch status { + case .waiting: + return tr("macToggleStatusButtonWaiting") + case .inactive: + return tr("macToggleStatusButtonActivate") + case .activating: + return tr("macToggleStatusButtonActivating") + case .active: + return tr("macToggleStatusButtonDeactivate") + case .deactivating: + return tr("macToggleStatusButtonDeactivating") + case .reasserting: + return tr("macToggleStatusButtonReasserting") + case .restarting: + return tr("macToggleStatusButtonRestarting") + } + } } extension TunnelDetailTableViewController: TunnelEditViewControllerDelegate {