|
|
|
//
|
|
|
|
// AppDelegate.swift
|
|
|
|
// LunaMac
|
|
|
|
//
|
|
|
|
// Created by Claire Davis on 9/21/22.
|
|
|
|
// Copyright © 2022 A Better Geek. All rights reserved.
|
|
|
|
//
|
|
|
|
/*
|
|
|
|
====== TO DO =======
|
|
|
|
* auto-update at a set interval
|
|
|
|
* make help menu item work (show the window)
|
|
|
|
* fix alignment of status bar icon (it's too high)
|
|
|
|
*/
|
|
|
|
|
|
|
|
import Cocoa
|
|
|
|
import Foundation
|
|
|
|
import CoreFoundation
|
|
|
|
|
|
|
|
@NSApplicationMain
|
|
|
|
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
|
|
|
|
|
|
|
|
// static array of possible lunar values
|
|
|
|
let phaseArray:[String:[String:NSString]] = [
|
|
|
|
"new" : [
|
|
|
|
"nice" : "New Moon",
|
|
|
|
"start" : "0",
|
|
|
|
"end" : "1",
|
|
|
|
"icon" : "🌑"
|
|
|
|
],
|
|
|
|
"waxc" : [
|
|
|
|
"nice" : "Waxing Crescent",
|
|
|
|
"start" : "1",
|
|
|
|
"end" : "6.38264692644",
|
|
|
|
"icon" : "🌒"
|
|
|
|
],
|
|
|
|
"firstq" : [
|
|
|
|
"nice" : "First Quarter",
|
|
|
|
"start" : "6.38264692644",
|
|
|
|
"end" : "8.38264692644",
|
|
|
|
"icon" : "🌓"
|
|
|
|
],
|
|
|
|
"waxg" : [
|
|
|
|
"nice" : "Waxing Gibbous",
|
|
|
|
"start" : "8.38264692644",
|
|
|
|
"end" : "13.76529385288",
|
|
|
|
"icon" : "🌔"
|
|
|
|
],
|
|
|
|
"full" : [
|
|
|
|
"nice" : "Full Moon",
|
|
|
|
"start" : "13.76529385288",
|
|
|
|
"end" : "15.76529385288",
|
|
|
|
"icon" : "🌕"
|
|
|
|
],
|
|
|
|
"wang" : [
|
|
|
|
"nice" : "Waning Gibbous",
|
|
|
|
"start" : "15.76529385288",
|
|
|
|
"end" : "21.14794077932",
|
|
|
|
"icon" : "🌖"
|
|
|
|
],
|
|
|
|
"lastq" : [
|
|
|
|
"nice" : "Last Quarter",
|
|
|
|
"start" : "21.14794077932",
|
|
|
|
"end" : "23.14794077932",
|
|
|
|
"icon" : "🌗"
|
|
|
|
],
|
|
|
|
"wanc" : [
|
|
|
|
"nice" : "Waning Crescent",
|
|
|
|
"start" : "23.14794077932",
|
|
|
|
"end" : "28.53058770576",
|
|
|
|
"icon" : "🌘"
|
|
|
|
],
|
|
|
|
"new2" : [
|
|
|
|
"nice" : "New Moon",
|
|
|
|
"start" : "28.53058770576",
|
|
|
|
"end" : "29.53058770576",
|
|
|
|
"icon" : "🌑"
|
|
|
|
],
|
|
|
|
"default" : [
|
|
|
|
"nice" : "Failed to calculate.",
|
|
|
|
"icon" : "⨂"
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
@IBAction func clickClick(_ sender: NSButtonCell) {
|
|
|
|
// show changelog
|
|
|
|
showLog()
|
|
|
|
}
|
|
|
|
|
|
|
|
// get system-wide menu bar
|
|
|
|
let statusBar = NSStatusBar.system
|
|
|
|
|
|
|
|
// make sure applet stays in memory
|
|
|
|
var statusBarItem: NSStatusItem?
|
|
|
|
|
|
|
|
// create a menu for the icon
|
|
|
|
let statusBarMenu = NSMenu(title: "LunaMac Menu")
|
|
|
|
|
|
|
|
// create empty timer for event loop
|
|
|
|
var lunaTimer = Timer()
|
|
|
|
|
|
|
|
// is the info box open?
|
|
|
|
var infoOpen = false
|
|
|
|
|
|
|
|
// is the changelog open?
|
|
|
|
var logOpen = false
|
|
|
|
|
|
|
|
// user settings
|
|
|
|
let defaults = UserDefaults.standard
|
|
|
|
|
|
|
|
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
|
|
|
// set icon size to square
|
|
|
|
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
|
|
|
|
|
|
|
|
// build the icon and menu
|
|
|
|
updateIcon()
|
|
|
|
|
|
|
|
// listen for theme changes (dark and light)
|
|
|
|
themeListener()
|
|
|
|
|
|
|
|
// start the the timer for keeping the icon updated automatically
|
|
|
|
startTimer()
|
|
|
|
}
|
|
|
|
|
|
|
|
// start the timer for auto-updating the icon
|
|
|
|
func startTimer() {
|
|
|
|
lunaTimer = Timer.scheduledTimer(timeInterval: 10, target: self, selector: #selector(updateIcon), userInfo: nil, repeats: true)
|
|
|
|
RunLoop.current.add(lunaTimer, forMode: RunLoop.Mode.common)
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc func menuWillOpen(_ menu: NSMenu) {
|
|
|
|
lunaTimer.invalidate()
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc func menuDidClose(_ menu: NSMenu) {
|
|
|
|
startTimer()
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc func buildMenu(key: String = "default") {
|
|
|
|
statusBarMenu.removeAllItems()
|
|
|
|
|
|
|
|
statusBarMenu.delegate = self
|
|
|
|
|
|
|
|
// filename suffix
|
|
|
|
/*
|
|
|
|
-dark dark mode
|
|
|
|
-alt-dark dark mode inverted
|
|
|
|
-emoji emoji
|
|
|
|
*/
|
|
|
|
var suffix = ""
|
|
|
|
|
|
|
|
// if key is new2, rename to new
|
|
|
|
var key2 = key.contains("2") ? "new" : key
|
|
|
|
|
|
|
|
// if iconPref is set to emoji
|
|
|
|
if (defaults.string(forKey: "iconPref") == "emoji") {
|
|
|
|
suffix += "-emoji"
|
|
|
|
}
|
|
|
|
// otherwise use monochrome icons
|
|
|
|
else {
|
|
|
|
// check for dark mode and set the right image file
|
|
|
|
if #available(OSX 10.14, *) {
|
|
|
|
let darkMode = UserDefaults.standard.string(forKey: "AppleInterfaceStyle")
|
|
|
|
|
|
|
|
if darkMode == "Dark" {
|
|
|
|
// if theme is set to dark alt make adjustments
|
|
|
|
if (defaults.string(forKey: "iconPref") == "darkalt") {
|
|
|
|
switch (key2) {
|
|
|
|
case "new" :
|
|
|
|
key2 = "full"
|
|
|
|
break
|
|
|
|
case "full" :
|
|
|
|
key2 = "new"
|
|
|
|
break
|
|
|
|
case "firstq" :
|
|
|
|
key2 = "lastq"
|
|
|
|
break
|
|
|
|
case "lastq" :
|
|
|
|
key2 = "firstq"
|
|
|
|
break
|
|
|
|
case "wanc", "wang", "waxc", "waxg" :
|
|
|
|
suffix += "-alt"
|
|
|
|
break
|
|
|
|
default :
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
suffix += "-dark"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// set image filename
|
|
|
|
let imgName = key2 + suffix + ".png"
|
|
|
|
|
|
|
|
statusBarItem?.button?.image = NSImage(named: imgName)
|
|
|
|
|
|
|
|
// add the menu to the menu bar icon
|
|
|
|
statusBarItem?.menu = statusBarMenu
|
|
|
|
|
|
|
|
let topTxt = phaseArray["default"]?["nice"] as String? ?? "Failed to calculate."
|
|
|
|
|
|
|
|
// add menu items to the menu
|
|
|
|
if (!topTxt.isEmpty) {
|
|
|
|
statusBarMenu.addItem(
|
|
|
|
withTitle: phaseArray[key]?["nice"] as String? ?? topTxt,
|
|
|
|
action: nil,
|
|
|
|
keyEquivalent: ""
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
statusBarMenu.addItem(NSMenuItem.separator())
|
|
|
|
|
|
|
|
statusBarMenu.addItem(
|
|
|
|
withTitle: "Update moon phase",
|
|
|
|
action: #selector(AppDelegate.updateIcon),
|
|
|
|
keyEquivalent: ""
|
|
|
|
)
|
|
|
|
|
|
|
|
statusBarMenu.addItem(NSMenuItem.separator())
|
|
|
|
|
|
|
|
// create menu item to hold submenu
|
|
|
|
let prefMenu = NSMenu(title: "Prefs")
|
|
|
|
|
|
|
|
// default
|
|
|
|
let optDefault = NSMenuItem(
|
|
|
|
title: "Default",
|
|
|
|
action: #selector(setPref),
|
|
|
|
keyEquivalent: ""
|
|
|
|
)
|
|
|
|
|
|
|
|
// alternate dark
|
|
|
|
let optDarkAlt = NSMenuItem(
|
|
|
|
title: "Alternate Dark",
|
|
|
|
action: #selector(setPref),
|
|
|
|
keyEquivalent: ""
|
|
|
|
)
|
|
|
|
|
|
|
|
// emoji
|
|
|
|
let optEmoji = NSMenuItem(
|
|
|
|
title: "Emoji",
|
|
|
|
action: #selector(setPref),
|
|
|
|
keyEquivalent: ""
|
|
|
|
)
|
|
|
|
|
|
|
|
// make sure the right menu item is checked
|
|
|
|
switch (defaults.string(forKey: "iconPref")) {
|
|
|
|
case "default" :
|
|
|
|
// standard icons
|
|
|
|
optDefault.state = NSControl.StateValue.on
|
|
|
|
break
|
|
|
|
case "darkalt" :
|
|
|
|
// inverted dark mode icons
|
|
|
|
optDarkAlt.state = NSControl.StateValue.on
|
|
|
|
break
|
|
|
|
case "emoji" :
|
|
|
|
// emoji icons
|
|
|
|
optEmoji.state = NSControl.StateValue.on
|
|
|
|
break
|
|
|
|
default :
|
|
|
|
// default to standard icons
|
|
|
|
optDefault.state = NSControl.StateValue.on
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
prefMenu.addItem(optDefault)
|
|
|
|
prefMenu.addItem(optDarkAlt)
|
|
|
|
prefMenu.addItem(optEmoji)
|
|
|
|
|
|
|
|
let prefMenuItem = NSMenuItem(
|
|
|
|
title: "Icon Style",
|
|
|
|
action: nil,
|
|
|
|
keyEquivalent: ""
|
|
|
|
)
|
|
|
|
|
|
|
|
statusBarMenu.addItem(prefMenuItem)
|
|
|
|
statusBarMenu.setSubmenu(prefMenu, for: prefMenuItem)
|
|
|
|
|
|
|
|
statusBarMenu.addItem(
|
|
|
|
withTitle: "About LunaMac...",
|
|
|
|
action: #selector(AppDelegate.showAbout),
|
|
|
|
keyEquivalent: ""
|
|
|
|
)
|
|
|
|
|
|
|
|
statusBarMenu.addItem(
|
|
|
|
withTitle: "Quit LunaMac",
|
|
|
|
action: #selector(AppDelegate.quitApp),
|
|
|
|
keyEquivalent: ""
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc func setPref(sender: NSMenuItem) {
|
|
|
|
// sender.title tells us what to do
|
|
|
|
switch (sender.title) {
|
|
|
|
case "Default" :
|
|
|
|
defaults.set("default", forKey: "iconPref")
|
|
|
|
break
|
|
|
|
case "Alternate Dark" :
|
|
|
|
defaults.set("darkalt", forKey: "iconPref")
|
|
|
|
break
|
|
|
|
case "Emoji" :
|
|
|
|
defaults.set("emoji", forKey: "iconPref")
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// rebuild the menu
|
|
|
|
updateIcon()
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc func showAbout() {
|
|
|
|
if (!infoOpen) {
|
|
|
|
// create infobox window object
|
|
|
|
let aboutWin = NSWindowController(windowNibName: "InfoBox")
|
|
|
|
|
|
|
|
aboutWin.loadWindow()
|
|
|
|
}
|
|
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc func showLog() {
|
|
|
|
if (!logOpen) {
|
|
|
|
// create changelog window object
|
|
|
|
// this creates duplicate windows
|
|
|
|
let logWin = NSWindowController(windowNibName: "Changelog")
|
|
|
|
|
|
|
|
logWin.loadWindow()
|
|
|
|
}
|
|
|
|
NSApp.activate(ignoringOtherApps: true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// update menu bar icon
|
|
|
|
@objc func updateIcon() {
|
|
|
|
// current system time in UTC
|
|
|
|
let sysDate = Date()
|
|
|
|
// get baseline date
|
|
|
|
// let baseDateStr = "2000-01-06 18:14:00 +0000"
|
|
|
|
|
|
|
|
// create date object from baseDate
|
|
|
|
// date components
|
|
|
|
var bdc = DateComponents()
|
|
|
|
bdc.year = 2000
|
|
|
|
bdc.month = 1
|
|
|
|
bdc.day = 6
|
|
|
|
bdc.hour = 18
|
|
|
|
bdc.minute = 14
|
|
|
|
// create date object
|
|
|
|
let ucal = Calendar(identifier: .gregorian)
|
|
|
|
let baseDate = ucal.date(from: bdc)
|
|
|
|
|
|
|
|
// days in a lunar cycle
|
|
|
|
let lunarDays = 29.53058770576
|
|
|
|
// seconds in a lunar cycle
|
|
|
|
let lunarSecs = lunarDays * (24 * 60 * 60)
|
|
|
|
|
|
|
|
// calculate seconds between sysDate and baseDate
|
|
|
|
// if baseDate is nil, default to current system time
|
|
|
|
let totalSecs = sysDate.timeIntervalSince(baseDate ?? Date())
|
|
|
|
|
|
|
|
if (totalSecs.sign != .minus) {
|
|
|
|
// a positive number is valid
|
|
|
|
// % calculates seconds of current cycle
|
|
|
|
let currSecs = totalSecs.truncatingRemainder(dividingBy: lunarSecs)
|
|
|
|
let currFrac = currSecs / lunarSecs
|
|
|
|
let currDays = currFrac * lunarDays
|
|
|
|
|
|
|
|
var newArr = [String:[String:NSString]]()
|
|
|
|
|
|
|
|
newArr = phaseArray.filter{
|
|
|
|
findPhase(
|
|
|
|
start: $0.value["start"]?.doubleValue ?? 0,
|
|
|
|
end: $0.value["end"]?.doubleValue ?? 0,
|
|
|
|
curr: currDays
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// we want newArr[0].value which is [String:NSString]
|
|
|
|
let theKey = newArr.first?.key ?? "default"
|
|
|
|
buildMenu(key: theKey)
|
|
|
|
} else {
|
|
|
|
// if dateDiff is negative, something went wrong.
|
|
|
|
buildMenu()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@objc func findPhase(start: Double, end: Double, curr: Double) -> Bool {
|
|
|
|
|
|
|
|
if ((curr >= start) && (curr <= end)) {
|
|
|
|
return true
|
|
|
|
} else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// quit application
|
|
|
|
@objc func quitApp() {
|
|
|
|
NSApp.terminate(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
func applicationWillTerminate(_ aNotification: Notification) {
|
|
|
|
// Insert code here to tear down your application
|
|
|
|
}
|
|
|
|
|
|
|
|
func themeListener() {
|
|
|
|
DistributedNotificationCenter.default.addObserver(
|
|
|
|
self,
|
|
|
|
selector: #selector(updateIcon),
|
|
|
|
name: .AppleInterfaceThemeChangedNotification,
|
|
|
|
object: nil
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension Notification.Name {
|
|
|
|
static let AppleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
|
|
|
|
}
|