//
//  KMLeftSideViewController.swift
//  PDF Reader Pro
//
//  Created by lxy on 2022/10/10.
//

import Cocoa

@objc protocol KMLeftSideViewControllerDelegate {
    @objc optional func controlStateChange(_ obj: KMLeftSideViewController,show:Bool)
    @objc optional func enterEditMode(_ obj: KMLeftSideViewController, _ pages: [Int])
    @objc optional func searchAction(searchString:String, isCase:Bool)
    
    @objc optional func controller(_ controller: KMLeftSideViewController, dispayDidChange dispay: KMPDFDisplayType)
    @objc optional func controller(controller: KMLeftSideViewController, itemClick item: Any?, itemKey: KMItemKey, params: Any?)
    
    @objc optional func controller(controller: KMLeftSideViewController, bookMarkDidChange bookMarks: [KMBookMarkItem])
    @objc optional func controller(controller: KMLeftSideViewController, rotateType: KMRotateType)
}

extension KMLeftSideViewController.Key {
    static let disableTableToolTipsKey = "SKDisableTableToolTips"
}

class KMLeftSideViewController: KMSideViewController {
    var dataSource : [KMLeftMethodMode] = [KMLeftMethodMode]()

    var type : KMLeftMethodMode = KMLeftMethodMode()
    var isShowPanel : Bool = false
    var norImage : [String] = []
    var selectImage : [String] = []
    var mainVC: KMMainViewController?
    var selectPages: [Int]?
    open weak var delegate: KMLeftSideViewControllerDelegate?
    
    let noteColumnId = NSUserInterfaceItemIdentifier(rawValue: "note")
    let authorColumnId = NSUserInterfaceItemIdentifier(rawValue: "author")
    
    struct Key {}
    
    let scalingIncrement: Float = 0.1
    
    deinit {
        KMPrint("KMLeftSideViewController deinit.")
        
        NotificationCenter.default.removeObserver(self)
        DistributedNotificationCenter.default().removeObserver(self)
    }
    
    override var nibName: NSNib.Name? {
        return "LeftSideView"
    }
    
    convenience init(type : KMLeftMethodMode) {
        self.init()
        self.type = type
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.reloadThumbnailSize()
        self.reloadSnapshotSize()
        
//        DistributedNotificationCenter.default().addObserver(self, selector: #selector(interfaceThemeDidChanged), name: NSApplication.interfaceThemeChangedNotification, object: nil)
    }
    
    func showPanelView(show: Bool) {
        self.isShowPanel = show
        if show {
            self.leftView.segmentedControl.selectedSegment = 0
        } else {
            self.leftView.segmentedControl.selectedSegment = UInt8.max
            self.type.methodType = .None
            self.delegate?.controlStateChange?(self, show: false)
        }
    }
    
    func refreshMethodType(methodType: BotaType) {
        let newType = KMLeftMethodMode()
        var show = true
        if self.type.methodType != methodType {
            newType.methodType = methodType
        }
        if self.type.methodType == methodType {
            show = false
        } else if methodType == .None {
            show = false
        }
        self.type = newType;
        self.delegate?.controlStateChange?(self,show:show)
    }
    
    // MARK: - New
    /*
         NSArrayController *findArrayController;
         NSArrayController *groupedFindArrayController;
     }
     */
    
    @IBOutlet var segmentedControl: KMSegmentedControl!
    
    @IBOutlet var thumbnailTableView: KMThumbnailTableView!
    @IBOutlet var tocOutlineView: KMTocOutlineView!
    @IBOutlet var noteOutlineView: KMNoteOutlineView!
    @IBOutlet var findTableView: KMBotaTableView!
    @IBOutlet var groupedFindTableView: KMBotaTableView!
    @IBOutlet var snapshotTableView: KMBotaTableView!
    
    @IBOutlet weak var leftListView: NSView!
    
    @IBOutlet var searchViewController: KMBotaSearchViewController!
    @IBOutlet weak var toolButtonBox: NSBox!
    @IBOutlet weak var toolButtonBoxLayoutConstraint: NSLayoutConstraint!
    @IBOutlet weak var snapshotNormalView: NSView!
    @IBOutlet weak var snapshotLabel: NSTextField!
    @IBOutlet weak var snapshotNormalMoreButton: NSButton!
    @IBOutlet weak var snapshotNormalSearchButton: NSButton!
    @IBOutlet weak var snapshotNormalZoomOutButton: NSButton!
    @IBOutlet weak var snapshotNormalZoomInButton: NSButton!
    @IBOutlet weak var snapshotSearchZoomOutButton: NSButton!
    @IBOutlet weak var snapshotSearchZoomInButton: NSButton!
    @IBOutlet weak var snapshotSearchField: KMLeftSideViewSearchField!
    @IBOutlet weak var snapshotDoneButton: NSButton!
    
    @IBOutlet weak var outlineView: NSView!
    @IBOutlet weak var outlineMoreButton: NSButton!
    @IBOutlet weak var outlineAddButton: NSButton!
    @IBOutlet weak var outlineSearchButton: NSButton!
    @IBOutlet weak var outlineLabel: NSTextField!
    @IBOutlet weak var outlineSearchField: KMLeftSideViewSearchField!
    @IBOutlet weak var outlineDoneButton: NSButton!
    
    @IBOutlet weak var noteView: NSView!
    @IBOutlet weak var noteMoreButton: NSButton!
    @IBOutlet weak var noteFilterButton: NSButton!
    @IBOutlet weak var noteSearchButton: NSButton!
    @IBOutlet weak var noteSearchField: KMLeftSideViewSearchField!
    @IBOutlet weak var noteTitleLabel: NSTextField!
    @IBOutlet weak var noteHeaderView: NSView!
    @IBOutlet weak var sortTypeBox: KMBox!
    @IBOutlet weak var sortTypeLabel: NSTextField!
    @IBOutlet weak var noteSortButton: NSButton!
    @IBOutlet weak var noteDoneButton: NSButton!
    
    @IBOutlet weak var thumbnailView: NSView!
    @IBOutlet weak var thumbnailZoomOutButton: NSButton!
    @IBOutlet weak var thumbnailZoomInButton: NSButton!
    @IBOutlet weak var thumbnailTitleLabel: NSTextField!
    
    @IBOutlet weak var emptySearchBox: NSBox!
    @IBOutlet weak var emptySearchLabel: NSTextField!
    
    var filterButtonLayer: NSView?
    var moreButtonLayer: KMButtonLayer?
    
    var thumbnails: [KMThumbnail] = []
    var isDisplayPageSize = false
    var thumbnailCacheSize: CGFloat = 32 * 3
    var snapshotCacheSize: CGFloat = 32 * 3
    private var _findState: KMFindState = .none
    var findState: KMFindState {
        get {
            return self._findState
        }
        set {
            if self._findState != newValue {
                self._findState = newValue
                
                self.displayFindState()
                if self._findState == .content {
                    self.search(self.searchField)
                } else if self.findState == .note {
//                    self.searchNotes(self.searchField)
                    self.searchNotes(nil)
                } else if self.findState == .snapshot {
                    self.searchNotes(self.searchField)
                }
            }
        }
    }
    
    var searchResults : [KMSearchMode] = [] {
        didSet {
            self.updataLeftSideFindView()
        }
    }
    var groupSearchResults: [KMSearchMode] = []
    var findPaneState: KMFindPaneState = .singular
    var isSearchOutlineMode = false
    var isSearchSnapshotMode = false
    var outlineIgnoreCaseFlag = false
    var noteTypeDict: [String : Any] = [:]
    
    private let MIN_SIDE_PANE_WIDTH: CGFloat = 270
    private let LABEL_COLUMNID = "label"
    var foldType: KMFoldAllAnnotationType = .none
    
    var tocType: KMFoldType = .none
    
    var snapshots: [KMSnapshotModel] = []
    var dirtySnapshots: [KMSnapshotWindowController] = []
    var searchSnapshots: [KMSnapshotModel] = []
    
    private var _noteSortType: KMNoteSortType = .none
    var noteSortType: KMNoteSortType {
        get {
            return self._noteSortType
        }
        set {
            self._noteSortType = newValue
            
            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMAnnotationSortTypeKeyNotification"), object: self)
        }
    }
    
    var isAscendSort = false {
        didSet {
            NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMAnnotationSortTypeKeyNotification"), object: self)
        }
    }
    
    lazy var leftSideEmptyVC: KMLeftSideEmptyFileViewController = {
        let vc = KMLeftSideEmptyFileViewController(nibName: "KMLeftSideEmptyFileViewController", bundle: nil)
        vc.view.wantsLayer = true
        return vc
    }()
    
    var leftMargin: CGFloat = 0
    
    lazy var leftView: KMBotaLeftView = {
        let view = KMBotaLeftView()
        return view
    }()
    
    private var _copysPages: [CPDFPage] = []
    var copyPages: [CPDFPage] {
        get {
            return self._copysPages
        }
    }
    
    private let kKMPDFViewOutlineDragDataType  = NSPasteboard.PasteboardType("kKMPDFViewOutlineDragDataType")
    private let KPDFThumbnailDoucumentURLForDraggedTypes  = NSPasteboard.PasteboardType("KPDFThumbnailDoucumentURLForDraggedTypes")
    
    var renamePDFOutline: CPDFOutline?
    var renamePDFOutlineTextField: NSTextField?
    var allFoldNotes: [CPDFAnnotation] = []
    var notes: [CPDFAnnotation] = []
    var canFoldNotes: [CPDFAnnotation] = []
    
    var isRenameNoteOutline = false
    
    // 注释列表
    // 注释列表数据源
    var annotations: [KMBOTAAnnotationSection] = []
    // 所有注释
    var allAnnotations: [CPDFAnnotation] = []
    // 注释搜索模式标记
    var noteSearchMode = false
    // 注释搜索数组
    var noteSearchArray: [CPDFAnnotation] = []
    // 注释搜索 忽略大小写标识
    var caseInsensitiveNoteSearch = false
    
    var mwcFlags: MwcFlags = MwcFlags()
    
    private var _dragPDFOutline: CPDFOutline?
    
    var updatingOutlineSelection = false
    
    fileprivate var dragFilePath: String?
    var filePromiseQueue: OperationQueue = {
        let queue = OperationQueue()
        return queue
    }()
    var dragIn = false
    var dragedIndexPaths: [Int] = []
    var updatingThumbnailSelection = false
    
