// // KMBookmark.swift // PDF Reader Pro // // Created by lizhe on 2024/2/5. // import Cocoa let kType = "type" let kChildren = "children" let kLabel = "label" let kPageIndex = "pageIndex" let kBDAlias = "_BDAlias" enum KMBookmarkType: String { case bookmark = "bookmark" case folder = "folder" case session = "session" case setup = "setup" case separator = "separator" case file = "file" } class KMBookmark: NSObject { var properties: [String: Any]? { get { var data: [String: Any] = documentSetup var bdAlias: SKAlias? if self.fileURL != nil { bdAlias = SKAlias.init(url: self.fileURL) } let tempSetup = [kType: bookmarkType.rawValue, kBDAlias: self.aliasData ?? (bdAlias != nil ? bdAlias?.data: NSData()), kPageIndex: NSNumber(integerLiteral: Int(pageIndex)), kLabel: self.label ] as [String : Any] data.merge(tempSetup) { (_, new) in new } return data } set { } } var bookmarkType: KMBookmarkType = .bookmark var label: String = "" var alternateIcon: NSImage = NSImage() var fileURL: URL? var pageIndex: UInt = 0 var pageNumber: NSNumber = 0 var documentSetup: [String: Any] = [:] { didSet { if documentSetup[kType] != nil { self.bookmarkType = KMBookmarkType(rawValue: documentSetup[kType] as! String ) ?? .bookmark } let string: String = documentSetup[kPageIndex] as? String ?? "0" let intValue: UInt = UInt(string) ?? 0 self.aliasData = documentSetup[kBDAlias] as? NSData self.pageIndex = intValue if documentSetup[kLabel] != nil { self.label = documentSetup[kLabel] as! String } } } var parent: KMBookmark? var children: [KMBookmark] = [] //子 var aliasData: NSData? var icon: NSImage { get { if let fileURL = self.fileURL { return NSWorkspace.shared.icon(forFile: fileURL.path) } else { return KMFileBookmark.missingFileImage() } } set { } } static func bookmark(url: URL, pageIndex: UInt, label: String) -> KMBookmark { let bookmark = KMBookmark() bookmark.fileURL = url bookmark.pageIndex = pageIndex bookmark.label = label return bookmark } static func bookmarks(urls: [URL]) -> [KMBookmark] { var bookmarks: [KMBookmark] = [] for url in urls { let bookmark = KMBookmark() bookmark.fileURL = url bookmarks.append(bookmark) } return bookmarks } func initWithProperties(properties: NSDictionary) -> KMBookmark { return KMBookmark() } // - (id)initWithProperties:(NSDictionary *)dictionary { // NSString *type = [dictionary objectForKey:TYPE_KEY]; // if ([type isEqualToString:SEPARATOR_STRING]) { // return (id)[[SKSeparatorBookmark alloc] init]; // } else if ([type isEqualToString:FOLDER_STRING] || [type isEqualToString:SESSION_STRING]) { // Class bookmarkClass = [type isEqualToString:FOLDER_STRING] ? [SKFolderBookmark class] : [SKSessionBookmark class]; // NSMutableArray *newChildren = [NSMutableArray array]; // SKBookmark *child; // for (NSDictionary *dict in [dictionary objectForKey:CHILDREN_KEY]) { // if ((child = [[SKBookmark alloc] initWithProperties:dict])) { // [newChildren addObject:child]; // [child release]; // } else // NSLog(@"Failed to read child bookmark: %@", dict); // } // return (id)[[bookmarkClass alloc] initFolderWithChildren:newChildren label:[dictionary objectForKey:LABEL_KEY]]; // } else if ([dictionary objectForKey:@"windowFrame"]) { // return (id)[[SKFileBookmark alloc] initWithSetup:dictionary label:[dictionary objectForKey:LABEL_KEY]]; // } else { // NSNumber *pageIndex = [dictionary objectForKey:PAGEINDEX_KEY]; // return (id)[[SKFileBookmark alloc] initWithAliasData:[dictionary objectForKey:ALIASDATA_KEY] pageIndex:(pageIndex ? [pageIndex unsignedIntegerValue] : NSNotFound) label:[dictionary objectForKey:LABEL_KEY]]; // } // } static func bookmark(properties: [String: Any]) -> KMBookmark? { guard let type: KMBookmarkType = KMBookmarkType(rawValue: properties[kType] as! String) else { return nil} var bookmark: KMBookmark = KMBookmark() if type == .separator { bookmark = KMSeparatorBookmark() } else if type == .folder || type == .session { let bookmarkClass: AnyClass = (type == .folder) ? KMFolderBookmark.self : KMSessionBookmark.self guard let childrenProperties = properties[kChildren] as? [[String: Any]] else { return nil } var newChildren = [KMBookmark]() for dict: [String: Any] in childrenProperties { if let child = KMBookmark.bookmark(properties: dict) { newChildren.append(child) } else { NSLog("Failed to read child bookmark: \(dict)") } } guard let label = properties[kLabel] as? String else { return nil } bookmark = KMFolderBookmark.bookmark(children: newChildren, label: label) } else if properties["windowFrame"] != nil { guard let label = properties[kLabel] as? String else { return nil } bookmark = KMFileBookmark.bookmark(setup: properties, label: label) bookmark.label = label } else { let pageIndex = properties[kPageIndex] as? UInt ?? UInt(NSNotFound) let label = properties[kLabel] as? String let aliasData = properties[kBDAlias] as? Data if aliasData != nil { bookmark = KMFileBookmark.bookmark(aliasData: aliasData! as NSData, pageIndex: Int(pageIndex), label: label!) } } return bookmark } static func bookmark(children: [KMBookmark], label: String) -> KMBookmark { let bookmark = KMFolderBookmark() return bookmark } static func bookmark(url: [KMBookmark], label: String) -> KMBookmark { let bookmark = KMFolderBookmark() return bookmark } static func bookmark(aliasData: NSData, pageIndex: Int, label: String) -> KMBookmark { let bookmark = KMFileBookmark() let alias = SKAlias.init(data: aliasData as Data) let url = alias?.fileURL bookmark.fileURL = url bookmark.aliasData = aliasData return bookmark } static func bookmark(setup: [String: Any], label: String) -> KMBookmark { let pageIndex = setup[kPageIndex] as? UInt ?? UInt(NSNotFound) let aliasData = setup[kBDAlias] as? Data let bookmark = KMFileBookmark.bookmark(aliasData: aliasData! as NSData, pageIndex: Int(pageIndex), label: label) return bookmark } // func resolveAliasData(aliasData: Data) -> URL? { // var resolvedURL: URL? // do { // // 尝试创建NSURL实例 // let aliasURL = URL(dataRepresentation: aliasData, relativeTo: nil) // // // 解析别名并获取原始路径 // try aliasURL?.resolveAliasFileURL() // // // 将NSURL转换为URL // if let resolvedFileURL = aliasURL as URL? { // resolvedURL = resolvedFileURL // } // } catch { // print("Error resolving alias data:", error) // } // return resolvedURL // } // - (id)initWithProperties:(NSDictionary *)dictionary { // NSString *type = [dictionary objectForKey:TYPE_KEY]; // if ([type isEqualToString:SEPARATOR_STRING]) { // return (id)[[SKSeparatorBookmark alloc] init]; // } else if ([type isEqualToString:FOLDER_STRING] || [type isEqualToString:SESSION_STRING]) { // Class bookmarkClass = [type isEqualToString:FOLDER_STRING] ? [SKFolderBookmark class] : [SKSessionBookmark class]; // NSMutableArray *newChildren = [NSMutableArray array]; // SKBookmark *child; // for (NSDictionary *dict in [dictionary objectForKey:CHILDREN_KEY]) { // if ((child = [[SKBookmark alloc] initWithProperties:dict])) { // [newChildren addObject:child]; // [child release]; // } else // NSLog(@"Failed to read child bookmark: %@", dict); // } // return (id)[[bookmarkClass alloc] initFolderWithChildren:newChildren label:[dictionary objectForKey:LABEL_KEY]]; // } else if ([dictionary objectForKey:@"windowFrame"]) { // return (id)[[SKFileBookmark alloc] initWithSetup:dictionary label:[dictionary objectForKey:LABEL_KEY]]; // } else { // NSNumber *pageIndex = [dictionary objectForKey:PAGEINDEX_KEY]; // return (id)[[SKFileBookmark alloc] initWithAliasData:[dictionary objectForKey:ALIASDATA_KEY] pageIndex:(pageIndex ? [pageIndex unsignedIntegerValue] : NSNotFound) label:[dictionary objectForKey:LABEL_KEY]]; // } // } func insert(child: KMBookmark, atIndex: Int) { children.insert(child, at: Int(atIndex)) child.parent = self } func isDescendant(of bookmark: KMBookmark?) -> Bool { if bookmark == nil { return false } if self == bookmark { return true } for child in bookmark!.children { if self.isDescendant(of: child) { return true } } return false } func isDescendant(ofArray bookmarks: [KMBookmark]) -> Bool { for bm in bookmarks { if self.isDescendant(of: bm) { return true } } return false } func removeObjectFromChildren(index: Int) { children[index].parent = nil children.remove(at: index) } func open() { var tempFileURL = fileURL if tempFileURL == nil { if documentSetup[kBDAlias] != nil { tempFileURL = SKAlias.init(data: documentSetup[kBDAlias] as? Data).fileURL } if tempFileURL == nil && documentSetup[KMDocumentSetupFileNameKey] != nil { tempFileURL = NSURL.fileURL(withPath: documentSetup[KMDocumentSetupFileNameKey] as? String ?? "") } } guard let tempFileURL = tempFileURL else { return } NSDocumentController.shared.openDocument(withContentsOf: tempFileURL, display: true, completionHandler: { document, result, error in if (error != nil) { NSApp.presentError(error!) } else { if self.pageIndex > 1 { (document as! KMMainDocument).mainViewController?.setPageNumber = self.pageIndex + 1 } } }) } func objectOfChidren(index: Int) -> KMBookmark{ return children[index] } } //MARK: Folder class KMFolderBookmark: KMBookmark { static func folderBookmark(label: String) -> KMFolderBookmark { let bookmark = KMFolderBookmark() bookmark.label = label return bookmark } override var bookmarkType: KMBookmarkType { get { return .folder } set { } } override func initWithProperties(properties: NSDictionary) -> KMBookmark { return KMFolderBookmark() } override var icon: NSImage { get { return NSImage(named: NSImage.folderName)! } set { } } override var alternateIcon: NSImage { get { return NSImage(named: NSImage.multipleDocumentsName)! } set { } } override func open() { var count = children.count while (count != 0) { count = count - 1 children[count].open() } } } class KMRootBookmark: KMFolderBookmark { static func bookmarkRoot(childrenProperties: NSArray) -> KMRootBookmark { let rootBookmark = KMRootBookmark() var childs: [KMBookmark] = [] for setup in childrenProperties { let bookmark = KMBookmark.bookmark(properties: setup as! [String : Any]) bookmark?.documentSetup = setup as! [String : Any] bookmark?.parent = rootBookmark if bookmark != nil { childs.append(bookmark!) } } rootBookmark.children = childs rootBookmark.label = NSLocalizedString("Bookmarks Menu", comment: "") return rootBookmark } override var icon: NSImage { get { var iconImage = NSImage() let iconSize = NSSize(width: 16.0, height: 16.0) let drawingHandler: (NSRect) -> Bool = { rect in NSColor(calibratedWhite: 0.0, alpha: 0.2).set() NSBezierPath(rect: NSRect(x: 1.0, y: 1.0, width: 14.0, height: 13.0)).fill() NSGraphicsContext.saveGraphicsState() let path = NSBezierPath() path.move(to: NSPoint(x: 2.0, y: 2.0)) path.line(to: NSPoint(x: 2.0, y: 15.0)) path.line(to: NSPoint(x: 7.0, y: 15.0)) path.line(to: NSPoint(x: 7.0, y: 13.0)) path.line(to: NSPoint(x: 14.0, y: 13.0)) path.line(to: NSPoint(x: 14.0, y: 2.0)) path.close() NSColor.white.set() NSShadow.setShadowWith(NSColor(calibratedWhite: 0.0, alpha: 0.33333), blurRadius: 2.0, yOffset: -1.0) path.fill() NSGraphicsContext.restoreGraphicsState() NSColor(calibratedRed: 0.162, green: 0.304, blue: 0.755, alpha: 1.0).set() NSRect(x: 2.0, y: 13.0, width: 5.0, height: 2.0).fill() NSColor(calibratedRed: 0.894, green: 0.396, blue: 0.202, alpha: 1.0).set() NSRect(x: 3.0, y: 4.0, width: 1.0, height: 1.0).fill() NSRect(x: 3.0, y: 7.0, width: 1.0, height: 1.0).fill() NSRect(x: 3.0, y: 10.0, width: 1.0, height: 1.0).fill() NSColor(calibratedWhite: 0.6, alpha: 1.0).set() NSRect(x: 5.0, y: 4.0, width: 1.0, height: 1.0).fill() NSRect(x: 5.0, y: 7.0, width: 1.0, height: 1.0).fill() NSRect(x: 5.0, y: 10.0, width: 1.0, height: 1.0).fill() for i in 0..<7 { for j in 0..<3 { NSColor(calibratedWhite: 0.45 + 0.1 * CGFloat(Float(arc4random()) / Float(UInt32.max)), alpha: 1.0).set() NSRect(x: 6.0 + CGFloat(i), y: 4.0 + CGFloat(3 * j), width: 1.0, height: 1.0).fill() } } let gradient = NSGradient(starting: NSColor(calibratedWhite: 0.0, alpha: 0.1), ending: NSColor(calibratedWhite: 0.0, alpha: 0.0)) gradient?.draw(in: NSRect(x: 2.0, y: 2.0, width: 12.0, height: 11.0), angle: 90.0) return true } iconImage = NSImage(size: iconSize, flipped: false, drawingHandler: drawingHandler) return iconImage } set { } } } //MARK: Session class KMSessionBookmark: KMFolderBookmark { override var bookmarkType: KMBookmarkType { get { return .session } set { } } static func bookmarkSession(setups: NSArray, label: String) -> KMSessionBookmark { let bookmark = KMSessionBookmark() var childs: [KMBookmark] = [] for setup in setups { let bookmark = KMBookmark() bookmark.documentSetup = setup as! [String : Any] childs.append(bookmark) } bookmark.children = childs bookmark.label = label return bookmark } override func initWithProperties(properties: NSDictionary) -> KMBookmark { return KMSessionBookmark() } override var icon: NSImage { get { return NSImage(named: NSImage.multipleDocumentsName)! } set { } } } //MARK: Separator class KMSeparatorBookmark: KMBookmark { override var bookmarkType: KMBookmarkType { get { return .separator } set { } } override func initWithProperties(properties: NSDictionary) -> KMBookmark { return KMSeparatorBookmark() } } //MARK: File class KMFileBookmark: KMBookmark { override var bookmarkType: KMBookmarkType { get { return .file } set { } } static func missingFileImage() -> NSImage { let image: NSImage = { let size = NSSize(width: 16.0, height: 16.0) return NSImage(size: size, flipped: false, drawingHandler: { rect in let genericDocImage = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kGenericDocumentIcon))) let questionMark = NSWorkspace.shared.icon(forFileType: NSFileTypeForHFSTypeCode(OSType(kQuestionMarkIcon))) genericDocImage.draw(in: rect, from: .zero, operation: .copy, fraction: 0.7) questionMark.draw(in: NSRect(x: 3.0, y: 2.0, width: 10.0, height: 10.0), from: .zero, operation: .sourceOver, fraction: 0.7) return true }) }() return image } override var icon: NSImage { get { if let fileURL = self.fileURL { return NSWorkspace.shared.icon(forFile: fileURL.path) } else { return KMFileBookmark.missingFileImage() } } set { } } } class AliasData: Codable { }