diff --git a/BrewBar/BrewBarApp.swift b/BrewBar/BrewBarApp.swift index a57dd7f..d7b4533 100644 --- a/BrewBar/BrewBarApp.swift +++ b/BrewBar/BrewBarApp.swift @@ -20,6 +20,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { var versionItem: NSMenuItem! var logWindow: LogWindowController? + var settingsWindow: SettingsWindowController? var logBuffer: String = "" var brewPath: String? @@ -68,40 +69,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { menu.addItem(NSMenuItem.separator()) - let intervalItem = NSMenuItem(title: "⏱ Refresh Interval", action: nil, keyEquivalent: "") - let intervalSubmenu = NSMenu() - - let options: [(String, TimeInterval)] = [ - ("1 heure", 3600), - ("6 heures", 21600), - ] - - for (label, interval) in options { - let item = NSMenuItem(title: label, action: #selector(setRefreshInterval(_:)), keyEquivalent: "") - item.representedObject = interval - intervalSubmenu.addItem(item) - } - - let customMenuItem = NSMenuItem() - let customView = NSView(frame: NSRect(x: 0, y: 0, width: 200, height: 30)) - - let textField = NSTextField(frame: NSRect(x: 10, y: 4, width: 120, height: 22)) - textField.placeholderString = "Secondes..." - textField.stringValue = "\(Int(refreshInterval))" - - let confirmButton = NSButton(frame: NSRect(x: 138, y: 4, width: 52, height: 22)) - confirmButton.title = "✓ Set" - confirmButton.bezelStyle = .rounded - confirmButton.target = self - confirmButton.action = #selector(applyCustomInterval(_:)) - - customView.addSubview(textField) - customView.addSubview(confirmButton) - customMenuItem.view = customView - intervalSubmenu.addItem(customMenuItem) - - intervalItem.submenu = intervalSubmenu - menu.addItem(intervalItem) + menu.addItem(NSMenuItem(title: "⚙️ Settings…", action: #selector(showSettings), keyEquivalent: ",")) versionItem = NSMenuItem(title: "🏷 Brew: ...", action: nil, keyEquivalent: "") menu.addItem(versionItem) @@ -141,20 +109,15 @@ class AppDelegate: NSObject, NSApplicationDelegate { } } - @objc func setRefreshInterval(_ sender: NSMenuItem) { - guard let interval = sender.representedObject as? TimeInterval else { return } - refreshInterval = interval // ← le set sauvegarde dans UserDefaults - restartTimer() - } - - @objc func applyCustomInterval(_ sender: NSButton) { - guard let view = sender.superview, - let textField = view.subviews.first(where: { $0 is NSTextField }) as? NSTextField, - let seconds = Int(textField.stringValue), seconds > 0 else { return } - - refreshInterval = TimeInterval(seconds) - restartTimer() - statusItem.menu?.cancelTracking() + @objc func showSettings() { + if settingsWindow == nil { + settingsWindow = SettingsWindowController(appDelegate: self) + } + settingsWindow?.syncUI() + settingsWindow?.showWindow(nil) + // accessory app: without activation the window can open behind others + NSApp.activate(ignoringOtherApps: true) + settingsWindow?.window?.makeKeyAndOrderFront(nil) } func resolveBrewPath() -> String { diff --git a/BrewBar/SettingsWindowController.swift b/BrewBar/SettingsWindowController.swift new file mode 100644 index 0000000..48579c4 --- /dev/null +++ b/BrewBar/SettingsWindowController.swift @@ -0,0 +1,127 @@ +import Cocoa + +class SettingsWindowController: NSWindowController { + weak var appDelegate: AppDelegate? + + let presets: [(label: String, interval: TimeInterval)] = [ + ("30 minutes", 1800), + ("1 hour", 3600), + ("6 hours", 21600), + ("12 hours", 43200), + ("24 hours", 86400), + ] + + var intervalPopup: NSPopUpButton! + var customMinutesField: NSTextField! + + convenience init(appDelegate: AppDelegate) { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 380, height: 200), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + window.title = "BrewBar Settings" + // the controller keeps the window alive; without this, closing the + // window would deallocate it and reopening would crash + window.isReleasedWhenClosed = false + + self.init(window: window) + self.appDelegate = appDelegate + + buildUI() + syncUI() + window.center() + } + + // MARK: - Layout + + func buildUI() { + guard let contentView = window?.contentView else { return } + + intervalPopup = NSPopUpButton() + for preset in presets { + intervalPopup.addItem(withTitle: preset.label) + } + intervalPopup.addItem(withTitle: "Custom") + intervalPopup.target = self + intervalPopup.action = #selector(presetPicked(_:)) + + customMinutesField = NSTextField() + customMinutesField.placeholderString = "60" + customMinutesField.alignment = .right + customMinutesField.target = self + customMinutesField.action = #selector(customMinutesEntered(_:)) + customMinutesField.widthAnchor.constraint(equalToConstant: 60).isActive = true + + let frequencyRow = NSStackView(views: [ + NSTextField(labelWithString: "Check for updates every:"), + intervalPopup, + ]) + frequencyRow.orientation = .horizontal + + let customRow = NSStackView(views: [ + customMinutesField, + NSTextField(labelWithString: "minutes"), + ]) + customRow.orientation = .horizontal + + let stack = NSStackView(views: [ + frequencyRow, + customRow, + ]) + stack.orientation = .vertical + stack.alignment = .leading + stack.spacing = 12 + + stack.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(stack) + NSLayoutConstraint.activate([ + stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20), + stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20), + stack.trailingAnchor.constraint(lessThanOrEqualTo: contentView.trailingAnchor, constant: -20), + stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20), + ]) + + contentView.layoutSubtreeIfNeeded() + window?.setContentSize(contentView.fittingSize) + } + + /// Push current values into the controls. Called on every open, since + /// values may have changed while the window was closed. + func syncUI() { + guard let appDelegate else { return } + + let interval = appDelegate.refreshInterval + if let presetIndex = presets.firstIndex(where: { $0.interval == interval }) { + intervalPopup.selectItem(at: presetIndex) + } else { + intervalPopup.selectItem(at: presets.count) // Custom + } + customMinutesField.stringValue = "\(Int(interval / 60))" + } + + // MARK: - Actions + + @objc func presetPicked(_ sender: NSPopUpButton) { + guard sender.indexOfSelectedItem < presets.count else { + window?.makeFirstResponder(customMinutesField) + return + } + applyInterval(presets[sender.indexOfSelectedItem].interval) + } + + @objc func customMinutesEntered(_ sender: NSTextField) { + guard let minutes = Int(sender.stringValue), minutes > 0 else { + syncUI() // reject invalid input by restoring the current value + return + } + applyInterval(TimeInterval(minutes * 60)) + } + + func applyInterval(_ interval: TimeInterval) { + appDelegate?.refreshInterval = interval + appDelegate?.restartTimer() + syncUI() + } +}