// // KMSnapshotWindowController.swift // PDF Reader Pro // // Created by tangchao on 2023/12/12. // import Cocoa @objc protocol KMSnapshotWindowControllerDelegate: NSObjectProtocol { @objc optional func snapshotControllerDidFinishSetup(_ controller: KMSnapshotWindowController) @objc optional func snapshotControllerWillClose(_ controller: KMSnapshotWindowController) @objc optional func snapshotControllerDidChange(_ controller: KMSnapshotWindowController) @objc optional func snapshotController(_ controller: KMSnapshotWindowController, miniaturizedRect isMiniaturize: Bool) -> NSRect } class KMSnapshotWindowController: KMBaseWindowController { @IBOutlet var pdfView: KMSnapshotPDFView! var hasWindow = false var forceOnTop = false weak var delegate: KMSnapshotWindowControllerDelegate? var string: String = "" var pageLabel: String = "" { didSet { self.synchronizeWindowTitleWithDocumentName() } } var animating = false var thumbnail: NSImage? var windowImage: NSImage? var EM_DASH_CHARACTER = 0x2014 /* #define EM_DASH_CHARACTER (unichar)0x2014 NSString *SKSnapshotCurrentSetupKey = @"currentSetup"; static char SKSnaphotWindowDefaultsObservationContext; */ private let SMALL_DELAY = 0.1 private let RESIZE_TIME_FACTOR = 0.6 private let PAGE_KEY = "page" private let RECT_KEY = "rect" private let SCALEFACTOR_KEY = "scaleFactor" private let AUTOFITS_KEY = "autoFits" private let WINDOWFRAME_KEY = "windowFrame" private let HASWINDOW_KEY = "hasWindow" private let PAGELABEL_KEY = "pageLabel" private let PAGEANDWINDOW_KEY = "pageAndWindow" private let SKSnapshotWindowFrameAutosaveName = "SKSnapshotWindow" private let SKSnapshotViewChangedNotification = "SKSnapshotViewChangedNotification" private let SKSnapshotPageCellLabelKey = "label" private let SKSnapshotPageCellHasWindowKey = "hasWindow" deinit { KMPrint("KMSnapshotWindowController deinit.") NotificationCenter.default.removeObserver(self) } override var windowNibName: NSNib.Name? { return "SnapshotWindow" } override func windowDidLoad() { super.windowDidLoad() if NSWindow.instancesRespond(to: NSSelectorFromString("setTabbingMode:")) { self.window?.tabbingMode = .disallowed } if NSWindow.instancesRespond(to: NSSelectorFromString("toggleFullScreen:")) { // [[self window] setCollectionBehavior:[[self window] collectionBehavior] | NSWindowCollectionBehaviorFullScreenAuxiliary]; self.window?.collectionBehavior.insert(.fullScreenAuxiliary) } if let data = self.window?.responds(to: NSSelectorFromString("contentLayoutRect")), data { // [[self window] setStyleMask:[[self window] styleMask] | NSFullSizeContentViewWindowMask]; self.window?.styleMask.insert(.fullSizeContentView) self.pdfView.frame = self.window?.contentLayoutRect ?? NSMakeRect(0, 0, 400, 400) } self._updateWindowLevel() // [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeys:[NSArray arrayWithObjects:SKSnapshotsOnTopKey, SKShouldAntiAliasKey, SKGreekingThresholdKey, SKBackgroundColorKey, SKPageBackgroundColorKey, nil] context:&SKSnaphotWindowDefaultsObservationContext]; // the window is initialially exposed. The windowDidExpose notification is useless, it has nothing to do with showing the window self.hasWindow = true } override func initNotification() { super.initNotification() NotificationCenter.default.addObserver(self, selector: #selector(preferenceInfoDidChange), name: KMPreferenceManager.didChangeNotification, object: nil) } /* + (NSSet *)keyPathsForValuesAffectingPageAndWindow { return [NSSet setWithObjects:PAGELABEL_KEY, HASWINDOW_KEY, nil]; } */ override func windowTitle(forDocumentDisplayName displayName: String) -> String { return String(format: "%@ %C %@", displayName, EM_DASH_CHARACTER, String(format: KMLocalizedString("Page %@", ""), self.pageLabel)) } func setNeedsDisplay(in rect: NSRect, of page: CPDFPage?) { var aRect = self.pdfView.convert(rect, from: page) let scale = self.pdfView.scaleFactor let maxX = ceil(NSMaxX(aRect) + scale) let maxY = ceil(NSMaxY(aRect) + scale) let minX = floor(NSMinX(aRect) - scale) let minY = floor(NSMinY(aRect) - scale) aRect = NSIntersectionRect(self.pdfView.bounds, NSMakeRect(minX, minY, maxX - minX, maxY - minY)) if (NSIsEmptyRect(aRect) == false) { // [pdfView setNeedsDisplayInRect:aRect]; self.pdfView.setNeedsDisplayIn(aRect, of: page) } } func setNeedsDisplay(for annotation: CPDFAnnotation?, on page: CPDFPage?) { if let anno = annotation { self.setNeedsDisplay(in: anno.displayRect(), of: page) } } func redisplay() { // [pdfView requiresDisplay]; self.pdfView.needsDisplay = true } @objc func handlePageChangedNotification(_ notification: NSNotification?) { // [self setPageLabel:[[pdfView currentPage] displayLabel]]; let label = "\(self.pdfView.currentPage().pageIndex()+1)" self.pageLabel = label self.handlePDFViewFrameChangedNotification(nil) } @objc func handleDocumentDidUnlockNotification(_ notification: NSNotification) { let label = "\(self.pdfView.currentPage().pageIndex()+1)" self.pageLabel = label self.handlePDFViewFrameChangedNotification(nil) } @objc func handlePDFViewFrameChangedNotification(_ notification: NSNotification?) { if let _ = self.delegate?.snapshotControllerDidChange?(self) { let note = NSNotification(name: NSNotification.Name(rawValue: SKSnapshotViewChangedNotification), object: self) NotificationQueue.default.enqueue(note as Notification, postingStyle: .whenIdle, coalesceMask: .onName, forModes: nil) } } @objc func handleViewChangedNotification(_ notification: NSNotification) { self.updateString() self.delegate?.snapshotControllerDidChange?(self) } @objc func handleDidAddRemoveAnnotationNotification(_ notification: NSNotification) { guard let annotation = notification.userInfo?["annotation"] as? CPDFAnnotation else { return } guard let page = notification.userInfo?["page"] as? CPDFPage else { return } if let data = page.document?.isEqual(to: self.pdfView.document), data && self.isPageVisible(page) { self.setNeedsDisplay(for: annotation, on: page) } } @objc func notifiyDidFinishSetup() { self.delegate?.snapshotControllerDidFinishSetup?(self) } /* - (void)handleDidMoveAnnotationNotification:(NSNotification *)notification { PDFAnnotation *annotation = [[notification userInfo] objectForKey:SKPDFViewAnnotationKey]; PDFPage *oldPage = [[notification userInfo] objectForKey:SKPDFViewOldPageKey]; PDFPage *newPage = [[notification userInfo] objectForKey:SKPDFViewNewPageKey]; if ([[newPage document] isEqual:[pdfView document]]) { if ([self isPageVisible:oldPage]) [self setNeedsDisplayForAnnotation:annotation onPage:oldPage]; if ([self isPageVisible:newPage]) [self setNeedsDisplayForAnnotation:annotation onPage:newPage]; } } #pragma mark Acessors - (NSRect)bounds { NSView *clipView = [[[pdfView documentView] enclosingScrollView] contentView]; return [pdfView convertRect:[pdfView convertRect:[clipView bounds] fromView:clipView] toPage:[pdfView currentPage]]; } */ func setPdfDocument(_ pdfDocument: CPDFDocument, setup: NSDictionary?) { let number = (setup?.object(forKey: PAGE_KEY) as? NSNumber)?.intValue ?? 0 let rect = NSRectFromString((setup?.object(forKey: RECT_KEY) as? String) ?? "") let scaleFactor = (setup?.object(forKey: SCALEFACTOR_KEY) as? NSNumber)?.doubleValue ?? 0 let autoFits = (setup?.object(forKey: AUTOFITS_KEY) as? NSNumber)?.boolValue ?? false self.setPdfDocument(pdfDocument, goToPageNumber: number, rect: rect, scaleFactor: scaleFactor, autoFits: autoFits) self.hasWindow = (setup?.object(forKey: HASWINDOW_KEY) as? NSNumber)?.boolValue ?? false let frame = NSRectFromString(setup?.object(forKey: WINDOWFRAME_KEY) as? String ?? "") if frame.isEmpty == false { self.window?.setFrame(frame, display: false) } } func isPageVisible(_ page: CPDFPage?) -> Bool { guard let doc = page?.document else { return false } let result = doc.isEqual(to: self.pdfView.document) if result == false { return result } return NSLocationInRange(Int(page!.pageIndex()), self.pdfView.displayedPageIndexRange()) } func pageIndex() -> UInt { return self.pdfView.currentPage().pageIndex() } func pageAndWindow() -> NSDictionary { return [SKSnapshotPageCellLabelKey : self.pageLabel, SKSnapshotPageCellHasWindowKey : NSNumber(booleanLiteral: self.hasWindow)] } override func setForceOnTop(_ flag: Bool) { super.setForceOnTop(flag) self.forceOnTop = flag if let data = self.window?.isVisible, data { self._updateWindowLevel() } } /* - (NSDictionary *)currentSetup { NSView *clipView = [[[pdfView documentView] enclosingScrollView] contentView]; NSRect rect = [pdfView convertRect:[pdfView convertRect:[clipView bounds] fromView:clipView] toPage:[pdfView currentPage]]; BOOL autoFits = [pdfView autoFits]; return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedInteger:[self pageIndex]], PAGE_KEY, NSStringFromRect(rect), RECT_KEY, [NSNumber numberWithDouble:[pdfView scaleFactor]], SCALEFACTOR_KEY, [NSNumber numberWithBool:autoFits], AUTOFITS_KEY, [NSNumber numberWithBool:[[self window] isVisible]], HASWINDOW_KEY, NSStringFromRect([[self window] frame]), WINDOWFRAME_KEY, nil]; } */ // MARK: - Actions @IBAction func doGoToNextPage(_ sender: AnyObject?) { self.pdfView.goToNextPage(sender) } @IBAction func doGoToPreviousPage(_ sender: AnyObject?) { self.pdfView.goToPreviousPage(sender) } @IBAction func doGoToFirstPage(_ sender: AnyObject?) { self.pdfView.goToFirstPage(sender) } @IBAction func doGoToLastPage(_ sender: AnyObject?) { self.pdfView.goToLastPage(sender) } @IBAction func doGoBack(_ sender: AnyObject?) { self.pdfView.km_goBack(sender) } @IBAction func doGoForward(_ sender: AnyObject?) { self.pdfView.km_goForward(sender) } @IBAction func doZoomIn(_ sender: AnyObject?) { self.pdfView.zoomIn(sender) } @IBAction func doZoomOut(_ sender: AnyObject?) { self.pdfView.zoomOut(sender) } // @IBAction func doZoomToPhysicalSize(_ sender: AnyObject?) { // self.pdfv // } // - (IBAction):(id)sender { // [pdfView setPhysicalScaleFactor:1.0]; // } @IBAction func doZoomToActualSize(_ sender: AnyObject?) { self.pdfView.scaleFactor = 1.0 } @IBAction func toggleAutoScale(_ sender: AnyObject?) { self.pdfView.autoFits = !self.pdfView.autoFits } // MARK: - Thumbnails func thumbnailWithSize(_ size: CGFloat) -> NSImage? { let clipView = self.pdfView.documentView().contentView var bounds = self.pdfView.convert(clipView.bounds, from: clipView) if (bounds.equalTo(.zero) || bounds.isNull) { return nil } let imageRep = self.pdfView.bitmapImageRepForCachingDisplay(in: bounds) var transform: NSAffineTransform? var thumbnailSize = bounds.size var shadowBlurRadius: CGFloat = 0.0 var shadowOffset: CGFloat = 0.0 var image: NSImage? if let data = imageRep { self.pdfView.cacheDisplay(in: bounds, to: data) } bounds.origin = NSZeroPoint if (size > 0.0) { shadowBlurRadius = round(size / 32.0) shadowOffset = -ceil(shadowBlurRadius * 0.75) if (NSHeight(bounds) > NSWidth(bounds)) { thumbnailSize = NSMakeSize(round((size - 2.0 * shadowBlurRadius) * NSWidth(bounds) / NSHeight(bounds) + 2.0 * shadowBlurRadius), size); } else { thumbnailSize = NSMakeSize(size, round((size - 2.0 * shadowBlurRadius) * NSHeight(bounds) / NSWidth(bounds) + 2.0 * shadowBlurRadius)); } transform = NSAffineTransform() transform?.translateX(by: shadowBlurRadius, yBy: shadowBlurRadius - shadowOffset) transform?.scaleX(by: (thumbnailSize.width - 2.0 * shadowBlurRadius) / NSWidth(bounds), yBy: (thumbnailSize.height - 2.0 * shadowBlurRadius) / NSHeight(bounds)) } if (thumbnailSize.equalTo(.zero)) { return nil } image = NSImage(size: thumbnailSize) if (image!.size.equalTo(.zero)) { return nil } image?.lockFocus() NSGraphicsContext.current?.imageInterpolation = .high transform?.concat() NSGraphicsContext.saveGraphicsState() // [[PDFView defaultPageBackgroundColor] set]; if (shadowBlurRadius > 0.0) { NSShadow.setShadowWith(.init(calibratedWhite: 0, alpha: 0.5), blurRadius: shadowBlurRadius, offset: NSMakeSize(shadowOffset, shadowOffset)) } __NSRectFill(bounds) NSGraphicsContext.current?.imageInterpolation = .default NSGraphicsContext.restoreGraphicsState() imageRep?.draw(in: bounds) image?.unlockFocus() return image } func thumbnailAttachment() -> NSAttributedString? { return self._thumbnailAttachment(size: 0) } func thumbnail512Attachment() -> NSAttributedString? { return self._thumbnailAttachment(size: 512) } func thumbnail256Attachment() -> NSAttributedString? { return self._thumbnailAttachment(size: 256) } func thumbnail128Attachment() -> NSAttributedString? { return self._thumbnailAttachment(size: 128) } func thumbnail64Attachment() -> NSAttributedString? { return self._thumbnailAttachment(size: 64) } func thumbnail32Attachment() -> NSAttributedString? { return self._thumbnailAttachment(size: 32) } // MARK: - Miniaturize / Deminiaturize func miniaturizedRectForDockingRect(_ dockRect: NSRect) -> NSRect { let clipView = self.pdfView.documentView().contentView let sourceRect = clipView.convert(clipView.bounds, to: nil) var targetRect: NSRect = .zero let windowSize = self.window?.frame.size ?? .zero let thumbSize = self.thumbnail?.size ?? .zero var thumbRatio: CGFloat = 0 if thumbSize.width > 0 { thumbRatio = thumbSize.height / thumbSize.width } var dockRatio: CGFloat = 0 if NSWidth(dockRect) > 0 { dockRatio = NSHeight(dockRect) / NSWidth(dockRect) } var scaleFactor: CGFloat = 0 var shadowRadius = round(fmax(thumbSize.width, thumbSize.height) / 32.0) var shadowOffset = ceil(0.75 * shadowRadius) if (thumbRatio > dockRatio) { targetRect = NSInsetRect(dockRect, 0.5 * NSWidth(dockRect) * (1.0 - dockRatio / thumbRatio), 0.0) scaleFactor = NSHeight(targetRect) / thumbSize.height } else { targetRect = NSInsetRect(dockRect, 0.0, 0.5 * NSHeight(dockRect) * (1.0 - thumbRatio / dockRatio)) scaleFactor = NSWidth(targetRect) / thumbSize.width } shadowRadius *= scaleFactor shadowOffset *= scaleFactor targetRect = NSOffsetRect(NSInsetRect(targetRect, shadowRadius, shadowRadius), 0.0, shadowOffset) if thumbRatio > dockRatio { if NSHeight(sourceRect) != 0 { scaleFactor = NSHeight(targetRect) / NSHeight(sourceRect) } } else { if NSWidth(sourceRect) != 0 { scaleFactor = NSWidth(targetRect) / NSWidth(sourceRect) } } return NSMakeRect(NSMinX(targetRect) - scaleFactor * NSMinX(sourceRect), NSMinY(targetRect) - scaleFactor * NSMinY(sourceRect), scaleFactor * windowSize.width, scaleFactor * windowSize.height); } func miniaturizeWindowFromRect(_ startRect: NSRect, toRect endRect: NSRect) { if (self.windowImage == nil) { self.windowImage = (self.window as? KMSnapshotWindow)?.windowImage } let miniaturizeWindow = KMAnimatedBorderlessWindow(contentRect: startRect) miniaturizeWindow.level = .floating miniaturizeWindow.backgroundImage = self.windowImage miniaturizeWindow.orderFront(nil) self.animating = true NSAnimationContext.runAnimationGroup { context in context.duration = RESIZE_TIME_FACTOR * miniaturizeWindow.animationResizeTime(endRect) miniaturizeWindow.animator().setFrame(endRect, display: true) } completionHandler: { if self.hasWindow { if let data = self.window?.responds(to: NSSelectorFromString("setAnimationBehavior:")), data { self.window?.animationBehavior = .none } self.window?.orderFront(nil) self._updateWindowLevel() if let data = self.window?.responds(to: NSSelectorFromString("setAnimationBehavior:")), data { self.window?.animationBehavior = .default } } miniaturizeWindow.orderOut(nil) self.animating = false } } func miniaturize() { if (self.animating) { return } if let dockRect = self.delegate?.snapshotController?(self, miniaturizedRect: true) { let startRect = self.window?.frame ?? .zero let endRect = self.miniaturizedRectForDockingRect(dockRect) self.miniaturizeWindowFromRect(startRect, toRect: endRect) if let data = self.window?.responds(to: NSSelectorFromString("setAnimationBehavior:")), data { self.window?.animationBehavior = .none } } self.window?.orderOut(nil) if let data = self.window?.responds(to: NSSelectorFromString("setAnimationBehavior:")), data { self.window?.animationBehavior = .default } self.hasWindow = false } func deminiaturize() { if (self.animating) { return } if let dockRect = self.delegate?.snapshotController?(self, miniaturizedRect: false) { let endRect = self.window?.frame ?? .zero let startRect = self.miniaturizedRectForDockingRect(dockRect) self.miniaturizeWindowFromRect(startRect, toRect: endRect) } else { self.showWindow(self) } self.hasWindow = true } /* #pragma mark KVO - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == &SKSnaphotWindowDefaultsObservationContext) { NSString *key = [keyPath substringFromIndex:7]; if ([key isEqualToString:SKSnapshotsOnTopKey]) { if ([[self window] isVisible]) [self updateWindowLevel]; } else if ([key isEqualToString:SKShouldAntiAliasKey]) { [pdfView setShouldAntiAlias:[[NSUserDefaults standardUserDefaults] boolForKey:SKShouldAntiAliasKey]]; [pdfView applyDefaultInterpolationQuality]; } else if ([key isEqualToString:SKGreekingThresholdKey]) { [pdfView setGreekingThreshold:[[NSUserDefaults standardUserDefaults] floatForKey:SKGreekingThresholdKey]]; } else if ([key isEqualToString:SKBackgroundColorKey]) { [pdfView setBackgroundColor:[[NSUserDefaults standardUserDefaults] colorForKey:SKBackgroundColorKey]]; } else if ([key isEqualToString:SKPageBackgroundColorKey]) { [pdfView applyDefaultPageBackgroundColor]; } } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } */ func updateString() { var mutableString = "" let clipView = self.pdfView.documentView().contentView let rect = clipView.convert(clipView.visibleRect, to: self.pdfView) for page in self.pdfView.displayedPages() { guard let _page = page as? CPDFPage else { continue } let frame = self.pdfView.convert(rect, to: _page) let sel = _page.selection(for: frame) if let data = sel?.hasCharacters(), data { if mutableString.isEmpty == false { mutableString.append("\n") } mutableString.append(sel?.string() ?? "") } } self.string = mutableString } @objc func goToRect(_ rectValue: NSValue) { self.pdfView.go(to: rectValue.rectValue, on: self.pdfView.currentPage()) self.pdfView.resetHistory() self.updateString() self.window?.makeFirstResponder(self.pdfView) self.handlePageChangedNotification(nil) NotificationCenter.default.addObserver(self, selector: #selector(handlePageChangedNotification), name: NSNotification.Name.CPDFViewPageChanged, object: self.pdfView) NotificationCenter.default.addObserver(self, selector: #selector(handleDocumentDidUnlockNotification), name: NSNotification.Name.CPDFDocumentDidUnlock, object: self.pdfView.document) let clipView = self.pdfView.documentView().contentView NotificationCenter.default.addObserver(self, selector: #selector(handlePDFViewFrameChangedNotification), name: NSView.frameDidChangeNotification, object: clipView) NotificationCenter.default.addObserver(self, selector: #selector(handlePDFViewFrameChangedNotification), name: NSView.boundsDidChangeNotification, object: clipView) NotificationCenter.default.addObserver(self, selector: #selector(handleViewChangedNotification), name: NSNotification.Name(SKSnapshotViewChangedNotification), object: self) NotificationCenter.default.addObserver(self, selector: #selector(handleDidAddRemoveAnnotationNotification), name: NSNotification.Name.CPDFListViewDidAddAnnotation, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleDidAddRemoveAnnotationNotification), name: NSNotification.Name.CPDFListViewDidRemoveAnnotation, object: nil) // NotificationCenter.default.addObserver(self, selector: #selector(handleDidMoveAnnotationNotification), name: SKPDFViewDidMoveAnnotationNotification, object: nil) self.perform(#selector(notifiyDidFinishSetup), with: nil, afterDelay: SMALL_DELAY) if self.hasWindow { self.showWindow(nil) } } func setPdfDocument(_ pdfDocument: CPDFDocument, goToPageNumber pageNum: Int, rect: NSRect, scaleFactor factor: CGFloat, autoFits: Bool) { let window = self.window self.pdfView.document = pdfDocument self.pdfView.scaleFactor = factor self.pdfView.autoScales = false self.pdfView.displaysPageBreaks = false self.pdfView.displayBox = .cropBox self.pdfView.setShouldAntiAlias(UserDefaults.standard.bool(forKey: KMShouldAntiAliasKey)) self.pdfView.setGreekingThreshold(UserDefaults.standard.float(forKey: KMGreekingThresholdKey).cgFloat) self.pdfView.backgroundColor = UserDefaults.standard.color(forKey: KMBackgroundColorKey) // [pdfView applyDefaultPageBackgroundColor]; self.pdfView.applyDefaultInterpolationQuality() // [self setWindowFrameAutosaveNameOrCascade:SKSnapshotWindowFrameAutosaveName]; self.window?.setFrameUsingName("KMSnapshotWindow") self.shouldCascadeWindows = false if let controlView = self.pdfView.scalePopUpButton { var controlFrame: NSRect = .zero var frame: NSRect = .zero NSDivideRect(self.pdfView.frame, &controlFrame, &frame, NSHeight(controlView.frame), .minY) controlFrame.size.width = NSWidth(controlView.frame) controlView.frame = controlFrame controlView.autoresizingMask = [.maxXMargin, .maxYMargin] window?.contentView?.addSubview(controlView) self.pdfView.frame = frame window?.backgroundColor = NSColor(calibratedWhite: 0.97, alpha: 1) let page = pdfDocument.page(at: UInt(pageNum)) frame = self.pdfView.convert(rect, from: page) var view: NSView? frame = self.pdfView.convert(frame, to: view) frame.size.height += NSHeight(controlFrame) // frame = [NSWindow frameRectForContentRect:frame styleMask:[window styleMask] & ~NSWindowStyleMaskFullSizeContentView]; var styleMask = window?.styleMask styleMask?.remove(.fullSizeContentView) frame = NSWindow.frameRect(forContentRect: frame, styleMask: styleMask!) frame.origin.x = NSMinX(window!.frame) frame.origin.y = NSMaxY(window!.frame) - NSHeight(frame) self.window?.setFrame(frame, display: false, animate: false) } self.pdfView.go(toPageIndex: pageNum, animated: false) if (autoFits) { self.pdfView.autoFits = autoFits } self.pdfView.autoScales = true // Delayed to allow PDFView to finish its bookkeeping // fixes bug of apparently ignoring the point but getting the page right. self.perform(#selector(goToRect), with: NSValue(rect: rect), afterDelay: SMALL_DELAY) } // MARK: - Noti Actions @objc func preferenceInfoDidChange(sender: Notification) { let info : [AnyHashable : Any] = sender.userInfo ?? [:] if info.keys.contains(KMPreferenceManager.keepSnapshotWindowToTopKey) { let data = KMPreferenceManager.shared.keepSnapshotWindowToTop if let data = self.window?.isVisible, data { self._updateWindowLevel() } } } } // MARK: - Private Methods extension KMSnapshotWindowController { private func _updateWindowLevel() { let onTop = self.forceOnTop || UserDefaults.standard.bool(forKey: KMSnapshotsOnTopKey) self.window?.level = onTop ? .floating : .normal self.window?.hidesOnDeactivate = onTop } private func _thumbnailAttachment(size: CGFloat) -> NSAttributedString? { guard let imageData = self.thumbnailWithSize(size)?.tiffRepresentation else { return nil } let wrapper = FileWrapper(regularFileWithContents: imageData) let filename = String(format: "snapshot_page_%lu.tiff", self.pageIndex() + 1) wrapper.filename = filename wrapper.preferredFilename = filename let attachment = NSTextAttachment(fileWrapper: wrapper) return NSAttributedString(attachment: attachment) } } extension KMSnapshotWindowController: NSWindowDelegate { func windowWillClose(_ notification: Notification) { // @try { [[NSUserDefaultsController sharedUserDefaultsController] removeObserver:self forKeys:[NSArray arrayWithObjects:SKSnapshotsOnTopKey, SKShouldAntiAliasKey, SKGreekingThresholdKey, SKBackgroundColorKey, SKPageBackgroundColorKey, nil]]; } // @catch (id e) {} self.delegate?.snapshotControllerWillClose?(self) self.delegate = nil // Yosemite and El Capitan have a retain cycle when we leave the PDFView with a document // if (RUNNING_AFTER(10_9) && RUNNING_BEFORE(10_12)) self.pdfView.document = nil } func windowDidMiniaturize(_ notification: Notification) { self.window?.orderOut(nil) self.hasWindow = false } func windowDidDeminiaturize(_ notification: Notification) { self._updateWindowLevel() self.hasWindow = true } } extension KMSnapshotWindowController: NSMenuItemValidation { func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { let action = menuItem.action if (action == #selector(doGoToNextPage)) { return self.pdfView.canGoToNextPage() } else if (action == #selector(doGoToPreviousPage)) { return self.pdfView.canGoToPreviousPage() } else if (action == #selector(doGoToFirstPage)) { return self.pdfView.canGoToFirstPage() } else if (action == #selector(doGoToLastPage)) { return self.pdfView.canGoToLastPage() } else if (action == #selector(doGoBack)) { return self.pdfView.km_canGoBack() } else if (action == #selector(doGoForward)) { return self.pdfView.km_canGoForward() } else if (action == #selector(doZoomIn)) { return self.pdfView.canZoomIn } else if (action == #selector(doZoomOut)) { return self.pdfView.canZoomOut } else if (action == #selector(doZoomToActualSize)) { return abs(pdfView.scaleFactor - 1.0 ) > 0.01 } // else if (action == #selector(doZoomToPhysicalSize)) { // return fabs(pdfView.physicalScaleFactor - 1.0 ) > 0.01 // } else if (action == #selector(toggleAutoScale)) { menuItem.state = self.pdfView.autoFits ? .on : .off return true } return true } }