    override func loadView() {
        super.loadView()
        
        self.initSubView()
        self.initDefalutValue()

        self.searchViewController.loadView()
        self.searchViewController.contentView = self.findTableView.enclosingScrollView
        self.searchField = self.searchViewController.searchField
            
        self.searchViewController.segmentedControl.setSegmentCount(2, with: 25)
        self.searchViewController.segmentedControl.setImage(NSImage(named: KMImageNameUXIconBtnSidebarListNor)!, for: 0)
        self.searchViewController.segmentedControl.setImage(NSImage(named: KMImageNameUXIconBtnSidebarPageNor)!, for: 1)
        self.searchViewController.segmentedControl.setToolTip(KMLocalizedString("Separate search results", nil), for: 0)
        self.searchViewController.segmentedControl.setToolTip(KMLocalizedString("Group search results by page", nil), for: 1)
        self.searchViewController.segmentedControl.isBackgroundHighlighted = true
        self.searchViewController.segmentedControl.selectedSegment = 0
        self.searchViewController.segmentedControl.block = { [unowned self] segIndex in
            if segIndex == 0 {
                self.findPaneState = .singular
                self.displayFindViewAnimating(false)
            } else {
                self.findPaneState = .grouped
                self.displayGroupedFindViewAnimating(false)
            }
        }
        self.leftView.segmentedControl.block = { [unowned self] segIndex in
            self.toolButtonBox.isHidden = false
            self.toolButtonBoxLayoutConstraint.constant = 40.0
            
            if (segIndex == 0) {
                if self.type.methodType == .Thumbnail {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Thumbnail"])
                self.refreshMethodType(methodType: .Thumbnail)
                DispatchQueue.main.async {
                    self.toolButtonBox.contentView = self.thumbnailView
                    self.displayThumbnailViewAnimating(false)
                }
            } else if (segIndex == 1) {
                if self.type.methodType == .Outline {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Outline"])
                self.refreshMethodType(methodType: .Outline)
                DispatchQueue.main.async {
                    self.toolButtonBox.contentView = self.outlineView
                    self.displayTocViewAnimating(false)
                }
            } else if (segIndex == 2) {
                if self.type.methodType == .Annotation {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Annotation"])
                self.refreshMethodType(methodType: .Annotation)
                DispatchQueue.main.async {
                    self.toolButtonBox.contentView = self.noteView
                    self.displayNoteViewAnimating(false)
                }
            } else if (segIndex == 3) {
                if self.type.methodType == .snapshot {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Snapshot"])
                self.refreshMethodType(methodType: .snapshot)
                self.toolButtonBox.contentView = self.snapshotNormalView
                self.updateSnapshotFilterPredicate()
                self.displaySnapshotViewAnimating(false)
                self.updataLeftSideSnapView()
            } else if (segIndex == 4) {
                if self.type.methodType == .Search {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                FMTrackEventManager.defaultManager.trackEvent(event: "LeftSidebar", withProperties: ["LeftSidebar_Btn": "Btn_LeftSidebar_Search"])
                self.refreshMethodType(methodType: .Search)
                DispatchQueue.main.async {
                    self.toolButtonBox.isHidden = true
                    self.toolButtonBoxLayoutConstraint.constant = 0
                    self.displayFindViewAnimating(false)
                }
            }
        }
        
        let menu = NSMenu()
        _ = menu.addItem(title: KMLocalizedString("Whole Words Only", "Menu item title"), action: #selector(toggleWholeWordSearch), target: self)
        _ = menu.addItem(title: KMLocalizedString("Ignore Case", "Menu item title"), action: #selector(toggleCaseInsensitiveSearch), target: self)
        (self.searchField.cell as? NSSearchFieldCell)?.searchMenuTemplate = menu
        (self.searchField.cell as? NSSearchFieldCell)?.placeholderString = KMLocalizedString("Search PDF", "placeholder")
        
        self.searchField.action = #selector(search)
        self.searchField.target = self
        
//        [thumbnailTableView setTypeSelectHelper:[SKTypeSelectHelper typeSelectHelperWithMatchOption:SKFullStringMatch]];
//        //支持拖拽的文字类型

//        [tocOutlineView setTypeSelectHelper:[SKTypeSelectHelper typeSelectHelperWithMatchOption:SKSubstringMatch]];
//        NSSortDescriptor *countDescriptor = [[[NSSortDescriptor alloc] initWithKey:SKGroupedSearchResultCountKey ascending:NO] autorelease];
//        [groupedFindArrayController setSortDescriptors:[NSArray arrayWithObjects:countDescriptor, nil]];
        
        if KMDataManager.ud_bool(forKey: Self.Key.disableTableToolTipsKey) == false {
            self.tocOutlineView.hasImageToolTips = true
            self.findTableView.hasImageToolTips = true
            self.groupedFindTableView.hasImageToolTips = true
        }
        self._updateViewColor()
    }
    
    func initSubView() {
        self.view.addSubview(self.leftView)
        self.leftView.frame = NSMakeRect(0, 0, 44, NSHeight(self.view.frame))
        self.leftView.autoresizingMask = [.height]
        
        self.thumb_initSubViews()
        self.outline_initSubViews()
        self.note_initSubViews()
        self.snapshot_initSubViews()
        self.search_initSubViews()
    }
    
    func initDefalutValue() {
        self.view.wantsLayer = true
        self.view.layer?.backgroundColor = KMAppearance.Layout.l0Color().cgColor
        
        self.leftView.wantsLayer = true
        self.leftView.layer?.backgroundColor = .white

        self.leftListView.wantsLayer = true
        self.leftListView.layer?.backgroundColor = KMAppearance.Layout.l0Color().cgColor
        
        self.emptySearchLabel.stringValue = KMLocalizedString("No Results",nil)
        self.emptySearchLabel.textColor = KMAppearance.Layout.h0Color()
        self.emptySearchBox.isHidden = true
        
        
        self.thumb_initDefalutValue()
        self.outline_initDefalutValue()
        self.note_initDefalutValue()
        self.snapshot_initDefalutValue()
        self.search_initDefalutValue()
    }
    
    func displayThumbnailViewAnimating(_ animate: Bool) {
        if let enclosingScrollView = self.thumbnailTableView.enclosingScrollView {
            self.replaceSideView(enclosingScrollView, animate: animate)
        }
        
        var frame = self.thumbnailTableView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.origin.x = self.leftMargin
        frame.size.height = self.thumbnailTableView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.thumbnailTableView.enclosingScrollView?.frame = frame
        
        self.resetThumbnails()
        
        frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.origin.x = self.leftMargin
        frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.noteOutlineView.enclosingScrollView?.frame = frame
        
        frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.origin.x = self.leftMargin
        frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.snapshotTableView.enclosingScrollView?.frame = frame
        
        self.updateThumbnailSelection()
    }
    
    func displayFindViewAnimating(_ animate: Bool) {
        self.replaceSideView(self.searchViewController.view, animate: animate)
        if (self.findState != .content) {
            self.findState = .content
        } else {
            self.displayFindState()
        }
        
        var frame = self.searchViewController.view.frame
        frame.origin.y = 0
        frame.size.height = self.searchViewController.view.superview?.frame.size.height ?? .zero
        self.searchViewController.view.frame = frame
        
        frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.noteOutlineView.enclosingScrollView?.frame = frame
        
        frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.snapshotTableView.enclosingScrollView?.frame = frame
        self.leftSideEmptyVC.emptySnapView.removeFromSuperview()
        
        self.updataLeftSideSnapView()
    }
    
    func displayFindState() {
        if (self.findState == .content) {
            self.displayFind()
        } else if (self.findState == .note) {
            self.displayNoteFind()
        } else if (self.findState == .snapshot) {
            self.displaySnapshotFind()
        }
    }
    
    func updataLeftSideFindView() {
        if (self.findState != .content) {
            return
        }
        if (self.searchResults.count > 0) {
            self.searchViewController.emptyBox.isHidden = true
            self.searchViewController.searchResultsView.isHidden = false
            self.searchViewController.searchResultsLabel.stringValue = String(format: KMLocalizedString("%ld Results", "Message in search table header"), self.searchResults.count)
        } else {
            self.searchViewController.emptyBox.isHidden = false
            self.searchViewController.searchResultsView.isHidden = true
        }
    }
    
    func displayGroupedFindViewAnimating(_ animate: Bool) {
        self.replaceSideView(self.searchViewController.view , animate: animate)
        if (self.findState != .content) {
            self.findState = .content
        } else {
            self.displayFindState()
        }
        
        var frame = self.searchViewController.view.frame
        frame.origin.y = 0
        frame.size.height = self.searchViewController.view.superview?.frame.size.height ?? 0
        self.searchViewController.view.frame = frame
        
        frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.noteOutlineView.enclosingScrollView?.frame = frame
        
        frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.snapshotTableView.enclosingScrollView?.frame = frame
        
        self.updataLeftSideSnapView()
    }
    
    func displayNoteViewAnimating(_ animate: Bool) {
        self.reloadAnnotation()
        
        self.searchViewController.contentView = nil
        if let data = self.noteOutlineView.enclosingScrollView {
            self.replaceSideView(data, animate: animate)
        }
        if (self.findState != .note) {
            self.findState = .note
        } else {
            self.displayFindState()
        }
        
        var frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.noteOutlineView.enclosingScrollView?.frame = frame
        
        frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.noteOutlineView.enclosingScrollView?.frame = frame
            
        frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.snapshotTableView.enclosingScrollView?.frame = frame
            
        let view = self.noteOutlineView.enclosingScrollView
        let viewFrame = view?.frame ?? .zero
        let emptyVcSize =  self.leftSideEmptyVC.emptyAnnotationView.frame.size
        self.leftSideEmptyVC.emptyAnnotationView.frame = NSMakeRect((viewFrame.size.width-emptyVcSize.width)/2.0,(viewFrame.size.height-emptyVcSize.height)/2.0, emptyVcSize.width, emptyVcSize.height)
        
        DispatchQueue.main.async {
            self.noteOutlineView.reloadData()
        }
    }
    
    func displaySnapshotViewAnimating(_ animate: Bool) {
        self.searchViewController.contentView = nil
        if let data = self.snapshotTableView.enclosingScrollView {
            self.replaceSideView(data, animate: animate)
        }
        if (self.findState != .snapshot) {
            self.findState = .snapshot
        } else {
            self.displayFindState()
        }
        
        var frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.snapshotTableView.enclosingScrollView?.frame = frame
        
        frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.noteOutlineView.enclosingScrollView?.frame = frame
        
        frame = self.tocOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.tocOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.tocOutlineView.enclosingScrollView?.frame = frame
        
//        [self updateSnapshotsIfNeeded];
        Task { @MainActor in
            self.snapshotTableView.reloadData()
        }
    }
    
    func displayFind() {
        self.searchField = self.searchViewController.searchField
        
        let menu = NSMenu()
        _ = menu.addItem(title: KMLocalizedString("Whole Words Only", "Menu item title"), action: #selector(toggleWholeWordSearch), target: self)
        _ = menu.addItem(title: KMLocalizedString("Ignore Case", "Menu item title"), action: #selector(toggleCaseInsensitiveSearch), target: self)
        (self.searchViewController.searchField.cell as? NSSearchFieldCell)?.searchMenuTemplate = menu
        (self.searchViewController.searchField.cell as? NSSearchFieldCell)?.placeholderString = KMLocalizedString("Search PDF", "placeholder")
        self.searchViewController.searchField.action = #selector(search)
        self.searchViewController.searchField.target = self

        if (self.findPaneState == .singular) {
            self.searchViewController.contentView = self.findTableView.enclosingScrollView
            
            self.findTableView.wantsLayer = true
            self.findTableView.layer?.backgroundColor = NSColor.red.cgColor
            DispatchQueue.main.async {
                self.findTableView.reloadData()
            }
        } else if (self.findPaneState == .grouped) {
            self.searchViewController.contentView = self.groupedFindTableView.enclosingScrollView
            var array = KMSearchMode.sortSearchResult(results: self.searchResults)
            array.sort(){$0.datas.count > $1.datas.count}
            self.groupSearchResults = array
            self.groupedFindTableView.reloadData()
        }
    }
    
    func displayTocViewAnimating(_ animate: Bool) {
        if let data = self.tocOutlineView.enclosingScrollView {
            self.replaceSideView(data, animate: animate)
        }
        
        var frame = self.tocOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.tocOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.tocOutlineView.enclosingScrollView?.frame = frame
            
        frame = self.noteOutlineView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.noteOutlineView.enclosingScrollView?.frame = frame
        
        frame = self.snapshotTableView.enclosingScrollView?.frame ?? .zero
        frame.origin.y = 0
        frame.size.height = self.snapshotTableView.enclosingScrollView?.superview?.frame.size.height ?? 0
        self.snapshotTableView.enclosingScrollView?.frame = frame
        
        let view = self.tocOutlineView.enclosingScrollView
        let viewFrame = view?.frame ?? .zero
        let emptyVcSize =  self.leftSideEmptyVC.emptyOutlineView.frame.size
        
        self.leftSideEmptyVC.emptyOutlineView.frame = NSMakeRect((viewFrame.size.width-emptyVcSize.width)/2.0,(viewFrame.size.height-emptyVcSize.height)/2.0, emptyVcSize.width, emptyVcSize.height);
        
        DispatchQueue.main.async {
            self.tocOutlineView.reloadData()
            self.updateOutlineSelection()
        }
    }
    
    override func requiresAlternateButton(forView aview: NSView?) -> Bool {
        return false
    }
    
    func displayNoteFind() {
        self.searchField = self.noteSearchField
        
        let menu = NSMenu()
        _ = menu.addItem(title: KMLocalizedString("Ignore Case", "Menu item title"), action: #selector(toggleCaseInsensitiveNoteSearch), target: self)
        (self.noteSearchField.cell as? NSSearchFieldCell)?.searchMenuTemplate = menu
        (self.noteSearchField.cell as? NSSearchFieldCell)?.placeholderString = KMLocalizedString("Search Notes", "placeholder")
        self.noteSearchField.action = #selector(searchNotes)
        self.noteSearchField.target = self
    }
    
    func displaySnapshotFind() {
        self.searchField = self.snapshotSearchField;
        
        let menu = NSMenu()
        _ = menu.addItem(title: KMLocalizedString("Ignore Case", "Menu item title"), action: #selector(toggleCaseInsensitiveNoteSearch), target: self)
        (self.snapshotSearchField.cell as? NSSearchFieldCell)?.searchMenuTemplate = menu
        (self.snapshotSearchField.cell as? NSSearchFieldCell)?.placeholderString = KMLocalizedString("Search Snapshots", "placeholder")
        self.snapshotSearchField.action = #selector(searchNotes)
        self.snapshotSearchField.target = self
        self.snapshotSearchField.delegate = self
    }
    
    func resetThumbnails() {
        self.thumbnails.removeAll()
        let pageLabels = self.listView?.document?.pageLabels() ?? []
        if (pageLabels.isEmpty == false) {
            for (i, label) in pageLabels.enumerated() {
                let thumbnail = KMThumbnail(image: nil, label: label, pageIndex: i)
                thumbnail.dirty = true
                self.thumbnails.append(thumbnail)
            }
        }
        
        DispatchQueue.main.async {
            let ris = self.thumbnailTableView.selectedRowIndexes
            self.thumbnailTableView.reloadData()
            self.thumbnailTableView.km_safe_selectRowIndexes(ris, byExtendingSelection: false)
        }
    }
    
    @IBAction func leftSideViewDoneButtonAction(_ sender: AnyObject?) {
        let button = sender as? NSButton
        let tag = button?.tag ?? 0
        if (tag == 310) {
            self.outlineSearchField.isHidden = true
            self.outlineDoneButton.isHidden = true
            self.outlineLabel.isHidden = false
            self.outlineSearchButton.isHidden = false
            self.outlineMoreButton.isHidden = false
            self.outlineAddButton.isHidden = false
        } else if (tag == 311) {
            self.noteSearchField.isHidden = true
            self.noteTitleLabel.isHidden = false
            self.noteSearchButton.isHidden = false
            self.noteDoneButton.isHidden = true
            self.noteFilterButton.isHidden = false
            self.noteMoreButton.isHidden = false
        } else if (tag == 312) {
            self.snapshotSearchField.isHidden = true
    //        leftSideController.snapshotSearchZoomInButton.hidden = YES;
            self.snapshotNormalSearchButton.isHidden = false
            self.snapshotDoneButton.isHidden = true
            self.snapshotLabel.isHidden = false
            self.snapshotNormalZoomOutButton.isHidden = false
            self.snapshotNormalZoomInButton.isHidden = false
        }
    }

    
    @IBAction func thumbnailSizeScaling(_ sender: NSButton) {
        let tag = sender.tag
        if (tag == 0 || tag == 1) {
            var scaling = KMDataManager.ud_float(forKey: Self.Key.thumbSizeScaling)
            if (scaling <= 0) {
                scaling = 1
            }
            if (tag == 0) { // Zoom In
                scaling += self.scalingIncrement
//                if scaling >= 2.2 {
//                    return
//                }
            } else if (tag == 1) { // Zoom Out
                scaling -= self.scalingIncrement
                if scaling <= 0.4 {
                    return
                }
            }
            KMDataManager.ud_set(scaling, forKey: Self.Key.thumbSizeScaling)

            let selectRow = self.thumbnailTableView.selectedRow
            self.thumbnailTableView.reloadData()
            
            self.thumbnailTableView.selectRowIndexes(IndexSet(integer: selectRow), byExtendingSelection: false)
        } else if (tag == 2 || tag == 3) {
            var scaling = KMDataManager.ud_float(forKey: Self.Key.snapshotSizeScaling)
            if (scaling <= 0) {
                scaling = 1
            }
            
            if (tag == 2) { // Zoom In
                scaling += self.scalingIncrement
            } else if (tag == 3) { // Zoom Out
                scaling -= self.scalingIncrement
            }
            
            KMDataManager.ud_set(scaling, forKey: Self.Key.snapshotSizeScaling)
            
            let selectRow = self.snapshotTableView.selectedRow
            self.snapshotTableView.reloadData()
            self.snapshotTableView.selectRowIndexes(IndexSet(integer: selectRow), byExtendingSelection: false)
        }
    }
    
    func tableView(_ tv: NSTableView, cutRowsWithIndexes rowIndexes: IndexSet) {
        if tv.isEqual(to: self.thumbnailTableView) {
            self._copysPages.removeAll()
            for idx in rowIndexes {
                if (idx != NSNotFound) {
                    if let page = self.listView.document.page(at: UInt(idx))?.copy() as? CPDFPage {
                        self._copysPages.append(page)
                    }
                }
            }
            self.tableView(tv, deleteRowsWithIndexes: rowIndexes)
        }
    }
}

// MARK: - KMBotaTableViewDelegate

extension KMLeftSideViewController: KMBotaTableViewDelegate {
    func tableView(_ aTableView: NSTableView, canCopyRowsWithIndexes rowIndexes: IndexSet) -> Bool {
        if aTableView.isEqual(to: self.thumbnailTableView) || aTableView.isEqual(to: self.findTableView) || aTableView.isEqual(to: self.groupedFindTableView) {
            return rowIndexes.count > 0
        }
        return false
    }
    
    func tableView(_ aTableView: NSTableView, canPasteFromPasteboard pboard: NSPasteboard) -> Bool {
        if aTableView.isEqual(to: self.thumbnailTableView) {
            if self._copysPages.count > 0 {
                return true
            }
        }
        return false
    }
    
    func tableView(_ aTableView: NSTableView, canDeleteRowsWithIndexes rowIndexes: IndexSet) -> Bool {
        if aTableView.isEqual(to: self.snapshotTableView) {
            return rowIndexes.count > 0
        } else if aTableView.isEqual(to: self.thumbnailTableView) {
            if self.listView.document.pageCount <= 1 {
                return false
            }
            return true
        }
        return false
    }
    
    func tableView(_ aTableView: NSTableView, pasteFromPasteboard pboard: NSPasteboard?) {
        if IAPProductsManager.default().isAvailableAllFunction() == false {
            KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
            return
        }
        
        if aTableView.isEqual(to: self.thumbnailTableView) {
            if self._copysPages.count > 0 {
                let index = self.listView.document.index(for: self.listView.currentPage()) + 1
                if (index == NSNotFound) {
                    return
                }
                for page in self._copysPages.reversed() {
                    self.listView.document.insertPageObject(page, at: index)
                    self.listView.layoutDocumentView()
                    self.resetThumbnails()
                    let pageIndex = min(index, self.listView.document.pageCount-1)
                    self.listView.go(toPageIndex: Int(pageIndex), animated: true)
                    (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).tableView(self.thumbnailTableView, deleteRowsWithIndexes: IndexSet(integer: IndexSet.Element(index)))
                }
            }
        }
    }
    
    func tableViewMoveLeft(_ aTableView: NSTableView) {
        if aTableView.isEqual(to: self.findTableView) || aTableView.isEqual(to: self.groupedFindTableView) {
//            [self updateFindResultHighlightsForDirection:NSSelectingPrevious];
        }
    }
    
    func tableViewMoveRight(_ aTableView: NSTableView) {
        if aTableView.isEqual(to: self.findTableView) || aTableView.isEqual(to: self.groupedFindTableView) {
//            [self updateFindResultHighlightsForDirection:NSSelectingNext];
        }
    }
    
    func tableView(_ aTableView: NSTableView, imageContextForRow rowIndex: Int) -> AnyObject? {
        if aTableView.isEqual(to: self.findTableView) {
            if rowIndex >= self.searchResults.count {
                return nil
            }
            let model = self.searchResults[rowIndex]
            let selection = model.selection
            let point = NSPoint(x: NSWidth(selection.bounds) * 0.5, y: NSHeight(selection.bounds) * 0.5)
            return CPDFDestination(document: self.listView.document, pageIndex: Int(selection.page.pageIndex()), at: point, zoom: self.listView.scaleFactor)
        } else if aTableView.isEqual(to: self.groupedFindTableView) {
            if rowIndex >= self.groupSearchResults.count {
                return nil
            }
//            let model = self.groupSearchResults[rowIndex]
//            let selection = model.selection
//            let point = NSPoint(x: NSWidth(selection.bounds) * 0.5, y: NSHeight(selection.bounds) * 0.5)
//            return CPDFDestination(document: self.listView.document, pageIndex: Int(selection.page.pageIndex()), at: point, zoom: self.listView.scaleFactor)
        }
        return nil
    }
    
    /*
     - (NSArray *)tableView:(NSTableView *)tv typeSelectHelperSelectionStrings:(SKTypeSelectHelper *)typeSelectHelper {
         if ([tv isEqual:leftSideController.thumbnailTableView]) {
             return pageLabels;
         }
         return nil;
     }

     - (void)tableView:(NSTableView *)tv typeSelectHelper:(SKTypeSelectHelper *)typeSelectHelper didFailToFindMatchForSearchString:(NSString *)searchString {
         if ([tv isEqual:leftSideController.thumbnailTableView]) {
             [statusBar setLeftStringValue:[NSString stringWithFormat:NSLocalizedString(@"No match: \"%@\"", @"Status message"), searchString]];
         }
     }

     - (void)tableView:(NSTableView *)tv typeSelectHelper:(SKTypeSelectHelper *)typeSelectHelper updateSearchString:(NSString *)searchString {
         if ([tv isEqual:leftSideController.thumbnailTableView]) {
             if (searchString.length > 0)
                 [statusBar setLeftStringValue:[NSString stringWithFormat:NSLocalizedString(@"Go to page: %@", @"Status message"), searchString]];
             else
                 [self updateLeftStatus];
         }
     }
     */
    
}

// MARK: - 扩展

extension KMLeftSideViewController {
    public func selectType(_ type: BotaType) {
        // 更新 type
        var show = true
        if (self.type.methodType == .None) {
            show = true
        } else {
            if (self.type.methodType == type) {
                show = false
            } else {
                show = true
            }
        }
        
        self.type = self.getMethodMode(show ? type : .None)
        // 将事件传递出去
        self.delegate?.controlStateChange?(self,show:show)
    }
    
    private func getMethodMode(_ type: BotaType) -> KMLeftMethodMode {
        let mode = KMLeftMethodMode()
        mode.methodType = type
        switch type {
        case .None:
            mode.methodName = ""
            break
        case .Thumbnail:
            mode.methodName = thumbnailMethodKey
            break
        case .Outline:
            mode.methodName = outlineMethodKey
            break
        case .BookMark:
            mode.methodName = bookMarkMethodKey
            break
        case .Annotation:
            mode.methodName = anntationMethodKey
            break
        case .Search:
            mode.methodName = searchMethodKey
            break
        case .From:
            mode.methodName = formMethodKey
            break
        case .Signature:
            mode.methodName = signatureMethodKey
            break
        case .snapshot:
            mode.methodName = snapshotMethodKey
        }
        return mode
    }
}

//MARK: Cache
extension KMLeftSideViewController {
    func clearNotification() {
    }
}

// MARK: - Private Methods

extension KMLeftSideViewController {
    func updateViewColor() {
        self._updateViewColor()
    }
    
    private func _updateViewColor() {
        if(KMAppearance.isDarkMode()){
            self.leftListView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor
            self.snapshotNormalView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor
            self.thumbnailView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor
            self.noteView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor
            self.outlineView.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor
            self.view.layer?.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1).cgColor
            self.thumbnailTableView.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1)
            self.groupedFindTableView.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1)
            self.tocOutlineView.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1)
            self.findTableView.backgroundColor = NSColor(red: 0.149, green: 0.157, blue: 0.169, alpha: 1)
            
            self.snapshotSearchField.layer?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor
            self.outlineSearchField.layer?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor
            self.noteSearchField.layer?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor
            self.segmentedControl.layer?.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1).cgColor
            
            self.snapshotSearchField.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1)
            self.outlineSearchField.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1)
            self.noteSearchField.backgroundColor = NSColor(red: 0.224, green: 0.235, blue: 0.243, alpha: 1)
        } else {
            self.leftListView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor
            self.snapshotNormalView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor
            self.thumbnailView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor
            self.noteView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor
            self.outlineView.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor
            self.view.layer?.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1, alpha: 1).cgColor
            
            self.thumbnailTableView.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1.000, alpha: 1)
            self.groupedFindTableView.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1.000, alpha: 1)
            self.tocOutlineView.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1.000, alpha: 1)
            self.findTableView.backgroundColor = NSColor(red: 0.988, green: 0.992, blue: 1.000, alpha: 1)
            
            self.snapshotSearchField.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor
            self.outlineSearchField.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor
            self.noteSearchField.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor
            self.segmentedControl.layer?.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1).cgColor
            
            self.snapshotSearchField.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1)
            self.outlineSearchField.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1)
            self.noteSearchField.backgroundColor = NSColor(red: 0.922, green: 0.925, blue: 0.941, alpha: 1)
        }
    }
    
    private func _hasContainString(_ searchString: String, rootOutline outline: CPDFOutline) -> Bool {
        var label = outline.label ?? ""
        var searchLabel = searchString
        if self.outlineIgnoreCaseFlag {
            label = label.lowercased()
            searchLabel = searchLabel.lowercased()
        }
        if label.contains(searchLabel) {
//        if ([outline.label rangeOfString:searchString options:self.outlineIgnoreCaseFlag?NSCaseInsensitiveSearch:0].location != NSNotFound){
            return true
        } else {
            var subHas = false
            for i in 0 ..< outline.numberOfChildren {
                if let subOutline = outline.child(at: i) {
                    subHas = self._hasContainString(searchString, rootOutline: subOutline)
                } else {
                    continue
                }
                if (subHas) {
                    break
                }
            }
            return subHas
        }
    }
}

