KMFontWell.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. //
  2. // KMFontWell.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by tangchao on 2023/11/7.
  6. //
  7. import Cocoa
  8. class KMFontWellCell: NSButtonCell {
  9. var textColor: NSColor?
  10. var hasTextColor: Bool = false
  11. private let FONTNAME_KEY = "fontName"
  12. private let FONTSIZE_KEY = "fontSize"
  13. private let TEXTCOLOR_KEY = "textColor"
  14. private let HASTEXTCOLOR_KEY = "hasTextColor"
  15. deinit {
  16. KMPrint("KMFontWellCell deinit.")
  17. }
  18. override init(textCell string: String) {
  19. super.init(textCell: string)
  20. self.commonInit()
  21. }
  22. required init(coder: NSCoder) {
  23. super.init(coder: coder)
  24. self.textColor = coder.decodeObject(forKey: TEXTCOLOR_KEY) as? NSColor
  25. self.hasTextColor = coder.decodeBool(forKey: HASTEXTCOLOR_KEY)
  26. self.commonInit()
  27. }
  28. func commonInit() {
  29. if self.textColor == nil {
  30. self.textColor = .black
  31. }
  32. self.bezelStyle = .shadowlessSquare
  33. self.setButtonType(.pushOnPushOff)
  34. self.state = .off
  35. }
  36. override func encode(with coder: NSCoder) {
  37. super.encode(with: coder)
  38. coder.encodeConditionalObject(self.textColor, forKey: TEXTCOLOR_KEY)
  39. coder.encode(self.hasTextColor, forKey: HASTEXTCOLOR_KEY)
  40. }
  41. override func copy(with zone: NSZone? = nil) -> Any {
  42. let cell = super.copy(with: zone) as? KMFontWellCell
  43. cell?.textColor = self.textColor?.copy(with: zone) as? NSColor
  44. cell?.hasTextColor = self.hasTextColor
  45. return cell as Any
  46. }
  47. override func drawBezel(withFrame frame: NSRect, in controlView: NSView) {
  48. KMDrawTextFieldBezel(frame, controlView)
  49. if (self.state == .on) {
  50. NSGraphicsContext.saveGraphicsState()
  51. NSColor.selectedControlColor.setFill()
  52. frame.fill(using: .plusDarker)
  53. NSGraphicsContext.restoreGraphicsState()
  54. }
  55. if (self.isHighlighted) {
  56. NSGraphicsContext.saveGraphicsState()
  57. NSColor(calibratedWhite: 0, alpha: 0.1).setFill()
  58. frame.frame(withWidth: 1, using: .plusDarker)
  59. NSGraphicsContext.restoreGraphicsState()
  60. }
  61. if (self.showsFirstResponder) {
  62. NSGraphicsContext.saveGraphicsState()
  63. NSFocusRingPlacement.only.set()
  64. _ = CGRect.fill(frame)
  65. NSGraphicsContext.restoreGraphicsState()
  66. }
  67. }
  68. override var attributedTitle: NSAttributedString {
  69. set {
  70. super.attributedTitle = newValue
  71. }
  72. get {
  73. if (self.hasTextColor) {
  74. var attrString = super.attributedTitle.mutableCopy() as? NSMutableAttributedString
  75. attrString?.addAttribute(.foregroundColor, value: self.textColor ?? NSColor.black, range: NSMakeRange(0, attrString?.length ?? 0))
  76. if let data = self.textColor?.usingColorSpaceName(.calibratedRGB)?.brightnessComponent, data > 0.8 {
  77. let shade = NSShadow()
  78. shade.shadowColor = .black
  79. shade.shadowBlurRadius = 1
  80. attrString?.addAttribute(.shadow, value: shade, range: NSMakeRange(0, attrString?.length ?? 0))
  81. }
  82. return attrString!
  83. } else {
  84. return super.attributedTitle
  85. }
  86. }
  87. }
  88. }
  89. private let SKFontWellWillBecomeActiveNotification = "SKFontWellWillBecomeActiveNotification"
  90. private let ACTION_KEY = "action"
  91. private let TARGET_KEY = "target"
  92. class KMFontWell: NSButton {
  93. var isActive: Bool {
  94. get {
  95. return self.state == .on
  96. }
  97. }
  98. var fontName: String? {
  99. get {
  100. return self.font?.fontName
  101. }
  102. set {
  103. if let _fontName = newValue {
  104. let newFont = NSFont(name: _fontName, size: self.font?.pointSize ?? 12)
  105. self.font = newFont
  106. }
  107. }
  108. }
  109. var fontSize: CGFloat {
  110. get {
  111. return self.font?.pointSize ?? 12
  112. }
  113. set {
  114. if let _fontName = self.fontName {
  115. let newFont = NSFont(name: _fontName, size: newValue)
  116. self.font = newFont
  117. }
  118. }
  119. }
  120. var textColor: NSColor? {
  121. get {
  122. return (self.cell as? KMFontWellCell)?.textColor
  123. }
  124. set {
  125. let didChange = self.textColor?.isEqual(to: newValue) ?? false
  126. (self.cell as? KMFontWellCell)?.textColor = newValue
  127. if didChange {
  128. self.needsDisplay = true
  129. }
  130. }
  131. }
  132. var hasTextColor: Bool {
  133. get {
  134. if let data = self.cell as? KMFontWellCell {
  135. return data.hasTextColor
  136. }
  137. return false
  138. }
  139. set {
  140. if self.hasTextColor != newValue {
  141. (self.cell as? KMFontWellCell)?.hasTextColor = newValue
  142. self.needsDisplay = true
  143. }
  144. }
  145. }
  146. private var _pri_target: AnyObject?
  147. private var _pri_action: Selector?
  148. /*
  149. @interface SKFontWell : NSButton {
  150. NSMutableDictionary *bindingInfo;
  151. }
  152. - (void)changeFontFromFontManager:(id)sender;
  153. - (void)changeAttributesFromFontManager:(id)sender;
  154. */
  155. override class var cellClass: AnyClass? {
  156. set {
  157. super.cellClass = newValue
  158. }
  159. get {
  160. return KMFontWellCell.self
  161. }
  162. }
  163. deinit {
  164. // SKENSURE_MAIN_THREAD(
  165. // [self unbind:FONTNAME_KEY];
  166. // [self unbind:FONTSIZE_KEY];
  167. // );
  168. NotificationCenter.default.removeObserver(self)
  169. // SKDESTROY(bindingInfo);
  170. }
  171. override init(frame frameRect: NSRect) {
  172. super.init(frame: frameRect)
  173. self.commonInit()
  174. }
  175. required init?(coder: NSCoder) {
  176. super.init(coder: coder)
  177. let oldCell = self.cell as? NSButtonCell
  178. if let data = oldCell?.isKind(of: Self.cellClass!), !data {
  179. let newCell = KMFontWellCell(textCell: "")
  180. newCell.alignment = oldCell!.alignment
  181. newCell.isEditable = oldCell!.isEditable
  182. newCell.target = oldCell?.target
  183. newCell.action = oldCell?.action
  184. self.cell = newCell
  185. }
  186. action = NSSelectorFromString(coder.decodeObject(forKey: ACTION_KEY) as? String ?? "")
  187. target = coder.decodeObject(forKey: TARGET_KEY) as AnyObject?
  188. self.commonInit()
  189. }
  190. override func encode(with coder: NSCoder) {
  191. super.encode(with: coder)
  192. coder.encode(NSStringFromSelector(action!), forKey: ACTION_KEY)
  193. coder.encodeConditionalObject(target, forKey: TARGET_KEY)
  194. }
  195. func commonInit() {
  196. if (self.font == nil) {
  197. self.font = .systemFont(ofSize: 0.0)
  198. }
  199. self.isEnabled = true
  200. self._fontChanged()
  201. super.action = #selector(_changeActive)
  202. super.target = self
  203. // bindingInfo = [[NSMutableDictionary alloc] init];
  204. // [self registerForDraggedTypes:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, NSPasteboardTypeColor, nil]];
  205. }
  206. override func draw(_ dirtyRect: NSRect) {
  207. super.draw(dirtyRect)
  208. // Drawing code here.
  209. }
  210. override func viewWillMove(toWindow newWindow: NSWindow?) {
  211. self._deactivate()
  212. super.viewWillMove(toWindow: newWindow)
  213. }
  214. @objc func fontPickerWillBecomeActive(_ notification: NSNotification) {
  215. let sender = notification.object
  216. if self.isEqual(to: sender) == false && self.isActive {
  217. self._deactivate()
  218. }
  219. }
  220. @objc func fontPanelWillClose(_ notification: NSNotification) {
  221. self._deactivate()
  222. }
  223. override var font: NSFont? {
  224. get {
  225. return super.font
  226. }
  227. set {
  228. let didChange = (self.font?.isEqual(to: newValue) == false)
  229. super.font = newValue
  230. if didChange {
  231. self._fontChanged()
  232. }
  233. }
  234. }
  235. override func activeFontWell() -> KMFontWell? {
  236. if self.isActive {
  237. return self
  238. }
  239. return super.activeFontWell()
  240. }
  241. func changeFontFromFontManager(_ sender: AnyObject?) {
  242. if (self.isActive) {
  243. if let _fontManager = sender as? NSFontManager {
  244. if let _font = self.font {
  245. self.font = _fontManager.convert(_font)
  246. }
  247. }
  248. // [self notifyFontBinding];
  249. self.sendAction(self._pri_action, to: self._pri_target)
  250. }
  251. }
  252. override var target: AnyObject? {
  253. get {
  254. return self._pri_target
  255. }
  256. set {
  257. self._pri_target = newValue
  258. }
  259. }
  260. override var action: Selector? {
  261. get {
  262. return self._pri_action
  263. }
  264. set {
  265. self._pri_action = newValue
  266. }
  267. }
  268. /*
  269. #define SKNSFontPanelDescriptorsPboardType @"NSFontPanelDescriptorsPboardType"
  270. #define SKNSFontPanelFamiliesPboardType @"NSFontPanelFamiliesPboardType"
  271. #define SKNSFontCollectionFontDescriptors @"NSFontCollectionFontDescriptors"
  272. #define FONT_KEY @"font"
  273. static char SKFontWellFontNameObservationContext;
  274. static char SKFontWellFontSizeObservationContext;
  275. @interface SKFontWell (SKPrivate)
  276. - (void)changeActive:(id)sender;
  277. - (void)fontChanged;
  278. @end
  279. @implementation SKFontWell
  280. + (void)initialize {
  281. // SKINITIALIZE;
  282. [self exposeBinding:FONTNAME_KEY];
  283. [self exposeBinding:FONTSIZE_KEY];
  284. [self exposeBinding:TEXTCOLOR_KEY];
  285. }
  286. + (NSSet *)keyPathsForValuesAffectingFontName {
  287. return [NSSet setWithObjects:FONT_KEY, nil];
  288. }
  289. + (NSSet *)keyPathsForValuesAffectingFontSize {
  290. return [NSSet setWithObjects:FONT_KEY, nil];
  291. }
  292. - (Class)valueClassForBinding:(NSString *)binding {
  293. if ([binding isEqualToString:FONTNAME_KEY])
  294. return [NSString class];
  295. else if ([binding isEqualToString:FONTNAME_KEY])
  296. return [NSNumber class];
  297. else if ([binding isEqualToString:TEXTCOLOR_KEY])
  298. return [NSColor class];
  299. else
  300. return [super valueClassForBinding:binding];
  301. }
  302. - (void)notifyFontBinding {
  303. NSDictionary *info = [self infoForBinding:FONTNAME_KEY];
  304. [[info objectForKey:NSObservedObjectKey] setValue:[self fontName] forKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  305. info = [self infoForBinding:FONTSIZE_KEY];
  306. [[info objectForKey:NSObservedObjectKey] setValue:[NSNumber numberWithDouble:[self fontSize]] forKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  307. }
  308. - (void)notifyTextColorBinding {
  309. NSDictionary *info = [self infoForBinding:TEXTCOLOR_KEY];
  310. if (info) {
  311. id value = [self textColor];
  312. NSString *transformerName = [[info objectForKey:NSOptionsKey] objectForKey:NSValueTransformerNameBindingOption];
  313. if (transformerName && [transformerName isEqual:[NSNull null]] == NO) {
  314. NSValueTransformer *valueTransformer = [NSValueTransformer valueTransformerForName:transformerName];
  315. value = [valueTransformer reverseTransformedValue:value];
  316. }
  317. [[info objectForKey:NSObservedObjectKey] setValue:value forKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  318. }
  319. }
  320. - (void)changeAttributesFromFontManager:(id)sender {
  321. if ([self isActive] && [self hasTextColor]) {
  322. [self setTextColor:[[sender convertAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[self textColor], NSForegroundColorAttributeName, nil]] valueForKey:NSForegroundColorAttributeName]];
  323. [self notifyTextColorBinding];
  324. [self sendAction:[self action] to:[self target]];
  325. }
  326. }
  327. #pragma mark Accessors
  328. - (SEL)action { return action; }
  329. - (void)setAction:(SEL)newAction { action = newAction; }
  330. - (id)target { return target; }
  331. - (void)setTarget:(id)newTarget { target = newTarget; }
  332. #pragma mark Binding support
  333. - (void)bind:(NSString *)bindingName toObject:(id)observableController withKeyPath:(NSString *)keyPath options:(NSDictionary *)options {
  334. if ([bindingName isEqualToString:FONTNAME_KEY] || [bindingName isEqualToString:FONTSIZE_KEY]) {
  335. if ([bindingInfo objectForKey:bindingName])
  336. [self unbind:bindingName];
  337. // NSDictionary *bindingsData = [NSDictionary dictionaryWithObjectsAndKeys:observableController, NSObservedObjectKey, [[keyPath copy] autorelease], NSObservedKeyPathKey, [[options copy] autorelease], NSOptionsKey, nil];
  338. // [bindingInfo setObject:bindingsData forKey:bindingName];
  339. void *context = NULL;
  340. if ([bindingName isEqualToString:FONTNAME_KEY])
  341. context = &SKFontWellFontNameObservationContext;
  342. else if ([bindingName isEqualToString:FONTSIZE_KEY])
  343. context = &SKFontWellFontSizeObservationContext;
  344. [observableController addObserver:self forKeyPath:keyPath options:0 context:context];
  345. [self observeValueForKeyPath:keyPath ofObject:observableController change:nil context:context];
  346. } else {
  347. [super bind:bindingName toObject:observableController withKeyPath:keyPath options:options];
  348. }
  349. [self setNeedsDisplay:YES];
  350. }
  351. - (void)unbind:(NSString *)bindingName {
  352. if ([bindingName isEqualToString:FONTNAME_KEY] || [bindingName isEqualToString:FONTSIZE_KEY]) {
  353. NSDictionary *info = [self infoForBinding:bindingName];
  354. [[info objectForKey:NSObservedObjectKey] removeObserver:self forKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  355. [bindingInfo removeObjectForKey:bindingName];
  356. } else {
  357. [super unbind:bindingName];
  358. }
  359. [self setNeedsDisplay:YES];
  360. }
  361. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  362. NSString *key = nil;
  363. if (context == &SKFontWellFontNameObservationContext)
  364. key = FONTNAME_KEY;
  365. else if (context == &SKFontWellFontSizeObservationContext)
  366. key = FONTSIZE_KEY;
  367. if (key) {
  368. NSDictionary *info = [self infoForBinding:key];
  369. id value = [[info objectForKey:NSObservedObjectKey] valueForKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  370. if (NSIsControllerMarker(value) == NO)
  371. [self setValue:value forKey:key];
  372. } else {
  373. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  374. }
  375. }
  376. - (NSDictionary *)infoForBinding:(NSString *)bindingName {
  377. return [bindingInfo objectForKey:bindingName] ?: [super infoForBinding:bindingName];
  378. }
  379. #pragma mark NSDraggingDestination protocol
  380. - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
  381. if ([self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]]) {
  382. [[self cell] setHighlighted:YES];
  383. [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
  384. [self setNeedsDisplay:YES];
  385. return NSDragOperationGeneric;
  386. } else
  387. return NSDragOperationNone;
  388. }
  389. - (void)draggingExited:(id <NSDraggingInfo>)sender {
  390. if ([self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]]) {
  391. [[self cell] setHighlighted:NO];
  392. [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
  393. [self setNeedsDisplay:YES];
  394. }
  395. }
  396. - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
  397. return [self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]];
  398. }
  399. - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender{
  400. NSPasteboard *pboard = [sender draggingPasteboard];
  401. NSString *type = [pboard availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]];
  402. NSFont *droppedFont = nil;
  403. NSColor *droppedColor = nil;
  404. @try {
  405. if ([type isEqualToString:SKNSFontPanelDescriptorsPboardType]) {
  406. NSData *data = [pboard dataForType:type];
  407. NSDictionary *dict = [data isKindOfClass:[NSData class]] ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil;
  408. if ([dict isKindOfClass:[NSDictionary class]]) {
  409. NSArray *fontDescriptors = [dict objectForKey:SKNSFontCollectionFontDescriptors];
  410. NSFontDescriptor *fontDescriptor = ([fontDescriptors isKindOfClass:[NSArray class]] && [fontDescriptors count]) ? [fontDescriptors objectAtIndex:0] : nil;
  411. if ([fontDescriptor isKindOfClass:[NSFontDescriptor class]]) {
  412. NSNumber *size = [[fontDescriptor fontAttributes] objectForKey:NSFontSizeAttribute] ?: [dict objectForKey:NSFontSizeAttribute];
  413. CGFloat fontSize = [size respondsToSelector:@selector(doubleValue)] ? [size doubleValue] : [self fontSize];
  414. droppedFont = [NSFont fontWithDescriptor:fontDescriptor size:fontSize];
  415. }
  416. }
  417. } else if ([type isEqualToString:SKNSFontPanelFamiliesPboardType]) {
  418. NSArray *families = [pboard propertyListForType:type];
  419. NSString *family = ([families isKindOfClass:[NSArray class]] && [families count]) ? [families objectAtIndex:0] : nil;
  420. if ([family isKindOfClass:[NSString class]])
  421. droppedFont = [[NSFontManager sharedFontManager] convertFont:[self font] toFamily:family];
  422. } else if ([type isEqualToString:NSPasteboardTypeColor]) {
  423. droppedColor = [NSColor colorFromPasteboard:pboard];
  424. }
  425. }
  426. @catch (id exception) {
  427. NSLog(@"Ignoring exception %@ when dropping on SKFontWell failed", exception);
  428. }
  429. if (droppedFont) {
  430. [self setFont:droppedFont];
  431. [self notifyFontBinding];
  432. [self sendAction:[self action] to:[self target]];
  433. }
  434. if (droppedColor) {
  435. [self setTextColor:droppedColor];
  436. [self notifyTextColorBinding];
  437. [self sendAction:[self action] to:[self target]];
  438. }
  439. [[self cell] setHighlighted:NO];
  440. [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
  441. [self setNeedsDisplay:YES];
  442. return droppedFont != nil || droppedColor != nil;
  443. }
  444. @end
  445. */
  446. }
  447. // MARK: - Private Methods
  448. extension KMFontWell {
  449. private func _fontChanged() {
  450. if self.isActive {
  451. NSFontManager.shared.setSelectedFont(self.font ?? .systemFont(ofSize: 0), isMultiple: false)
  452. }
  453. self.title = String(format: "%@ %.0f", self.font?.displayName ?? "", self.fontSize)
  454. self.needsDisplay = true
  455. }
  456. @objc private func _changeActive(_ sender: AnyObject?) {
  457. if (self.isEnabled) {
  458. if (self.isActive) {
  459. self._activate()
  460. } else {
  461. self._deactivate()
  462. }
  463. }
  464. }
  465. private func _activate() {
  466. let nc = NotificationCenter.default
  467. let fm = NSFontManager.shared
  468. nc.post(name: NSNotification.Name(rawValue: SKFontWellWillBecomeActiveNotification), object: self)
  469. fm.setSelectedFont(self.font ?? .systemFont(ofSize: 0), isMultiple: false)
  470. fm.orderFrontFontPanel(self)
  471. nc.addObserver(self, selector: #selector(fontPickerWillBecomeActive), name: NSNotification.Name(SKFontWellWillBecomeActiveNotification), object: nil)
  472. nc.addObserver(self, selector: #selector(fontPanelWillClose), name: NSWindow.willCloseNotification, object: fm.fontPanel(true))
  473. self.state = .on
  474. self.setKeyboardFocusRingNeedsDisplay(self.bounds)
  475. self.needsDisplay = true
  476. }
  477. private func _deactivate() {
  478. NotificationCenter.default.removeObserver(self)
  479. self.state = .off
  480. self.setKeyboardFocusRingNeedsDisplay(self.bounds)
  481. self.needsDisplay = true
  482. }
  483. }