Skip to content

Commit

Permalink
dynamically update the menu when option is pressed - fixes #3
Browse files Browse the repository at this point in the history
  • Loading branch information
acheronfail committed May 21, 2018
1 parent b734282 commit ef3ddda
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 37 deletions.
16 changes: 8 additions & 8 deletions PixelPicker.xcodeproj/project.pbxproj
Expand Up @@ -332,18 +332,18 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
BA74C14220AA409D00306B27 /* NSColor.swift in Sources */,
BA74C13320A6F16C00306B27 /* ShowAndHideCursor.m in Sources */,
BA71817220AE2DB400619700 /* PPMenuShortcutView.m in Sources */,
15492103207B53AE00E45BBC /* Util.swift in Sources */,
BA74C0F820A692D100306B27 /* PPOverlayController.swift in Sources */,
BA74C14E20ADA33E00306B27 /* PPColor.swift in Sources */,
B3B4C2C11E25894B009F8E4E /* AppDelegate.swift in Sources */,
BA22232920B04D660001069C /* AppDelegate+Menu.swift in Sources */,
BA74C14E20ADA33E00306B27 /* PPColor.swift in Sources */,
15FC8CC8204E2E2C000B5E1E /* PPState.swift in Sources */,
BA74C13120A6E95900306B27 /* PPOverlayPanel.swift in Sources */,
B3B4C2C11E25894B009F8E4E /* AppDelegate.swift in Sources */,
BA74C14420AA609500306B27 /* PPOverlayWrapper.swift in Sources */,
BA74C13320A6F16C00306B27 /* ShowAndHideCursor.m in Sources */,
BA74C14020AA390400306B27 /* PPOverlayPreview.swift in Sources */,
15FC8CC8204E2E2C000B5E1E /* PPState.swift in Sources */,
BA74C14420AA609500306B27 /* PPOverlayWrapper.swift in Sources */,
BA74C0F820A692D100306B27 /* PPOverlayController.swift in Sources */,
BA74C14220AA409D00306B27 /* NSColor.swift in Sources */,
15492103207B53AE00E45BBC /* Util.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
77 changes: 56 additions & 21 deletions PixelPicker/AppDelegate+Menu.swift
Expand Up @@ -20,10 +20,43 @@ let concentrationModifiers: [(String, NSEvent.ModifierFlags)] = [
]

extension AppDelegate: NSMenuDelegate {
// Unregister/register the activating shortcut when the menu is opened/closed
// so it can't be called when setting a new shortcut.
func menuWillOpen(_ menu: NSMenu) { unregisterActivatingShortcut() }
func menuDidClose(_ menu: NSMenu) { registerActivatingShortcut() }
// Unregister the activating shortcut when the menu is opened/closed so it can't be called when
// setting a new shortcut. Also start a run loop observer so we know when the modifierFlags have
// changed (used to dynamically update the menu).
func menuWillOpen(_ menu: NSMenu) {
unregisterActivatingShortcut()
if runLoopObserver == nil {
let activites = CFRunLoopActivity.beforeWaiting.rawValue
runLoopObserver = CFRunLoopObserverCreateWithHandler(nil, activites, true, 0, { [unowned self] (_, _) in
self.updateMenuItems()
})
CFRunLoopAddObserver(CFRunLoopGetCurrent(), runLoopObserver, CFRunLoopMode.commonModes)
}
}

// Re-register the activating shortcut, and remove the run loop observer.
func menuDidClose(_ menu: NSMenu) {
registerActivatingShortcut()
if (runLoopObserver != nil) {
CFRunLoopObserverInvalidate(runLoopObserver)
runLoopObserver = nil
}
}

// Updates the titles of the recently picked colors - if the `option` key is pressed, then
// the colors will be in the format they were *when* they were picked, otherwise they'll be
// in the currently chosen format.
private func updateMenuItems() {
// Update recent picks list with correct titles.
let alternate = NSEvent.modifierFlags.contains(.option)
for item in contextMenu.items {
if let pickedColor = item.representedObject as? PPPickedColor {
item.title = alternate
? pickedColor.asString
: PPState.shared.chosenFormat.asString(withColor: pickedColor.color)
}
}
}

// TODO: look into only updating the menu rather than rebuilding it each time.
// Might not be worth it - it doesn't seem expensive to build it every time it's opened...
Expand All @@ -40,39 +73,45 @@ extension AppDelegate: NSMenuDelegate {
buildConcentrationMenu()
buildFloatPrecisionSlider()
buildShortcutMenuItem()

let launchAtLoginItem = contextMenu.addItem(withTitle: "Launch \(APP_NAME) at Login", action: #selector(launchAtLogin(_:)), keyEquivalent: "")
launchAtLoginItem.state = LaunchAtLogin.isEnabled ? .on : .off
buildLaunchAtLoginItem()

contextMenu.addItem(.separator())
contextMenu.addItem(withTitle: "About", action: #selector(showAboutPanel), keyEquivalent: "")
contextMenu.addItem(withTitle: "Quit \(APP_NAME)", action: #selector(quitApplication), keyEquivalent: "")
}

private func buildLaunchAtLoginItem() {
let item = contextMenu.addItem(withTitle: "Launch \(APP_NAME) at Login", action: #selector(launchAtLogin(_:)), keyEquivalent: "")
item.state = LaunchAtLogin.isEnabled ? .on : .off
}

@objc private func launchAtLogin(_ sender: NSMenuItem) {
LaunchAtLogin.isEnabled = !LaunchAtLogin.isEnabled
}

// Show the user's recent picks in the menu.
private func buildRecentPicks() {
// Recent picks.
// TODO: it would be nice to hold the `option` key and have the recent picks show in the
// currently chosen format, and when clicked copy in that format.
// https://stackoverflow.com/q/11208632/5552584
if PPState.shared.recentPicks.count > 0 {
contextMenu.addItem(.separator())
contextMenu.addItem(withTitle: "Recently Picked", action: nil, keyEquivalent: "")
let format = PPState.shared.chosenFormat
for pickedColor in PPState.shared.recentPicks {
// TODO: copy to clipboard when clicked, if alt then in current format
let item = contextMenu.addItem(withTitle: pickedColor.asString, action: #selector(copyRecentPick(_:)), keyEquivalent: "")
let item = contextMenu.addItem(withTitle: format.asString(withColor: pickedColor.color), action: #selector(copyRecentPick(_:)), keyEquivalent: "")
item.representedObject = pickedColor
item.image = circleImage(withSize: 12, color: pickedColor.color)
}
}
}


// Copies the recently picked color (associated with the menu item) to the clipboard.
// If the `option` key is pressed, then it copies the color in the same format it was
// when it was picked (otherwise, it copies it in the currently chosen format).
@objc private func copyRecentPick(_ sender: NSMenuItem) {
if let pickedColor = sender.representedObject as? PPPickedColor {
copyToPasteboard(stringValue: pickedColor.asString)
let value = NSEvent.modifierFlags.contains(.option)
? pickedColor.asString
: PPState.shared.chosenFormat.asString(withColor: pickedColor.color)
copyToPasteboard(stringValue: value)
}
}

Expand Down Expand Up @@ -116,13 +155,9 @@ extension AppDelegate: NSMenuDelegate {

// Update state.
PPState.shared.floatPrecision = newValue

// Update recent picks list with new precision.
for item in contextMenu.items {
if let pickedColor = item.representedObject as? PPPickedColor {
item.title = pickedColor.asString
}
}
updateMenuItems()
}

// Build a submenu with each case in the PPColor enum.
Expand Down
20 changes: 12 additions & 8 deletions PixelPicker/AppDelegate.swift
Expand Up @@ -13,10 +13,14 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))

// This controller manages the pixel picker itself.
@IBOutlet weak var overlayController: PPOverlayController!
var contextMenu: NSMenu = NSMenu()

// The actual menu bar item.
var menuBarItem: NSStatusItem! = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)

// The menu that drops down from the menu bar item.
var contextMenu: NSMenu = NSMenu()
// When the menu bar is opened, we observe the run loop for changes in modifierFlags.
var runLoopObserver: CFRunLoopObserver? = nil

// Setup logging and load state.
func applicationWillFinishLaunching(_ notification: Notification) {
let minimumSeverity: LogSeverity = PPState.shared.defaults.bool(forKey: "debugMode") ? .debug : .info
Expand All @@ -37,7 +41,7 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
menuBarItem.image = ICON
menuBarItem.action = #selector(onMenuClick)
menuBarItem.sendAction(on: [.leftMouseUp, .rightMouseUp])

registerActivatingShortcut()

Log.info?.message("Sucessfully launched.")
Expand All @@ -46,13 +50,13 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
func applicationWillTerminate(_ aNotification: Notification) {
PPState.shared.saveToDisk()
}

func registerActivatingShortcut() {
if let shortcut = PPState.shared.activatingShortcut {
MASShortcutMonitor.shared().register(shortcut, withAction: showPicker)
}
}

func unregisterActivatingShortcut() {
MASShortcutMonitor.shared().unregisterShortcut(PPState.shared.activatingShortcut)
}
Expand All @@ -61,7 +65,7 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
let leftClickToggles = PPState.shared.defaults.bool(forKey: "leftClickActivates")
let pickerEvent: NSEvent.EventType = leftClickToggles ? .leftMouseUp : .rightMouseUp
let dropdownEvent: NSEvent.EventType = leftClickToggles ? .rightMouseUp : .leftMouseUp

let event = NSApp.currentEvent!
if event.type == dropdownEvent {
rebuildContextMenu()
Expand All @@ -70,7 +74,7 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
showPicker()
}
}

@objc func showPicker() {
overlayController.showPicker()
}
Expand Down

0 comments on commit ef3ddda

Please sign in to comment.