// MARK: - NSTableViewDelegate, NSTableViewDataSource

extension KMLeftSideViewController: NSTableViewDelegate, NSTableViewDataSource {
    func numberOfRows(in tableView: NSTableView) -> Int {
        if tableView.isEqual(to: self.thumbnailTableView) {
            return self.thumbnails.count
        } else if tableView.isEqual(to: self.findTableView) {
            return self.searchResults.count
        } else if tableView.isEqual(to: self.groupedFindTableView) {
            return self.groupSearchResults.count
        } else if tableView.isEqual(to: self.snapshotTableView) {
            if self.isSearchSnapshotMode {
                return self.searchSnapshots.count
            }
            return self.snapshots.count
        }
        return 0
    }
    
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        if tableView.isEqual(to: self.thumbnailTableView) {
            let cell = tableView.makeView(withIdentifier: KMThumbnailTableviewCell.km_identifier, owner: self) as! KMThumbnailTableviewCell
            let thumbnail = self.thumbnails[row]
            cell.pageNumLabel.stringValue = thumbnail.label
//            cell.thumImage.image = thumbnail.image
            cell.pageView.page = self.listView.document.page(at: UInt(row))
//            if let _image = thumbnail.image {
//                let multiplierHToW = _image.size.height / (_image.size.width == 0 ? 1 : _image.size.width)
//                let multiplierWToH = _image.size.width  / (_image.size.height == 0 ? 1 : _image.size.height)
//                if (_image.size.height > _image.size.width) {
//                    NSLayoutConstraint.deactivate([cell.imageAspectRatioLayout])
//                    cell.imageAspectRatioLayout = NSLayoutConstraint(item: cell.thumImage, attribute: .height, relatedBy: .equal, toItem: cell.thumImage, attribute: .width, multiplier: multiplierHToW, constant: 0)
//                    NSLayoutConstraint.activate([cell.imageAspectRatioLayout])
//                } else {
//                    NSLayoutConstraint.deactivate([cell.imageAspectRatioLayout])
//                    cell.imageAspectRatioLayout = NSLayoutConstraint(item: cell.thumImage, attribute: .width, relatedBy: .equal, toItem: cell.thumImage, attribute: .height, multiplier: multiplierWToH, constant: 0)
//                    NSLayoutConstraint.activate([cell.imageAspectRatioLayout])
//                }
//            }

            if (self.isDisplayPageSize) {
                cell.sizeLabel.isHidden = false
                //获取Page的真实尺寸
                let page = self.listView.document.page(at: UInt(row))
                let rect = page?.bounds(for: .cropBox) ?? .zero
                let w =  KMPageSizeTool.conversion(withUnit: "mm", value: (CGRectGetWidth(rect)/595 * 210))
                let h =  KMPageSizeTool.conversion(withUnit: "mm", value: (CGRectGetHeight(rect)/842 * 297))
                if let data = page?.rotation, data == 90 || data == 270 {
                    cell.sizeLabel.stringValue = String(format: "%.f × %.f %@", h.stringToCGFloat(), w.stringToCGFloat(), KMLocalizedString("mm", nil))
                } else {
                    cell.sizeLabel.stringValue = String(format: "%.f × %.f %@", w.stringToCGFloat(), h.stringToCGFloat(), KMLocalizedString("mm", nil))
                }
            } else {
                cell.sizeLabel.isHidden = true
            }
            cell.sizeTopConstant.constant = cell.sizeLabel.isHidden ? -cell.sizeLabel.frame.size.height : 0
            if (self.thumbnailTableView.selectedRowIndexes.contains(row)) {
                cell.isSelectCell = true
            } else {
                cell.isSelectCell = false
            }
            return cell
        } else if (tableView.isEqual(to: self.findTableView)) {
            let cell = tableView.makeView(withIdentifier: KMFindTableviewCell.km_identifier, owner: self) as! KMFindTableviewCell
             let selection = searchResults[row]
            if let data = tableColumn?.identifier.rawValue, data == kResultsColumnId.rawValue {
                cell.resultLabel.attributedStringValue = selection.attributedString
                cell.resultLabel.textColor = KMAppearance.Layout.h0Color()
            } else if let data = tableColumn?.identifier.rawValue, data == kPageColumnId.rawValue {
                cell.resultLabel.stringValue = "\(Int(selection.selection.page?.pageIndex() ?? 0) + 1)"
                cell.resultLabel.textColor = KMAppearance.Layout.h2Color()
            }
            return cell
        } else if tableView.isEqual(to: self.snapshotTableView) {
            let cell = tableView.makeView(withIdentifier: KMSnapshotTableViewCell.km_identifier, owner: self) as! KMSnapshotTableViewCell
            var snapshot: KMSnapshotModel?
            if self.isSearchSnapshotMode {
                snapshot = self.searchSnapshots[row]
            } else {
                snapshot = self.snapshots[row]
            }
            
            cell.snapshotImage.image = snapshot?.windowC?.thumbnail
            cell.snapshotLabel.stringValue = snapshot?.windowC?.pageLabel ?? ""
            if let data = snapshot?.windowC?.hasWindow, data {
                cell.snapshotImageView.isHidden = false
            } else {
                cell.snapshotImageView.isHidden = true
            }
            cell.isSelectCell = snapshot?.isSelected ?? false
            if (row == tableView.selectedRow) {
                cell.snapshotImageView.image = NSImage(named: KMImageNameUXIconSidebarSnapshotWindowSel)
            } else {
                cell.snapshotImageView.image = NSImage(named: KMImageNameUXIconSidebarSnapshotWindowNor)
            }
            return cell
        }
        return nil
    }
    
    func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
        if tableView.isEqual(to: self.thumbnailTableView) {
            let scaling = KMDataManager.ud_float(forKey: Self.Key.thumbSizeScaling)
            let thumbnailSize = NSMakeSize(self.thumbnailCacheSize, self.thumbnailCacheSize)
            
            let newScaling: CGFloat = scaling.cgFloat + 0.1
            let newThumbnailHeight = thumbnailSize.width * newScaling
            if (newThumbnailHeight > MIN_SIDE_PANE_WIDTH) {
                self.thumbnailZoomOutButton.isEnabled = false
            } else {
                self.thumbnailZoomOutButton.isEnabled = true
            }
            if ((scaling - 0.1) < 0.3) {
                self.thumbnailZoomInButton.isEnabled = false
            } else {
                self.thumbnailZoomInButton.isEnabled = true
            }
            var labelHeight = 0.0
            if (self.isDisplayPageSize) {
                labelHeight = 56.0
            } else {
                labelHeight = 41.5
            }
            let cellHeight = thumbnailSize.height + labelHeight
            var thumbSize: NSSize = .zero
            if (scaling > 0) {
                thumbSize = NSMakeSize(thumbnailSize.width * scaling.cgFloat, cellHeight * scaling.cgFloat)
            } else {
                thumbSize = NSMakeSize(thumbnailSize.width, cellHeight)
            }
            return thumbSize.height
        } else if tableView.isEqual(to: self.snapshotTableView) {
            let scaling = KMDataManager.ud_float(forKey: Self.Key.snapshotSizeScaling)
            let snapshotSize = (self.snapshots.safe_element(for: row) as? KMSnapshotModel)?.windowC?.thumbnail?.size ?? CGSizeMake(120, 63)
            var newScaling = scaling + 0.1
            let newSnapshotHeight = snapshotSize.width * newScaling.cgFloat;
            if (newSnapshotHeight > MIN_SIDE_PANE_WIDTH) {
                self.snapshotNormalZoomInButton.isEnabled = false
            } else {
                self.snapshotNormalZoomInButton.isEnabled = true
            }
            if ((scaling - 0.1) < 0.3 || (newSnapshotHeight < 150.0)) {
                self.snapshotNormalZoomOutButton.isEnabled = false
            } else {
                self.snapshotNormalZoomOutButton.isEnabled = true
            }

            let cellHeight = snapshotSize.height + 24.0
            var thumbSize: NSSize = .zero
            if (scaling > 0) {
                thumbSize = NSMakeSize(snapshotSize.width * scaling.cgFloat, cellHeight * scaling.cgFloat)
            } else {
                thumbSize = NSMakeSize(snapshotSize.width, cellHeight)
            }
            return thumbSize.height
        } else if (tableView.isEqual(to: self.findTableView)) {
            return 40.0
        } else if tableView.isEqual(to: self.groupedFindTableView) {
            return 16
        }
        return tableView.rowHeight
    }
    
    func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
        if (tableView.isEqual(to: self.findTableView)) {
            let rowView = KMBotaTableRowView()
            return rowView
        } else if tableView.isEqual(to: self.groupedFindTableView) {
            let rowView = KMGroupFindTableRowView()
            rowView.totalNumber = self.groupSearchResults.first?.datas.count ?? 0
            let model = self.groupSearchResults[row]
            rowView.number = model.datas.count
            rowView.pageIndex = Int(model.selectionPageIndex) + 1
            return rowView
        }
        return nil
    }
    
    func tableViewSelectionIsChanging(_ notification: Notification) {
        
    }
    func tableViewSelectionDidChange(_ notification: Notification) {
        if self.findTableView.isEqual(to: notification.object) {
            //            [self updateFindResultHighlightsForDirection:NSDirectSelection];
            let row = self.findTableView.selectedRow
            if row >= 0 {
                let model = self.searchResults[row]
                self.listView.go(to: model.selection, animated: true)
                self.listView.setHighlightedSelection(model.selection, animated: true)
                self.listView.setNeedsDisplayAnnotationViewForVisiblePages()
            }
        } else if self.groupedFindTableView.isEqual(to: notification.object) {
            //            [self updateFindResultHighlightsForDirection:NSDirectSelection];
        } else if self.thumbnailTableView.isEqual(to: notification.object) {
            let row = self.thumbnailTableView.selectedRow
            let curPage = self.listView.document.index(for: self.listView.currentPage())
            
            if (row != -1 && row != curPage) {
                self.listView.go(toPageIndex: row, animated: true)
            }
            self.thumbnailTableView.ks_reloadData()
        } else if self.snapshotTableView.isEqual(to: notification.object) {
            let row = self.snapshotTableView.selectedRow
            // 更新选中数据
            for (i, model) in self.snapshots.enumerated() {
                model.isSelected = i == row
            }
                    
            if (row != -1) {
                let controller = (self.snapshots.safe_element(for: row) as? KMSnapshotModel)?.windowC
                if let data = controller?.window?.isVisible, data {
                    controller?.window?.orderFront(self)
                }
            }
//            self.snapshotTableView.reloadData()
            self.snapshotTableView.ks_reloadData()
        }
    }
    
    func tableView(_ tableView: NSTableView, validateDrop info: NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
        if dropOperation == .on || tableView.isEqual(to: self.thumbnailTableView) == false {
            return NSDragOperation(rawValue: 0)
        }
        let pboard = info.draggingPasteboard
        if (pboard.availableType(from: [.localDraggedTypes]) != nil) {
            return .move
        } else if (pboard.availableType(from: [.fileURL]) != nil) && tableView.isEqual(to: self.thumbnailTableView) {
            guard let pbItems = pboard.pasteboardItems else {
                return NSDragOperation(rawValue: 0)
            }
            
            var hasValidFile = false
            for item in pbItems {
                guard let data = item.string(forType: .fileURL), let _url = URL(string: data) else {
                    continue
                }
                let type = _url.pathExtension.lowercased()
                if type == "pdf" || KMImageToPDFMethod.supportedImageTypes().contains(type) {
                    hasValidFile = true
                }
            }
            
            if (!hasValidFile) {
                return NSDragOperation(rawValue: 0)
            } else {
                return .move
            }
         }
        return NSDragOperation(rawValue: 0)
    }
    
    func tableView(_ tableView: NSTableView, acceptDrop info: NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
        var result = false
        if tableView.isEqual(to: self.thumbnailTableView) == false {
            return result
        }
        
        let pasteboard = info.draggingPasteboard
        if (pasteboard.availableType(from: [.localDraggedTypes]) != nil) {
            result = true
            guard let rowData = pasteboard.data(forType: .localDraggedTypes) else {
                return false
            }
            let rowIndexes = NSKeyedUnarchiver.unarchiveObject(with: rowData) as? IndexSet ?? IndexSet()
            guard let urlData = pasteboard.data(forType: KPDFThumbnailDoucumentURLForDraggedTypes) else {
                return false
            }
            let url = NSKeyedUnarchiver.unarchiveObject(with: urlData) as? String
            if url != nil && url != self.listView.document.documentURL.absoluteString {
                if (pasteboard.availableType(from: [.fileURL]) != nil) {
                    let fileNames = pasteboard.propertyList(forType: .fileURL) as? [String] ?? []
                    if (fileNames.count == 1) {
                        let path = fileNames.first ?? ""
                        let pathExtension = path.pathExtension.lowercased()
                        if pathExtension == "pdf" {
                            var index = row
                            var insertIndexSet = IndexSet()
                            let pdf = CPDFDocument(url: URL(fileURLWithPath: path))
                            if let data = pdf?.isEncrypted, data {
                                KMBaseWindowController.checkPassword(url: URL(fileURLWithPath: path), type: .owner) { success, resultPassword in
                                    if success && resultPassword.isEmpty == false {
                                        for i in 0 ..< (pdf?.pageCount ?? 0) {
                                            if let page = pdf?.page(at: i).copy() as? CPDFPage {
                                                self.listView.document.insertPageObject(page, at: UInt(index))
                                                insertIndexSet.insert(index)
                                                index += 1
                                            }
                                        }
                                        self.insertPages(insertIndexSet, pageAt: index-1)
                                    }
                                }
                            } else {
                                for i in 0 ..< (pdf?.pageCount ?? 0) {
                                    let page = pdf?.page(at: i)
                                    self.listView.document.insertPageObject(page, at: UInt(index))
                                    insertIndexSet.insert(index)
                                    index += 1
                                }
                                self.insertPages(insertIndexSet, pageAt: index-1)
                            }
                        }
                        return true
                    }
                }
            }
            var pageIndex = 0
            var pages: [CPDFPage] = []
            for idx in rowIndexes {
                if let page = self.listView.document.page(at: UInt(idx))?.copy() as? CPDFPage {
                    pages.append(page)
                }
            }
            var toPage: CPDFPage?
            if row < self.listView.document.pageCount {
                toPage = self.listView.document.page(at: UInt(row))
            }
            var toPageIndex = 0
            if (toPage != nil) {
                toPageIndex = Int(self.listView.document.index(for: toPage))
            } else {
                toPageIndex = Int(self.listView.document.pageCount)
            }
            for i in 0 ..< pages.count {
                let page = pages[i]
                self.listView.document.insertPageObject(page, at: UInt(toPageIndex+i))
                pageIndex += 1
                self.listView.go(toPageIndex: toPageIndex+i, animated: false)
            }
            self.listView.layoutDocumentView()
            var deletepages: [CPDFPage] = []
            for idx in rowIndexes {
                if idx < toPageIndex {
                    if((idx+pageIndex) < self.listView.document.pageCount) {
                        if let page = self.listView.document.page(at: UInt(idx)) {
                            deletepages.append(page)
                        }
                        self.listView.document.removePage(at: IndexSet(integer: idx))
                    }
                } else {
                    if((idx+pageIndex) < self.listView.document.pageCount) {
                        if let page = self.listView.document.page(at: UInt(idx+pageIndex)) {
                            deletepages.append(page)
                        }
                        self.listView.document.removePage(at: IndexSet(integer: idx+pageIndex))
                    }
                }
            }
            self.listView.layoutDocumentView()
            self.resetThumbnails()
        } else if pasteboard.availableType(from: [.fileURL]) != nil && tableView.isEqual(to: self.thumbnailTableView) {
            if IAPProductsManager.default().isAvailableAllFunction() == false {
                KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
                return false
            }

            let fileNames = pasteboard.propertyList(forType: .fileURL)
            var insertIndexSet = IndexSet()
            var index = row

//            if (fileNames.count == 1) {
            if fileNames != nil {
//                var path = fileNames.first as? String ?? ""
                let path = fileNames as? String ?? ""
                let url = URL(string: path)
                let pathExtension = url?.pathExtension.lowercased() ?? ""
                if pathExtension == "pdf" {
                    let pdf = CPDFDocument(url: url!)
                    if let data = pdf?.isEncrypted, data {
                        KMBaseWindowController.checkPassword(url: url!, type: .owner) { success, resultPassword in
                            if success && resultPassword.isEmpty == false {
                                pdf?.unlock(withPassword: resultPassword)
                                for i in 0 ..< (pdf?.pageCount ?? 0) {
                                    //                                    let page = pdf?.page(at: i).copy() as? CPDFPage
                                    if let page = pdf?.page(at: i) {
                                        self.listView.document.insertPageObject(page, at: UInt(index))
                                        insertIndexSet.insert(index)
                                        index += 1
                                    }
                                }
                                self.insertPages(insertIndexSet, pageAt: index-1)
                            }
                        }
                    } else {
                        for i in 0 ..< (pdf?.pageCount ?? 0) {
//                            let page = pdf?.page(at: i).copy() as? CPDFPage
                            if let page = pdf?.page(at: i) {
                                self.listView.document.insertPageObject(page, at: UInt(index))
                                insertIndexSet.insert(index)
                                index += 1
                            }
                        }
                        self.insertPages(insertIndexSet, pageAt: index-1)
                    }
                } else if KMImageToPDFMethod.supportedImageTypes().contains(pathExtension) {
                    if let image = NSImage(contentsOfFile: url?.path ?? "") {
                        //                    PDFPage * page = [[[PDFPage alloc] initWithImage:image] autorelease];
                        //                    [pdfView.document insertPage:page atIndex:index];
                        _ = self.listView.document.km_insert(image: image, at: UInt(index))
                        insertIndexSet.insert(index)
                        self.insertPages(insertIndexSet, pageAt: index-1)
                    }
                }
                result = true
            } else {
                result = false
            }
        }
        return result == false
    }
    
    func tableView(_ tableView: NSTableView, writeRowsWith rowIndexes: IndexSet, to pboard: NSPasteboard) -> Bool {
        if tableView.isEqual(to: self.thumbnailTableView) {
            let idx = rowIndexes.first ?? NSNotFound
            if (idx != NSNotFound && self.listView.document.isLocked == false) {
                let page = self.listView.document.page(at: UInt(idx))
                var fileExt: String?
                let tiffData = page?.PDFListViewTIFFData(for: page?.bounds(for: self.listView.displayBox) ?? .zero)
                if self.listView.document.allowsPrinting {
//                    NSData *pdfData = [page dataRepresentation];
                    fileExt = "pdf"
                    // filenames
                    pboard.declareTypes([.pdf, .tiff, .fileURL, .filePromise, .localDraggedTypes, KPDFThumbnailDoucumentURLForDraggedTypes], owner: self)
//                    let newDoc = CPDFDocument()
//                    newDoc?.insertPageObject(page, at: 0)
//                    let data = newDoc?.dataRepresentation()
            
//                    [pboard setData:pdfData forType:NSPasteboardTypePDF];
                    pboard.setData(tiffData, forType: .pdf)
                    
//                    NSData *zNSIndexSetData = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
                    let zNSIndexSetData = NSKeyedArchiver.archivedData(withRootObject: rowIndexes)
                    pboard.setData(zNSIndexSetData, forType: .localDraggedTypes)
                    
                    let documentURL = NSKeyedArchiver.archivedData(withRootObject: self.listView.document.documentURL?.absoluteString)
//                    NSString *docmentName = [[[pdfView.document.documentURL path] lastPathComponent] stringByDeletingPathExtension];
                    var docmentName = self.listView.document.documentURL.deletingPathExtension().lastPathComponent
//                    __block NSMutableString *pagesName = nil;
                    var pagesName = ""
                    if (rowIndexes.count > 1) {
                        pagesName = " pages"
                    } else {
                        pagesName = " page"
                    }
                    let tFileName = String(format: "%@ %@", pagesName, self.fileNameWithSelectedPages(rowIndexes))
                    let pdf = CPDFDocument()
                    for idx in rowIndexes {
//                        var copyPage = self.listView.document.page(at: UInt(idx)).copy() as? CPDFPage
                        let copyPage = self.listView.document.page(at: UInt(idx))
                        pdf?.insertPageObject(copyPage, at: pdf?.pageCount ?? 0)
                    }
                    let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
                    var cachesDir = paths.first ?? ""
                    docmentName = String(format: "%@%@", docmentName, tFileName)
                    if (docmentName.count > 50) {
                        docmentName = docmentName.substring(to: 50)
                    }
//                    cachesDir = [[cachesDir stringByAppendingPathComponent:docmentName] stringByAppendingPathExtension:@"pdf"];
                    cachesDir = "\(cachesDir)/\(docmentName).pdf"
                    let success = pdf?.write(toFile: cachesDir) ?? false
                    if (success) {
//                        [pboard setPropertyList:@[cachesDir] forType:NSFilenamesPboardType];
                        pboard.setPropertyList([cachesDir], forType: .fileURL)
                    } else{
//                        [pboard setPropertyList:@[@""] forType:NSFilenamesPboardType];
                        pboard.setPropertyList([], forType: .fileURL)
                    }
                    pboard.setData(documentURL, forType: KPDFThumbnailDoucumentURLForDraggedTypes)
                } else {
                    fileExt = "tiff"
                    pboard.declareTypes([.tiff, .filePromise], owner: self)
                }
                pboard.setData(tiffData, forType: .tiff)
                // kPasteboardTypeFileURLPromise
                pboard.setPropertyList([fileExt], forType: .filePromise)

                return true
            }
        } else if self.snapshotTableView.isEqual(to: tableView) {
            let idx = rowIndexes.first ?? NSNotFound
            if (idx != NSNotFound) {
                let snapshot = (self.snapshots.safe_element(for: idx) as? KMSnapshotModel)?.windowC
                if let data = snapshot?.thumbnailWithSize(0)?.tiffRepresentation {
                    pboard.declareTypes([.tiff, .filePromise], owner: self)
                    pboard.setData(data, forType: .tiff)
                    pboard.setPropertyList(["tiff"], forType: .filePromise)
                    return true
                }
            }
        }
        return false
    }
    
    func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forRowIndexes rowIndexes: IndexSet) {
        self.dragIn = true
    }
    
    func tableView(_ tableView: NSTableView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
        self.dragIn = false
    }
    
    @objc dynamic func tableView(_ aTableView: NSTableView, deleteRowsWithIndexes rowIndexes: IndexSet) {
        if IAPProductsManager.default().isAvailableAllFunction() == false {
            KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
            return
        }
        if aTableView.isEqual(to: self.snapshotTableView) {
            var controllers: [KMSnapshotWindowController] = []
            for i in rowIndexes {
                let model: KMSnapshotModel? = self.snapshots.safe_element(for: i) as? KMSnapshotModel
                if let winC = model?.windowC {
                    controllers.append(winC)
                }
            }
            for c in controllers {
                c.close()
            }
        } else if aTableView.isEqual(to: self.thumbnailTableView) {
            for idx in rowIndexes {
                if idx >= self.listView.document.pageCount {
                    return
                }
                if let page = self.listView.document.page(at: UInt(idx)) {
                    for anno in page.annotations {
                        page.removeAnnotation(anno)
                    }
                    (self.listView.undoManager?.prepare(withInvocationTarget: self) as AnyObject).insertPage(page, pageAt:  idx)
                    self.listView.document.removePage(at: IndexSet(integer: idx))
                }
            }
            if rowIndexes.isEmpty == false {
                self.listView.layoutDocumentView()
                self.resetThumbnails()
                let idx = rowIndexes.first ?? -1
                let index = min(idx, Int(self.listView.document.pageCount)-1)
                self.listView.go(toPageIndex: index, animated: false)
            }
        }
    }
    
    func tableView(_ aTableView: NSTableView, copyRowsWithIndexes rowIndexes: IndexSet) {
        if IAPProductsManager.default().isAvailableAllFunction() == false {
            KMPurchaseCompareWindowController.sharedInstance().showWindow(nil)
            return
        }
        if aTableView.isEqual(to: self.thumbnailTableView) {
            self._copysPages.removeAll()
            if self.listView.document.isLocked == false {
                for idx in rowIndexes {
                    if let page = self.listView.document.page(at: UInt(idx))?.copy() as? CPDFPage {
                        self._copysPages.append(page)
                    }
                }
            }
        } else if aTableView.isEqual(to: self.findTableView) {
            var string = ""
            for idx in rowIndexes {
                let match = self.searchResults[idx].selection
                string.append("* ")
//                [string appendFormat:NSLocalizedString(@"Page %@", @""), [match firstPageLabel]];
                string = string.appendingFormat(KMLocalizedString("Page %@", ""), "\(match.safeFirstPage()?.pageIndex() ?? 0)")
//                [string appendFormat:@"", [[match contextString] string]];
                string = string.appendingFormat(": %@\n", match.string() ?? "")
            }
            let pboard = NSPasteboard.general
            pboard.clearContents()
            pboard.writeObjects([string as NSPasteboardWriting])
        } else if aTableView.isEqual(to: self.groupedFindTableView) {
            var string = ""
            for idx in rowIndexes {
                let result = self.groupSearchResults[idx]
                let matches = result.datas
                string.append("* ")
                string = string.appendingFormat(KMLocalizedString("Page %@", ""), "\(result.selectionPageIndex)")
                string.append(": ")
                string = string.appendingFormat(KMLocalizedString("%ld Results", ""), matches.count)
//                [string appendFormat:@":\n\t%@\n", [[matches valueForKeyPath:@"contextString.string"] componentsJoinedByString:@"\n\t"]];
                var tmpString = ""
                for model in matches {
                    tmpString.append("\(model.selection.string() ?? "")")
                }
                string = string.appendingFormat(":\n\t%@\n", tmpString)
            }
            let pboard = NSPasteboard.general
            pboard.clearContents()
            pboard.writeObjects([string as NSPasteboardWriting])
        }
    }
    
    func tableView(_ tableView: NSTableView, namesOfPromisedFilesDroppedAtDestination dropDestination: URL, forDraggedRowsWith indexSet: IndexSet) -> [String] {
        if self.thumbnailTableView.isEqual(to: tableView) {
            var fileURLArray: [String] = []
            if indexSet.count > 1 {
                var docmentName = ""
                let tFileName = String(format: "%@", self.fileNameWithSelectedPages(indexSet))
                let pdf = CPDFDocument()
                for idx in indexSet {
                    if idx != NSNotFound && self.listView.document.isLocked == false {
//                        let copyPage = self.listView.document.page(at: UInt(idx)).copy() as? CPDFPage
                        let copyPage = self.listView.document.page(at: UInt(idx))
                        pdf?.insertPageObject(copyPage, at: pdf?.pageCount ?? 0)
                    }
                }
                let fileURL = dropDestination.appendingPathComponent(tFileName).appendingPathExtension("pdf").uniqueFileURL()
                docmentName = fileURL.path
                let success = pdf?.write(toFile: docmentName) ?? false
                if(success) {
                    fileURLArray.append(fileURL.lastPathComponent)
                }
            } else {
                if let page = self.listView.document.page(at: UInt(indexSet.first ?? 0)) {
                    var fileURL = dropDestination.appendingPathComponent(self.draggedFileName(for: page))
                    if let data = self.listView?.document?.allowsPrinting, data {
                        fileURL = fileURL.appendingPathExtension("pdf").uniqueFileURL()
                        let pdf = CPDFDocument()
//                        let copyPage = page.copy() as? CPDFPage
                        let copyPage = page
                        pdf?.insertPageObject(copyPage, at: pdf?.pageCount ?? 0)
                        let success = pdf?.write(toFile: fileURL.path) ?? false
                        if success {
                            fileURLArray.append(fileURL.lastPathComponent)
                        }
                    } else {
                        fileURL = fileURL.appendingPathExtension("tiff").uniqueFileURL()
                        let fileData = page.PDFListViewTIFFData(for: page.bounds(for: self.listView?.displayBox ?? .cropBox))
                        let success = (try?fileData?.write(to: fileURL)) != nil
                        if success {
                            fileURLArray.append(fileURL.lastPathComponent)
                        }
                    }
                }
            }
            return fileURLArray
        } else if self.snapshotTableView.isEqual(to: tableView) {
            let idx = indexSet.first ?? NSNotFound
            if (idx != NSNotFound) {
                if let snapshot = (self.snapshots.safe_element(for: idx) as? KMSnapshotModel)?.windowC {
                    if let page = self.listView?.document?.page(at: snapshot.pageIndex()) {
                        var fileURL = dropDestination.appendingPathComponent(self.draggedFileName(for: page)).appendingPathExtension("tiff")
                        fileURL = fileURL.uniqueFileURL()
                        if ((try?snapshot.thumbnailWithSize(0)?.tiffRepresentation?.write(to: fileURL)) != nil) {
                            return [fileURL.lastPathComponent]
                        }
                    }
                }
            }
        }
        return []
    }
    
    func tableViewColumnDidResize(_ notification: Notification) {
        guard let column = notification.userInfo?["NSTableColumn"] as? NSTableColumn else {
            return
        }
        if column.identifier.rawValue == IMAGE_COLUMNID {
            if self.thumbnailTableView.isEqual(to: notification.object) {
                self.thumbnailTableView.noteHeightOfRows(withIndexesChanged: NSIndexSet(indexesIn: NSMakeRange(0, self.thumbnailTableView.numberOfRows)) as IndexSet)
            } else if self.snapshotTableView.isEqual(to: notification.object) {
                self.snapshotTableView.noteHeightOfRows(withIndexesChanged: NSIndexSet(indexesIn: NSMakeRange(0, self.snapshotTableView.numberOfRows)) as IndexSet)
            }
        }
    }
    
    func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
        if tableView.isEqual(to: self.groupedFindTableView) {
//            [leftSideController.groupedFindArrayController setSortDescriptors:[tv sortDescriptors]];
        }
    }
    
    /*
     #pragma mark NSTableView delegate protocol

     - (void)tableView:(NSTableView *)tv shareRowsWithIndexes:(NSIndexSet *)rowIndexes {
         if ([tv isEqual:leftSideController.thumbnailTableView]) {
             NSUInteger idx = [rowIndexes firstIndex];
             if (idx != NSNotFound) {
                 PDFPage *page = [[pdfView document] pageAtIndex:idx];
                 NSString *fileName = [[[pdfView document] documentURL] lastPathComponent];
                 NSString *folderPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
                 NSString *filePath = [folderPath stringByAppendingPathComponent:[NSString stringWithFormat:@"Untitled"]];
                 filePath = [filePath stringByAppendingPathExtension:[fileName pathExtension]];
                 
                 [self fileWithPage:page atPath:filePath];
                 
                 if (rint(NSAppKitVersionNumber) < NSAppKitVersionNumber10_8) {
                     [KMMailHelper sendFileWithPaths:[NSArray arrayWithObject:filePath]];
                 } else {
                     NSSharingService *service = [NSSharingService sharingServiceNamed:NSSharingServiceNameComposeEmail];
                     [service performWithItems:[NSArray arrayWithObject:[NSURL fileURLWithPath:filePath]]];
                 }
             }
         }
     }

     - (void)fileWithPage:(PDFPage *)page atPath:(NSString *)filePath {
         NSData *data = [page dataRepresentation];
         PDFDocument *document = [[PDFDocument alloc] initWithData:data];
         
         [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
         BOOL success =  [document writeToURL:[NSURL fileURLWithPath:filePath]];
         if (success) {
             NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
             NSURL *url = [NSURL fileURLWithPath:filePath];
             [workspace activateFileViewerSelectingURLs:[NSArray arrayWithObject:url]];
         }
         
         [document release];
     */
}

