// // KMInfoWindowController.swift // PDF Reader Pro // // Created by tangchao on 2023/10/10. // import Cocoa /* #define SKInfoWindowFrameAutosaveName @"SKInfoWindow" */ private let LABEL_COLUMN_ID = "label" private let VALUE_COLUMN_ID = "value" private let KMInfoVersionKey = "Version" private let KMInfoPageCountKey = "PageCount" private let KMInfoPageSizeKey = "PageSize" private let KMInfoPageWidthKey = "PageWidth" private let KMInfoPageHeightKey = "PageHeight" private let KMInfoKeywordsStringKey = "Keywords" private let KMInfoEncryptedKey = "Encrypted" private let KMInfoAllowsPrintingKey = "AllowsPrinting" private let KMInfoAllowsCopyingKey = "AllowsCopying" private let KMInfoAllowsDocumentAssemblyKey = "AllowsDocumentAssembly" private let KMInfoAllowsContentAccessibilityKey = "AllowsContentAccessibility" private let KMInfoAllowsCommentingKey = "AllowsCommenting" private let KMInfoAllowsFormFieldEntryKey = "AllowsFormFieldEntry" private let KMInfoFileNameKey = "FileName" private let KMInfoFileSizeKey = "FileSize" private let KMInfoPhysicalSizeKey = "PhysicalSize" private let KMInfoLogicalSizeKey = "LogicalSize" private let KMInfoTagsKey = "Tags" private let KMInfoRatingKey = "Rating" private let kKMWindowDidBecomeMainNotificationName = Notification.Name(rawValue: "KMWindowDidBecomeMainNotificationName") typealias KMInfoWindowC = KMInfoWindowController class KMInfoWindowController: NSWindowController { public static let windowDidBecomeMainNotification = kKMWindowDidBecomeMainNotificationName @IBOutlet weak var summaryTableView: NSTableView! @IBOutlet weak var attributesTableView: NSTableView! @IBOutlet weak var tabView: NSTabView! var info: NSDictionary? var editDictionary: NSMutableDictionary = NSMutableDictionary() private var _summaryKeys: [String] = [] var summaryKeys: [String] { get { return self._summaryKeys } } private var _attributesKeys: [String] = [] var attributesKeys: [String] { get { return self._attributesKeys } } private var _labels: [String : String] = [:] var labels: [String : String] { get { return self._labels } } private weak var _currentDocument: NSDocument? deinit { NotificationCenter.default.removeObserver(self) Swift.debugPrint("KMInfoWindowController 已释放") } static let shared = KMInfoWindowController() convenience init() { self.init(windowNibName: "InfoWindow") self.info = nil; self._summaryKeys = [KMInfoFileNameKey, KMInfoFileSizeKey, KMInfoPageSizeKey, KMInfoPageCountKey, KMInfoVersionKey, "", KMInfoEncryptedKey, KMInfoAllowsPrintingKey, KMInfoAllowsCopyingKey, KMInfoAllowsDocumentAssemblyKey, KMInfoAllowsContentAccessibilityKey, KMInfoAllowsCommentingKey, KMInfoAllowsFormFieldEntryKey] self._attributesKeys = [PDFDocumentAttribute.titleAttribute.rawValue, PDFDocumentAttribute.authorAttribute.rawValue, PDFDocumentAttribute.subjectAttribute.rawValue, PDFDocumentAttribute.creatorAttribute.rawValue, PDFDocumentAttribute.producerAttribute.rawValue, PDFDocumentAttribute.creationDateAttribute.rawValue, PDFDocumentAttribute.modificationDateAttribute.rawValue, KMInfoKeywordsStringKey] self._labels = [KMInfoFileNameKey : KMLocalizedString("File name:"), KMInfoFileSizeKey : KMLocalizedString("File size:"), KMInfoPageSizeKey : KMLocalizedString("Page size:"), KMInfoPageCountKey : KMLocalizedString("Page count:"), KMInfoVersionKey : KMLocalizedString("PDF Version:"), KMInfoEncryptedKey : KMLocalizedString("Encrypted:"), KMInfoAllowsPrintingKey : KMLocalizedString("Printing:"), KMInfoAllowsCopyingKey : KMLocalizedString("Content Copying:"), KMInfoAllowsDocumentAssemblyKey : KMLocalizedString("Document Assembly:"), KMInfoAllowsContentAccessibilityKey : KMLocalizedString("Content Copying for Accessibility:"), KMInfoAllowsCommentingKey : KMLocalizedString("Commenting:"), KMInfoAllowsFormFieldEntryKey : KMLocalizedString("Filling of form fields:"), PDFDocumentAttribute.titleAttribute.rawValue : KMLocalizedString("Title:"), PDFDocumentAttribute.authorAttribute.rawValue : KMLocalizedString("Author:"), PDFDocumentAttribute.subjectAttribute.rawValue : KMLocalizedString("Subject:"), PDFDocumentAttribute.creatorAttribute.rawValue : KMLocalizedString("Content Creator:"), PDFDocumentAttribute.producerAttribute.rawValue : KMLocalizedString("PDF Producer:"), PDFDocumentAttribute.creationDateAttribute.rawValue : KMLocalizedString("Creation date:"), PDFDocumentAttribute.modificationDateAttribute.rawValue : KMLocalizedString("Modification date:"), KMInfoKeywordsStringKey : KMLocalizedString("Keywords:")] } override func loadWindow() { super.loadWindow() } override func windowDidLoad() { super.windowDidLoad() self.window?.title = NSLocalizedString("Document Info", comment: "") self.updateForDocument(NSApp.mainWindow?.windowController?.document as? NSDocument) self.summaryTableView.selectionHighlightStyle = .none self.attributesTableView.selectionHighlightStyle = .none for item in self.tabView.tabViewItems { if item.isEqual(to: self.tabView.tabViewItems.first) { item.label = NSLocalizedString("Summary", comment: "") } else { item.label = NSLocalizedString("Attributes", comment: "") } } self.tabView.selectTabViewItem(at: 1) NotificationCenter.default.addObserver(self, selector: #selector(_handleViewFrameDidChangeNotification), name: NSView.frameDidChangeNotification, object: self.attributesTableView.enclosingScrollView) NotificationCenter.default.addObserver(self, selector: #selector(_handleWindowDidBecomeMainNotification), name: NSWindow.didBecomeMainNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_handleWindowDidResignMainNotification), name: NSWindow.didResignMainNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_handlePDFDocumentInfoDidChangeNotification), name: NSNotification.Name.PDFDocumentDidUnlock, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_handleWindowWillCloseNotification), name: NSWindow.willCloseNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(_km_handleWindowDidBecomeMainNotification), name: KMInfoWindowC.windowDidBecomeMainNotification, object: nil) } func updateForDocument(_ doc: NSDocument?) { self._currentDocument = doc let info = self._infoForDocument(doc) var dic = NSMutableDictionary(dictionary: info) for key in self.editDictionary.allKeys { dic.setObject(self.editDictionary[key], forKey: key as! NSCopying) } self.info = dic self.summaryTableView.reloadData() self.attributesTableView.reloadData() } public func info(for document: NSDocument) -> NSDictionary { return self._infoForDocument(document) } // MARK: - Private Methods private func _infoForDocument(_ doc: NSDocument?) -> NSDictionary { var dictionary: NSMutableDictionary = NSMutableDictionary() var logicalSize: Int64 = 0 var physicalSize: Int64 = 0 if let data = doc, data.isKind(of: KMMainDocument.self) { if let pdfDoc = (data as! KMMainDocument).mainViewController?.document { dictionary.addEntries(from: pdfDoc.documentAttributes()) dictionary.setValue(String(format: "%ld.%ld", pdfDoc.majorVersion, pdfDoc.minorVersion), forKey: KMInfoVersionKey) dictionary.setValue(NSNumber(value: pdfDoc.pageCount), forKey: KMInfoPageCountKey) if (pdfDoc.pageCount > 0) { let page: CPDFPage = pdfDoc.page(at:0)! let cropSize = page.bounds(for: .cropBox).size let mediaSize = page.bounds(for: .mediaBox).size dictionary.setValue(KMSizeString(cropSize, mediaSize), forKey: KMInfoPageSizeKey) dictionary.setValue(NSNumber(value: cropSize.width), forKey: KMInfoPageWidthKey) dictionary.setValue(NSNumber(value: cropSize.height), forKey: KMInfoPageHeightKey) } let fileSize = pdfDoc.documentURL.path.getFileSize() if fileSize > 0 { dictionary.setValue(NSNumber(value: fileSize), forKey: KMInfoFileSizeKey) } if let keyworks = dictionary.value(forKey: CPDFDocumentAttribute.keywordsAttribute.rawValue) { if (keyworks as AnyObject).isKind(of: NSArray.self) { dictionary.setValue(keyworks, forKey: KMInfoKeywordsStringKey) } } dictionary.setValue(NSNumber(booleanLiteral: pdfDoc.isEncrypted), forKey: KMInfoEncryptedKey) dictionary.setValue(NSNumber(booleanLiteral: pdfDoc.allowsPrinting), forKey: KMInfoAllowsPrintingKey) dictionary.setValue(NSNumber(booleanLiteral: pdfDoc.allowsCopying), forKey: KMInfoAllowsCopyingKey) dictionary.setValue(NSNumber(booleanLiteral: pdfDoc.allowsCommenting), forKey: KMInfoAllowsCommentingKey) dictionary.setValue(NSNumber(booleanLiteral: pdfDoc.allowsFormFieldEntry), forKey: KMInfoAllowsDocumentAssemblyKey) dictionary.setValue(NSNumber(booleanLiteral: pdfDoc.allowsFormFieldEntry), forKey: KMInfoAllowsFormFieldEntryKey) } } dictionary.setValue(doc?.fileURL?.path.lastPathComponent, forKey: KMInfoFileNameKey) dictionary.setValue(NSNumber(value: physicalSize), forKey: KMInfoPhysicalSizeKey) dictionary.setValue(NSNumber(value: logicalSize), forKey: KMInfoLogicalSizeKey) return dictionary } @objc private func _handleViewFrameDidChangeNotification(_ sender: Notification) { self.attributesTableView.noteHeightOfRows(withIndexesChanged: IndexSet(integer: self.attributesKeys.count-1)) } @objc private func _handleWindowDidBecomeMainNotification(_ sender: Notification) { self.editDictionary.removeAllObjects() self.updateForDocument((sender.object as? NSWindow)?.windowController?.document as? NSDocument) } @objc private func _handleWindowDidResignMainNotification(_ sender: Notification) { self.editDictionary.removeAllObjects() self.updateForDocument(nil) } @objc private func _handlePDFDocumentInfoDidChangeNotification(_ sender: Notification) { guard let doc = NSApp.mainWindow?.windowController?.document else { return } if (doc.isKind(of: KMMainDocument.self)) { guard let pdfDocument = (doc as! KMMainDocument).mainViewController?.document else { return } if (pdfDocument.isEqual(to: sender.object)) { self.editDictionary.removeAllObjects() self.updateForDocument((doc as! NSDocument)) } } } @objc private func _handleWindowWillCloseNotification(_ sender: Notification) { if let object = sender.object as? AnyObject, object.isEqual(self.window) { if (self.editDictionary.count > 0) { if let windowController = NSApp.mainWindow?.windowController, windowController.isKind(of: KMBrowserWindowController.self) { let document = (windowController as! KMBrowserWindowController).document as? KMMainDocument var dic: NSMutableDictionary = NSMutableDictionary(dictionary: document?.mainViewController?.document?.documentAttributes() ?? [:]) var isSave = false for key in self.editDictionary.allKeys { if let data = key as? NSString, data.isEqual(to: KMInfoKeywordsStringKey) { let string = self.editDictionary[key] if let data = string as? NSString, data.isEqual(to: dic[PDFDocumentAttribute.keywordsAttribute.rawValue]) { isSave = true dic.setObject(string as Any, forKey: CPDFDocumentAttribute.keywordsAttribute.rawValue as NSCopying) } } else { if let data = dic[key] as? NSString, data.isEqual(to: self.editDictionary[key]) == false { isSave = true dic.setObject(self.editDictionary[key] as Any, forKey: key as! NSCopying) } } } if (isSave) { document?.mainViewController?.document?.setDocumentAttributes(dic as? [CPDFDocumentAttribute : Any]) document?.save(nil) } } } self.editDictionary.removeAllObjects() } } @objc private func _km_handleWindowDidBecomeMainNotification(_ sender: Notification) { self.editDictionary.removeAllObjects() self.updateForDocument(sender.object as? NSDocument) } override func showWindow(_ sender: Any?) { super.showWindow(sender) self.editDictionary.removeAllObjects() self.attributesTableView.reloadData() } } extension KMInfoWindowController: NSTableViewDelegate, NSTableViewDataSource { func numberOfRows(in tableView: NSTableView) -> Int { if (tableView.isEqual(to: self.summaryTableView)) { return self.summaryKeys.count } else if (tableView.isEqual(to: self.attributesTableView)) { return self.attributesKeys.count } return 0 } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { var shortDateFormatter = DateFormatter() shortDateFormatter.dateStyle = .short shortDateFormatter.timeStyle = .none var keys: [String]? if (tableView.isEqual(to: self.summaryTableView)) { keys = self.summaryKeys } else if (tableView.isEqual(to: self.attributesTableView)) { keys = self.attributesKeys } let key = keys![row] let tcID = tableColumn?.identifier.rawValue var value: Any? if (key.isEmpty) { value = "" } else { if let data = tcID, data == LABEL_COLUMN_ID { value = self.labels[key] != nil ? self.labels[key] : key.appending(":") } else if let data = tcID, data == VALUE_COLUMN_ID { value = self.info?.object(forKey: key) if (value == nil) { value = "-" } else if let data = value as? NSDate { value = shortDateFormatter.string(from: data as Date) } else if let data = value as? NSNumber { if (key == KMInfoEncryptedKey) { value = data.boolValue ? KMLocalizedString("Yes") : KMLocalizedString("No") } else if (key == KMInfoFileSizeKey) { value = String.formatFileSize(data.int64Value) } else { value = key == KMInfoPageCountKey ? data.stringValue : (data.boolValue ? KMLocalizedString("Allowed") : KMLocalizedString("Not Allowed")) } } } } let cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: tcID ?? ""), owner: self) as? NSTableCellView cellView?.textField?.stringValue = (value as? String) ?? "" if (tableView.isEqual(to: self.attributesTableView) && tableColumn?.identifier.rawValue == VALUE_COLUMN_ID) { if (row == 0 || row == 1 || row == 2 || row == 3 || row == 7) { cellView?.textField?.bezelStyle = .squareBezel cellView?.textField?.isEditable = true cellView?.textField?.isSelectable = true cellView?.textField?.isBordered = true cellView?.textField?.isBezeled = true cellView?.textField?.drawsBackground = true cellView?.textField?.delegate = self if (row == 1) { let cell = tableView.view(atColumn: 1, row: 0, makeIfNecessary: true) as? NSTableCellView cell?.textField?.nextKeyView = cellView!.textField } else if (row == 2) { let cell = tableView.view(atColumn: 1, row: 1, makeIfNecessary: true) as? NSTableCellView cell?.textField?.nextKeyView = cellView!.textField } else if (row == 3) { let cell = tableView.view(atColumn: 1, row: 2, makeIfNecessary: true) as? NSTableCellView cell?.textField?.nextKeyView = cellView!.textField } else if (row == 7) { let cell = tableView.view(atColumn: 1, row: 3, makeIfNecessary: true) as? NSTableCellView cell?.textField?.nextKeyView = cellView!.textField } } else { cellView?.textField?.isEditable = false cellView?.textField?.isSelectable = false cellView?.textField?.isBordered = false cellView?.textField?.isBezeled = false cellView?.textField?.drawsBackground = false cellView?.textField?.delegate = nil; } cellView?.textField?.tag = row } return cellView } func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { var rowHeight = tableView.rowHeight if (tableView.isEqual(to: self.attributesTableView) && row == tableView.numberOfRows-1) { rowHeight = fmax(rowHeight, NSHeight(tableView.enclosingScrollView?.bounds ?? NSZeroRect) - tableView.numberOfRows.cgFloat * (rowHeight + tableView.intercellSpacing.height) + rowHeight) } return rowHeight } } extension KMInfoWindowController: NSTextFieldDelegate { func controlTextDidChange(_ obj: Notification) { if let textField = obj.object as? NSTextField { if (textField.tag == 0) { self.editDictionary.setObject(textField.stringValue, forKey: PDFDocumentAttribute.titleAttribute.rawValue as NSCopying) } else if (textField.tag == 1) { self.editDictionary.setObject(textField.stringValue, forKey: PDFDocumentAttribute.authorAttribute.rawValue as NSCopying) } else if (textField.tag == 2) { self.editDictionary.setObject(textField.stringValue, forKey: PDFDocumentAttribute.subjectAttribute.rawValue as NSCopying) } else if (textField.tag == 3) { self.editDictionary.setObject(textField.stringValue, forKey: PDFDocumentAttribute.creatorAttribute.rawValue as NSCopying) } else if (textField.tag == 7) { self.editDictionary.setObject(textField.stringValue, forKey: KMInfoKeywordsStringKey as NSCopying) } } } } private let CM_PER_POINT = 0.035277778 private let INCH_PER_POINT = 0.013888889 private func KMSizeString(_ size: NSSize, _ altSize: NSSize) -> String { var useMetric = false if let data = ((NSLocale.current as NSLocale).object(forKey: .usesMetricSystem) as? NSNumber) { useMetric = data.boolValue } let factor = useMetric ? CM_PER_POINT : INCH_PER_POINT let units = useMetric ? KMLocalizedString("cm") : KMLocalizedString("in") if (NSEqualSizes(size, altSize)) { return String(format: "%.2f x %.2f %@", size.width * factor, size.height * factor, units) } else { return String(format: "%.2f x %.2f %@ (%.2f x %.2f %@)", size.width * factor, size.height * factor, units, altSize.width * factor, altSize.height * factor, units) } }