Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions Clipy/Sources/Managers/MenuManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4310,13 +4310,28 @@ class BoardManPanel: NSPanel {
maxHistoryStepper.integerValue = max(1, AppEnvironment.current.defaults.integer(forKey: Constants.UserDefaults.maxHistorySize))
maxHistoryStepper.target = self
maxHistoryStepper.action = #selector(maxHistorySizeChanged(_:))
maxHistoryStepper.identifier = NSUserInterfaceItemIdentifier("BoardManVisibleHistoryStepper")
contentView.addSubview(maxHistoryStepper)
maxHistorySizeStepper = maxHistoryStepper

let maxHistoryValue = NSTextField(labelWithString: "\(maxHistoryStepper.integerValue)")
let maxHistoryValue = NSTextField(frame: .zero)
maxHistoryValue.cell = BoardManCenteredTextFieldCell(textCell: "\(maxHistoryStepper.integerValue)")
maxHistoryValue.alignment = .right
maxHistoryValue.font = NSFont.systemFont(ofSize: 11)
maxHistoryValue.font = NSFont.monospacedDigitSystemFont(ofSize: 11, weight: .regular)
maxHistoryValue.textColor = .labelColor
maxHistoryValue.isEditable = true
maxHistoryValue.isSelectable = true
maxHistoryValue.isEnabled = true
maxHistoryValue.target = self
maxHistoryValue.action = #selector(maxHistorySizeFieldChanged(_:))
maxHistoryValue.delegate = self
let maxHistoryFormatter = NumberFormatter()
maxHistoryFormatter.numberStyle = .none
maxHistoryFormatter.minimum = 1
maxHistoryFormatter.maximum = 1000
maxHistoryFormatter.allowsFloats = false
maxHistoryValue.formatter = maxHistoryFormatter
maxHistoryValue.identifier = NSUserInterfaceItemIdentifier("BoardManVisibleHistoryField")
contentView.addSubview(maxHistoryValue)
maxHistorySizeValueLabel = maxHistoryValue

Expand Down Expand Up @@ -5030,6 +5045,7 @@ class BoardManPanel: NSPanel {
durationStepper.integerValue = selectedTimedPinPreset.value
durationStepper.target = self
durationStepper.action = #selector(timedPinDurationChanged(_:))
durationStepper.identifier = NSUserInterfaceItemIdentifier("BoardManTimedPinDurationStepper")
contentView.addSubview(durationStepper)
timedPinDurationStepper = durationStepper

Expand Down Expand Up @@ -6381,8 +6397,9 @@ class BoardManPanel: NSPanel {
inputPasteCommandButton?.frame = NSRect(x: originX, y: originY - 70, width: width, height: 20)
placeLabeledRow(label: languageLabel, control: languagePopup, originX: originX, originY: originY - 112, width: width)
maxHistorySizeLabel?.frame = NSRect(x: originX, y: originY - 157, width: fieldLabelWidth, height: 16)
maxHistorySizeStepper?.frame = NSRect(x: originX + fieldLabelWidth + 12, y: originY - 164, width: 24, height: rowH)
maxHistorySizeValueLabel?.frame = NSRect(x: originX + fieldLabelWidth + 48, y: originY - 164, width: 82, height: rowH)
let visibleHistoryX = originX + fieldLabelWidth + 12
maxHistorySizeValueLabel?.frame = NSRect(x: visibleHistoryX, y: originY - 164, width: 82, height: rowH)
maxHistorySizeStepper?.frame = NSRect(x: visibleHistoryX + 90, y: originY - 164, width: 24, height: rowH)
placeLabeledRow(label: statusItemLabel, control: statusItemPopup, originX: originX, originY: originY - 204, width: width)
}

Expand Down Expand Up @@ -7882,16 +7899,24 @@ class BoardManPanel: NSPanel {
AppEnvironment.current.defaults.set(sender.state == .on, forKey: Constants.UserDefaults.inputPasteCommand)
}

@objc private func maxHistorySizeChanged(_ sender: NSStepper) {
let value = max(1, sender.integerValue)
sender.integerValue = value
maxHistorySizeValueLabel?.stringValue = "\(value)"
private func applyMaxHistorySize(_ rawValue: Int) {
let value = min(1000, max(1, rawValue))
maxHistorySizeStepper?.integerValue = value
maxHistorySizeValueLabel?.integerValue = value
AppEnvironment.current.defaults.set(value, forKey: Constants.UserDefaults.maxHistorySize)
AppEnvironment.current.defaults.synchronize()
AppEnvironment.current.dataCleanService.cleanDatas()
onRefreshRequested?()
}

@objc private func maxHistorySizeChanged(_ sender: NSStepper) {
applyMaxHistorySize(sender.integerValue)
}

@objc private func maxHistorySizeFieldChanged(_ sender: NSTextField) {
applyMaxHistorySize(sender.integerValue)
}

@objc private func statusItemChanged(_ sender: NSPopUpButton) {
let selectedRaw = sender.selectedItem?.representedObject as? String ?? sender.titleOfSelectedItem
AppEnvironment.current.defaults.set(BoardManPanel.statusItemValue(for: selectedRaw),
Expand Down Expand Up @@ -9596,7 +9621,9 @@ extension BoardManPanel: RecordViewDelegate {
extension BoardManPanel: NSTextFieldDelegate {
func controlTextDidEndEditing(_ notification: Notification) {
guard let field = notification.object as? NSTextField else { return }
if field === timedPinDurationValueLabel {
if field === maxHistorySizeValueLabel {
maxHistorySizeFieldChanged(field)
} else if field === timedPinDurationValueLabel {
timedPinDurationFieldChanged(field)
} else if field === timestampShortcutDelayField {
timestampShortcutDelayFieldChanged(field)
Expand Down
117 changes: 64 additions & 53 deletions ClipyTests/EntitlementGateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1037,13 +1037,20 @@ final class BoardManPanelLayoutTests {
let originalTimestampFormat = defaults.string(forKey: Constants.UserDefaults.boardManTimestampFormat)
let originalTimestampPosition = defaults.string(forKey: Constants.UserDefaults.boardManTimestampPosition)
let originalLanguage = defaults.string(forKey: Constants.UserDefaults.boardManLanguage)
let originalMaxHistory = defaults.object(forKey: Constants.UserDefaults.maxHistorySize)
defaults.set("relative", forKey: Constants.UserDefaults.boardManTimestampFormat)
defaults.set("below", forKey: Constants.UserDefaults.boardManTimestampPosition)
defaults.set("English", forKey: Constants.UserDefaults.boardManLanguage)
defaults.set(100, forKey: Constants.UserDefaults.maxHistorySize)
defer {
defaults.set(originalTimestampFormat, forKey: Constants.UserDefaults.boardManTimestampFormat)
defaults.set(originalTimestampPosition, forKey: Constants.UserDefaults.boardManTimestampPosition)
defaults.set(originalLanguage, forKey: Constants.UserDefaults.boardManLanguage)
if let originalMaxHistory {
defaults.set(originalMaxHistory, forKey: Constants.UserDefaults.maxHistorySize)
} else {
defaults.removeObject(forKey: Constants.UserDefaults.maxHistorySize)
}
}

let panel = BoardManPanel()
Expand Down Expand Up @@ -1172,59 +1179,7 @@ final class BoardManPanelLayoutTests {
await settlePanelLayout(panel)
assertTopLevelLayout(panel, mode: "Settings category \(category.tag)", expectsSearch: false)

let descendants = allSubviews(of: root)
if category.title == "Appearance" {
let relativePopupIDs = [
"BoardManRelativeNumberStylePopup",
"BoardManRelativeUnitStylePopup",
"BoardManRelativeSuffixStylePopup",
"BoardManRelativeNowStylePopup"
]
for identifier in relativePopupIDs {
let popup = descendants.first { $0.identifier?.rawValue == identifier } as? NSPopUpButton
#expect(popup?.isHidden == false, "Missing visible relative timestamp popup: \(identifier)")
#expect(abs((popup?.frame.height ?? 0) - 30) <= 0.5)
}
} else if category.title == "History" {
let shortcutToggle = descendants.first {
$0.identifier?.rawValue == "BoardManTimestampShortcutEnabledButton"
} as? NSButton
let shortcutDelay = descendants.first {
$0.identifier?.rawValue == "BoardManTimestampShortcutDelayField"
} as? NSTextField
let presetPopup = descendants.first {
$0.identifier?.rawValue == "BoardManTimedPinPresetPopup"
} as? NSPopUpButton
let addPreset = descendants.first {
$0.identifier?.rawValue == "BoardManTimedPinPresetAddButton"
} as? NSButton
let removePreset = descendants.first {
$0.identifier?.rawValue == "BoardManTimedPinPresetRemoveButton"
} as? NSButton
#expect(shortcutToggle?.isHidden == false)
#expect(shortcutToggle?.target != nil && shortcutToggle?.action != nil)
#expect(shortcutDelay?.isHidden == false)
#expect(presetPopup?.isHidden == false)
#expect((presetPopup?.numberOfItems ?? 0) >= 1)
#expect(addPreset?.target != nil && addPreset?.action != nil)
#expect(removePreset?.target != nil && removePreset?.action != nil)
} else if category.title == "Snippets" {
let manage = descendants.first {
$0.identifier?.rawValue == "BoardManManageSnippetsButton"
} as? NSButton
let shortcutScroll = descendants.first {
$0.identifier?.rawValue == "BoardManSnippetShortcutScrollView"
} as? NSScrollView
#expect(manage?.isHidden == false)
#expect(shortcutScroll?.isHidden == false)
if let manage, let shortcutScroll {
let gap = shortcutScroll.frame.minY - manage.frame.maxY
#expect(gap >= 10 && gap <= 18,
"Manage Snippets should sit directly below the shortcut list with deliberate spacing.")
#expect(manage.frame.minY >= 20,
"Manage Snippets is still pinned against the bottom edge.")
}
}
assertSettingsCategoryControls(title: category.title, descendants: allSubviews(of: root))
}
}

Expand Down Expand Up @@ -1275,6 +1230,62 @@ final class BoardManPanelLayoutTests {
#expect((historyTable?.enclosingScrollView?.frame.height ?? 999) < 190)
}

private func assertSettingsCategoryControls(title: String, descendants: [NSView]) {
if title == "General" {
let field = descendants.first { $0.identifier?.rawValue == "BoardManVisibleHistoryField" } as? NSTextField
let stepper = descendants.first { $0.identifier?.rawValue == "BoardManVisibleHistoryStepper" } as? NSStepper
#expect(field?.isHidden == false && field?.isEditable == true)
#expect(field?.target != nil && field?.action != nil)
#expect(stepper?.target != nil && stepper?.action != nil)
if let field, let stepper {
#expect(field.frame.minX < stepper.frame.minX,
"Visible history input should sit to the left of its stepper, matching Pin duration.")
#expect(abs(field.frame.midY - stepper.frame.midY) <= 0.5,
"Visible history input and stepper are vertically misaligned.")
field.integerValue = 250
_ = field.sendAction(field.action, to: field.target)
#expect(AppEnvironment.current.defaults.integer(forKey: Constants.UserDefaults.maxHistorySize) == 250)
#expect(stepper.integerValue == 250)
}
} else if title == "Appearance" {
let identifiers = ["BoardManRelativeNumberStylePopup", "BoardManRelativeUnitStylePopup",
"BoardManRelativeSuffixStylePopup", "BoardManRelativeNowStylePopup"]
for identifier in identifiers {
let popup = descendants.first { $0.identifier?.rawValue == identifier } as? NSPopUpButton
#expect(popup?.isHidden == false, "Missing visible relative timestamp popup: \(identifier)")
#expect(abs((popup?.frame.height ?? 0) - 30) <= 0.5)
}
} else if title == "History" {
let toggle = descendants.first { $0.identifier?.rawValue == "BoardManTimestampShortcutEnabledButton" } as? NSButton
let delay = descendants.first { $0.identifier?.rawValue == "BoardManTimestampShortcutDelayField" } as? NSTextField
let preset = descendants.first { $0.identifier?.rawValue == "BoardManTimedPinPresetPopup" } as? NSPopUpButton
let add = descendants.first { $0.identifier?.rawValue == "BoardManTimedPinPresetAddButton" } as? NSButton
let remove = descendants.first { $0.identifier?.rawValue == "BoardManTimedPinPresetRemoveButton" } as? NSButton
let field = descendants.first { $0.identifier?.rawValue == "BoardManTimedPinDurationField" } as? NSTextField
let stepper = descendants.first { $0.identifier?.rawValue == "BoardManTimedPinDurationStepper" } as? NSStepper
#expect(toggle?.isHidden == false && toggle?.target != nil && toggle?.action != nil)
#expect(delay?.isHidden == false)
#expect(preset?.isHidden == false && (preset?.numberOfItems ?? 0) >= 1)
#expect(add?.target != nil && add?.action != nil)
#expect(remove?.target != nil && remove?.action != nil)
if let field, let stepper {
#expect(field.frame.minX < stepper.frame.minX,
"Pin duration input should remain on the left of its stepper.")
#expect(abs(field.frame.midY - stepper.frame.midY) <= 0.5)
}
} else if title == "Snippets" {
let manage = descendants.first { $0.identifier?.rawValue == "BoardManManageSnippetsButton" } as? NSButton
let scroll = descendants.first { $0.identifier?.rawValue == "BoardManSnippetShortcutScrollView" } as? NSScrollView
#expect(manage?.isHidden == false && scroll?.isHidden == false)
if let manage, let scroll {
let gap = scroll.frame.minY - manage.frame.maxY
#expect(gap >= 10 && gap <= 18,
"Manage Snippets should sit directly below the shortcut list with deliberate spacing.")
#expect(manage.frame.minY >= 20, "Manage Snippets is still pinned against the bottom edge.")
}
}
}

private func settlePanelLayout(_ panel: BoardManPanel) async {
await withCheckedContinuation { continuation in
DispatchQueue.main.async {
Expand Down
Loading