// MARK: - NSOutlineViewDelegate, NSOutlineViewDataSource

extension KMLeftSideViewController: NSOutlineViewDelegate, NSOutlineViewDataSource {
    func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
        if outlineView.isEqual(to: self.tocOutlineView) {
            let isLocked = self.listView.document?.isLocked ?? true
            if isLocked { // 文档不存在 或 已加锁
                return 0
            }
            if item == nil { // 第一层
                // 获取根
                guard let outline = self.listView.document.outlineRoot() else {
                    let view = self.tocOutlineView.enclosingScrollView
                    let viewFrame = view?.frame ?? .zero
                    let emptyVcSize =  self.leftSideEmptyVC.emptyOutlineView.frame.size
//
                    self.leftSideEmptyVC.emptyOutlineView.frame = NSMakeRect((viewFrame.size.width-emptyVcSize.width)/2.0,(viewFrame.size.height-emptyVcSize.height)/2.0, emptyVcSize.width, emptyVcSize.height)
                    self.leftSideEmptyVC.emptyOutlineView.autoresizingMask = [.minXMargin, .maxXMargin, .minYMargin, .maxYMargin]
                    self.tocOutlineView.enclosingScrollView?.documentView?.addSubview(self.leftSideEmptyVC.emptyOutlineView)
                    self.leftSideEmptyVC.deleteOutlineBtn.isEnabled = false
                    return 0
                }
                if outline.numberOfChildren == 0 {
//                    return 0
                }
                
                if self.isSearchOutlineMode { // 是否为搜索模块
                    if self._hasContainString(self.outlineSearchField.stringValue, rootOutline: outline) {
                        self.showSearchOutlineBlankState(false)
                    } else {
                        self.showSearchOutlineBlankState(true)
                        return 0
                    }
                } else {
                    if outline.numberOfChildren > 0 { // 有数据
                        self.leftSideEmptyVC.emptyOutlineView.removeFromSuperview()
                        self.leftSideEmptyVC.deleteOutlineBtn.isEnabled = true
                    } else { // 没有数据
                        let view = self.tocOutlineView.enclosingScrollView
                        let viewFrame = view?.frame ?? .zero
                        let emptyVcSize =  self.leftSideEmptyVC.emptyOutlineView.frame.size
    //
                        self.leftSideEmptyVC.emptyOutlineView.frame = NSMakeRect((viewFrame.size.width-emptyVcSize.width)/2.0,(viewFrame.size.height-emptyVcSize.height)/2.0, emptyVcSize.width, emptyVcSize.height)
                        self.leftSideEmptyVC.emptyOutlineView.autoresizingMask = [.minXMargin, .maxXMargin, .minYMargin, .maxYMargin]
                        self.tocOutlineView.enclosingScrollView?.documentView?.addSubview(self.leftSideEmptyVC.emptyOutlineView)
                        self.leftSideEmptyVC.deleteOutlineBtn.isEnabled = false
                        return 0
                    }
                }

                // 搜索按钮
                if outline.numberOfChildren > 0 {
                    self.outlineSearchButton.isEnabled = true
                } else {
                    self.outlineSearchButton.isEnabled = false
                }
                
                if (self.isSearchOutlineMode) {
                    var num = 0
                    for i in 0 ..< outline.numberOfChildren {
                        if let child = outline.child(at: i) {
                            if self._hasContainString(self.outlineSearchField.stringValue, rootOutline: child) {
                                num += 1
                            }
                        }
                    }
                    return num
                } else {
                    let array = self.listView.document.bookmarks() ?? [CPDFBookmark]()
                    var bookmarkNum = 0
                    if array.isEmpty == false {
                        bookmarkNum = 1
                    }
                    return Int(outline.numberOfChildren) + bookmarkNum
                }
            } else { // 第二层 +
                if self.isSearchOutlineMode {
                    if let data = item as? String, data == "Bookmarks" { // 书签group
                        return 0
                    } else if let child = item as? CPDFOutline { // 大纲
                        if child.numberOfChildren == 0 {
                            return 0
                        }
                        var num = 0
                        for i in 0 ..< child.numberOfChildren {
                            if let _child = child.child(at: i) {
                                if self._hasContainString(self.outlineSearchField.stringValue, rootOutline: _child) {
                                    num += 1
                                }
                            }
                        }
                        return num
                    } else if item is CPDFBookmark { // 书签
                        return 0
                    }
                } else {
                    if let data = item as? String, data == "Bookmarks" { // 书签group
                        return (self.listView.document?.bookmarks().count) ?? 0
                    } else if item is CPDFOutline { // 大纲
                        return Int((item as? CPDFOutline)?.numberOfChildren ?? 0)
                    } else if item is CPDFBookmark { // 书签
                        return 0
                    }
                }
            }
        } else if outlineView.isEqual(to: self.noteOutlineView) {
            if self.noteSearchMode {
                return self.noteSearchArray.count
            }
            var count = 0
            for section in self.annotations {
                if let cnt = section.annotations?.count {
                    count += cnt
                }
            }
            if (self.allAnnotations.count < 1) {
                self.noteSearchButton.isEnabled = false
                self.noteFilterButton.isEnabled = false
            } else {
                self.noteSearchButton.isEnabled = true
                self.noteFilterButton.isEnabled = true
            }
            if count < 1 {
                self.noteOutlineView.usesAlternatingRowBackgroundColors = false
                let view = self.noteOutlineView.enclosingScrollView
                let viewFrame = view?.frame ?? .zero
                let emptyVcSize =  self.leftSideEmptyVC.emptyAnnotationView.frame.size
                self.leftSideEmptyVC.emptyAnnotationView.frame = NSMakeRect((viewFrame.size.width-emptyVcSize.width)/2.0,(viewFrame.size.height-emptyVcSize.height)/2.0, emptyVcSize.width, emptyVcSize.height)
                
                self.leftSideEmptyVC.emptyAnnotationView.autoresizingMask = [.minXMargin, .maxXMargin, .minYMargin, .maxYMargin]
                self.noteOutlineView.enclosingScrollView?.documentView?.addSubview(self.leftSideEmptyVC.emptyAnnotationView)
                self.leftSideEmptyVC.exportAnnotationBtn.isEnabled = false
                self.leftSideEmptyVC.deleteAnnotationBtn.isEnabled = false
                
                if (self.leftView.segmentedControl.selectedSegment == KMSelectedSegmentType.annotation.rawValue) {
                    self.noteHeaderView.isHidden = true
                    self.toolButtonBoxLayoutConstraint.constant = 40.0
                }
            } else {
                self.noteOutlineView.usesAlternatingRowBackgroundColors = false
                
                self.leftSideEmptyVC.emptyAnnotationView.removeFromSuperview()
                self.leftSideEmptyVC.exportAnnotationBtn.isEnabled = true
                self.leftSideEmptyVC.deleteAnnotationBtn.isEnabled = true
                if (self.leftView.segmentedControl.selectedSegment == KMSelectedSegmentType.annotation.rawValue) {
                    self.noteHeaderView.isHidden = false
                    self.toolButtonBoxLayoutConstraint.constant = 64.0
                }
            }
            
            return count
        }
        return 0
    }
    
    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        if outlineView.isEqual(to: self.tocOutlineView) {
            let isLocked = self.listView.document?.isLocked ?? true
            if isLocked { // 文档不存在 或 已加锁
                return ""
            }
            
            let array = self.listView.document.bookmarks() ?? [CPDFBookmark]()
            var bookmarkNum = 0
            if array.isEmpty == false {
                bookmarkNum = 1
            }
            if item == nil {
                if self.isSearchOutlineMode {
                    guard let outline = self.listView.document.outlineRoot() else {
                        return ""
                    }
                    if outline.numberOfChildren == 0 {
                        return ""
                    }
                    var array: [CPDFOutline] = []
                    for i in 0 ..< outline.numberOfChildren {
                        if let child = outline.child(at: i) {
                            if self._hasContainString(self.outlineSearchField.stringValue, rootOutline: child) {
                                array.append(child)
                            }
                        }
                    }
                    
                    if index < array.count {
                        return array[index]
                    }
                    return ""
                } else {
                    if index == 0 && bookmarkNum == 1 {
                        return "Bookmarks"
                    } else {
                        let _index = bookmarkNum == 1 ? index-1 : index
                        let outline = self.listView.document.outlineRoot()
                        var obj = outline?.child(at: UInt(_index))
                        return obj as Any
                    }
                }
            } else {
                if let data = item as? String, data == "Bookmarks" {
                    return array[index]
                } else if let ol = item as? CPDFOutline {
                    if self.isSearchOutlineMode == false {
                        return ol.child(at: UInt(index)) as Any
                    }
                    var array: [CPDFOutline] = []
                    for i in 0 ..< ol.numberOfChildren {
                        if let _child = ol.child(at: i) {
                            if self._hasContainString(self.outlineSearchField.stringValue, rootOutline: _child) {
                                array.append(_child)
                            }
                        }
                    }
                    
                    return array[index]
                } else if item is CPDFBookmark {
                    return ""
                }
            }
        } else if outlineView.isEqual(to: self.noteOutlineView) {
            if self.noteSearchMode {
                return self.noteSearchArray[index]
            }
            var tempArray: [KMBOTAAnnotationItem] = []
            for secion in self.annotations {
                if secion.annotations?.count != 0 {
                    for _item in secion.annotations! {
                        tempArray.append(_item)
                    }
                }
            }
            return tempArray[index] as Any
        }
        return item as Any
    }
    
    func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: Any?) -> Any? {
        if outlineView.isEqual(to: self.tocOutlineView) {
//            let tcID = tableColumn?.identifier.rawValue ?? ""
//            var ol = item as? CPDFOutline
//            if(tcID == LABEL_COLUMNID) {
//                if (self.isSearchOutlineMode) {
////                    NSString *roughString = [[ol label] stringByCollapsingWhitespaceAndNewlinesAndRemovingSurroundingWhitespaceAndNewlines]? : @"";
////                    NSArray *arr = [self allRangeOfRoughString:roughString searchString:self.leftSideController.outlineSearchField.stringValue];
////                    NSMutableAttributedString *attributeString = [[[NSMutableAttributedString alloc] initWithString:roughString] autorelease];
////                    for (NSUInteger i = 0; i <arr.count ; i++) {
////                        NSValue * rangeValue = arr[i];
////                        NSRange range = rangeValue.rangeValue;
////                        range.location += i * self.leftSideController.outlineSearchField.stringValue.length;
////                        [attributeString addAttribute:NSFontAttributeName value:[NSFont boldSystemFontOfSize:13] range:range];
////                    }
////                    [attributeString addAttribute:NSForegroundColorAttributeName value:[KMAppearance KMColor_Layout_H0] range:NSMakeRange(0, roughString.length)];
////                    return attributeString;
//                } else {
//                    var attributedString = NSMutableAttributedString()
////                    attributedString
//                    attributedString.append(.init(string: ol?.label ?? "", attributes: [.foregroundColor : KMAppearance.Layout.h0Color()]))
////                    NSString *roughString = [[ol label] stringByCollapsingWhitespaceAndNewlinesAndRemovingSurroundingWhitespaceAndNewlines]? : @"";
////                    NSDictionary *dictAttr1 = @{NSForegroundColorAttributeName:[KMAppearance KMColor_Layout_H0]};
////                    NSAttributedString *attr1 = [[NSAttributedString alloc]initWithString:roughString attributes:dictAttr1];
////                    [attributedString appendAttributedString:attr1];
//
//                    return attributedString
//                }
//
//            }
//                                                                                        else if([tcID isEqualToString:PAGE_COLUMNID]) {
//                NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc]init] autorelease];
//                NSString *roughString = [ol pageLabel]? : @"";
//                NSDictionary *dictAttr1 = @{NSForegroundColorAttributeName:[KMAppearance KMColor_Layout_H0]};
//                NSAttributedString *attr1 = [[NSAttributedString alloc]initWithString:roughString attributes:dictAttr1];
//                [attributedString appendAttributedString:attr1];
//
//                return attributedString;
//            }
                                                                                        
        }
//        else if ([ov isEqual:rightSideController.noteOutlineView]) {
//            NSString *tcID = [tableColumn  identifier];
//            PDFAnnotation *note = item;
//            if (tableColumn == nil || [tcID isEqualToString:NOTE_COLUMNID])
//                return [note objectValue];
//            else if([tcID isEqualToString:TYPE_COLUMNID]){
//                NSString *noteType = [note type];
//                if ([note isKindOfClass:[PDFAnnotationButtonWidget class]]) {
//                    PDFAnnotationButtonWidget *buttonWidget = (PDFAnnotationButtonWidget *)note;
//                    if (buttonWidget.controlType == kPDFWidgetRadioButtonControl) {
//                        noteType = SKAnnotationFormRadioButtonKey;
//                    } else if (buttonWidget.controlType == kPDFWidgetCheckBoxControl){
//                        noteType = SKAnnotationFormCheckBoxKey;
//                    } else if (buttonWidget.controlType == kPDFWidgetPushButtonControl){
//                        noteType = SKAnnotationFormActionButtonKey;
//                    }
//                } else if ([note isKindOfClass:[PDFAnnotationTextWidget class]]){
//                    noteType = SKAnnotationFormTextFieldKey;
//                } else if ([note isKindOfClass:[PDFAnnotationChoiceWidget class]]){
//                    PDFAnnotationChoiceWidget *choiceWidget = (PDFAnnotationChoiceWidget *)note;
//                    if (choiceWidget.isListChoice) {
//                        noteType = SKAnnotationFormListMenuKey;
//                    } else {
//                        noteType = SKAnnotationFormComboBoxKey;
//                    }
//
//                }
//                 return [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:note == [pdfView activeAnnotation]], SKAnnotationTypeImageCellActiveKey, noteType, SKAnnotationTypeImageCellTypeKey,[note color],SKAnnotationTypeImageCellColorKey, nil];
//            }
//            else if([tcID isEqualToString:COLOR_COLUMNID])
//                return [note type] ? [note color] : nil;
//            else if([tcID isEqualToString:PAGE_COLUMNID])
//                return [[note page] displayLabel];
//            else if([tcID isEqualToString:AUTHOR_COLUMNID])
//                return [note type] ? [note userName] : nil;
//            else if([tcID isEqualToString:DATE_COLUMNID])
//                return [note type] ? [note modificationDate] : nil;
//        }
        return nil
    }
    
    func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
        if outlineView.isEqual(to: self.tocOutlineView) {
            let cell = outlineView.makeView(withIdentifier: KMTocTableCellView.km_identifier, owner: self) as! KMTocTableCellView
            let tcID = tableColumn?.identifier.rawValue
            
            var title = ""
            var pageLabel = ""
            if let data = item as? String, data == "Bookmarks" {
                title = NSLocalizedString("Bookmarks", comment: "")
            } else if let ol = item as? CPDFOutline {
                title = ol.label
                if ol.actionType == .page {
                    pageLabel = "\((ol.destination?.pageIndex ?? 0) + 1)"
                }
            } else if let bk = item as? CPDFBookmark {
                title = bk.label
                pageLabel = "\(bk.pageIndex + 1)"
            }
            
            if tcID == kLabelColumnId.rawValue {
                if (self.isSearchOutlineMode) {
//                    NSString *roughString = [[ol label] stringByCollapsingWhitespaceAndNewlinesAndRemovingSurroundingWhitespaceAndNewlines];
                    let roughString = title
                    let attributeString = NSMutableAttributedString(string: roughString)
                    let searchString = self.outlineSearchField.stringValue
                    var _roughString = roughString
                    var _searchString = searchString
                    if self.outlineIgnoreCaseFlag {
                        _roughString = _roughString.lowercased()
                        _searchString = _searchString.lowercased()
                    }
                    
                    let ranges = _roughString.ranges(of: _searchString)
                    for range in ranges.nsRnage {
                        attributeString.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: 13), range: range)
                    }
                    cell.tocLabel.attributedStringValue = attributeString
                } else {
                    cell.tocLabel.stringValue = title
                }
                cell.pageLabel.stringValue = pageLabel
            }
