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.
 
 
 
 
 

297 lines
12 KiB

// Copyright © 2015 Abhishek Banthia
import Cocoa
import EventKit
class ClockerTextBackgroundView: NSView {
private var themeDidChangeNotification: NSObjectProtocol?
override func awakeFromNib() {
wantsLayer = true
layer?.cornerRadius = 8.0
layer?.masksToBounds = false
layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in
self.layer?.backgroundColor = Themer.shared().textBackgroundColor().cgColor
}
}
deinit {
if let themeDidChangeNotif = themeDidChangeNotification {
NotificationCenter.default.removeObserver(themeDidChangeNotif)
}
}
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 var calendarsTableView: NSTableView!
@IBOutlet var showNextMeetingInMenubarControl: NSSegmentedControl!
@IBOutlet var backgroundView: NSView!
@IBOutlet var nextMeetingBackgroundView: NSView!
private var themeDidChangeNotification: NSObjectProtocol?
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)
themeDidChangeNotification = NotificationCenter.default.addObserver(forName: .themeDidChangeNotification, object: nil, queue: OperationQueue.main) { _ in
self.setup()
}
if #available(macOS 10.14, *) {
noAccessView.material = .underWindowBackground
}
}
deinit {
if let themeDidChangeNotif = themeDidChangeNotification {
NotificationCenter.default.removeObserver(themeDidChangeNotif)
}
}
@objc func calendarAccessStatusChanged() {
verifyCalendarAccess()
view.window?.windowController?.showWindow(nil)
view.window?.makeKeyAndOrderFront(nil)
}
override func viewWillAppear() {
super.viewWillAppear()
verifyCalendarAccess()
showSegmentedControl.selectedSegment = DataStore.shared().shouldDisplay(ViewType.upcomingEventView) ? 0 : 1
showNextMeetingInMenubarControl.isEnabled = DataStore.shared().shouldDisplay(.menubarCompactMode) ? false : true
}
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.".localized()
setGrantAccess(title: "Grant Access".localized())
} 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.".localized()
setGrantAccess(title: "Launch Preferences".localized())
// Remove upcoming event view if possible
UserDefaults.standard.set("NO", forKey: CLShowUpcomingEventView)
}
@IBAction func grantAccess(_: Any) {
if grantAccessButton.title == "Grant Access".localized() {
(parent as? CenteredTabViewController)?.selectedTabViewItemIndex = 3 // 3 is the Permissions View
} 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 {
Logger.info("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 var headerLabel: NSTextField!
@IBOutlet var upcomingEventView: NSTextField!
@IBOutlet var allDayMeetingsLabel: NSTextField!
@IBOutlet var showNextMeetingLabel: NSTextField!
@IBOutlet var nextMeetingAccessoryLabel: NSTextField!
@IBOutlet var truncateTextLabel: NSTextField!
@IBOutlet var showEventsFromLabel: NSTextField!
@IBOutlet var charactersField: NSTextField!
@IBOutlet var truncateAccessoryLabel: NSTextField!
private func setup() {
// Grant access button's text color is taken care above.
headerLabel.stringValue = "Upcoming Event View Options".localized()
upcomingEventView.stringValue = "Show Upcoming Event View".localized()
allDayMeetingsLabel.stringValue = "Show All Day Meetings".localized()
showNextMeetingLabel.stringValue = "Show Next Meeting Title in Menubar".localized()
truncateTextLabel.stringValue = "Truncate menubar text longer than".localized()
charactersField.stringValue = "characters".localized()
showEventsFromLabel.stringValue = "Show events from".localized()
truncateAccessoryLabel.stringValue = "If meeting title is \"Meeting with Neel\" and truncate length is set to 5, text in menubar will appear as \"Meeti...\"".localized()
[headerLabel, upcomingEventView, allDayMeetingsLabel,
showNextMeetingLabel, nextMeetingAccessoryLabel, truncateTextLabel,
showEventsFromLabel, charactersField, truncateAccessoryLabel].forEach { $0?.textColor = Themer.shared().mainTextColor() }
}
}
extension CalendarViewController: NSTableViewDataSource {
func numberOfRows(in _: NSTableView) -> Int {
let hasCalendarAccess = EventCenter.sharedCenter().calendarAccessGranted()
return hasCalendarAccess ? calendars.count : 0
}
}
extension CalendarViewController: NSTableViewDelegate {
func tableView(_: NSTableView, shouldSelectRow _: Int) -> Bool {
return false
}
func 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 _: 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!
}