macOS: Manage tunnels window: Tunnels list
Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
extension NSTableView {
|
||||
func dequeueReusableCell<T: NSView>() -> T {
|
||||
let identifier = NSUserInterfaceItemIdentifier(NSStringFromClass(T.self))
|
||||
if let cellView = makeView(withIdentifier: identifier, owner: self) {
|
||||
//swiftlint:disable:next force_cast
|
||||
return cellView as! T
|
||||
}
|
||||
let cellView = T()
|
||||
cellView.identifier = identifier
|
||||
return cellView
|
||||
}
|
||||
}
|
||||
@@ -116,7 +116,11 @@ class StatusMenu: NSMenu {
|
||||
}
|
||||
|
||||
@objc func manageTunnelsClicked() {
|
||||
print("Unimplemented")
|
||||
let manageTunnelsRootVC = ManageTunnelsRootViewController(tunnelsManager: tunnelsManager)
|
||||
let window = NSWindow(contentViewController: manageTunnelsRootVC)
|
||||
window.setFrameAutosaveName(NSWindow.FrameAutosaveName("ManageTunnelsWindow")) // Auto-save window position and size
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
window.makeKeyAndOrderFront(self)
|
||||
}
|
||||
|
||||
@objc func importTunnelsClicked() {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
class TunnelListCell: NSView {
|
||||
var tunnel: TunnelContainer? {
|
||||
didSet(value) {
|
||||
// Bind to the tunnel's name
|
||||
nameLabel.stringValue = tunnel?.name ?? ""
|
||||
nameObservationToken = tunnel?.observe(\TunnelContainer.name) { [weak self] tunnel, _ in
|
||||
self?.nameLabel.stringValue = tunnel.name
|
||||
}
|
||||
// Bind to the tunnel's status
|
||||
statusImageView.image = TunnelListCell.image(for: tunnel?.status)
|
||||
statusObservationToken = tunnel?.observe(\TunnelContainer.status) { [weak self] tunnel, _ in
|
||||
self?.statusImageView.image = TunnelListCell.image(for: tunnel.status)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nameLabel: NSTextField = {
|
||||
let nameLabel = NSTextField()
|
||||
nameLabel.isEditable = false
|
||||
nameLabel.isSelectable = false
|
||||
nameLabel.isBordered = false
|
||||
nameLabel.maximumNumberOfLines = 1
|
||||
nameLabel.lineBreakMode = .byTruncatingTail
|
||||
return nameLabel
|
||||
}()
|
||||
|
||||
let statusImageView = NSImageView()
|
||||
|
||||
private var statusObservationToken: AnyObject?
|
||||
private var nameObservationToken: AnyObject?
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
addSubview(statusImageView)
|
||||
addSubview(nameLabel)
|
||||
statusImageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
nameLabel.backgroundColor = .clear
|
||||
NSLayoutConstraint.activate([
|
||||
self.leadingAnchor.constraint(equalTo: statusImageView.leadingAnchor),
|
||||
statusImageView.trailingAnchor.constraint(equalTo: nameLabel.leadingAnchor),
|
||||
statusImageView.widthAnchor.constraint(equalToConstant: 20),
|
||||
nameLabel.trailingAnchor.constraint(equalTo: self.trailingAnchor),
|
||||
statusImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor),
|
||||
nameLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor)
|
||||
])
|
||||
}
|
||||
|
||||
required init?(coder decoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
static func image(for status: TunnelStatus?) -> NSImage? {
|
||||
guard let status = status else { return nil }
|
||||
switch status {
|
||||
case .active, .restarting, .reasserting:
|
||||
return NSImage(named: NSImage.statusAvailableName)
|
||||
case .activating, .waiting:
|
||||
return NSImage(named: NSImage.statusPartiallyAvailableName)
|
||||
case .deactivating, .inactive:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
override func prepareForReuse() {
|
||||
nameLabel.stringValue = ""
|
||||
statusImageView.image = nil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
class ManageTunnelsRootViewController: NSViewController {
|
||||
|
||||
let tunnelsManager: TunnelsManager
|
||||
|
||||
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 = 30
|
||||
let verticalSpacing: CGFloat = 20
|
||||
|
||||
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)
|
||||
])
|
||||
|
||||
let tunnelsListVC = TunnelsListTableViewController(tunnelsManager: tunnelsManager)
|
||||
let tunnelsListView = tunnelsListVC.view
|
||||
let tunnelDetailView = NSView()
|
||||
tunnelDetailView.wantsLayer = true
|
||||
tunnelDetailView.layer?.backgroundColor = NSColor.gray.cgColor
|
||||
|
||||
addChild(tunnelsListVC)
|
||||
view.addSubview(tunnelsListView)
|
||||
view.addSubview(tunnelDetailView)
|
||||
|
||||
tunnelsListView.translatesAutoresizingMaskIntoConstraints = false
|
||||
tunnelDetailView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
tunnelsListView.topAnchor.constraint(equalTo: container.topAnchor),
|
||||
tunnelsListView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
||||
tunnelsListView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
||||
tunnelDetailView.leadingAnchor.constraint(equalTo: tunnelsListView.trailingAnchor, constant: horizontalSpacing),
|
||||
tunnelDetailView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
||||
tunnelsListView.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.3)
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
|
||||
class TunnelsListTableViewController: NSViewController {
|
||||
|
||||
let tunnelsManager: TunnelsManager
|
||||
|
||||
let tableView: NSTableView = {
|
||||
let tableView = NSTableView()
|
||||
tableView.addTableColumn(NSTableColumn(identifier: NSUserInterfaceItemIdentifier("TunnelsList")))
|
||||
tableView.headerView = nil
|
||||
tableView.rowSizeStyle = .medium
|
||||
return tableView
|
||||
}()
|
||||
|
||||
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() {
|
||||
tableView.dataSource = self
|
||||
tableView.delegate = self
|
||||
|
||||
let scrollView = NSScrollView()
|
||||
scrollView.hasVerticalScroller = true
|
||||
scrollView.autohidesScrollers = true
|
||||
|
||||
let clipView = NSClipView()
|
||||
clipView.documentView = tableView
|
||||
scrollView.contentView = clipView
|
||||
|
||||
self.view = scrollView
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelsListTableViewController: NSTableViewDataSource {
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
return tunnelsManager.numberOfTunnels()
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelsListTableViewController: NSTableViewDelegate {
|
||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||
let cell: TunnelListCell = tableView.dequeueReusableCell()
|
||||
cell.tunnel = tunnelsManager.tunnel(at: row)
|
||||
return cell
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user