//                else if([tcID isEqualToString:PAGE_COLUMNID]) {
//                cell.pageLabel.stringValue = [ol pageLabel];
//            }
            return cell
        } else if outlineView.isEqual(to: self.noteOutlineView) {
            let model = item as? KMBOTAAnnotationItem
            var note: CPDFAnnotation?
            if self.noteSearchMode {
                note = item as? CPDFAnnotation
            } else {
                note = (item as? KMBOTAAnnotationItem)?.annotation
            }
            let cell = outlineView.makeView(withIdentifier: KMNoteTableViewCell.km_identifier, owner: self) as! KMNoteTableViewCell
            cell.cellNote = note
            
            let noteColor = note?.color
            var noteType = note?.type ?? ""
            var noteString = ""
            if let data = note {
                noteString = KMBOTAAnnotationTool.fetchContentLabelString(annotation: data)
            }

            let pageString = "\((note?.page?.pageIndex() ?? 0) + 1)"
            var dateString = ""
            if let date = note?.modificationDate() {
                dateString = KMTools.timeString(timeDate: date)
            }
            let authorString = note?.userName() ?? ""
            let noteTextString = note?.string() ?? ""
//            if([noteType isEqualToString:@"Redact"]) {
//                noteString = @"Redact";
//            }
//
            let timeKey = self.noteTypeDict[Self.Key.noteFilterTime] as? Bool
            if timeKey == nil || timeKey == false {
                cell.timeLabel.stringValue =  dateString
                cell.timeLabel.isHidden = false
            } else {
                cell.timeLabel.isHidden = true
            }
            
            let pageKey = self.noteTypeDict[Self.Key.noteFilterPage] as? Bool
            if pageKey == nil || pageKey == false {
                let labelsize = cell.pageLabel.stringValue.boundingRect(with: CGSizeMake(CGFloat(MAXFLOAT),CGRectGetHeight(cell.pageLabel.bounds)), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [.font : NSFont.systemFont(ofSize: 12)])
                cell.pageLabelWidthConstraint.constant = labelsize.size.width + 5
                cell.pageLabel.stringValue = pageString
                cell.pageLabel.isHidden = false
            } else {
                cell.pageLabel.isHidden = true
            }
            
            let autherKey = self.noteTypeDict[Self.Key.noteFilterAuther] as? Bool
            if autherKey == nil || autherKey == false {
                cell.autherLabel.stringValue = authorString
                cell.autherLabel.isHidden = false
            } else {
                cell.autherLabel.isHidden = true
            }

            let imageView = KMNoteTypeImageView()
            if let data = note {
                if data.isKind(of: CPDFStampAnnotation.self) {
                    let annotation_stamp = data as! CPDFStampAnnotation
                    if (annotation_stamp.stampImage() != nil) {
                        noteType = SKNStampString
                    }
                } else if data.isKind(of: CPDFLineAnnotation.self) {
                    let annotation_line = data as! CPDFLineAnnotation
                    if annotation_line.startLineStyle == .none && annotation_line.endLineStyle == .none {
                        noteType = SKNLine_NoneString
                    } else if annotation_line.startLineStyle == .none && annotation_line.endLineStyle == .openArrow {
                        noteType = SKNLine_OpenArrowString
                    } else {
                        noteType = SKNLine_NoneString
                    }
                } else if data.isKind(of: CPDFButtonWidgetAnnotation.self) {
                    let buttonWidget = data as! CPDFButtonWidgetAnnotation
                    if buttonWidget.controlType() == .radioButtonControl {
                        noteType = KMAnnotationFormRadioButtonKey
                    } else if buttonWidget.controlType() == .checkBoxControl {
                        noteType = KMAnnotationFormCheckBoxKey
                    } else if buttonWidget.controlType() == .pushButtonControl {
                        noteType = KMAnnotationFormActionButtonKey
                    }
                } else if data.isKind(of: CPDFTextWidgetAnnotation.self) {
                    noteType = KMAnnotationFormTextFieldKey
                } else if data.isKind(of: CPDFChoiceWidgetAnnotation.self) {
                    let choiceWidget = data as! CPDFChoiceWidgetAnnotation
                    if choiceWidget.isListChoice {
                        noteType = KMAnnotationFormListMenuKey
                    } else {
                        noteType = KMAnnotationFormComboBoxKey
                    }
                } else if data.isKind(of: KMTableAnnotation.self) {
                    noteType = "Ink_Table"
                } else if data.isKind(of: KMSelfSignAnnotation.self) {
                    let selfSignNote = data as! KMSelfSignAnnotation
                    if selfSignNote.annotationType == .signFalse {
                        noteType = "KMSelfSignTypeFalseActionButtonKey"
                    } else if (selfSignNote.annotationType == .signature) {
                        noteType = "KMSelfSignTypeTureActionButtonKey"
                    } else if (selfSignNote.annotationType == .signCircle) {
                        noteType = "KMSelfSignTypeCircleActionButtonKey"
                    } else if (selfSignNote.annotationType == .signLine) {
                        noteType = "KMSelfSignTypeLineActionButtonKey"
                    } else if (selfSignNote.annotationType == .signDot) {
                        noteType = "KMSelfSignTypeDotActionButtonKey"
                    } else if (selfSignNote.annotationType == .signText) {
                        noteType = "KMSelfSignTypeTextActionButtonKey"
                    }
                } else if data.isKind(of: CPDFSquareAnnotation.self) {
                    noteType = SKNSquareString
                } else if data.isKind(of: CPDFTextAnnotation.self) {
                    noteType = SKNNoteString
                } else if data.isKind(of: CPDFFreeTextAnnotation.self) {
                    noteType = SKNFreeTextString
                } else if data.isKind(of: CPDFCircleAnnotation.self) {
                    noteType = SKNCircleString
                } else if data.isKind(of: CPDFSignatureAnnotation.self) {
                    noteType = SKNSignatureString
                } else if data.isKind(of: CPDFInkAnnotation.self) {
                    noteType = SKNInkString
                } else if data.isKind(of: CPDFMarkupAnnotation.self) {
                    let anno = data as! CPDFMarkupAnnotation
                    if anno.markupType() == .highlight {
                        noteType = SKNHighlightString
                    } else if anno.markupType() == .underline {
                        noteType = SKNUnderlineString
                    } else if anno.markupType() == .strikeOut {
                        noteType = SKNStrikeOutString
                    }
                }
            }
            
            cell.typeImageView.image = imageView.noteTypeImage(withType: noteType, color: noteColor ?? .red)
            cell.typeImageView.isHidden = false

            if note is CPDFMarkupAnnotation {
                cell.isFold = true
            }

            cell.noteContentBox.isHidden = true
            cell.noteImageView.isHidden = true
            cell.foldButton.isHidden = true
            cell.annotationContentLabel.isHidden = false

            cell.noteContentLabel.stringValue = noteString

            cell.contentView.isHidden = false
            cell.contentViewHidden(false)
            if let data = note {
                if data.isKind(of: CPDFMarkupAnnotation.self) {
                    let  markup = data as! CPDFMarkupAnnotation
                    var contentString = KMBOTAAnnotationTool.fetchText(text: markup.markupContent())
                    contentString = contentString.replacingOccurrences(of: "\r", with: "")
                    contentString = contentString.replacingOccurrences(of: "\n", with: "")
                    cell.noteContentLabel.stringValue = contentString
                    if(contentString.isEmpty == false) {
                        cell.foldButton.isHidden = false
                    }
                    let attributeStr = NSMutableAttributedString(string: contentString)
                    if (markup.markupType() == .highlight) {
                        attributeStr.addAttribute(.backgroundColor, value: noteColor as Any, range: NSMakeRange(0, contentString.count))
                    } else if (markup.markupType() == .strikeOut) {
                        attributeStr.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, contentString.count))
                        attributeStr.addAttribute(.strikethroughColor, value: noteColor as Any, range: NSMakeRange(0, contentString.count))
                    } else if (markup.markupType() == .underline) {
                        attributeStr.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: NSMakeRange(0, contentString.count))
                        attributeStr.addAttribute(.underlineColor, value: noteColor as Any, range: NSMakeRange(0, contentString.count))
                    }
                    cell.annotationContentLabel.attributedStringValue = attributeStr
                    
                    if self.foldType == .unfold {
                        //                if let data = model?.foldType, data == .unfold { // 全部展开
                        cell.isFold = false
                    } else if self.foldType == .fold {
                        //                else if let data = model?.foldType, data == .fold { // 全部折叠
                        cell.isFold = true
                    } else { // 混合
                        if self.allFoldNotes.isEmpty == false && self.allFoldNotes.contains(data) {
                            cell.isFold = false
                        } else {
                            cell.isFold = true
                        }
                    }
                    // noteString.isEmpty == false &&
                    if (contentString.isEmpty == false) {
                        cell.contentViewHidden(false)
                    } else {
                        cell.contentViewHidden(true)
                    }
                } else if data.isKind(of: CPDFLineAnnotation.self) || noteType == SKNSquareString || noteType == SKNCircleString || noteType == SKNInkString {
                    noteString = data.page?.string(for: data.bounds) ?? ""
                    cell.annotationContentLabel.stringValue = noteString
                    
                    if (noteString.isEmpty == false) {
                        cell.contentViewHidden(false)
                    } else {
                        cell.contentViewHidden(true)
                    }
                } else if data.isKind(of: CPDFStampAnnotation.self) {
                    if data.isKind(of: KMSelfSignAnnotation.self) {
                        let newAnnotation = note as! KMSelfSignAnnotation
                        let type = newAnnotation.annotationType
                        var returnString = ""
                        if (type == .signFalse) {
                            returnString = KMLocalizedString("X", nil)
                        } else if (type == .signature) {
                            returnString = KMLocalizedString("Check mark", nil)
                        } else if (type == .signCircle) {
                            returnString = KMLocalizedString("Circle", nil)
                        } else if (type == .signLine) {
                            returnString = KMLocalizedString("Line", nil)
                        } else if (type == .signDot) {
                            returnString = KMLocalizedString("Dot", nil)
                        } else if (type == .signText) {
                            returnString = KMLocalizedString("Text", nil)
                        }
                        cell.annotationContentLabel.stringValue = returnString
                    } else {
                        cell.annotationContentLabel.isHidden = true
                        
                        cell.noteImageView.isHidden = false
                        let anno = note as! CPDFStampAnnotation
                        cell.noteImageView.image = anno.stampImage()
                    }
                } else if data.isKind(of: CPDFTextAnnotation.self) {
                    cell.foldButton.isHidden = false
                    if noteString.isEmpty {
                        cell.annotationContentLabel.stringValue = noteTextString
                    }else{
                        cell.annotationContentLabel.stringValue = noteString
                        cell.noteContentLabel.stringValue = noteTextString
                    }
                    
                    if (self.foldType == .unfold) {
                        cell.isFold = false
                    } else if (self.foldType == .fold) {
                        cell.isFold = true
                    } else {
                        if self.allFoldNotes.isEmpty == false && self.allFoldNotes.contains(data) {
                            cell.isFold = false
                        } else {
                            cell.isFold = true
                        }
                    }
                    if (noteString.isEmpty == false || noteTextString.isEmpty == false) {
                        cell.contentView.isHidden = false
                    } else {
                        cell.contentView.isHidden = true
                        cell.contentViewHidden(true)
                    }
                    // noteString.isEmpty == false &&
                    if (noteTextString.isEmpty == false) {
                        cell.foldButton.isHidden = false
                    }else{
                        cell.foldButton.isHidden = true
                    }
                    
                } else {
                    cell.annotationContentLabel.stringValue = noteString
                    cell.imageViewHeightConstraint.constant = cell.contentView.frame.size.height
                    if (noteString.isEmpty == false) {
                        cell.contentViewHidden(false)
                    } else {
                        cell.contentViewHidden(true)
                    }
                }
            }

            cell.autherLayoutConstraint.constant = cell.autherLabel.isHidden ? -(cell.autherLabel.bounds.size.width) + 10.0 : 10.0
            cell.typeImageViewLayoutConstraint.constant = cell.typeImageView.isHidden ? -(cell.typeImageView.bounds.size.width) : 0.0
            cell.contentBoxLayoutConstraint.constant = cell.noteContentBox.isHidden ? -(cell.noteContentBox.bounds.size.height+8.0) : 8.0
            if let data = note {
                if data.isKind(of: CPDFStampAnnotation.self) && data.isKind(of: KMSelfSignAnnotation.self) == false {
                    
                } else {
                    if data.isKind(of: CPDFMarkupAnnotation.self) {
                        if (!cell.isFold) {
                            self.allFoldNotes.append(data)
                        } else {
                            cell.imageViewHeightConstraint.constant = 18.0 + 8
                        }
                    } else {
                        cell.imageViewHeightConstraint.constant = 18.0 + 8
                    }
                }
            }
            cell.isUnFoldNote = { [unowned self] cellNote, isUnfold in
                model?.foldType = isUnfold ? .unfold : .fold
//                let COLUMN_INDENTATION: CGFloat = 16
//                if let _cell = tableColumn?.dataCell as? NSCell {
//                    _cell.objectValue = cell.annotationContentLabel.attributedStringValue
//                    let bound = NSMakeRect(0.0, 0.0, fmax(10.0, NSWidth(outlineView.frame) - COLUMN_INDENTATION - outlineView.indentationPerLevel-40), CGFLOAT_MAX)
//                    let height = _cell.cellSize(forBounds: bound).height
//                    KMPrint(height)
//                }
                if cellNote is CPDFMarkupAnnotation {
//                    let content = cell.annotationContentLabel.attributedStringValue
                    
//                    let bound = content.string.boundingRect(with: NSMakeSize(150, CGFLOAT_MAX), options: [.usesFontLeading, .usesLineFragmentOrigin], attributes: nil)
//                    KMPrint(bound)
                    // 154 34 273
                    // 273 - 188 = 85 noteContentHeightConstraint maltlineLabelLayoutConstraint
                    model?.foldH = isUnfold ? 30 : (cell.noteContentHeightConstraint.constant + cell.maltlineLabelLayoutConstraint.constant + 85)
                }
                if (isUnfold) {
                    if let data = note {
                        if (self.allFoldNotes.contains(data)) {
                            self.allFoldNotes.removeObject(data)
                        }
                    }

                    if (self.allFoldNotes.count == 0) {
                        self.foldType = .fold
                    } else {
                        self.foldType = .none
                    }
                } else {
                    if let data = note {
                        if self.allFoldNotes.contains(data) == false {
                            self.allFoldNotes.append(data)
                        }
                    }

//                    if (rightSideController.allFoldNotes.count == rightSideController.canFoldNotes.count) {
                    self.foldType = .unfold
//                    } else {
//                        self.foldType = KMFoldAllAnnotationType_None;
//                    }
                }
            }
            return cell
        }
        return nil
    }
    
    func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
        if outlineView.isEqual(self.noteOutlineView) {
            if let model = item as? KMBOTAAnnotationItem {
                if model.foldType == .fold {
                    return model.foldH
                }
                if let anno = model.annotation {
                    return KMBOTAAnnotationTool.fetchCellHeight(annotation: anno, maxSize: CGSize(width: 260+40 - 16, height: 1000))
                }
            }
            return 30
        } else if outlineView.isEqual(self.tocOutlineView) {
            if let tempItem = item as? CPDFOutline {
                let string: NSString = tempItem.label as NSString
                let paragraphStyle = NSMutableParagraphStyle()
                paragraphStyle.lineHeightMultiple = 1.32
                paragraphStyle.alignment = .left
                let attributes = [NSAttributedString.Key.paragraphStyle: paragraphStyle,
                                  NSAttributedString.Key.font : NSFont.SFProTextRegularFont(14.0)]
                let size = string.boundingRect(with: CGSizeMake(outlineView.frame.size.width - 30, 200), options: NSString.DrawingOptions(rawValue: 3), attributes: attributes)
                return max(40, size.height + 16)
            }
            return 40
        }
        return outlineView.rowHeight
    }
    
    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        if outlineView.isEqual(self.tocOutlineView) {
//            var _item: CPDFOutline?
//            if item == nil && self.listView.document.isLocked == false {
//                _item = self.listView.document.outlineRoot()
//            }
            if let data = item as? String, data == "Bookmarks" {
                return true
            } else if item is CPDFOutline {
                return ((item as? CPDFOutline)?.numberOfChildren ?? 0) != 0
            } else if item is CPDFBookmark {
                return false
            }
            
            if (self.isSearchOutlineMode) {
//                 return [self subOutLineContainString:self.leftSideController.outlineSearchField.stringValue rootOutline:(PDFOutline *)item];
            } else {
//                return ((_item?.numberOfChildren ?? 0) != 0)
            }
        } else if outlineView.isEqual(to: self.noteOutlineView) {
    //        return [item hasNoteText];
            return false
        }
        return false
    }
    
    func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? {
        if outlineView.isEqual(self.tocOutlineView) {
            let itemView = KMBotaTableRowView()
            return itemView
        } else if outlineView.isEqual(self.noteOutlineView) {
            let itemView = KMBotaTableRowView()
            return itemView;
        }
        return nil
    }
    
    func outlineViewSelectionDidChange(_ notification: Notification) {
        if self.tocOutlineView.isEqual(to: notification.object) {
            if self.dragIn {
                return
            }
            if self.updatingOutlineSelection == false {
                self.updatingOutlineSelection = true
                self.goToSelectedOutlineItem(nil)
                self.updatingOutlineSelection = false
            }
        }
    }
    
    func outlineViewItemDidExpand(_ notification: Notification) {
        if self.tocOutlineView.isEqual(to: notification.object) {
            self.updateOutlineSelection()
        }
    }
    
    func outlineViewItemDidCollapse(_ notification: Notification) {
        if self.tocOutlineView.isEqual(to: notification.object) {
            self.updateOutlineSelection()
        }
    }
    
    func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
        var dragOp = NSDragOperation(rawValue: 0)
        if outlineView.isEqual(to: self.noteOutlineView) {
            let pboard = info.draggingPasteboard
            if pboard.canReadObject(forClasses: [NSColor.self], options: [:]) && index == NSOutlineViewDropOnItemIndex {
                if let note = item as? CPDFAnnotation, note.type != nil {
                    dragOp = .every
                }
            }
        } else if outlineView.isEqual(to: self.tocOutlineView) {
            if (index == -1) {
                dragOp = NSDragOperation(rawValue: 0)
            } else {
                dragOp =  .move
            }
        }
        return dragOp
    }
    
    func outlineView(_ outlineView: NSOutlineView, writeItems items: [Any], to pasteboard: NSPasteboard) -> Bool {
        if outlineView.isEqual(to: self.tocOutlineView) {
            if (self.tocOutlineView.selectedRowIndexes.count > 1) {
                return false
            }
            let tIndex = IndexSet(integer: self.tocOutlineView.clickedRow)
            self.tocOutlineView.deselectRow(tIndex.first ?? 0)
            self._dragPDFOutline = items.first as? CPDFOutline

            let set = IndexSet(integer: 0)
            let zNSIndexSetData = NSKeyedArchiver.archivedData(withRootObject: set)
//            [pasteboard declareTypes:[NSArray arrayWithObject:kKMPDFViewOutlineDragDataType] owner:self];
            pasteboard.declareTypes([.localDraggedTypes], owner: self)
            pasteboard.setData(zNSIndexSetData, forType: .localDraggedTypes)
            return true
        }
        return false
    }
    
    func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
        if outlineView.isEqual(to: self.noteOutlineView) {
            let pboard = info.draggingPasteboard
            if pboard.canReadObject(forClasses: [NSColor.self], options: [:]) {
//                [item setColor:[NSColor colorFromPasteboard:pboard] alternate:isAlt updateDefaults:isShift];
                let isShift = NSEvent.modifierFlags.contains(.shift)
                let isAlt = NSEvent.modifierFlags.contains(.option)
                guard let note = item as? CPDFAnnotation else {
                    NSSound.beep()
                    return false
                }
                if let color = NSColor(from: pboard) {
                    note.setColor(color, alternate: isAlt, updateDefaults: isShift)
                    return true
                }
                return false
            }
        } else if outlineView.isEqual(to: self.tocOutlineView) {
            if (index < 0) {
                return false
            }
//            guard let outline = item as? CPDFOutline else {
//                return false
//            }
            let outline = item as? CPDFOutline
            
            guard let dragPDFOL = self._dragPDFOutline else {
                return false
            }

            //root,drag item to root
            if ((outline?.parent == nil)) {
                //fetch root
                var root = dragPDFOL
                while (root.parent != nil) {
                    root = root.parent
                }
                if dragPDFOL.parent.isEqual(to: root) {
                    if dragPDFOL.index > index {
                        self.dragPDFOutline(self._dragPDFOutline, toIndex: index, newParentOutline: root)
                    } else {
                        self.dragPDFOutline(self._dragPDFOutline, toIndex: index-1, newParentOutline: root)
                    }
                } else {
                    self.dragPDFOutline(self._dragPDFOutline, toIndex: index, newParentOutline: root)
                }
            } else {
                //在同一个层级内移动
                if dragPDFOL.parent.isEqual(to: item) {
                    if (dragPDFOL.index > 0) {
                        if dragPDFOL.index > index {
                            self.dragPDFOutline(self._dragPDFOutline, toIndex: index, newParentOutline: outline)
                        }else{
                            self.dragPDFOutline(self._dragPDFOutline, toIndex: index-1, newParentOutline: outline)
                        }
                    } else {
                        return false
                    }
                } else {
                    var tOutlline: CPDFOutline? = outline
                    var isContains = false
                    while (tOutlline != nil) {
                        if tOutlline!.isEqual(to: self._dragPDFOutline) {
                            isContains = true
                            break
                        }
                        tOutlline = tOutlline?.parent
                    }
                    if (!isContains) {
                        self.dragPDFOutline(self._dragPDFOutline, toIndex: index, newParentOutline: outline)
                    }
                }
            }
            return true
        }
        return false
    }
    
    func outlineView(_ outlineView: NSOutlineView, willDisplayOutlineCell cell: Any, for tableColumn: NSTableColumn?, item: Any) {
        if outlineView.isEqual(to: self.tocOutlineView) {
            if outlineView.selectionHighlightStyle == .regular && outlineView.isRowSelected(outlineView.row(forItem: item)) {
                (cell as? NSCell)?.backgroundStyle = .lowered
            }
        }
    }
    
    func outlineView(_ outlineView: NSOutlineView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, byItem item: Any?) {
        if outlineView.isEqual(to: self.noteOutlineView) {
//            PDFAnnotation *note = item;
            var note: CPDFAnnotation?
            = item as? CPDFAnnotation
            if let data = item as? CPDFAnnotation {
                note = data
            } else if let data = note as? KMBOTAAnnotationItem {
                note = data.annotation
            }
            if let data = note?.type, data.isEmpty == false {
                if tableColumn?.identifier.rawValue == self.noteColumnId.rawValue {
                    let string1 = (object as? String) ?? ""
                    let string2 = note?.string() ?? ""
                    if string1 != string2 {
                        note?.setString(string1)
                    }
                } else if tableColumn?.identifier.rawValue == self.authorColumnId.rawValue {
                    let string1 = (object as? String) ?? ""
                    let string2 = note?.userName() ?? ""
                    if string1 != string2 {
                        note?.setUserName(string1)
                    }
                }
            }
        }
    }
    
    func outlineView(_ outlineView: NSOutlineView, dataCellFor tableColumn: NSTableColumn?, item: Any) -> NSCell? {
        if self.noteOutlineView.isEqual(to: outlineView) {
            if tableColumn == nil {
                if let anno = item as? CPDFAnnotation, anno.type == nil {
                    return outlineView.tableColumn(withIdentifier: self.noteColumnId)?.dataCell(forRow: outlineView.row(forItem: item)) as? NSCell
                }
            }
        }
        return tableColumn?.dataCell(forRow: outlineView.row(forItem: item)) as? NSCell
    }
    
    func outlineView(_ outlineView: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool {
        if outlineView.isEqual(to: self.noteOutlineView) {
            if (tableColumn == nil) {
                if self.listView.hideNotes == false {
                    if let anno = item as? CPDFAnnotation, anno.isNote() {
                        //                if ([pdfView hideNotes] == NO && [[(SKNoteText *)item note] isNote]) {
                        //                    PDFAnnotation *annotation = [(SKNoteText *)item note];
                        self.listView.scrollAnnotationToVisible(anno)
                        self.listView.updateActiveAnnotations([anno])
                        //                    [self showNote:annotation];
                        //                    SKNoteWindowController *noteController = (SKNoteWindowController *)[self windowControllerForNote:annotation];
                        //                    [[noteController window] makeFirstResponder:[noteController textView]];
                        //                    [[noteController textView] selectAll:nil];
                    }
                }
                return false
            } else if tableColumn?.identifier.rawValue == self.noteColumnId.rawValue || tableColumn?.identifier.rawValue == self.authorColumnId.rawValue {
                return true
            }
        }
        return false
    }
    
    func outlineView(_ outlineView: NSOutlineView, toolTipFor cell: NSCell, rect: NSRectPointer, tableColumn: NSTableColumn?, item: Any, mouseLocation: NSPoint) -> String {
        if outlineView.isEqual(to: self.noteOutlineView) {
            if tableColumn == nil || tableColumn?.identifier.rawValue == self.noteColumnId.rawValue {
                return (item as? CPDFAnnotation)?.string() ?? ""
            }
        }
        return ""
    }
    
    func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
        self.dragIn = true
    }
    
    func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
        self.dragIn = false
    }
    
    /*

     #pragma mark NSOutlineView delegate protocol
     - (void)outlineView:(NSOutlineView *)ov didClickTableColumn:(NSTableColumn *)tableColumn {
         if ([ov isEqual:rightSideController.noteOutlineView]) {
             NSTableColumn *oldTableColumn = [ov highlightedTableColumn];
             NSTableColumn *newTableColumn = ([NSEvent modifierFlags] & NSEventModifierFlagCommand) ? nil : tableColumn;
             NSMutableArray *sortDescriptors = nil;
             BOOL ascending = YES;
             if ([oldTableColumn isEqual:newTableColumn]) {
                 sortDescriptors = [[[rightSideController.noteArrayController sortDescriptors] mutableCopy] autorelease];
                 [sortDescriptors replaceObjectAtIndex:0 withObject:[[sortDescriptors firstObject] reversedSortDescriptor]];
                 ascending = [[sortDescriptors firstObject] ascending];
             } else {
                 NSString *tcID = [newTableColumn identifier];
                 NSSortDescriptor *pageIndexSortDescriptor = [[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationPageIndexKey ascending:ascending] autorelease];
                 NSSortDescriptor *boundsSortDescriptor = [[[NSSortDescriptor alloc] initWithKey:SKPDFAnnotationBoundsOrderKey ascending:ascending selector:@selector(compare:)] autorelease];
                 sortDescriptors = [NSMutableArray arrayWithObjects:pageIndexSortDescriptor, boundsSortDescriptor, nil];
                 if ([tcID isEqualToString:TYPE_COLUMNID]) {
                     [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationTypeKey ascending:YES selector:@selector(noteTypeCompare:)] autorelease] atIndex:0];
                 } else if ([tcID isEqualToString:COLOR_COLUMNID]) {
                     [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationColorKey ascending:YES selector:@selector(colorCompare:)] autorelease] atIndex:0];
                 } else if ([tcID isEqualToString:NOTE_COLUMNID]) {
                     [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationStringKey ascending:YES selector:@selector(localizedCaseInsensitiveNumericCompare:)] autorelease] atIndex:0];
                 } else if ([tcID isEqualToString:AUTHOR_COLUMNID]) {
                     [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationUserNameKey ascending:YES selector:@selector(localizedCaseInsensitiveNumericCompare:)] autorelease] atIndex:0];
                 } else if ([tcID isEqualToString:DATE_COLUMNID]) {
                     [sortDescriptors insertObject:[[[NSSortDescriptor alloc] initWithKey:SKNPDFAnnotationModificationDateKey ascending:YES] autorelease] atIndex:0];
                 }
                 if (oldTableColumn)
                     [ov setIndicatorImage:nil inTableColumn:oldTableColumn];
                 [ov setHighlightedTableColumn:newTableColumn];
             }
             [rightSideController.noteArrayController setSortDescriptors:sortDescriptors];
             if (newTableColumn)
                 [ov setIndicatorImage:[NSImage imageNamed:ascending ? @"NSAscendingSortIndicator" : @"NSDescendingSortIndicator"]
                         inTableColumn:newTableColumn];
             [ov reloadData];
         }
     }

     - (void)outlineViewColumnDidResize:(NSNotification *)notification{
         if (mwcFlags.autoResizeNoteRows &&
             [[notification object] isEqual:rightSideController.noteOutlineView] &&
             [[[[notification userInfo] objectForKey:@"NSTableColumn"] identifier] isEqualToString:NOTE_COLUMNID] &&
             [(SKScrollView *)[[notification object] enclosingScrollView] isResizingSubviews] == NO) {
             [rowHeights removeAllFloats];
             [rightSideController.noteOutlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [rightSideController.noteOutlineView numberOfRows])]];
         }
     }

     - (void)sizeOutlineViewToContents:(NSOutlineView*) outlineView;
     {
         NSInteger rowCount = [outlineView numberOfRows];
         for (NSInteger i = 0; i < rowCount; i++){
             CGFloat rowHeight = 0;
             PDFOutline *outline = [leftSideController.tocOutlineView itemAtRow:i];
             if (!outline){
                 continue;
             }
             NSMutableAttributedString *attributedString = [[[NSMutableAttributedString alloc]init] autorelease];
             NSDictionary *dictAttr1 = @{NSForegroundColorAttributeName:[KMAppearance KMColor_Layout_H0]};
             NSAttributedString *attr1 = [[NSAttributedString alloc]initWithString:outline.label attributes:dictAttr1];
             [attributedString appendAttributedString:attr1];
             
     //        NSTableCellView *viewS = [leftSideController.tocOutlineView viewAtColumn:0 row:i  makeIfNecessary:YES];
             NSTableColumn *tableColumn = [leftSideController.tocOutlineView tableColumnWithIdentifier:LABEL_COLUMNID];
             //    id cell = [tableColumn dataCell];
             id cell = [tableColumn dataCellForRow:i];
             [cell setObjectValue:attributedString];
             CGFloat w = leftSideController.view.frame.size.width - 86;//[tableColumn width] > 260 ? [tableColumn width] : 260;
             
             NSInteger num = [self getNum:outline];
             CGFloat gap = [leftSideController.tocOutlineView indentationPerLevel];
             rowHeight = [cell cellSizeForBounds:NSMakeRect(0.0, 0.0, w - (num > 0?16:0) - gap*num, CGFLOAT_MAX)].height;
             rowHeight = fmax(rowHeight, [leftSideController.tocOutlineView rowHeight]) + 25;
             [rowHeights setFloat:rowHeight forKey:outline];
             
             if (@available(macOS 10.13, *)) {
                 
             } else {
                 rowHeight = 40.0;
             }
     //        CGRect fram = viewS.frame;
     //        viewS.frame = CGRectMake(fram.origin.x, fram.origin.y, fram.size.width, rowHeight);
         }
         [leftSideController.tocOutlineView reloadData];
     }
*/
    func noteItems(_ items: NSArray) -> NSArray {
        let noteItems = NSMutableArray()
         
        for item in items {
            guard let anno = (item as? KMBOTAAnnotationItem)?.annotation else {
                continue
            }
            if anno.type == nil {
//                 item = [(SKNoteText *)item note];
             }
            if noteItems.contains(anno) == false {
                noteItems.add(anno)
            }
         }
         return noteItems
     }
}

