KMFontWell.swift 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  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 ?? .center
  181. newCell.isEditable = oldCell?.isEditable ?? true
  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. if let data = self.action {
  193. coder.encode(NSStringFromSelector(data), forKey: ACTION_KEY)
  194. }
  195. coder.encodeConditionalObject(target, forKey: TARGET_KEY)
  196. }
  197. func commonInit() {
  198. if (self.font == nil) {
  199. self.font = .systemFont(ofSize: 0.0)
  200. }
  201. self.isEnabled = true
  202. self._fontChanged()
  203. super.action = #selector(_changeActive)
  204. super.target = self
  205. // bindingInfo = [[NSMutableDictionary alloc] init];
  206. // [self registerForDraggedTypes:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, NSPasteboardTypeColor, nil]];
  207. }
  208. override func draw(_ dirtyRect: NSRect) {
  209. super.draw(dirtyRect)
  210. // Drawing code here.
  211. }
  212. override func viewWillMove(toWindow newWindow: NSWindow?) {
  213. self._deactivate()
  214. super.viewWillMove(toWindow: newWindow)
  215. }
  216. @objc func fontPickerWillBecomeActive(_ notification: NSNotification) {
  217. let sender = notification.object
  218. if self.isEqual(to: sender) == false && self.isActive {
  219. self._deactivate()
  220. }
  221. }
  222. @objc func fontPanelWillClose(_ notification: NSNotification) {
  223. self._deactivate()
  224. }
  225. override var font: NSFont? {
  226. get {
  227. return super.font
  228. }
  229. set {
  230. let didChange = (self.font?.isEqual(to: newValue) == false)
  231. super.font = newValue
  232. if didChange {
  233. self._fontChanged()
  234. }
  235. }
  236. }
  237. override func activeFontWell() -> KMFontWell? {
  238. if self.isActive {
  239. return self
  240. }
  241. return super.activeFontWell()
  242. }
  243. func changeFontFromFontManager(_ sender: AnyObject?) {
  244. if (self.isActive) {
  245. if let _fontManager = sender as? NSFontManager {
  246. if let _font = self.font {
  247. self.font = _fontManager.convert(_font)
  248. }
  249. }
  250. // [self notifyFontBinding];
  251. self.sendAction(self._pri_action, to: self._pri_target)
  252. }
  253. }
  254. override var target: AnyObject? {
  255. get {
  256. return self._pri_target
  257. }
  258. set {
  259. self._pri_target = newValue
  260. }
  261. }
  262. override var action: Selector? {
  263. get {
  264. return self._pri_action
  265. }
  266. set {
  267. self._pri_action = newValue
  268. }
  269. }
  270. /*
  271. #define SKNSFontPanelDescriptorsPboardType @"NSFontPanelDescriptorsPboardType"
  272. #define SKNSFontPanelFamiliesPboardType @"NSFontPanelFamiliesPboardType"
  273. #define SKNSFontCollectionFontDescriptors @"NSFontCollectionFontDescriptors"
  274. #define FONT_KEY @"font"
  275. static char SKFontWellFontNameObservationContext;
  276. static char SKFontWellFontSizeObservationContext;
  277. @interface SKFontWell (SKPrivate)
  278. - (void)changeActive:(id)sender;
  279. - (void)fontChanged;
  280. @end
  281. @implementation SKFontWell
  282. + (void)initialize {
  283. // SKINITIALIZE;
  284. [self exposeBinding:FONTNAME_KEY];
  285. [self exposeBinding:FONTSIZE_KEY];
  286. [self exposeBinding:TEXTCOLOR_KEY];
  287. }
  288. + (NSSet *)keyPathsForValuesAffectingFontName {
  289. return [NSSet setWithObjects:FONT_KEY, nil];
  290. }
  291. + (NSSet *)keyPathsForValuesAffectingFontSize {
  292. return [NSSet setWithObjects:FONT_KEY, nil];
  293. }
  294. - (Class)valueClassForBinding:(NSString *)binding {
  295. if ([binding isEqualToString:FONTNAME_KEY])
  296. return [NSString class];
  297. else if ([binding isEqualToString:FONTNAME_KEY])
  298. return [NSNumber class];
  299. else if ([binding isEqualToString:TEXTCOLOR_KEY])
  300. return [NSColor class];
  301. else
  302. return [super valueClassForBinding:binding];
  303. }
  304. - (void)notifyFontBinding {
  305. NSDictionary *info = [self infoForBinding:FONTNAME_KEY];
  306. [[info objectForKey:NSObservedObjectKey] setValue:[self fontName] forKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  307. info = [self infoForBinding:FONTSIZE_KEY];
  308. [[info objectForKey:NSObservedObjectKey] setValue:[NSNumber numberWithDouble:[self fontSize]] forKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  309. }
  310. - (void)notifyTextColorBinding {
  311. NSDictionary *info = [self infoForBinding:TEXTCOLOR_KEY];
  312. if (info) {
  313. id value = [self textColor];
  314. NSString *transformerName = [[info objectForKey:NSOptionsKey] objectForKey:NSValueTransformerNameBindingOption];
  315. if (transformerName && [transformerName isEqual:[NSNull null]] == NO) {
  316. NSValueTransformer *valueTransformer = [NSValueTransformer valueTransformerForName:transformerName];
  317. value = [valueTransformer reverseTransformedValue:value];
  318. }
  319. [[info objectForKey:NSObservedObjectKey] setValue:value forKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  320. }
  321. }
  322. - (void)changeAttributesFromFontManager:(id)sender {
  323. if ([self isActive] && [self hasTextColor]) {
  324. [self setTextColor:[[sender convertAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[self textColor], NSForegroundColorAttributeName, nil]] valueForKey:NSForegroundColorAttributeName]];
  325. [self notifyTextColorBinding];
  326. [self sendAction:[self action] to:[self target]];
  327. }
  328. }
  329. #pragma mark Accessors
  330. - (SEL)action { return action; }
  331. - (void)setAction:(SEL)newAction { action = newAction; }
  332. - (id)target { return target; }
  333. - (void)setTarget:(id)newTarget { target = newTarget; }
  334. #pragma mark Binding support
  335. - (void)bind:(NSString *)bindingName toObject:(id)observableController withKeyPath:(NSString *)keyPath options:(NSDictionary *)options {
  336. if ([bindingName isEqualToString:FONTNAME_KEY] || [bindingName isEqualToString:FONTSIZE_KEY]) {
  337. if ([bindingInfo objectForKey:bindingName])
  338. [self unbind:bindingName];
  339. // NSDictionary *bindingsData = [NSDictionary dictionaryWithObjectsAndKeys:observableController, NSObservedObjectKey, [[keyPath copy] autorelease], NSObservedKeyPathKey, [[options copy] autorelease], NSOptionsKey, nil];
  340. // [bindingInfo setObject:bindingsData forKey:bindingName];
  341. void *context = NULL;
  342. if ([bindingName isEqualToString:FONTNAME_KEY])
  343. context = &SKFontWellFontNameObservationContext;
  344. else if ([bindingName isEqualToString:FONTSIZE_KEY])
  345. context = &SKFontWellFontSizeObservationContext;
  346. [observableController addObserver:self forKeyPath:keyPath options:0 context:context];
  347. [self observeValueForKeyPath:keyPath ofObject:observableController change:nil context:context];
  348. } else {
  349. [super bind:bindingName toObject:observableController withKeyPath:keyPath options:options];
  350. }
  351. [self setNeedsDisplay:YES];
  352. }
  353. - (void)unbind:(NSString *)bindingName {
  354. if ([bindingName isEqualToString:FONTNAME_KEY] || [bindingName isEqualToString:FONTSIZE_KEY]) {
  355. NSDictionary *info = [self infoForBinding:bindingName];
  356. [[info objectForKey:NSObservedObjectKey] removeObserver:self forKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  357. [bindingInfo removeObjectForKey:bindingName];
  358. } else {
  359. [super unbind:bindingName];
  360. }
  361. [self setNeedsDisplay:YES];
  362. }
  363. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  364. NSString *key = nil;
  365. if (context == &SKFontWellFontNameObservationContext)
  366. key = FONTNAME_KEY;
  367. else if (context == &SKFontWellFontSizeObservationContext)
  368. key = FONTSIZE_KEY;
  369. if (key) {
  370. NSDictionary *info = [self infoForBinding:key];
  371. id value = [[info objectForKey:NSObservedObjectKey] valueForKeyPath:[info objectForKey:NSObservedKeyPathKey]];
  372. if (NSIsControllerMarker(value) == NO)
  373. [self setValue:value forKey:key];
  374. } else {
  375. [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
  376. }
  377. }
  378. - (NSDictionary *)infoForBinding:(NSString *)bindingName {
  379. return [bindingInfo objectForKey:bindingName] ?: [super infoForBinding:bindingName];
  380. }
  381. #pragma mark NSDraggingDestination protocol
  382. - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
  383. if ([self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]]) {
  384. [[self cell] setHighlighted:YES];
  385. [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
  386. [self setNeedsDisplay:YES];
  387. return NSDragOperationGeneric;
  388. } else
  389. return NSDragOperationNone;
  390. }
  391. - (void)draggingExited:(id <NSDraggingInfo>)sender {
  392. if ([self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]]) {
  393. [[self cell] setHighlighted:NO];
  394. [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
  395. [self setNeedsDisplay:YES];
  396. }
  397. }
  398. - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender {
  399. return [self isEnabled] && [sender draggingSource] != self && [[sender draggingPasteboard] availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]];
  400. }
  401. - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender{
  402. NSPasteboard *pboard = [sender draggingPasteboard];
  403. NSString *type = [pboard availableTypeFromArray:[NSArray arrayWithObjects:SKNSFontPanelDescriptorsPboardType, SKNSFontPanelFamiliesPboardType, ([self hasTextColor] ? NSPasteboardTypeColor : nil), nil]];
  404. NSFont *droppedFont = nil;
  405. NSColor *droppedColor = nil;
  406. @try {
  407. if ([type isEqualToString:SKNSFontPanelDescriptorsPboardType]) {
  408. NSData *data = [pboard dataForType:type];
  409. NSDictionary *dict = [data isKindOfClass:[NSData class]] ? [NSKeyedUnarchiver unarchiveObjectWithData:data] : nil;
  410. if ([dict isKindOfClass:[NSDictionary class]]) {
  411. NSArray *fontDescriptors = [dict objectForKey:SKNSFontCollectionFontDescriptors];
  412. NSFontDescriptor *fontDescriptor = ([fontDescriptors isKindOfClass:[NSArray class]] && [fontDescriptors count]) ? [fontDescriptors objectAtIndex:0] : nil;
  413. if ([fontDescriptor isKindOfClass:[NSFontDescriptor class]]) {
  414. NSNumber *size = [[fontDescriptor fontAttributes] objectForKey:NSFontSizeAttribute] ?: [dict objectForKey:NSFontSizeAttribute];
  415. CGFloat fontSize = [size respondsToSelector:@selector(doubleValue)] ? [size doubleValue] : [self fontSize];
  416. droppedFont = [NSFont fontWithDescriptor:fontDescriptor size:fontSize];
  417. }
  418. }
  419. } else if ([type isEqualToString:SKNSFontPanelFamiliesPboardType]) {
  420. NSArray *families = [pboard propertyListForType:type];
  421. NSString *family = ([families isKindOfClass:[NSArray class]] && [families count]) ? [families objectAtIndex:0] : nil;
  422. if ([family isKindOfClass:[NSString class]])
  423. droppedFont = [[NSFontManager sharedFontManager] convertFont:[self font] toFamily:family];
  424. } else if ([type isEqualToString:NSPasteboardTypeColor]) {
  425. droppedColor = [NSColor colorFromPasteboard:pboard];
  426. }
  427. }
  428. @catch (id exception) {
  429. NSLog(@"Ignoring exception %@ when dropping on SKFontWell failed", exception);
  430. }
  431. if (droppedFont) {
  432. [self setFont:droppedFont];
  433. [self notifyFontBinding];
  434. [self sendAction:[self action] to:[self target]];
  435. }
  436. if (droppedColor) {
  437. [self setTextColor:droppedColor];
  438. [self notifyTextColorBinding];
  439. [self sendAction:[self action] to:[self target]];
  440. }
  441. [[self cell] setHighlighted:NO];
  442. [self setKeyboardFocusRingNeedsDisplayInRect:[self bounds]];
  443. [self setNeedsDisplay:YES];
  444. return droppedFont != nil || droppedColor != nil;
  445. }
  446. @end
  447. */
  448. }
  449. // MARK: - Private Methods
  450. extension KMFontWell {
  451. private func _fontChanged() {
  452. if self.isActive {
  453. NSFontManager.shared.setSelectedFont(self.font ?? .systemFont(ofSize: 0), isMultiple: false)
  454. }
  455. self.title = String(format: "%@ %.0f", self.font?.displayName ?? "", self.fontSize)
  456. self.needsDisplay = true
  457. }
  458. @objc private func _changeActive(_ sender: AnyObject?) {
  459. if (self.isEnabled) {
  460. if (self.isActive) {
  461. self._activate()
  462. } else {
  463. self._deactivate()
  464. }
  465. }
  466. }
  467. private func _activate() {
  468. let nc = NotificationCenter.default
  469. let fm = NSFontManager.shared
  470. nc.post(name: NSNotification.Name(rawValue: SKFontWellWillBecomeActiveNotification), object: self)
  471. fm.setSelectedFont(self.font ?? .systemFont(ofSize: 0), isMultiple: false)
  472. fm.orderFrontFontPanel(self)
  473. nc.addObserver(self, selector: #selector(fontPickerWillBecomeActive), name: NSNotification.Name(SKFontWellWillBecomeActiveNotification), object: nil)
  474. nc.addObserver(self, selector: #selector(fontPanelWillClose), name: NSWindow.willCloseNotification, object: fm.fontPanel(true))
  475. self.state = .on
  476. self.setKeyboardFocusRingNeedsDisplay(self.bounds)
  477. self.needsDisplay = true
  478. }
  479. private func _deactivate() {
  480. NotificationCenter.default.removeObserver(self)
  481. self.state = .off
  482. self.setKeyboardFocusRingNeedsDisplay(self.bounds)
  483. self.needsDisplay = true
  484. }
  485. }