|
@@ -147,10 +147,13 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
|
|
|
private let scrollView = NSScrollView()
|
|
private let scrollView = NSScrollView()
|
|
|
private let listDocumentView = FlippedDocumentView()
|
|
private let listDocumentView = FlippedDocumentView()
|
|
|
private let listStack = NSStackView()
|
|
private let listStack = NSStackView()
|
|
|
|
|
+ private let selectAllRow = PrintContactsSelectAllRow()
|
|
|
private let printButton = PrintContactsPrintButton()
|
|
private let printButton = PrintContactsPrintButton()
|
|
|
private let emptyLabel = NSTextField(labelWithString: "No contacts found")
|
|
private let emptyLabel = NSTextField(labelWithString: "No contacts found")
|
|
|
|
|
|
|
|
private let horizontalInset: CGFloat = 16
|
|
private let horizontalInset: CGFloat = 16
|
|
|
|
|
+ private let rowContentInset: CGFloat = 16
|
|
|
|
|
+ private let checkboxNameSpacing: CGFloat = 12
|
|
|
private let searchHeight: CGFloat = 44
|
|
private let searchHeight: CGFloat = 44
|
|
|
private let contactRowHeight: CGFloat = 52
|
|
private let contactRowHeight: CGFloat = 52
|
|
|
|
|
|
|
@@ -192,6 +195,7 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
|
|
|
searchIconView.contentTintColor = AppTheme.textSecondary
|
|
searchIconView.contentTintColor = AppTheme.textSecondary
|
|
|
emptyLabel.textColor = AppTheme.textSecondary
|
|
emptyLabel.textColor = AppTheme.textSecondary
|
|
|
backButton.refreshAppearance()
|
|
backButton.refreshAppearance()
|
|
|
|
|
+ selectAllRow.refreshAppearance()
|
|
|
printButton.refreshAppearance()
|
|
printButton.refreshAppearance()
|
|
|
rowViews.forEach { $0.refreshAppearance() }
|
|
rowViews.forEach { $0.refreshAppearance() }
|
|
|
}
|
|
}
|
|
@@ -283,12 +287,15 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
|
|
|
printButton.translatesAutoresizingMaskIntoConstraints = false
|
|
printButton.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
|
|
backButton.onClick = { [weak self] in self?.dismiss() }
|
|
backButton.onClick = { [weak self] in self?.dismiss() }
|
|
|
|
|
+ selectAllRow.applyLayout(contentInset: rowContentInset, checkboxNameSpacing: checkboxNameSpacing)
|
|
|
|
|
+ selectAllRow.onClick = { [weak self] in self?.toggleSelectAll() }
|
|
|
printButton.onClick = { [weak self] in self?.printContacts() }
|
|
printButton.onClick = { [weak self] in self?.printContacts() }
|
|
|
|
|
|
|
|
addSubview(backButton)
|
|
addSubview(backButton)
|
|
|
addSubview(searchContainer)
|
|
addSubview(searchContainer)
|
|
|
searchContainer.addSubview(searchIconView)
|
|
searchContainer.addSubview(searchIconView)
|
|
|
searchContainer.addSubview(searchField)
|
|
searchContainer.addSubview(searchField)
|
|
|
|
|
+ addSubview(selectAllRow)
|
|
|
addSubview(scrollView)
|
|
addSubview(scrollView)
|
|
|
addSubview(emptyLabel)
|
|
addSubview(emptyLabel)
|
|
|
addSubview(printButton)
|
|
addSubview(printButton)
|
|
@@ -313,9 +320,14 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
|
|
|
searchField.trailingAnchor.constraint(equalTo: searchContainer.trailingAnchor, constant: -16),
|
|
searchField.trailingAnchor.constraint(equalTo: searchContainer.trailingAnchor, constant: -16),
|
|
|
searchField.centerYAnchor.constraint(equalTo: searchContainer.centerYAnchor),
|
|
searchField.centerYAnchor.constraint(equalTo: searchContainer.centerYAnchor),
|
|
|
|
|
|
|
|
|
|
+ selectAllRow.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
|
|
|
|
|
+ selectAllRow.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
|
|
|
|
|
+ selectAllRow.topAnchor.constraint(equalTo: searchContainer.bottomAnchor, constant: 12),
|
|
|
|
|
+ selectAllRow.heightAnchor.constraint(equalToConstant: 36),
|
|
|
|
|
+
|
|
|
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
|
|
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: horizontalInset),
|
|
|
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
|
|
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -horizontalInset),
|
|
|
- scrollView.topAnchor.constraint(equalTo: searchContainer.bottomAnchor, constant: 16),
|
|
|
|
|
|
|
+ scrollView.topAnchor.constraint(equalTo: selectAllRow.bottomAnchor, constant: 8),
|
|
|
scrollView.bottomAnchor.constraint(equalTo: printButton.topAnchor, constant: -28),
|
|
scrollView.bottomAnchor.constraint(equalTo: printButton.topAnchor, constant: -28),
|
|
|
|
|
|
|
|
listDocumentView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor),
|
|
listDocumentView.topAnchor.constraint(equalTo: scrollView.contentView.topAnchor),
|
|
@@ -368,7 +380,9 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
|
|
|
let row = ContactRowView(
|
|
let row = ContactRowView(
|
|
|
name: contact.displayName,
|
|
name: contact.displayName,
|
|
|
isSelected: selectedIDs.contains(contact.identifier),
|
|
isSelected: selectedIDs.contains(contact.identifier),
|
|
|
- rowHeight: contactRowHeight
|
|
|
|
|
|
|
+ rowHeight: contactRowHeight,
|
|
|
|
|
+ contentInset: rowContentInset,
|
|
|
|
|
+ checkboxNameSpacing: checkboxNameSpacing
|
|
|
)
|
|
)
|
|
|
row.onToggle = { [weak self] in
|
|
row.onToggle = { [weak self] in
|
|
|
self?.toggleSelection(for: contact.identifier)
|
|
self?.toggleSelection(for: contact.identifier)
|
|
@@ -378,6 +392,36 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
|
|
|
row.leadingAnchor.constraint(equalTo: listStack.leadingAnchor).isActive = true
|
|
row.leadingAnchor.constraint(equalTo: listStack.leadingAnchor).isActive = true
|
|
|
row.trailingAnchor.constraint(equalTo: listStack.trailingAnchor).isActive = true
|
|
row.trailingAnchor.constraint(equalTo: listStack.trailingAnchor).isActive = true
|
|
|
}
|
|
}
|
|
|
|
|
+ updateSelectAllRow()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func selectAllState() -> ContactSelectionState {
|
|
|
|
|
+ let contacts = filteredContacts()
|
|
|
|
|
+ guard !contacts.isEmpty else { return .unchecked }
|
|
|
|
|
+ let selectedCount = contacts.filter { selectedIDs.contains($0.identifier) }.count
|
|
|
|
|
+ if selectedCount == 0 { return .unchecked }
|
|
|
|
|
+ if selectedCount == contacts.count { return .checked }
|
|
|
|
|
+ return .indeterminate
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func updateSelectAllRow() {
|
|
|
|
|
+ let contacts = filteredContacts()
|
|
|
|
|
+ selectAllRow.isHidden = contacts.isEmpty
|
|
|
|
|
+ selectAllRow.selectionState = selectAllState()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private func toggleSelectAll() {
|
|
|
|
|
+ let contacts = filteredContacts()
|
|
|
|
|
+ switch selectAllState() {
|
|
|
|
|
+ case .checked, .indeterminate:
|
|
|
|
|
+ contacts.forEach { selectedIDs.remove($0.identifier) }
|
|
|
|
|
+ case .unchecked:
|
|
|
|
|
+ contacts.forEach { selectedIDs.insert($0.identifier) }
|
|
|
|
|
+ }
|
|
|
|
|
+ for (row, contact) in zip(rowViews, contacts) {
|
|
|
|
|
+ row.setSelected(selectedIDs.contains(contact.identifier))
|
|
|
|
|
+ }
|
|
|
|
|
+ updateSelectAllRow()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func toggleSelection(for identifier: String) {
|
|
private func toggleSelection(for identifier: String) {
|
|
@@ -391,6 +435,7 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
|
|
|
row.setSelected(selectedIDs.contains(identifier))
|
|
row.setSelected(selectedIDs.contains(identifier))
|
|
|
break
|
|
break
|
|
|
}
|
|
}
|
|
|
|
|
+ updateSelectAllRow()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private func printContacts() {
|
|
private func printContacts() {
|
|
@@ -422,17 +467,91 @@ final class PrintContactsOverlayView: NSView, AppearanceRefreshable {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// MARK: - Selection Checkbox
|
|
|
|
|
+
|
|
|
|
|
+private enum ContactSelectionState {
|
|
|
|
|
+ case unchecked
|
|
|
|
|
+ case checked
|
|
|
|
|
+ case indeterminate
|
|
|
|
|
+
|
|
|
|
|
+ var symbolName: String {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .unchecked: "square"
|
|
|
|
|
+ case .checked: "checkmark.square.fill"
|
|
|
|
|
+ case .indeterminate: "minus.square.fill"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var accessibilityDescription: String {
|
|
|
|
|
+ switch self {
|
|
|
|
|
+ case .unchecked: "Not selected"
|
|
|
|
|
+ case .checked: "Selected"
|
|
|
|
|
+ case .indeterminate: "Partially selected"
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+private final class ContactCheckboxView: NSView, AppearanceRefreshable {
|
|
|
|
|
+ var state: ContactSelectionState = .unchecked {
|
|
|
|
|
+ didSet { refreshAppearance() }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private let iconView = NSImageView()
|
|
|
|
|
+
|
|
|
|
|
+ init() {
|
|
|
|
|
+ super.init(frame: .zero)
|
|
|
|
|
+ translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ iconView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+ addSubview(iconView)
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ iconView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
|
|
|
|
+ iconView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
|
|
|
|
+ iconView.topAnchor.constraint(equalTo: topAnchor),
|
|
|
|
|
+ iconView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
|
|
|
|
+ widthAnchor.constraint(equalToConstant: 20),
|
|
|
|
|
+ heightAnchor.constraint(equalToConstant: 20),
|
|
|
|
|
+ ])
|
|
|
|
|
+ refreshAppearance()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @available(*, unavailable)
|
|
|
|
|
+ required init?(coder: NSCoder) { nil }
|
|
|
|
|
+
|
|
|
|
|
+ func refreshAppearance() {
|
|
|
|
|
+ if let image = NSImage(
|
|
|
|
|
+ systemSymbolName: state.symbolName,
|
|
|
|
|
+ accessibilityDescription: state.accessibilityDescription
|
|
|
|
|
+ ) {
|
|
|
|
|
+ let weight: NSFont.Weight = state == .unchecked ? .regular : .semibold
|
|
|
|
|
+ let config = NSImage.SymbolConfiguration(pointSize: 20, weight: weight)
|
|
|
|
|
+ iconView.image = image.withSymbolConfiguration(config)
|
|
|
|
|
+ }
|
|
|
|
|
+ switch state {
|
|
|
|
|
+ case .unchecked:
|
|
|
|
|
+ iconView.contentTintColor = AppTheme.textSecondary
|
|
|
|
|
+ case .checked, .indeterminate:
|
|
|
|
|
+ iconView.contentTintColor = AppTheme.blue
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// MARK: - Contact Row
|
|
// MARK: - Contact Row
|
|
|
|
|
|
|
|
private final class ContactRowView: NSControl, AppearanceRefreshable {
|
|
private final class ContactRowView: NSControl, AppearanceRefreshable {
|
|
|
var onToggle: (() -> Void)?
|
|
var onToggle: (() -> Void)?
|
|
|
|
|
|
|
|
|
|
+ private let checkboxView = ContactCheckboxView()
|
|
|
private let nameLabel = NSTextField(labelWithString: "")
|
|
private let nameLabel = NSTextField(labelWithString: "")
|
|
|
- private let checkmarkView = NSImageView()
|
|
|
|
|
private var isSelected = false
|
|
private var isSelected = false
|
|
|
private let rowHeight: CGFloat
|
|
private let rowHeight: CGFloat
|
|
|
|
|
|
|
|
- init(name: String, isSelected: Bool, rowHeight: CGFloat) {
|
|
|
|
|
|
|
+ init(
|
|
|
|
|
+ name: String,
|
|
|
|
|
+ isSelected: Bool,
|
|
|
|
|
+ rowHeight: CGFloat,
|
|
|
|
|
+ contentInset: CGFloat,
|
|
|
|
|
+ checkboxNameSpacing: CGFloat
|
|
|
|
|
+ ) {
|
|
|
self.isSelected = isSelected
|
|
self.isSelected = isSelected
|
|
|
self.rowHeight = rowHeight
|
|
self.rowHeight = rowHeight
|
|
|
super.init(frame: .zero)
|
|
super.init(frame: .zero)
|
|
@@ -444,19 +563,15 @@ private final class ContactRowView: NSControl, AppearanceRefreshable {
|
|
|
nameLabel.lineBreakMode = .byTruncatingTail
|
|
nameLabel.lineBreakMode = .byTruncatingTail
|
|
|
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
|
|
|
- checkmarkView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
-
|
|
|
|
|
|
|
+ addSubview(checkboxView)
|
|
|
addSubview(nameLabel)
|
|
addSubview(nameLabel)
|
|
|
- addSubview(checkmarkView)
|
|
|
|
|
NSLayoutConstraint.activate([
|
|
NSLayoutConstraint.activate([
|
|
|
heightAnchor.constraint(equalToConstant: rowHeight),
|
|
heightAnchor.constraint(equalToConstant: rowHeight),
|
|
|
- nameLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
|
|
|
|
|
- nameLabel.trailingAnchor.constraint(lessThanOrEqualTo: checkmarkView.leadingAnchor, constant: -12),
|
|
|
|
|
|
|
+ checkboxView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: contentInset),
|
|
|
|
|
+ checkboxView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
|
|
|
+ nameLabel.leadingAnchor.constraint(equalTo: checkboxView.trailingAnchor, constant: checkboxNameSpacing),
|
|
|
|
|
+ nameLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -contentInset),
|
|
|
nameLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
nameLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
|
- checkmarkView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
|
|
|
|
|
- checkmarkView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
|
|
|
- checkmarkView.widthAnchor.constraint(equalToConstant: 22),
|
|
|
|
|
- checkmarkView.heightAnchor.constraint(equalToConstant: 22),
|
|
|
|
|
])
|
|
])
|
|
|
refreshAppearance()
|
|
refreshAppearance()
|
|
|
}
|
|
}
|
|
@@ -470,25 +585,19 @@ private final class ContactRowView: NSControl, AppearanceRefreshable {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func refreshAppearance() {
|
|
func refreshAppearance() {
|
|
|
- let symbolName = isSelected ? "checkmark.circle.fill" : "circle"
|
|
|
|
|
- let symbolWeight: NSFont.Weight = isSelected ? .semibold : .regular
|
|
|
|
|
- if let image = NSImage(systemSymbolName: symbolName, accessibilityDescription: isSelected ? "Selected" : "Not selected") {
|
|
|
|
|
- let config = NSImage.SymbolConfiguration(pointSize: 20, weight: symbolWeight)
|
|
|
|
|
- checkmarkView.image = image.withSymbolConfiguration(config)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ checkboxView.state = isSelected ? .checked : .unchecked
|
|
|
|
|
|
|
|
if isSelected {
|
|
if isSelected {
|
|
|
layer?.borderWidth = 0
|
|
layer?.borderWidth = 0
|
|
|
layer?.backgroundColor = AppTheme.homeActiveBackground.cgColor
|
|
layer?.backgroundColor = AppTheme.homeActiveBackground.cgColor
|
|
|
nameLabel.textColor = AppTheme.homeActiveForeground
|
|
nameLabel.textColor = AppTheme.homeActiveForeground
|
|
|
- checkmarkView.contentTintColor = AppTheme.homeActiveForeground
|
|
|
|
|
} else {
|
|
} else {
|
|
|
layer?.borderWidth = 1
|
|
layer?.borderWidth = 1
|
|
|
layer?.borderColor = AppTheme.paywallBorder.cgColor
|
|
layer?.borderColor = AppTheme.paywallBorder.cgColor
|
|
|
layer?.backgroundColor = AppTheme.cardBackground.cgColor
|
|
layer?.backgroundColor = AppTheme.cardBackground.cgColor
|
|
|
nameLabel.textColor = AppTheme.textPrimary
|
|
nameLabel.textColor = AppTheme.textPrimary
|
|
|
- checkmarkView.contentTintColor = AppTheme.textSecondary
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ checkboxView.refreshAppearance()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override func mouseUp(with event: NSEvent) {
|
|
override func mouseUp(with event: NSEvent) {
|
|
@@ -509,6 +618,74 @@ extension PrintContactsOverlayView: NSTextFieldDelegate {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// MARK: - Select All Row
|
|
|
|
|
+
|
|
|
|
|
+private final class PrintContactsSelectAllRow: NSControl, AppearanceRefreshable {
|
|
|
|
|
+ var onClick: (() -> Void)?
|
|
|
|
|
+
|
|
|
|
|
+ private let checkboxView = ContactCheckboxView()
|
|
|
|
|
+ private let titleLabel = NSTextField(labelWithString: "Select all")
|
|
|
|
|
+ private var contentInset: CGFloat = 16
|
|
|
|
|
+ private var checkboxNameSpacing: CGFloat = 12
|
|
|
|
|
+ private var leadingConstraint: NSLayoutConstraint?
|
|
|
|
|
+ private var titleLeadingConstraint: NSLayoutConstraint?
|
|
|
|
|
+
|
|
|
|
|
+ var selectionState: ContactSelectionState {
|
|
|
|
|
+ get { checkboxView.state }
|
|
|
|
|
+ set { checkboxView.state = newValue }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ func applyLayout(contentInset: CGFloat, checkboxNameSpacing: CGFloat) {
|
|
|
|
|
+ self.contentInset = contentInset
|
|
|
|
|
+ self.checkboxNameSpacing = checkboxNameSpacing
|
|
|
|
|
+ leadingConstraint?.constant = contentInset
|
|
|
|
|
+ titleLeadingConstraint?.constant = checkboxNameSpacing
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ init() {
|
|
|
|
|
+ super.init(frame: .zero)
|
|
|
|
|
+ translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+
|
|
|
|
|
+ titleLabel.font = AppTheme.regularFont(size: 15)
|
|
|
|
|
+ titleLabel.isSelectable = false
|
|
|
|
|
+ titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
|
+
|
|
|
|
|
+ addSubview(checkboxView)
|
|
|
|
|
+ addSubview(titleLabel)
|
|
|
|
|
+
|
|
|
|
|
+ let leading = checkboxView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: contentInset)
|
|
|
|
|
+ let titleLeading = titleLabel.leadingAnchor.constraint(equalTo: checkboxView.trailingAnchor, constant: checkboxNameSpacing)
|
|
|
|
|
+ leadingConstraint = leading
|
|
|
|
|
+ titleLeadingConstraint = titleLeading
|
|
|
|
|
+
|
|
|
|
|
+ NSLayoutConstraint.activate([
|
|
|
|
|
+ leading,
|
|
|
|
|
+ checkboxView.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
|
|
|
+ titleLeading,
|
|
|
|
|
+ titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
|
|
|
+ titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor),
|
|
|
|
|
+ ])
|
|
|
|
|
+ refreshAppearance()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @available(*, unavailable)
|
|
|
|
|
+ required init?(coder: NSCoder) { nil }
|
|
|
|
|
+
|
|
|
|
|
+ func refreshAppearance() {
|
|
|
|
|
+ titleLabel.textColor = AppTheme.textPrimary
|
|
|
|
|
+ checkboxView.refreshAppearance()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override func mouseUp(with event: NSEvent) {
|
|
|
|
|
+ guard bounds.contains(convert(event.locationInWindow, from: nil)) else { return }
|
|
|
|
|
+ onClick?()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ override func resetCursorRects() {
|
|
|
|
|
+ addCursorRect(bounds, cursor: .pointingHand)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// MARK: - Toolbar Button
|
|
// MARK: - Toolbar Button
|
|
|
|
|
|
|
|
private final class PrintContactsToolbarButton: NSControl, AppearanceRefreshable {
|
|
private final class PrintContactsToolbarButton: NSControl, AppearanceRefreshable {
|