// MARK: - KMCustomOutlineViewDelegate, KMCustomOutlineViewDataSource

extension KMLeftSideViewController: KMCustomOutlineViewDelegate, KMCustomOutlineViewDataSource {
    func outlineView(_ anOutlineView: NSOutlineView, canDeleteItems items: [Any]) -> Bool {
        if anOutlineView.isEqual(to: self.noteOutlineView) {
            return self.listView.hideNotes == false && items.count > 0
        } else if anOutlineView.isEqual(to: self.tocOutlineView) {
            return items.count > 0
        }
        return false
    }
    
    func outlineView(_ anOutlineView: NSOutlineView, deleteItems items: [Any]) {
        if anOutlineView.isEqual(to: self.noteOutlineView) {
            if (items.isEmpty) {
                return
            }
            for item in self.noteItems(items as NSArray) {
                guard let anno = item as? CPDFAnnotation else {
                    continue
                }
                self.listView.remove(anno)
            }
            self.listView.undoManager?.setActionName(KMLocalizedString("Remove Note", "Undo action name"))
            
            self.reloadAnnotation()
        } else if anOutlineView.isEqual(to: self.tocOutlineView) {
            self.outlineContextMenuItemClicked_RemoveEntry(nil)
        }
    }

    func outlineView(_ anOutlineView: NSOutlineView, canCopyItems items: [Any]) -> Bool {
        if anOutlineView.isEqual(to: self.noteOutlineView) {
            if (items.count == 1) {
//                PDFAnnotation *annotation = [[self noteItems:items] lastObject];
//                if ([annotation isKindOfClass:[PDFAnnotationStamp class]] ||
//                    [annotation isKindOfClass:[PDFAnnotationLink class]]) {
//                    return NO;
//                }
            }
            return items.count > 0
        }
        return false
    }
    
