// SPDX-License-Identifier: MIT // Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. import Cocoa class ManageTunnelsRootViewController: NSViewController { let tunnelsManager: TunnelsManager var tunnelsListVC: TunnelsListTableViewController? var tunnelDetailVC: TunnelDetailTableViewController? let tunnelDetailContainerView = NSView() var tunnelDetailContentVC: NSViewController? init(tunnelsManager: TunnelsManager) { self.tunnelsManager = tunnelsManager super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func loadView() { view = NSView() let horizontalSpacing: CGFloat = 20 let verticalSpacing: CGFloat = 20 let centralSpacing: CGFloat = 10 let container = NSLayoutGuide() view.addLayoutGuide(container) NSLayoutConstraint.activate([ container.topAnchor.constraint(equalTo: view.topAnchor, constant: verticalSpacing), view.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: verticalSpacing), container.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: horizontalSpacing), view.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: horizontalSpacing) ]) tunnelsListVC = TunnelsListTableViewController(tunnelsManager: tunnelsManager) tunnelsListVC!.delegate = self let tunnelsListView = tunnelsListVC!.view addChild(tunnelsListVC!) view.addSubview(tunnelsListView) view.addSubview(tunnelDetailContainerView) tunnelsListView.translatesAutoresizingMaskIntoConstraints = false tunnelDetailContainerView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ tunnelsListView.topAnchor.constraint(equalTo: container.topAnchor), tunnelsListView.bottomAnchor.constraint(equalTo: container.bottomAnchor), tunnelsListView.leadingAnchor.constraint(equalTo: container.leadingAnchor), tunnelDetailContainerView.topAnchor.constraint(equalTo: container.topAnchor), tunnelDetailContainerView.bottomAnchor.constraint(equalTo: container.bottomAnchor), tunnelDetailContainerView.leadingAnchor.constraint(equalTo: tunnelsListView.trailingAnchor, constant: centralSpacing), tunnelDetailContainerView.trailingAnchor.constraint(equalTo: container.trailingAnchor) ]) } private func setTunnelDetailContentVC(_ contentVC: NSViewController) { if let currentContentVC = tunnelDetailContentVC { currentContentVC.view.removeFromSuperview() currentContentVC.removeFromParent() } addChild(contentVC) tunnelDetailContainerView.addSubview(contentVC.view) contentVC.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ tunnelDetailContainerView.topAnchor.constraint(equalTo: contentVC.view.topAnchor), tunnelDetailContainerView.bottomAnchor.constraint(equalTo: contentVC.view.bottomAnchor), tunnelDetailContainerView.leadingAnchor.constraint(equalTo: contentVC.view.leadingAnchor), tunnelDetailContainerView.trailingAnchor.constraint(equalTo: contentVC.view.trailingAnchor) ]) tunnelDetailContentVC = contentVC } } extension ManageTunnelsRootViewController: TunnelsListTableViewControllerDelegate { func tunnelsSelected(tunnelIndices: [Int]) { assert(!tunnelIndices.isEmpty) if tunnelIndices.count == 1 { let tunnel = tunnelsManager.tunnel(at: tunnelIndices.first!) let tunnelDetailVC = TunnelDetailTableViewController(tunnelsManager: tunnelsManager, tunnel: tunnel) setTunnelDetailContentVC(tunnelDetailVC) self.tunnelDetailVC = tunnelDetailVC } else if tunnelIndices.count > 1 { let multiSelectionVC: ButtonedDetailViewController if let buttonedDetailVC = tunnelDetailContentVC as? ButtonedDetailViewController { multiSelectionVC = buttonedDetailVC } else { multiSelectionVC = ButtonedDetailViewController() } multiSelectionVC.setButtonTitle(tr(format: "macButtonDeleteTunnels (%d)", tunnelIndices.count)) multiSelectionVC.onButtonClicked = { [weak tunnelsListVC] in tunnelsListVC?.handleRemoveTunnelAction() } setTunnelDetailContentVC(multiSelectionVC) self.tunnelDetailVC = nil } } func tunnelsListEmpty() { let noTunnelsVC = ButtonedDetailViewController() noTunnelsVC.setButtonTitle(tr("macButtonImportTunnels")) noTunnelsVC.onButtonClicked = { [weak self] in guard let self = self else { return } ImportPanelPresenter.presentImportPanel(tunnelsManager: self.tunnelsManager, sourceVC: self) } setTunnelDetailContentVC(noTunnelsVC) self.tunnelDetailVC = nil } } extension ManageTunnelsRootViewController { override func keyDown(with event: NSEvent) { let modifierFlags = event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue let isCmdOrCmdShiftDown = (modifierFlags == NSEvent.ModifierFlags.command.rawValue || modifierFlags == NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue) if event.specialKey == .delete { tunnelsListVC?.handleRemoveTunnelAction() } else if isCmdOrCmdShiftDown { switch event.charactersIgnoringModifiers { case "n": tunnelsListVC?.handleAddEmptyTunnelAction() case "o": tunnelsListVC?.handleImportTunnelAction() case "t": tunnelDetailVC?.handleToggleActiveStatusAction() case "e": tunnelDetailVC?.handleEditTunnelAction() default: break } } } }