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.
302 lines
12 KiB
302 lines
12 KiB
6 years ago
|
// Copyright © 2015 Abhishek Banthia
|
||
|
|
||
|
import Cocoa
|
||
|
import EventKit
|
||
|
|
||
|
class ClockerTextBackgroundView: NSView {
|
||
|
|
||
|
override func awakeFromNib() {
|
||
|
wantsLayer = true
|
||
|
layer?.cornerRadius = 8.0
|
||
|
layer?.masksToBounds = false
|
||
|
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
|
||
|
|
||
|
NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in
|
||
|
self.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
|
||
|
}
|
||
|
}
|
||
|
|
||
|
override func updateLayer() {
|
||
|
super.updateLayer()
|
||
|
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class CalendarViewController: ParentViewController {
|
||
|
|
||
|
@IBOutlet var showSegmentedControl: NSSegmentedControl!
|
||
|
@IBOutlet var allDaysSegmentedControl: NSSegmentedControl!
|
||
|
@IBOutlet var truncateTextField: NSTextField!
|
||
|
@IBOutlet var noAccessView: NSVisualEffectView!
|
||
|
@IBOutlet var informationField: NSTextField!
|
||
|
@IBOutlet var grantAccessButton: NSButton!
|
||
|
@IBOutlet weak var calendarsTableView: NSTableView!
|
||
|
|
||
|
@IBOutlet weak var showNextMeetingInMenubarControl: NSSegmentedControl!
|
||
|
@IBOutlet weak var backgroundView: NSView!
|
||
|
@IBOutlet weak var nextMeetingBackgroundView: NSView!
|
||
|
|
||
|
private lazy var calendars: [Any] = EventCenter.sharedCenter().fetchSourcesAndCalendars()
|
||
|
|
||
|
override func viewDidLoad() {
|
||
|
super.viewDidLoad()
|
||
|
|
||
|
setup()
|
||
|
|
||
|
NotificationCenter.default.addObserver(self,
|
||
|
selector: #selector(calendarAccessStatusChanged),
|
||
|
name: .calendarAccessGranted,
|
||
|
object: nil)
|
||
|
|
||
|
NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { (notification) in
|
||
|
self.setup()
|
||
|
}
|
||
|
|
||
|
if #available(macOS 10.14, *) {
|
||
|
noAccessView.material = .underWindowBackground
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@objc func calendarAccessStatusChanged() {
|
||
|
verifyCalendarAccess()
|
||
|
|
||
|
view.window?.windowController?.showWindow(nil)
|
||
|
view.window?.makeKeyAndOrderFront(nil)
|
||
|
}
|
||
|
|
||
|
override func viewWillAppear() {
|
||
|
super.viewWillAppear()
|
||
|
|
||
|
verifyCalendarAccess()
|
||
|
|
||
|
if DataStore.shared().shouldDisplay(ViewType.upcomingEventView) {
|
||
|
showSegmentedControl.selectedSegment = 0
|
||
|
} else {
|
||
|
showSegmentedControl.selectedSegment = 1
|
||
|
}
|
||
|
|
||
|
// If the menubar mode is compact, we can't show meetings in the menubar. So disable toggling that option.
|
||
|
showNextMeetingInMenubarControl.isEnabled = !(DataStore.shared().shouldDisplay(.menubarCompactMode))
|
||
|
}
|
||
|
|
||
|
private func verifyCalendarAccess() {
|
||
|
let hasCalendarAccess = EventCenter.sharedCenter().calendarAccessGranted()
|
||
|
let hasNotDeterminedCalendarAccess = EventCenter.sharedCenter().calendarAccessNotDetermined()
|
||
|
let hasDeniedCalendarAccess = EventCenter.sharedCenter().calendarAccessDenied()
|
||
|
|
||
|
noAccessView.isHidden = hasCalendarAccess
|
||
|
|
||
|
if hasNotDeterminedCalendarAccess {
|
||
|
informationField.stringValue = "Clocker is more useful when it can display events from your calendars."
|
||
|
setGrantAccess(title: "Grant Access")
|
||
|
} else if hasDeniedCalendarAccess {
|
||
|
// The informationField text is taken care off in the XIB. Just set the grant button to empty because we can't do anything.
|
||
|
setGrantAccess(title: CLEmptyString)
|
||
|
} else {
|
||
|
calendarsTableView.reloadData()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private func setGrantAccess(title: String) {
|
||
|
let style = NSMutableParagraphStyle()
|
||
|
style.alignment = .center
|
||
|
|
||
|
guard let boldFont = NSFont(name: "Avenir-Medium", size: 14.0) else { return }
|
||
|
|
||
|
let attributesDictionary: [NSAttributedString.Key: Any] = [
|
||
|
NSAttributedString.Key.paragraphStyle: style,
|
||
|
NSAttributedString.Key.font: boldFont,
|
||
|
NSAttributedString.Key.foregroundColor: Themer.shared().mainTextColor()
|
||
|
]
|
||
|
let attributedString = NSAttributedString(string: title,
|
||
|
attributes: attributesDictionary)
|
||
|
grantAccessButton.attributedTitle = attributedString
|
||
|
}
|
||
|
|
||
|
private func onCalendarAccessDenial() {
|
||
|
informationField.stringValue = "Clocker is more useful when it can display events from your calendars. You can change this setting in System Preferences › Security & Privacy › Privacy."
|
||
|
setGrantAccess(title: "Launch Preferences")
|
||
|
|
||
|
// Remove upcoming event view if possible
|
||
|
UserDefaults.standard.set("NO", forKey: CLShowUpcomingEventView)
|
||
|
}
|
||
|
|
||
|
@IBAction func grantAccess(_: Any) {
|
||
|
if grantAccessButton.title == "Grant Access" {
|
||
|
OneWindowController.shared().openPermissions()
|
||
|
NSApp.activate(ignoringOtherApps: true)
|
||
|
} else if grantAccessButton.title == "Launch Preferences" {
|
||
|
NSWorkspace.shared.launchApplication("System Preferences")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@IBAction func showNextMeetingAction(_ sender: NSSegmentedControl) {
|
||
|
|
||
|
// We need to start the menubar timer if it hasn't been started already
|
||
|
guard let delegate = NSApplication.shared.delegate as? AppDelegate else {
|
||
|
assertionFailure()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
let statusItemHandler = delegate.statusItemForPanel()
|
||
|
|
||
|
if sender.selectedSegment == 0 {
|
||
|
|
||
|
if let isValid = statusItemHandler.menubarTimer?.isValid, isValid == true {
|
||
|
print("Timer is already in progress")
|
||
|
updateStatusItem()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
} else {
|
||
|
|
||
|
statusItemHandler.invalidateTimer(showIcon: true, isSyncing: false)
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
@IBAction func showUpcomingEventView(_ sender: NSSegmentedControl) {
|
||
|
var showUpcomingEventView = "YES"
|
||
|
|
||
|
if sender.selectedSegment == 1 {
|
||
|
showUpcomingEventView = "NO"
|
||
|
}
|
||
|
|
||
|
UserDefaults.standard.set(showUpcomingEventView, forKey: CLShowUpcomingEventView)
|
||
|
|
||
|
if DataStore.shared().shouldDisplay(ViewType.showAppInForeground) {
|
||
|
let floatingWindow = FloatingWindowController.shared()
|
||
|
floatingWindow.determineUpcomingViewVisibility()
|
||
|
return
|
||
|
}
|
||
|
|
||
|
guard let panel = PanelController.panel() else { return }
|
||
|
if sender.selectedSegment == 1 {
|
||
|
panel.removeUpcomingEventView()
|
||
|
Logger.log(object: ["Show": "NO"], for: "Upcoming Event View")
|
||
|
} else {
|
||
|
panel.showUpcomingEventView()
|
||
|
Logger.log(object: ["Show": "YES"], for: "Upcoming Event View")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private func updateStatusItem() {
|
||
|
guard let statusItem = (NSApplication.shared.delegate as? AppDelegate)?.statusItemForPanel() else {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
statusItem.performTimerWork()
|
||
|
}
|
||
|
|
||
|
@IBOutlet weak var headerLabel: NSTextField!
|
||
|
@IBOutlet weak var upcomingEventView: NSTextField!
|
||
|
@IBOutlet weak var allDayMeetingsLabel: NSTextField!
|
||
|
@IBOutlet weak var showNextMeetingLabel: NSTextField!
|
||
|
@IBOutlet weak var nextMeetingAccessoryLabel: NSTextField!
|
||
|
@IBOutlet weak var truncateTextLabel: NSTextField!
|
||
|
@IBOutlet weak var showEventsFromLabel: NSTextField!
|
||
|
@IBOutlet weak var charactersField: NSTextField!
|
||
|
@IBOutlet weak var truncateAccessoryLabel: NSTextField!
|
||
|
|
||
|
private func setup() {
|
||
|
// Grant access button's text color is taken care above.
|
||
|
headerLabel.stringValue = "Upcoming Event View Options"
|
||
|
upcomingEventView.stringValue = "Show Upcoming Event View"
|
||
|
allDayMeetingsLabel.stringValue = "Show All Day Meetings"
|
||
|
showNextMeetingLabel.stringValue = "Show Next Meeting Title in Menubar"
|
||
|
truncateTextLabel.stringValue = "Truncate menubar text longer than"
|
||
|
charactersField.stringValue = "characters"
|
||
|
showEventsFromLabel.stringValue = "Show events from"
|
||
|
truncateAccessoryLabel.stringValue = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\""
|
||
|
|
||
|
[headerLabel, upcomingEventView, allDayMeetingsLabel, showNextMeetingLabel, nextMeetingAccessoryLabel, truncateTextLabel, showEventsFromLabel, charactersField, truncateAccessoryLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() }
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extension CalendarViewController: NSTableViewDataSource {
|
||
|
|
||
|
func numberOfRows(in tableView: NSTableView) -> Int {
|
||
|
let hasCalendarAccess = EventCenter.sharedCenter().calendarAccessGranted()
|
||
|
return hasCalendarAccess ? calendars.count : 0
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extension CalendarViewController: NSTableViewDelegate {
|
||
|
|
||
|
func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
|
||
|
|
||
|
guard let currentSource = calendars[row] as? String, !currentSource.isEmpty else {
|
||
|
return 30.0
|
||
|
}
|
||
|
|
||
|
return 24.0
|
||
|
}
|
||
|
|
||
|
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
|
||
|
|
||
|
if let currentSource = calendars[row] as? String, let message = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "sourceCellView"), owner: self) as? SourceTableViewCell {
|
||
|
message.sourceName.stringValue = currentSource
|
||
|
return message
|
||
|
}
|
||
|
|
||
|
|
||
|
if let currentSource = calendars[row] as? CalendarInfo, let calendarCell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "calendarCellView"), owner: self) as? CalendarTableViewCell {
|
||
|
calendarCell.calendarName.stringValue = currentSource.calendar.title
|
||
|
calendarCell.calendarSelected.state = currentSource.selected ? NSControl.StateValue.on : NSControl.StateValue.off
|
||
|
calendarCell.calendarSelected.target = self
|
||
|
calendarCell.calendarSelected.tag = row
|
||
|
calendarCell.calendarSelected.wantsLayer = true
|
||
|
calendarCell.calendarSelected.action = #selector(calendarSelected(_:))
|
||
|
return calendarCell
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
|
||
|
}
|
||
|
|
||
|
@objc func calendarSelected(_ checkbox: NSButton) {
|
||
|
let currentSelection = checkbox.tag
|
||
|
|
||
|
var sourcesAndCalendars = calendars
|
||
|
|
||
|
if var calInfo = sourcesAndCalendars[currentSelection] as? CalendarInfo {
|
||
|
calInfo.selected = (checkbox.state == .on)
|
||
|
sourcesAndCalendars[currentSelection] = calInfo
|
||
|
}
|
||
|
|
||
|
updateSelectedCalendars(sourcesAndCalendars)
|
||
|
}
|
||
|
|
||
|
private func updateSelectedCalendars(_ selection: [Any]) {
|
||
|
|
||
|
var selectedCalendars: [String] = []
|
||
|
|
||
|
for obj in selection {
|
||
|
if let calInfo = obj as? CalendarInfo, calInfo.selected {
|
||
|
selectedCalendars.append(calInfo.calendar.calendarIdentifier)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
UserDefaults.standard.set(selectedCalendars, forKey: CLSelectedCalendars)
|
||
|
|
||
|
calendars = EventCenter.sharedCenter().fetchSourcesAndCalendars()
|
||
|
|
||
|
EventCenter.sharedCenter().filterEvents()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class SourceTableViewCell: NSTableCellView {
|
||
|
@IBOutlet var sourceName: NSTextField!
|
||
|
}
|
||
|
|
||
|
class CalendarTableViewCell: NSTableCellView {
|
||
|
@IBOutlet var calendarName: NSTextField!
|
||
|
@IBOutlet var calendarSelected: NSButton!
|
||
|
}
|
||
|
|
||
|
|