    func outlineView(_ anOutlineView: NSOutlineView, copyItems items: [Any]) {
        if anOutlineView.isEqual(to: self.noteOutlineView) && items.isEmpty == false {
//            let pboard = NSPasteboard.general
//            var copiedItems: [Any] = []
//            var attrString = NSMutableAttributedString()
//            var isAttributed = false
//            var item: AnyObject?
            
//            for (item in [self noteItems:items]) {
//                if ([item isMovable])
//                    [copiedItems addObject:item];
//            }
//            for (item in items) {
//                if ([attrString length])
//                    [attrString replaceCharactersInRange:NSMakeRange([attrString length], 0) withString:@"\n\n"];
//                if ([(PDFAnnotation *)item type] == nil && [[(SKNoteText *)item note] isNote]) {
//                    [attrString appendAttributedString:[(SKNoteText *)item text]];
//                    isAttributed = YES;
//                } else {
//                    [attrString replaceCharactersInRange:NSMakeRange([attrString length], 0) withString:[item string] ?: @""];
//                }
//            }
//
//            [pboard clearContents];
//            if (isAttributed)
//                [pboard writeObjects:[NSArray arrayWithObjects:attrString, nil]];
//            else
//                [pboard writeObjects:[NSArray arrayWithObjects:[attrString string], nil]];
//            if ([copiedItems count] > 0)
//                [pboard writeObjects:copiedItems];
        }
    }

