mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-15 00:34:08 +09:00
feat: added Clock module (#929)
This commit is contained in:
36
Modules/Clock/config.plist
Normal file
36
Modules/Clock/config.plist
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Name</key>
|
||||
<string>Clock</string>
|
||||
<key>State</key>
|
||||
<false/>
|
||||
<key>Symbol</key>
|
||||
<string>clock.fill</string>
|
||||
<key>Widgets</key>
|
||||
<dict>
|
||||
<key>label</key>
|
||||
<dict>
|
||||
<key>Default</key>
|
||||
<false/>
|
||||
<key>Title</key>
|
||||
<string>CLK</string>
|
||||
<key>Order</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
<key>sensors</key>
|
||||
<dict>
|
||||
<key>Default</key>
|
||||
<true/>
|
||||
<key>Preview</key>
|
||||
<dict>
|
||||
<key>Values</key>
|
||||
<string>23.03.2023 13:45</string>
|
||||
</dict>
|
||||
<key>Order</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
133
Modules/Clock/main.swift
Normal file
133
Modules/Clock/main.swift
Normal file
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// main.swift
|
||||
// Clock
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 23/03/2023
|
||||
// Using Swift 5.0
|
||||
// Running on macOS 13.2
|
||||
//
|
||||
// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Kit
|
||||
|
||||
public struct Clock_t: Codable {
|
||||
public var id: String = UUID().uuidString
|
||||
public var enabled: Bool = true
|
||||
|
||||
public var name: String
|
||||
public var format: String
|
||||
public var tz: String
|
||||
|
||||
public var value: Date? = nil
|
||||
|
||||
public func formatted() -> String {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = self.format
|
||||
formatter.timeZone = TimeZone(fromUTC: self.tz)
|
||||
return formatter.string(from: self.value ?? Date())
|
||||
}
|
||||
}
|
||||
|
||||
internal class ClockReader: Reader<Date> {
|
||||
public override func read() {
|
||||
self.callback(Date())
|
||||
}
|
||||
}
|
||||
|
||||
public class Clock: Module {
|
||||
private let popupView: Popup = Popup()
|
||||
private let settingsView: Settings = Settings()
|
||||
|
||||
private var reader: ClockReader = ClockReader()
|
||||
|
||||
private var list: [Clock_t] {
|
||||
if let objects = Store.shared.data(key: "\(Clock.title)_list") {
|
||||
let decoder = JSONDecoder()
|
||||
if let objectsDecoded = try? decoder.decode(Array.self, from: objects) as [Clock_t] {
|
||||
return objectsDecoded
|
||||
}
|
||||
}
|
||||
return [Clock.local]
|
||||
}
|
||||
|
||||
public init() {
|
||||
super.init(
|
||||
popup: self.popupView,
|
||||
settings: self.settingsView
|
||||
)
|
||||
guard self.available else { return }
|
||||
|
||||
self.reader.callbackHandler = { [unowned self] value in
|
||||
guard let value else { return }
|
||||
self.callback(value)
|
||||
}
|
||||
|
||||
self.addReader(self.reader)
|
||||
self.reader.readyCallback = { [unowned self] in
|
||||
self.readyHandler()
|
||||
}
|
||||
}
|
||||
|
||||
private func callback(_ value: Date) {
|
||||
var clocks: [Clock_t] = self.list.filter({ $0.enabled })
|
||||
var list: [Stack_t] = []
|
||||
|
||||
for (i, c) in clocks.enumerated() {
|
||||
clocks[i].value = value
|
||||
list.append(Stack_t(key: c.name, value: clocks[i].formatted()))
|
||||
}
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.popupView.callback(clocks)
|
||||
})
|
||||
|
||||
self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in
|
||||
switch w.item {
|
||||
case let widget as SensorsWidget: widget.setValues(list)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Clock {
|
||||
static let title: String = "Clock"
|
||||
static var local: Clock_t {
|
||||
Clock_t(name: localizedString("Local time"), format: "YYYY-MM-DD HH:mm:ss", tz: "local")
|
||||
}
|
||||
static var zones: [KeyValue_t] {
|
||||
[
|
||||
KeyValue_t(key: "local", value: "Local"),
|
||||
KeyValue_t(key: "separator", value: "separator"),
|
||||
KeyValue_t(key: "-12", value: "UTC-12:00"),
|
||||
KeyValue_t(key: "-11", value: "UTC-11:00"),
|
||||
KeyValue_t(key: "-10", value: "UTC-10:00"),
|
||||
KeyValue_t(key: "-9", value: "UTC-9:00"),
|
||||
KeyValue_t(key: "-8", value: "UTC-8:00"),
|
||||
KeyValue_t(key: "-7", value: "UTC-7:00"),
|
||||
KeyValue_t(key: "-6", value: "UTC-6:00"),
|
||||
KeyValue_t(key: "-5", value: "UTC-5:00"),
|
||||
KeyValue_t(key: "-4", value: "UTC-4:00"),
|
||||
KeyValue_t(key: "-3", value: "UTC-3:00"),
|
||||
KeyValue_t(key: "-2", value: "UTC-2:00"),
|
||||
KeyValue_t(key: "-1", value: "UTC-1:00"),
|
||||
KeyValue_t(key: "0", value: "UTC"),
|
||||
KeyValue_t(key: "1", value: "UTC+1:00"),
|
||||
KeyValue_t(key: "2", value: "UTC+2:00"),
|
||||
KeyValue_t(key: "3", value: "UTC+3:00"),
|
||||
KeyValue_t(key: "4", value: "UTC+4:00"),
|
||||
KeyValue_t(key: "5", value: "UTC+5:00"),
|
||||
KeyValue_t(key: "6", value: "UTC+6:00"),
|
||||
KeyValue_t(key: "7", value: "UTC+7:00"),
|
||||
KeyValue_t(key: "8", value: "UTC+8:00"),
|
||||
KeyValue_t(key: "9", value: "UTC+9:00"),
|
||||
KeyValue_t(key: "10", value: "UTC+10:00"),
|
||||
KeyValue_t(key: "11", value: "UTC+11:00"),
|
||||
KeyValue_t(key: "12", value: "UTC+12:00"),
|
||||
KeyValue_t(key: "13", value: "UTC+13:00"),
|
||||
KeyValue_t(key: "14", value: "UTC+14:00")
|
||||
]
|
||||
}
|
||||
}
|
||||
215
Modules/Clock/popup.swift
Normal file
215
Modules/Clock/popup.swift
Normal file
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// popup.swift
|
||||
// Clock
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 24/03/2023
|
||||
// Using Swift 5.0
|
||||
// Running on macOS 13.2
|
||||
//
|
||||
// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Kit
|
||||
|
||||
internal class Popup: PopupWrapper {
|
||||
public init() {
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0))
|
||||
|
||||
self.orientation = .vertical
|
||||
self.spacing = Constants.Popup.margins
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
internal func callback(_ list: [Clock_t]) {
|
||||
defer {
|
||||
let h = self.arrangedSubviews.map({ $0.bounds.height + self.spacing }).reduce(0, +) - self.spacing
|
||||
if h > 0 && self.frame.size.height != h {
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: h))
|
||||
self.sizeCallback?(self.frame.size)
|
||||
}
|
||||
}
|
||||
|
||||
var views = self.subviews.filter{ $0 is ClockView }.compactMap{ $0 as? ClockView }
|
||||
if list.count < views.count && !views.isEmpty {
|
||||
views.forEach{ $0.removeFromSuperview() }
|
||||
views = []
|
||||
}
|
||||
|
||||
list.forEach { (c: Clock_t) in
|
||||
if let view = views.first(where: { $0.clock.id == c.id }) {
|
||||
view.update(c)
|
||||
} else {
|
||||
self.addArrangedSubview(ClockView(width: self.frame.width, clock: c))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClockView: NSStackView {
|
||||
public var clock: Clock_t
|
||||
|
||||
open override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: self.bounds.width, height: self.bounds.height)
|
||||
}
|
||||
|
||||
private var ready: Bool = false
|
||||
|
||||
private let clockView: ClockChart = ClockChart()
|
||||
private let nameField: NSTextField = TextView()
|
||||
private let timeField: NSTextField = TextView()
|
||||
|
||||
init(width: CGFloat, clock: Clock_t) {
|
||||
self.clock = clock
|
||||
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: width, height: 44))
|
||||
|
||||
self.orientation = .horizontal
|
||||
self.spacing = 5
|
||||
self.edgeInsets = NSEdgeInsets(
|
||||
top: 5,
|
||||
left: 5,
|
||||
bottom: 5,
|
||||
right: 5
|
||||
)
|
||||
self.wantsLayer = true
|
||||
self.layer?.cornerRadius = 2
|
||||
|
||||
self.clockView.widthAnchor.constraint(equalToConstant: 34).isActive = true
|
||||
|
||||
let container: NSStackView = NSStackView()
|
||||
container.orientation = .vertical
|
||||
container.spacing = 2
|
||||
container.distribution = .fillEqually
|
||||
container.alignment = .left
|
||||
|
||||
self.nameField.font = NSFont.systemFont(ofSize: 11, weight: .light)
|
||||
self.setTZ()
|
||||
self.nameField.cell?.truncatesLastVisibleLine = true
|
||||
|
||||
self.timeField.font = NSFont.systemFont(ofSize: 13, weight: .regular)
|
||||
self.timeField.stringValue = clock.formatted()
|
||||
self.timeField.cell?.truncatesLastVisibleLine = true
|
||||
|
||||
container.addArrangedSubview(self.nameField)
|
||||
container.addArrangedSubview(self.timeField)
|
||||
|
||||
self.addArrangedSubview(self.clockView)
|
||||
self.addArrangedSubview(container)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func updateLayer() {
|
||||
self.layer?.backgroundColor = isDarkMode ? NSColor(hexString: "#111111", alpha: 0.25).cgColor : NSColor(hexString: "#f5f5f5", alpha: 1).cgColor
|
||||
}
|
||||
|
||||
private func setTZ() {
|
||||
self.nameField.stringValue = "\(self.clock.name)"
|
||||
if let tz = Clock.zones.first(where: { $0.key == self.clock.tz }) {
|
||||
self.nameField.stringValue += " (\(tz.value))"
|
||||
}
|
||||
}
|
||||
|
||||
public func update(_ newClock: Clock_t) {
|
||||
if self.clock.tz != newClock.tz {
|
||||
self.clock = newClock
|
||||
self.setTZ()
|
||||
}
|
||||
|
||||
if (self.window?.isVisible ?? false) || !self.ready {
|
||||
self.timeField.stringValue = newClock.formatted()
|
||||
if let value = newClock.value {
|
||||
self.clockView.setValue(value.convertToTimeZone(TimeZone(fromUTC: newClock.tz)))
|
||||
}
|
||||
self.ready = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClockChart: NSView {
|
||||
private var color: NSColor = Color.systemAccent.additional as! NSColor
|
||||
|
||||
private let calendar = Calendar.current
|
||||
private var hour: Int!
|
||||
private var minute: Int!
|
||||
private var second: Int!
|
||||
|
||||
private let hourLayer = CALayer()
|
||||
private let minuteLayer = CALayer()
|
||||
private let secondsLayer = CALayer()
|
||||
private let pinLayer = CAShapeLayer()
|
||||
|
||||
override init(frame: CGRect = NSRect.zero) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
public override func draw(_ dirtyRect: NSRect) {
|
||||
super.draw(dirtyRect)
|
||||
|
||||
guard (self.hour != nil), (self.minute != nil), (self.second != nil) else { return }
|
||||
|
||||
let context = NSGraphicsContext.current!.cgContext
|
||||
context.saveGState()
|
||||
context.setFillColor(self.color.cgColor)
|
||||
context.fillEllipse(in: dirtyRect)
|
||||
context.restoreGState()
|
||||
|
||||
let anchor = CGPoint(x: 0.5, y: 0)
|
||||
let center = CGPoint(x: dirtyRect.size.width / 2, y: dirtyRect.size.height / 2)
|
||||
|
||||
let hourAngle: CGFloat = CGFloat(Double(hour) * (360.0 / 12.0)) + CGFloat(Double(minute) * (1.0 / 60.0) * (360.0 / 12.0))
|
||||
let minuteAngle: CGFloat = CGFloat(minute) * CGFloat(360.0 / 60.0)
|
||||
let secondsAngle: CGFloat = CGFloat(self.second) * CGFloat(360.0 / 60.0)
|
||||
|
||||
self.hourLayer.backgroundColor = NSColor.white.cgColor
|
||||
self.hourLayer.anchorPoint = anchor
|
||||
self.hourLayer.position = center
|
||||
self.hourLayer.bounds = CGRect(x: 0, y: 0, width: 3, height: dirtyRect.size.width / 2 - 7)
|
||||
self.hourLayer.transform = CATransform3DMakeRotation(-hourAngle / 180 * CGFloat(Double.pi), 0, 0, 1)
|
||||
self.layer?.addSublayer(self.hourLayer)
|
||||
|
||||
self.minuteLayer.backgroundColor = NSColor.white.cgColor
|
||||
self.minuteLayer.anchorPoint = anchor
|
||||
self.minuteLayer.position = center
|
||||
self.minuteLayer.bounds = CGRect(x: 0, y: 0, width: 2, height: dirtyRect.size.width / 2 - 4)
|
||||
self.minuteLayer.transform = CATransform3DMakeRotation(-minuteAngle / 180 * CGFloat(Double.pi), 0, 0, 1)
|
||||
self.layer?.addSublayer(self.minuteLayer)
|
||||
|
||||
self.secondsLayer.backgroundColor = NSColor.red.cgColor
|
||||
self.secondsLayer.anchorPoint = anchor
|
||||
self.secondsLayer.position = center
|
||||
self.secondsLayer.bounds = CGRect(x: 0, y: 0, width: 1, height: dirtyRect.size.width / 2 - 2)
|
||||
self.secondsLayer.transform = CATransform3DMakeRotation(-secondsAngle / 180 * CGFloat(Double.pi), 0, 0, 1)
|
||||
self.layer?.addSublayer(self.secondsLayer)
|
||||
|
||||
self.pinLayer.fillColor = NSColor.white.cgColor
|
||||
self.pinLayer.anchorPoint = anchor
|
||||
self.pinLayer.path = CGMutablePath(roundedRect: CGRect(
|
||||
x: center.x - 3 / 2,
|
||||
y: center.y - 3 / 2,
|
||||
width: 3,
|
||||
height: 3
|
||||
), cornerWidth: 4, cornerHeight: 4, transform: nil)
|
||||
self.layer?.addSublayer(self.pinLayer)
|
||||
}
|
||||
|
||||
public func setValue(_ value: Date) {
|
||||
self.hour = self.calendar.component(.hour, from: value)
|
||||
self.minute = self.calendar.component(.minute, from: value)
|
||||
self.second = self.calendar.component(.second, from: value)
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.display()
|
||||
})
|
||||
}
|
||||
}
|
||||
267
Modules/Clock/settings.swift
Normal file
267
Modules/Clock/settings.swift
Normal file
@@ -0,0 +1,267 @@
|
||||
//
|
||||
// settings.swift
|
||||
// Clock
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 24/03/2023
|
||||
// Using Swift 5.0
|
||||
// Running on macOS 13.2
|
||||
//
|
||||
// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import Cocoa
|
||||
import Kit
|
||||
|
||||
let nameColumnID = NSUserInterfaceItemIdentifier(rawValue: "name")
|
||||
let formatColumnID = NSUserInterfaceItemIdentifier(rawValue: "format")
|
||||
let tzColumnID = NSUserInterfaceItemIdentifier(rawValue: "tz")
|
||||
let statusColumnID = NSUserInterfaceItemIdentifier(rawValue: "status")
|
||||
|
||||
internal class Settings: NSStackView, Settings_v, NSTableViewDelegate, NSTableViewDataSource, NSTextFieldDelegate {
|
||||
public var callback: (() -> Void) = {}
|
||||
|
||||
private var list: [Clock_t] {
|
||||
get {
|
||||
if let objects = Store.shared.data(key: "\(Clock.title)_list") {
|
||||
let decoder = JSONDecoder()
|
||||
if let objectsDecoded = try? decoder.decode(Array.self, from: objects) as [Clock_t] {
|
||||
return objectsDecoded
|
||||
}
|
||||
}
|
||||
return [Clock.local]
|
||||
}
|
||||
set {
|
||||
if newValue.isEmpty {
|
||||
Store.shared.remove("\(Clock.title)_list")
|
||||
} else {
|
||||
let encoder = JSONEncoder()
|
||||
if let encoded = try? encoder.encode(newValue){
|
||||
Store.shared.set(key: "\(Clock.title)_list", value: encoded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var selectedRow: Int = -1
|
||||
|
||||
private let scrollView = NSScrollView()
|
||||
private let tableView = NSTableView()
|
||||
private var footerView: NSStackView? = nil
|
||||
private var deleteButton: NSButton? = nil
|
||||
|
||||
public init() {
|
||||
super.init(frame: NSRect.zero)
|
||||
|
||||
self.orientation = .vertical
|
||||
self.distribution = .gravityAreas
|
||||
self.edgeInsets = NSEdgeInsets(
|
||||
top: Constants.Settings.margin,
|
||||
left: Constants.Settings.margin,
|
||||
bottom: Constants.Settings.margin,
|
||||
right: Constants.Settings.margin
|
||||
)
|
||||
self.spacing = 0
|
||||
|
||||
self.scrollView.documentView = self.tableView
|
||||
self.scrollView.hasHorizontalScroller = false
|
||||
self.scrollView.hasVerticalScroller = true
|
||||
self.scrollView.autohidesScrollers = true
|
||||
self.scrollView.backgroundColor = NSColor.clear
|
||||
self.scrollView.drawsBackground = true
|
||||
|
||||
self.tableView.frame = self.scrollView.bounds
|
||||
self.tableView.delegate = self
|
||||
self.tableView.dataSource = self
|
||||
self.tableView.allowsMultipleSelection = false
|
||||
self.tableView.focusRingType = .none
|
||||
self.tableView.gridColor = .gridColor
|
||||
self.tableView.columnAutoresizingStyle = .firstColumnOnlyAutoresizingStyle
|
||||
self.tableView.allowsColumnResizing = false
|
||||
self.tableView.gridStyleMask = [.solidVerticalGridLineMask, .solidHorizontalGridLineMask]
|
||||
self.tableView.usesAlternatingRowBackgroundColors = true
|
||||
if #available(macOS 11.0, *) {
|
||||
self.tableView.style = .plain
|
||||
}
|
||||
self.tableView.rowHeight = 27
|
||||
|
||||
let nameColumn = NSTableColumn(identifier: nameColumnID)
|
||||
nameColumn.headerCell.title = localizedString("Name")
|
||||
nameColumn.headerCell.alignment = .center
|
||||
let formatColumn = NSTableColumn(identifier: formatColumnID)
|
||||
formatColumn.headerCell.title = localizedString("Format")
|
||||
formatColumn.headerCell.alignment = .center
|
||||
formatColumn.width = 160
|
||||
let tzColumn = NSTableColumn(identifier: tzColumnID)
|
||||
tzColumn.headerCell.title = localizedString("Time zone")
|
||||
tzColumn.headerCell.alignment = .center
|
||||
tzColumn.width = 132
|
||||
let statusColumn = NSTableColumn(identifier: statusColumnID)
|
||||
statusColumn.headerCell.title = ""
|
||||
statusColumn.width = 16
|
||||
|
||||
self.tableView.addTableColumn(nameColumn)
|
||||
self.tableView.addTableColumn(formatColumn)
|
||||
self.tableView.addTableColumn(tzColumn)
|
||||
self.tableView.addTableColumn(statusColumn)
|
||||
|
||||
let separator = NSBox()
|
||||
separator.boxType = .separator
|
||||
|
||||
self.addArrangedSubview(self.scrollView)
|
||||
self.addArrangedSubview(separator)
|
||||
self.addArrangedSubview(self.footer())
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
self.scrollView.heightAnchor.constraint(equalToConstant: 278)
|
||||
])
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func footer() -> NSView {
|
||||
let view = NSStackView()
|
||||
view.heightAnchor.constraint(equalToConstant: 27).isActive = true
|
||||
view.spacing = 0
|
||||
view.orientation = .horizontal
|
||||
|
||||
var addButton: NSButton {
|
||||
let btn = NSButton()
|
||||
btn.widthAnchor.constraint(equalToConstant: 27).isActive = true
|
||||
btn.heightAnchor.constraint(equalToConstant: 27).isActive = true
|
||||
btn.bezelStyle = .regularSquare
|
||||
btn.translatesAutoresizingMaskIntoConstraints = false
|
||||
if #available(macOS 11.0, *) {
|
||||
btn.image = iconFromSymbol(name: "plus", scale: .large)
|
||||
} else {
|
||||
btn.title = "Add"
|
||||
}
|
||||
btn.isBordered = false
|
||||
btn.action = #selector(self.addNewClock)
|
||||
btn.target = self
|
||||
btn.toolTip = localizedString("Add new clock")
|
||||
btn.focusRingType = .none
|
||||
return btn
|
||||
}
|
||||
var deleteButton: NSButton {
|
||||
let btn = NSButton()
|
||||
btn.widthAnchor.constraint(equalToConstant: 27).isActive = true
|
||||
btn.heightAnchor.constraint(equalToConstant: 27).isActive = true
|
||||
btn.bezelStyle = .regularSquare
|
||||
btn.translatesAutoresizingMaskIntoConstraints = false
|
||||
if #available(macOS 11.0, *) {
|
||||
btn.image = iconFromSymbol(name: "minus", scale: .large)
|
||||
} else {
|
||||
btn.title = "Add"
|
||||
}
|
||||
btn.isBordered = false
|
||||
btn.action = #selector(self.deleteClock)
|
||||
btn.target = self
|
||||
btn.toolTip = localizedString("Delete selected clock")
|
||||
btn.focusRingType = .none
|
||||
return btn
|
||||
}
|
||||
self.deleteButton = deleteButton
|
||||
|
||||
view.addArrangedSubview(addButton)
|
||||
view.addArrangedSubview(NSView())
|
||||
|
||||
self.footerView = view
|
||||
return view
|
||||
}
|
||||
|
||||
func load(widgets: [Kit.widget_t]) {
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
|
||||
func numberOfRows(in tableView: NSTableView) -> Int {
|
||||
return self.list.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||||
guard let id = tableColumn?.identifier else { return nil }
|
||||
let cell = NSTableCellView()
|
||||
let item = self.list[row]
|
||||
|
||||
switch id {
|
||||
case nameColumnID, formatColumnID:
|
||||
let text: NSTextField = NSTextField()
|
||||
text.identifier = id
|
||||
text.drawsBackground = false
|
||||
text.isBordered = false
|
||||
text.sizeToFit()
|
||||
text.delegate = self
|
||||
text.stringValue = id == nameColumnID ? item.name : item.format
|
||||
text.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
cell.addSubview(text)
|
||||
text.widthAnchor.constraint(equalTo: cell.widthAnchor).isActive = true
|
||||
text.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true
|
||||
case tzColumnID:
|
||||
let select: NSPopUpButton = selectView(action: #selector(self.toggleTZ), items: Clock.zones, selected: item.tz)
|
||||
select.identifier = NSUserInterfaceItemIdentifier("\(row)")
|
||||
select.sizeToFit()
|
||||
cell.addSubview(select)
|
||||
case statusColumnID:
|
||||
let button: NSButton = NSButton(frame: NSRect(x: 0, y: 5, width: 10, height: 10))
|
||||
button.identifier = NSUserInterfaceItemIdentifier("\(row)")
|
||||
button.setButtonType(.switch)
|
||||
button.state = item.enabled ? .on : .off
|
||||
button.action = #selector(self.toggleClock)
|
||||
button.title = ""
|
||||
button.isBordered = false
|
||||
button.isTransparent = false
|
||||
button.target = self
|
||||
button.sizeToFit()
|
||||
cell.addSubview(button)
|
||||
default: break
|
||||
}
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
func controlTextDidChange(_ notification: Notification) {
|
||||
if let textField = notification.object as? NSTextField, let id = textField.identifier {
|
||||
let i = self.tableView.selectedRow
|
||||
switch id {
|
||||
case nameColumnID:
|
||||
self.list[i].name = textField.stringValue
|
||||
case formatColumnID:
|
||||
self.list[i].format = textField.stringValue
|
||||
default: return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tableViewSelectionDidChange(_ notification: Notification) {
|
||||
if self.tableView.selectedRow == -1 {
|
||||
self.deleteButton?.removeFromSuperview()
|
||||
} else {
|
||||
if let btn = self.deleteButton {
|
||||
self.footerView?.addArrangedSubview(btn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func toggleTZ(_ sender: NSMenuItem) {
|
||||
guard let key = sender.representedObject as? String, let id = sender.identifier, let i = Int(id.rawValue) else { return }
|
||||
self.list[i].tz = key
|
||||
}
|
||||
|
||||
@objc private func toggleClock(_ sender: NSButton) {
|
||||
guard let id = sender.identifier, let i = Int(id.rawValue) else { return }
|
||||
self.list[i].enabled = sender.state == NSControl.StateValue.on
|
||||
}
|
||||
|
||||
@objc private func addNewClock(_ sender: Any) {
|
||||
self.list.append(Clock_t(name: "Clock \(self.list.count)", format: Clock.local.format, tz: Clock.local.tz))
|
||||
self.tableView.reloadData()
|
||||
}
|
||||
@objc private func deleteClock(_ sender: Any) {
|
||||
guard self.tableView.selectedRow != -1 else { return }
|
||||
self.list.remove(at: self.tableView.selectedRow)
|
||||
self.tableView.reloadData()
|
||||
self.deleteButton?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user