mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
389 lines
16 KiB
Swift
389 lines
16 KiB
Swift
//
|
|
// AppSettings.swift
|
|
// Stats
|
|
//
|
|
// Created by Serhiy Mytrovtsiy on 15/04/2020.
|
|
// Using Swift 5.0.
|
|
// Running on macOS 10.15.
|
|
//
|
|
// Copyright © 2020 Serhiy Mytrovtsiy. All rights reserved.
|
|
//
|
|
|
|
import Cocoa
|
|
import StatsKit
|
|
import os.log
|
|
|
|
class ApplicationSettings: NSView {
|
|
private let width: CGFloat = 540
|
|
private let height: CGFloat = 480
|
|
private let deviceInfoHeight: CGFloat = 300
|
|
|
|
private var updateIntervalValue: AppUpdateInterval {
|
|
get {
|
|
return store.string(key: "update-interval", defaultValue: AppUpdateIntervals.atStart.rawValue)
|
|
}
|
|
}
|
|
|
|
private var temperatureUnitsValue: String {
|
|
get {
|
|
return store.string(key: "temperature_units", defaultValue: "system")
|
|
}
|
|
set {
|
|
store.set(key: "temperature_units", value: newValue)
|
|
}
|
|
}
|
|
|
|
private var updateButton: NSButton? = nil
|
|
private let updateWindow: UpdateWindow = UpdateWindow()
|
|
|
|
init() {
|
|
super.init(frame: NSRect(x: 0, y: 0, width: width, height: height))
|
|
self.wantsLayer = true
|
|
self.layer?.backgroundColor = .clear
|
|
|
|
self.addDeviceInfo()
|
|
self.addSettings()
|
|
}
|
|
|
|
required public init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
public override func viewDidMoveToWindow() {
|
|
if let button = self.updateButton, let version = updater.latest {
|
|
if version.newest {
|
|
button.title = LocalizedString("Update application")
|
|
} else {
|
|
button.title = LocalizedString("Check for update")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func addSettings() {
|
|
let view: NSView = NSView(frame: NSRect(x: 0, y: 1, width: self.width-1, height: self.height - self.deviceInfoHeight))
|
|
let rowHeight: CGFloat = 40
|
|
let rowHorizontalPadding: CGFloat = 16
|
|
|
|
let leftPanel: NSView = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width/2, height: view.frame.height))
|
|
leftPanel.wantsLayer = true
|
|
|
|
var processorInfo = ""
|
|
if systemKit.device.info?.cpu?.name != "" {
|
|
processorInfo += "\(systemKit.device.info?.cpu?.name ?? LocalizedString("Unknown"))\n"
|
|
}
|
|
processorInfo += "\(systemKit.device.info?.cpu?.physicalCores ?? 0) cores (\(systemKit.device.info?.cpu?.logicalCores ?? 0) threads)"
|
|
leftPanel.addSubview(makeInfoRow(
|
|
frame: NSRect(x: rowHorizontalPadding, y: rowHeight*3, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight+8),
|
|
title: LocalizedString("Processor"),
|
|
value: processorInfo
|
|
))
|
|
|
|
let sizeFormatter = ByteCountFormatter()
|
|
sizeFormatter.allowedUnits = [.useGB]
|
|
sizeFormatter.countStyle = .memory
|
|
leftPanel.addSubview(makeInfoRow(
|
|
frame: NSRect(x: rowHorizontalPadding, y: rowHeight*2, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
|
|
title: LocalizedString("Memory"),
|
|
value: "\(sizeFormatter.string(fromByteCount: Int64(systemKit.device.info?.ram?.total ?? 0)))"
|
|
))
|
|
|
|
let gpus = systemKit.device.info?.gpu
|
|
var gpu: String = LocalizedString("Unknown")
|
|
if gpus != nil {
|
|
if gpus?.count == 1 {
|
|
gpu = gpus![0].name
|
|
} else {
|
|
gpu = ""
|
|
gpus!.forEach{ gpu += "\($0.name)\n" }
|
|
}
|
|
}
|
|
leftPanel.addSubview(makeInfoRow(
|
|
frame: NSRect(x: rowHorizontalPadding, y: rowHeight*1, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
|
|
title: LocalizedString("Graphics"),
|
|
value: gpu
|
|
))
|
|
|
|
leftPanel.addSubview(makeInfoRow(
|
|
frame: NSRect(x: rowHorizontalPadding, y: 0, width: leftPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
|
|
title: LocalizedString("Disk"),
|
|
value: "\(systemKit.device.info?.disk?.model ?? systemKit.device.info?.disk?.name ?? LocalizedString("Unknown"))"
|
|
))
|
|
|
|
let rightPanel: NSView = NSView(frame: NSRect(x: self.width/2, y: 0, width: view.frame.width/2, height: view.frame.height))
|
|
|
|
rightPanel.addSubview(makeSelectRow(
|
|
frame: NSRect(x: rowHorizontalPadding*0.5, y: rowHeight*3, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
|
|
title: LocalizedString("Check for updates"),
|
|
action: #selector(self.toggleUpdateInterval),
|
|
items: AppUpdateIntervals.allCases.map{ $0.rawValue },
|
|
selected: self.updateIntervalValue
|
|
))
|
|
|
|
let temperature = SelectRow(
|
|
frame: NSRect(
|
|
x: rowHorizontalPadding*0.5,
|
|
y: rowHeight*2,
|
|
width: rightPanel.frame.width - (rowHorizontalPadding*1.5),
|
|
height: rowHeight
|
|
),
|
|
title: LocalizedString("Temperature"),
|
|
action: #selector(toggleTemperatureUnits),
|
|
items: TemperatureUnits,
|
|
selected: self.temperatureUnitsValue
|
|
)
|
|
temperature.subviews.forEach { (v: NSView) in
|
|
if let view = v as? LabelField {
|
|
view.textColor = .secondaryLabelColor
|
|
}
|
|
}
|
|
rightPanel.addSubview(temperature)
|
|
|
|
rightPanel.addSubview(makeSettingRow(
|
|
frame: NSRect(x: rowHorizontalPadding*0.5, y: rowHeight*1, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
|
|
title: LocalizedString("Show icon in dock"),
|
|
action: #selector(self.toggleDock),
|
|
state: store.bool(key: "dockIcon", defaultValue: false)
|
|
))
|
|
|
|
rightPanel.addSubview(makeSettingRow(
|
|
frame: NSRect(x: rowHorizontalPadding*0.5, y: 0, width: rightPanel.frame.width - (rowHorizontalPadding*1.5), height: rowHeight),
|
|
title: LocalizedString("Start at login"),
|
|
action: #selector(self.toggleLaunchAtLogin),
|
|
state: LaunchAtLogin.isEnabled
|
|
))
|
|
|
|
view.addSubview(leftPanel)
|
|
view.addSubview(rightPanel)
|
|
self.addSubview(view)
|
|
}
|
|
|
|
func makeSelectRow(frame: NSRect, title: String, action: Selector, items: [String], selected: String) -> NSView {
|
|
let row: NSView = NSView(frame: frame)
|
|
|
|
let rowTitle: NSTextField = LabelField(frame: NSRect(x: 0, y: (row.frame.height - 32)/2, width: row.frame.width - 52, height: 32), title)
|
|
rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
|
|
rowTitle.textColor = .secondaryLabelColor
|
|
|
|
let select: NSPopUpButton = NSPopUpButton(frame: NSRect(x: row.frame.width - 50, y: (row.frame.height-28)/2, width: 50, height: 28))
|
|
select.target = self
|
|
select.action = action
|
|
|
|
let menu = NSMenu()
|
|
items.forEach { (color: String) in
|
|
if color.contains("separator") {
|
|
menu.addItem(NSMenuItem.separator())
|
|
} else {
|
|
let interfaceMenu = NSMenuItem(title: color, action: nil, keyEquivalent: "")
|
|
menu.addItem(interfaceMenu)
|
|
if selected == color {
|
|
interfaceMenu.state = .on
|
|
}
|
|
}
|
|
}
|
|
|
|
select.menu = menu
|
|
select.sizeToFit()
|
|
|
|
rowTitle.setFrameSize(NSSize(width: row.frame.width - select.frame.width, height: rowTitle.frame.height))
|
|
select.setFrameOrigin(NSPoint(x: row.frame.width - select.frame.width, y: select.frame.origin.y))
|
|
|
|
row.addSubview(select)
|
|
row.addSubview(rowTitle)
|
|
|
|
return row
|
|
}
|
|
|
|
private func makeInfoRow(frame: NSRect, title: String, value: String) -> NSView {
|
|
let row: NSView = NSView(frame: frame)
|
|
let titleWidth = title.widthOfString(usingFont: .systemFont(ofSize: 13, weight: .light)) + 10
|
|
|
|
let rowTitle: NSTextField = TextView(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: titleWidth, height: 17))
|
|
rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
|
|
rowTitle.textColor = .secondaryLabelColor
|
|
rowTitle.stringValue = title
|
|
|
|
let rowValue: NSTextField = TextView(frame: NSRect(x: titleWidth, y: (row.frame.height - 16)/2, width: row.frame.width - titleWidth, height: 17))
|
|
rowValue.font = NSFont.systemFont(ofSize: 13, weight: .light)
|
|
rowValue.alignment = .right
|
|
rowValue.stringValue = value
|
|
rowValue.isSelectable = true
|
|
|
|
if value.contains("\n") {
|
|
rowValue.frame = NSRect(x: titleWidth, y: 0, width: rowValue.frame.width, height: row.frame.height)
|
|
}
|
|
|
|
row.addSubview(rowTitle)
|
|
row.addSubview(rowValue)
|
|
|
|
return row
|
|
}
|
|
|
|
private func makeSettingRow(frame: NSRect, title: String, action: Selector, state: Bool) -> NSView {
|
|
let row: NSView = NSView(frame: frame)
|
|
let state: NSControl.StateValue = state ? .on : .off
|
|
|
|
let rowTitle: NSTextField = TextView(frame: NSRect(x: 0, y: (row.frame.height - 16)/2, width: row.frame.width - 52, height: 17))
|
|
rowTitle.font = NSFont.systemFont(ofSize: 13, weight: .light)
|
|
rowTitle.textColor = .secondaryLabelColor
|
|
rowTitle.stringValue = title
|
|
|
|
var toggle: NSControl = NSControl()
|
|
if #available(OSX 10.15, *) {
|
|
let switchButton = NSSwitch(frame: NSRect(x: row.frame.width - 50, y: 0, width: 50, height: row.frame.height))
|
|
switchButton.state = state
|
|
switchButton.action = action
|
|
switchButton.target = self
|
|
|
|
toggle = switchButton
|
|
} else {
|
|
let button: NSButton = NSButton(frame: NSRect(x: row.frame.width - 30, y: 0, width: 30, height: row.frame.height))
|
|
button.setButtonType(.switch)
|
|
button.state = state
|
|
button.title = ""
|
|
button.action = action
|
|
button.isBordered = false
|
|
button.isTransparent = true
|
|
button.target = self
|
|
|
|
toggle = button
|
|
}
|
|
|
|
row.addSubview(toggle)
|
|
row.addSubview(rowTitle)
|
|
|
|
return row
|
|
}
|
|
|
|
private func addDeviceInfo() {
|
|
let view: NSView = NSView(frame: NSRect(x: 0, y: self.height - self.deviceInfoHeight, width: self.width, height: self.deviceInfoHeight))
|
|
let leftPanel: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.width/2, height: self.deviceInfoHeight))
|
|
|
|
let deviceImageView: NSImageView = NSImageView(image: systemKit.device.model.icon)
|
|
deviceImageView.frame = NSRect(x: (leftPanel.frame.width - 160)/2, y: ((self.deviceInfoHeight - 120)/2) + 22, width: 160, height: 120)
|
|
|
|
let deviceNameField: NSTextField = TextView(frame: NSRect(x: 0, y: 72, width: leftPanel.frame.width, height: 20))
|
|
deviceNameField.alignment = .center
|
|
deviceNameField.font = NSFont.systemFont(ofSize: 14, weight: .regular)
|
|
deviceNameField.stringValue = systemKit.device.model.name
|
|
deviceNameField.isSelectable = true
|
|
deviceNameField.toolTip = systemKit.device.modelIdentifier
|
|
|
|
let osField: NSTextField = TextView(frame: NSRect(x: 0, y: 52, width: leftPanel.frame.width, height: 18))
|
|
osField.alignment = .center
|
|
osField.font = NSFont.systemFont(ofSize: 12, weight: .regular)
|
|
osField.stringValue = "macOS \(systemKit.device.os?.name ?? LocalizedString("Unknown")) (\(systemKit.device.os?.version.getFullVersion() ?? ""))"
|
|
osField.isSelectable = true
|
|
|
|
leftPanel.addSubview(deviceImageView)
|
|
leftPanel.addSubview(deviceNameField)
|
|
leftPanel.addSubview(osField)
|
|
|
|
let rightPanel: NSView = NSView(frame: NSRect(x: self.width/2, y: 0, width: self.width/2, height: self.deviceInfoHeight))
|
|
|
|
let iconView: NSImageView = NSImageView(frame: NSRect(x: (leftPanel.frame.width - 100)/2, y: ((self.deviceInfoHeight - 100)/2) + 32, width: 100, height: 100))
|
|
iconView.image = NSImage(named: NSImage.Name("AppIcon"))!
|
|
|
|
let infoView: NSView = NSView(frame: NSRect(x: 0, y: 54, width: self.width/2, height: 42))
|
|
|
|
let statsName: NSTextField = TextView(frame: NSRect(x: 0, y: 20, width: leftPanel.frame.width, height: 22))
|
|
statsName.alignment = .center
|
|
statsName.font = NSFont.systemFont(ofSize: 20, weight: .regular)
|
|
statsName.stringValue = "Stats"
|
|
statsName.isSelectable = true
|
|
|
|
let versionNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
|
|
let buildNumber = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String
|
|
|
|
let statsVersion: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: leftPanel.frame.width, height: 16))
|
|
statsVersion.alignment = .center
|
|
statsVersion.font = NSFont.systemFont(ofSize: 12, weight: .regular)
|
|
statsVersion.stringValue = "\(LocalizedString("Version")) \(versionNumber)"
|
|
statsVersion.isSelectable = true
|
|
statsVersion.toolTip = "Build number: \(buildNumber)"
|
|
|
|
infoView.addSubview(statsName)
|
|
infoView.addSubview(statsVersion)
|
|
|
|
let button: NSButton = NSButton(frame: NSRect(x: (rightPanel.frame.width - 160)/2, y: 20, width: 160, height: 28))
|
|
button.title = LocalizedString("Check for update")
|
|
button.bezelStyle = .rounded
|
|
button.target = self
|
|
button.action = #selector(updateAction)
|
|
self.updateButton = button
|
|
|
|
rightPanel.addSubview(iconView)
|
|
rightPanel.addSubview(infoView)
|
|
rightPanel.addSubview(button)
|
|
|
|
view.addSubview(leftPanel)
|
|
view.addSubview(rightPanel)
|
|
|
|
self.addSubview(view)
|
|
}
|
|
|
|
@objc func updateAction(_ sender: NSObject) {
|
|
updater.check() { result, error in
|
|
if error != nil {
|
|
os_log(.error, log: log, "error updater.check(): %s", "\(error!.localizedDescription)")
|
|
return
|
|
}
|
|
|
|
guard error == nil, let version: version_s = result else {
|
|
os_log(.error, log: log, "download error(): %s", "\(error!.localizedDescription)")
|
|
return
|
|
}
|
|
|
|
DispatchQueue.main.async(execute: {
|
|
self.updateWindow.open(version)
|
|
return
|
|
})
|
|
}
|
|
}
|
|
|
|
@objc private func toggleUpdateInterval(_ sender: NSMenuItem) {
|
|
if let newUpdateInterval = AppUpdateIntervals(rawValue: sender.title) {
|
|
store.set(key: "update-interval", value: newUpdateInterval.rawValue)
|
|
NotificationCenter.default.post(name: .changeCronInterval, object: nil, userInfo: nil)
|
|
}
|
|
}
|
|
|
|
@objc private func toggleTemperatureUnits(_ sender: NSMenuItem) {
|
|
guard let key = sender.representedObject as? String else {
|
|
return
|
|
}
|
|
self.temperatureUnitsValue = key
|
|
}
|
|
|
|
@objc func toggleDock(_ sender: NSControl) {
|
|
var state: NSControl.StateValue? = nil
|
|
if #available(OSX 10.15, *) {
|
|
state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
|
|
} else {
|
|
state = sender is NSButton ? (sender as! NSButton).state: nil
|
|
}
|
|
|
|
if state != nil {
|
|
store.set(key: "dockIcon", value: state! == NSControl.StateValue.on)
|
|
}
|
|
let dockIconStatus = state == NSControl.StateValue.on ? NSApplication.ActivationPolicy.regular : NSApplication.ActivationPolicy.accessory
|
|
NSApp.setActivationPolicy(dockIconStatus)
|
|
if state == .off {
|
|
NSApplication.shared.activate(ignoringOtherApps: true)
|
|
}
|
|
}
|
|
|
|
@objc func toggleLaunchAtLogin(_ sender: NSControl) {
|
|
var state: NSControl.StateValue? = nil
|
|
if #available(OSX 10.15, *) {
|
|
state = sender is NSSwitch ? (sender as! NSSwitch).state: nil
|
|
} else {
|
|
state = sender is NSButton ? (sender as! NSButton).state: nil
|
|
}
|
|
|
|
LaunchAtLogin.isEnabled = state! == NSControl.StateValue.on
|
|
if !store.exist(key: "runAtLoginInitialized") {
|
|
store.set(key: "runAtLoginInitialized", value: true)
|
|
}
|
|
}
|
|
}
|