    func outlineView(_ anOutlineView: NSOutlineView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray {
        if self.noteOutlineView.isEqual(to: anOutlineView) {
            let count = self.noteOutlineView.numberOfRows
            let texts = NSMutableArray(capacity: count)
            for i in 0 ..< count {
                let item = self.noteOutlineView.item(atRow: i)
                let string = (item as? CPDFAnnotation)?.string() ?? ""
                texts.add(string)
            }
            return texts
        } else if self.tocOutlineView.isEqual(to: anOutlineView) {
            let count = self.tocOutlineView.numberOfRows
            let array = NSMutableArray(capacity: count)
            for i in 0 ..< count {
                //                [array addObject:[[(PDFOutline *)[leftSideController.tocOutlineView itemAtRow:i] label] lossyStringUsingEncoding:NSASCIIStringEncoding]];
                let item = self.tocOutlineView.item(atRow: i) as? CPDFOutline
                array.add(item?.label ?? "")
            }
            return array
        }
        return NSArray()
    }
    
    func outlineView(_ anOutlineView: NSOutlineView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, didFailToFindMatchForSearchString searchString: String) {
//        if ([ov isEqual:rightSideController.noteOutlineView]) {
//            [statusBar setRightStringValue:[NSString stringWithFormat:NSLocalizedString(@"No match: \"%@\"", @"Status message"), searchString]];
//        } else if ([ov isEqual:leftSideController.tocOutlineView]) {
//            [statusBar setLeftStringValue:[NSString stringWithFormat:NSLocalizedString(@"No match: \"%@\"", @"Status message"), searchString]];
//        }
    }
    
    func tableView(_ aTableView: NSTableView, typeSelectHelper aTypeSelectHelper: SKTypeSelectHelper, updateSearchString searchString: String) {
//        if ([typeSelectHelper isEqual:[leftSideController.thumbnailTableView typeSelectHelper]] || [typeSelectHelper isEqual:[pdfView typeSelectHelper]]) {
//            if (searchString)
//                [statusBar setLeftStringValue:[NSString stringWithFormat:NSLocalizedString(@"Go to page: %@", @"Status message"), searchString]];
//            else
//                [self updateLeftStatus];
//        } else if ([typeSelectHelper isEqual:[rightSideController.noteOutlineView typeSelectHelper]]) {
//            if (searchString)
//                [statusBar setRightStringValue:[NSString stringWithFormat:NSLocalizedString(@"Finding note: \"%@\"", @"Status message"), searchString]];
//            else
//                [self updateRightStatus];
//        } else if ([typeSelectHelper isEqual:[leftSideController.tocOutlineView typeSelectHelper]]) {
//            if (searchString)
//                [statusBar setLeftStringValue:[NSString stringWithFormat:NSLocalizedString(@"Finding: \"%@\"", @"Status message"), searchString]];
//            else
//                [self updateLeftStatus];
//        }
    }
}

// MARK: - Other

extension KMLeftSideViewController {
    @objc func goToSelectedOutlineItem(_ sender: AnyObject?) {
        let outlineItem = self.tocOutlineView.item(atRow: self.tocOutlineView.selectedRow)
        let outline = self.tocOutlineView
        if let cnt = outline?.selectedRowIndexes.count, cnt == 1 {
            if outlineItem is CPDFOutline {
                let outline = (outlineItem as! CPDFOutline)
                if let des = outline.destination {
                    self.listView.go(to: des)
                } else if let action = outline.action {
                    self.listView.perform(action)
                }
            } else if outlineItem is CPDFBookmark {
                let bookmark = outlineItem as! CPDFBookmark
                self.listView.go(toPageIndex: bookmark.pageIndex, animated: true)
            }
        }
    }
    
    @objc func goToSelectedFindResults(_ sender: AnyObject?) {
        KMPrint("KMLeftSideViewController-goToSelectedFindResults...")
    }
    
    func tableView(_ tv: NSTableView, extractRowsWithIndexes rowIndexes: IndexSet) {
        if tv.isEqual(to: self.thumbnailTableView) {
            guard let document = self.listView.document else {
                return
            }
            let pages = NSMutableArray()
            for idx in self.thumbnailTableView.selectedRowIndexes {
                if (idx < document.pageCount) {
                    if let page = self.listView.document.page(at: UInt(idx)) {
                        pages.add(page)
                    }
                }
            }
            let fileName = document.getFileNameAccordingSelctPages(pages as! [CPDFPage])
            let saveAccessCtr = KMSavePanelAccessoryController()
            let outputSavePanel = NSSavePanel()
            outputSavePanel.allowedFileTypes = ["pdf"]
            outputSavePanel.accessoryView = saveAccessCtr.view
            outputSavePanel.nameFieldStringValue = fileName
            outputSavePanel.beginSheetModal(for: self.view.window!) { result in
                if (result == .OK) {
                    DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
                        let vc = KMProgressWindowController()
                        self.view.window?.beginSheet(vc.window!)
                        let saveFilePath = outputSavePanel.url?.path

                        DispatchQueue.global().async {
                            let pdf = CPDFDocument()
                            let success = pdf?.extractAsOneDocument(withPages: pages as! [CPDFPage], savePath: saveFilePath) ?? false
                            DispatchQueue.main.async {
                                if (success) {
                                    if (saveAccessCtr.openAutomaticButton.state == .on) {
                                        NSDocumentController.shared.km_safe_openDocument(withContentsOf: outputSavePanel.url!, display: true) { _, _, _ in
                                            
                                        }
                                    } else {
                                        KMTools.viewFile(at: saveFilePath!)
                                    }
                                }
                                NSApp.endSheet(vc.window!)
                                vc.close()
                            }
                        }
                    }
                }
            }
        }
    }
    
    func fileNameWithSelectedPages(_ itemIndexes: IndexSet) -> String {
        var pagesName = ""
        if (itemIndexes.count > 1) {
            pagesName.append(" pages")
        } else {
            pagesName.append(" page")
        }
        
        let docmentName = self.listView.document?.documentURL.deletingPathExtension().lastPathComponent ?? ""
        let tFileName = String(format: "%@ %@", pagesName,KMTools.parseIndexSet(indexSet: itemIndexes))
        return String(format: "%@%@", docmentName,tFileName)
    }
    
    func draggedFileName(for page: CPDFPage) -> String {
        let pageIndex = "\(page.pageIndex() + 1)"
        var fileName = ""
        if let doc = self.view.window?.windowController?.document as? NSDocument {
//            fileName = doc.displayName.deletingPathExtension
            fileName = doc.fileURL?.deletingPathExtension().lastPathComponent ?? (MainBundle.nameSpaceName ?? "")
        }
        return "\(fileName)-Page \(pageIndex)"
    }
    
    func switchType(_ type: BotaType) {
        if type == .Thumbnail {
            self.leftView.segmentedControl.selectedSegment = 0
        } else if type == .Outline {
            self.leftView.segmentedControl.selectedSegment = 1
        } else if type == .Annotation {
            self.leftView.segmentedControl.selectedSegment = 2
        } else if type == .snapshot {
            self.leftView.segmentedControl.selectedSegment = 3
        } else if type == .Search {
            self.leftView.segmentedControl.selectedSegment = 4
        }
    }
    
    var TINY_SIZE: Float {
        get {
            return 32.0
        }
    }
    
    var SMALL_SIZE: Float {
        get {
            return 64.0
        }
    }
    
    var LARGE_SIZE: Float {
        get {
            return 128.0
        }
    }
    
    var HUGE_SIZE: Float {
        get {
            return 256.0
        }
    }
    
    var FUDGE_SIZE: Float {
        get {
            return 0.1
        }
    }
    
    func updateTableFont() {
        let font = NSFont.systemFont(ofSize: KMPreference.shared.outlineFontSize.cgFloat)
        self.tocOutlineView.font = font
        self.noteOutlineView.font = font
        self.findTableView.font = font
        self.groupedFindTableView.font = font
    }

}