You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
417 lines
12 KiB
417 lines
12 KiB
// |
|
// 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) { |
|
// 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() |
|
|
|
// set icon size to square |
|
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength) |
|
} |
|
|
|
// 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") |
|
}
|
|
|