//
//  KMLeftSideViewController.swift
//  EaseUS PDF Editor
//
//  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)
    
    @objc optional func controller(controller: KMLeftSideViewController, listViewSelectionDidChange object: Any?, info: [String : Any]?)
}

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

class KMLeftSideViewController: KMSideViewController {
    @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 bookmarkViewC: KMBookMarkViewController?
    var noteFilterButtonHoverView = KMHoverView()
    var noteFilterSelected = false
    
    var stopRepeatLoad: Bool = false
    
    lazy var leftSideEmptyVC: KMLeftSideEmptyFileViewController = {
        let vc = KMLeftSideEmptyFileViewController(nibName: "KMLeftSideEmptyFileViewController", bundle: nil)
        vc.view.wantsLayer = true
        return vc
    }()
    
    lazy var leftView: KMBotaLeftView = {
        let view = KMBotaLeftView()
        return view
    }()

    static let kMinSidePaneWidth: CGFloat       = 270
    static let kTinySize: Float                 = 32.0
    static let kSmallSize: Float                = 64.0
    static let kLargeSize: Float                = 128.0
    static let kHugeSize: Float                 = 256.0
    static let kFudgeSize: Float                = 0.1
    static let kOutlineRootBookmarkItem = "Bookmarks"
    
    var isFirst = false  //打开阅读页初始化
    
    var type : KMLeftMethodMode = KMLeftMethodMode()
    var isShowPanel : Bool = false
    weak var delegate: KMLeftSideViewControllerDelegate?
    
    let noteColumnId = NSUserInterfaceItemIdentifier(rawValue: "note")
    let authorColumnId = NSUserInterfaceItemIdentifier(rawValue: "author")
    
    struct Key {}
    
    var mwcFlags: MwcFlags = MwcFlags()
    let scalingIncrement: Float = 0.1
    
    var filterButtonLayer: NSView?
    var moreButtonLayer: KMButtonLayer?
    
    // 缩略图
    var thumbnails: [KMThumbnail] = []
    var isDisplayPageSize = false
    var thumbnailCacheSize: CGFloat = 32 * 3
    var updatingThumbnailSelection = false
    
    // 大纲
    var isSearchOutlineMode = false
    var outlineIgnoreCaseFlag = false
    var tocType: KMFoldType = .none
    var renamePDFOutline: CPDFOutline?
    var renamePDFOutlineTextField: NSTextField?
    private var _dragPDFOutline: CPDFOutline?
    var updatingOutlineSelection = false
    
    // 注释
    var noteTypeDict: [String : Any] = [:]
    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)
        }
    }
    
    var isRenameNoteOutline = false

    // 所有注释
    var allAnnotations: [CPDFAnnotation] = []
    // 注释搜索模式标记
    var noteSearchMode = false
    // 注释搜索数组
    var noteSearchArray: [KMBotaAnnotationBaseModel] = []
    // 注释搜索 忽略大小写标识
    var caseInsensitiveNoteSearch = false
    // 注释列表数据源
    var annoListModel: KMAnnotationListModel?
    var editNoteTextField: NSTextField?
    weak var editNote: CPDFAnnotation?
    
    // 快照
    var snapshotCacheSize: CGFloat = 32 * 3
    var isSearchSnapshotMode = false
    var snapshots: [KMSnapshotModel] = []
    var dirtySnapshots: [KMSnapshotWindowController] = []
    var searchSnapshots: [KMSnapshotModel] = []
    var dataUpdating = false
    
    // 搜索
    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(nil)
                } else if self.findState == .snapshot {
                    self.searchNotes(self.searchField)
                }
            }
        }
    }
    
    var searchResults : [KMSearchMode] = [] {
        didSet {
            self.updataLeftSideFindView()
        }
    }
    var groupSearchResults: [KMSearchMode] = []
    var findPaneState: KMFindPaneState = .singular
    var searchResultIndex: Int = 0
    
    var leftMargin: CGFloat = 0
    
    private var _copysPages: [CPDFPage] = []
    var copyPages: [CPDFPage] {
        get {
            return self._copysPages
        }
    }
    var dragIn = false
    
    let model = KMBotaModel()
    
    private let KPDFThumbnailDoucumentURLForDraggedTypes  = NSPasteboard.PasteboardType("KPDFThumbnailDoucumentURLForDraggedTypes")
    
    private var deletePages_ = Set<CPDFPage>()
    
    let userFbHanddler = KMUserFeekbackHanddler()
    let noteReplyHanddler = KMNoteReplyHanddler()
    
    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.noteReplyHanddler.viewC = self
        
        self.reloadThumbnailSize()
        self.reloadSnapshotSize()
    }
    
    func showPanelView(show: Bool) {
        self.isShowPanel = show
        if show {
            self.leftView.segmentedControl.selectedSegment = 1
        } 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)
    }
    
    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 = { [weak self] segIndex in
            if segIndex == 0 {
                self?.findPaneState = .singular
                self?.displayFindViewAnimating(false)
            } else {
                self?.findPaneState = .grouped
                self?.displayGroupedFindViewAnimating(false)
            }
        }
        self.leftView.helpButton.coverAction = { [weak self] button, action in
            if action == .enter {
                let hasEnterPageEdit = self?.mainViewController?.hasEnterPageEdit() ?? false
                if hasEnterPageEdit {
                    return
                }
                let hasEnterRedact = self?.mainViewController?.hasEnterRedact() ?? false
                if hasEnterRedact {
                    return
                }
                
                self?.userFbHanddler.showUserFbPopView(sender: button)
            }
        }
        self.leftView.itemClick = { itemV, idx in
            if idx == 1 { // TTS
                
                
                self.delegate?.controller?(controller: self, itemClick: nil, itemKey: .tts, params: nil)
            }
        }
        self.leftView.segmentedControl.block = { [unowned self] segIndex in
            if (segIndex == 1) {
                if self.type.methodType == .Thumbnail {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                self.refreshMethodType(methodType: .Thumbnail)
                KMMainThreadExecute {
                    self.toolButtonBox.isHidden = false
                    self.toolButtonBoxLayoutConstraint.constant = 40.0
                    self.toolButtonBox.contentView = self.thumbnailView
                    self.displayThumbnailViewAnimating(false)
                }
            } else if (segIndex == 2) {
                if self.type.methodType == .Outline {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                self.refreshMethodType(methodType: .Outline)
                DispatchQueue.main.async {
                    self.toolButtonBox.isHidden = false
                    self.toolButtonBoxLayoutConstraint.constant = 40.0
                    self.toolButtonBox.contentView = self.outlineView
                    
                    self.bookmarkViewC?.view.isHidden = true
                    self.displayTocViewAnimating(false)
                }
            } else if (segIndex == 3) {
                if self.type.methodType == .Annotation {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                self.refreshMethodType(methodType: .Annotation)
                DispatchQueue.main.async {
                    self.toolButtonBox.contentView = self.noteView
                    self.toolButtonBox.isHidden = false
                    self.toolButtonBoxLayoutConstraint.constant = 40.0
                    
                    self.bookmarkViewC?.view.isHidden = true
                    self.displayNoteViewAnimating(false)
                }
            } else if (segIndex == 4) {
                if self.type.methodType == .BookMark {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                self.refreshMethodType(methodType: .BookMark)
                KMMainThreadExecute {
//                DispatchQueue.main.async {
                    self.toolButtonBox.isHidden = true
                    self.toolButtonBox.contentView = nil
                    self.toolButtonBoxLayoutConstraint.constant = 0
                    self.displayBookmarkController()
                }
            } else if (segIndex == 5) {
                if self.type.methodType == .snapshot {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                self.refreshMethodType(methodType: .snapshot)
                self.toolButtonBox.contentView = self.snapshotNormalView
                self.toolButtonBox.isHidden = false
                self.toolButtonBoxLayoutConstraint.constant = 40.0
                
                self.updateSnapshotFilterPredicate()
                self.displaySnapshotViewAnimating(false)
                self.updataLeftSideSnapView()
            } else if (segIndex == 0) {
                if self.type.methodType == .Search {
                    self.leftView.segmentedControl.selectedSegment = UInt8.max
                    self.refreshMethodType(methodType: .None)
                    return
                }
                self.refreshMethodType(methodType: .Search)
                DispatchQueue.main.async {
                    self.toolButtonBox.isHidden = true
                    self.toolButtonBox.contentView = nil
                    self.toolButtonBoxLayoutConstraint.constant = 0
                    self.bookmarkViewC?.view.isHidden = true
                    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.leftView.km_add_leading_constraint()
        self.leftView.km_add_top_constraint()
        self.leftView.km_add_height_constraint(equalTo: self.view)
        self.leftView.km_add_width_constraint(constant: 44)
        
        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) {
        self.bookmarkViewC?.view.isHidden = true
        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.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.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)
        
        self.reloadAnnotation()
    }
    
    func displayBookmarkController() {
        if self.bookmarkViewC == nil {
            let viewC = KMBookMarkViewController(nibName: "KMBookMarkViewController", bundle: nil)
            self.bookmarkViewC = viewC
            self.bookmarkViewC?.listView = self.listView
        }
        if let view = self.bookmarkViewC?.view {
            self.bookmarkViewC?.view.isHidden = false
            self.bookmarkViewC?.listView = self.listView
            self.replaceSideView(view, animate: false)
            
            self.bookmarkViewC?.reloadData()
        }
    }
    
    func displaySnapshotViewAnimating(_ animate: Bool) {
        self.searchViewController.contentView = nil
        self.bookmarkViewC?.view.isHidden = true
        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
        
        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 Thread.current.isMainThread {
            self.tocOutlineView.reloadData()
        } else {
            Task { @MainActor in
                self.tocOutlineView.reloadData()
            }
        }
        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()
        }
    }
    
    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(ks: Bool = true) {
        self.thumbnails.removeAll()
        
        KMThumbnailCache.shared.clearCache()
        
        let pageLabels = self.pdfDocument()?.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)
            }
        }
        
        KMMainThreadExecute {
            let ris = self.thumbnailTableView.selectedRowIndexes
            self.thumbnailTableView.reloadData()
            if ks {
                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
            self.noteFilterButtonHoverView.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
            } 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.pdfDocument()?.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.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.currentPageIndex()
                if (index == NSNotFound) {
                    return
                }
                let idx = index + 1
                for page in self._copysPages.reversed() {
                    self.pdfDocument()?.insertPageObject(page, at: UInt(idx))
                    self.layoutDocumentView()
                    self.resetThumbnails()
                    let pageIndex = min(idx, self.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(.selectingPrevious)
        }
    }
    
    func tableViewMoveRight(_ aTableView: NSTableView) {
        if aTableView.isEqual(to: self.findTableView) || aTableView.isEqual(to: self.groupedFindTableView) {
            self.updateFindResultHighlightsForDirection(.selectingNext)
        }
    }
    
    func tableViewMoveUp(_ aTableView: NSTableView) {
        if aTableView.isEqual(to: self.findTableView) {
            self.findTableView.km_safe_selectRowIndexes(.init(integer: self.findTableView.selectedRow-1), byExtendingSelection: false)
            self.findTableView.scrollRowToVisible(self.findTableView.selectedRow)
        }
    }
    
    func tableViewMoveDown(_ aTableView: NSTableView) {
        if aTableView.isEqual(to: self.findTableView) {
            self.findTableView.km_safe_selectRowIndexes(.init(integer: self.findTableView.selectedRow+1), byExtendingSelection: false)
            self.findTableView.scrollRowToVisible(self.findTableView.selectedRow)
        }
    }
    
    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 x = selection.bounds.origin.x + NSWidth(selection.bounds) * 0.5
            let y = selection.bounds.origin.y + NSHeight(selection.bounds) * 0.5
            let point = NSPoint(x: x, y: y)
            return CPDFDestination(document: self.pdfDocument(), pageIndex: Int(model.selectionPageIndex), at: point, zoom: self.scaleFactor().cgFloat)
        } 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
    }
}

// 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)
        }
    }
    
    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
            if self.thumbnails.count > row {
                let thumbnail = self.thumbnails[row]
                let page = self.pdfDocument()?.page(at: UInt(row))
                cell.pageNumLabel.stringValue = thumbnail.label
                cell.pageView.isThumb = true
                cell.pageView.page = page
                
                let bks = self.listView?.document.bookmarks()
                var hasBookmark = false
                for bk in bks ?? [] {
                    if row == bk.pageIndex {
                        hasBookmark = true
                        break
                    }
                }
                cell.bkIcon.isHidden = !hasBookmark

                if (self.isDisplayPageSize) {
                    cell.sizeLabel.isHidden = false
                    //获取Page的真实尺寸
                    let rect = page?.bounds(for: .cropBox) ?? .zero
                    let w =  KMPageSizeTool.conversion(withUnit: "mm", value: (NSWidth(rect)/595 * 210))
                    let h =  KMPageSizeTool.conversion(withUnit: "mm", value: (NSHeight(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.stringValue = "0 mm"
                    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
            if searchResults.count > row {
                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.selectionPageIndex) + 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 {
                if self.searchSnapshots.count > row {
                    snapshot = self.searchSnapshots[row]
                }
            } else {
                if self.snapshots.count > row {
                    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 > Self.kMinSidePaneWidth) {
                self.thumbnailZoomOutButton.isEnabled = false
            } else {
                self.thumbnailZoomOutButton.isEnabled = true
            }
            if ((scaling - 0.1) < 0.4) {
                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 ?? NSMakeSize(120, 63)
            var newScaling = scaling + 0.1
            let newSnapshotHeight = snapshotSize.width * newScaling.cgFloat
            if (newSnapshotHeight > Self.kMinSidePaneWidth) {
                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.stopRepeatLoad == true {
            
        } else {
            self.delegate?.controller?(controller: self, listViewSelectionDidChange: notification.object, info: nil)
        }
        
        
        if self.findTableView.isEqual(to: notification.object) {
            //            [self updateFindResultHighlightsForDirection:NSDirectSelection];
            let row = self.findTableView.selectedRow
            if row >= 0 {
                let model = self.searchResults[row]
                let isEditing =  self.listView?.isEditing() ?? false
                if isEditing {
                    self.mainViewController?.srHanddler.showSelection(model.selection)
                    return
                }
                
                self.listView?.go(to: model.selection, animated: true)
//                self.listView?.setHighlightedSelection(model.selection, animated: true)
                DispatchQueue.main.asyncAfter(deadline: .now()+0.3) {
                    self.listView?.setHighlightedSelections([model.selection])
                    self.listView?.setNeedsDisplayAnnotationViewForVisiblePages()
                }
            }
        } else if self.groupedFindTableView.isEqual(to: notification.object) {
            //            [self updateFindResultHighlightsForDirection:NSDirectSelection];
        } else if self.thumbnailTableView.isEqual(to: notification.object) {
            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.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.pdfDocument()?.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 {
                                        self.model.insertedDocumentSet.insert(pdf!)
                                        for i in 0 ..< (pdf?.pageCount ?? 0) {
                                            if let page = pdf?.page(at: i).copy() as? CPDFPage {
                                                self.pdfDocument()?.insertPageObject(page, at: UInt(index))
                                                insertIndexSet.insert(index)
                                                index += 1
                                            }
                                        }
                                        self.insertPages(insertIndexSet, pageAt: index-1)
                                    }
                                }
                            } else {
                                if let data = pdf {
                                    self.model.insertedDocumentSet.insert(data)
                                }
                                for i in 0 ..< (pdf?.pageCount ?? 0) {
                                    let page = pdf?.page(at: i)
                                    self.pdfDocument()?.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.pdfDocument()?.page(at: UInt(idx))?.copy() as? CPDFPage {
                    pages.append(page)
                }
            }
            var toPage: CPDFPage?
            if row < self.pageCount() {
                toPage = self.pdfDocument()?.page(at: UInt(row))
            }
            var toPageIndex = 0
            if (toPage != nil) {
                toPageIndex = Int(self.pdfDocument()?.index(for: toPage) ?? 0)
            } else {
                toPageIndex = self.pageCount()
            }
            for i in 0 ..< pages.count {
                let page = pages[i]
                self.pdfDocument()?.insertPageObject(page, at: UInt(toPageIndex+i))
                pageIndex += 1
                self.listView?.go(toPageIndex: toPageIndex+i-1, animated: false)
            }
            self.layoutDocumentView()
            for idx in rowIndexes {
                if idx < toPageIndex {
                    if((idx+pageIndex) < self.pageCount()) {
                        if let page = self.pdfDocument()?.page(at: UInt(idx)) {
                            self.deletePages_.insert(page)
                        }
                        self.pdfDocument()?.removePage(at: IndexSet(integer: idx))
                    }
                } else {
                    if((idx+pageIndex) < self.pageCount()) {
                        if let page = self.pdfDocument()?.page(at: UInt(idx+pageIndex)) {
                            self.deletePages_.insert(page)
                        }
                        self.pdfDocument()?.removePage(at: IndexSet(integer: idx+pageIndex))
                    }
                }
            }
            if rowIndexes.isEmpty == false {
                self.listView?.updateActiveAnnotations([])
                if let win = self.view.window {
                    KMTools.setDocumentEditedState(window: win)
                }
            }
            self.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 != nil {
                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 {
                        self.view.window?.windowController?.kCheckPassword(url: url!, type: .owner, completion: { [weak self] success, resultPassword in
                            if success && resultPassword.isEmpty == false {
                                pdf?.unlock(withPassword: resultPassword)
                                self?.model.insertedDocumentSet.insert(pdf!)
                                for i in 0 ..< (pdf?.pageCount ?? 0) {
                                    if let page = pdf?.page(at: i) {
                                        self?.pdfDocument()?.insertPageObject(page, at: UInt(index))
                                        insertIndexSet.insert(index)
                                        index += 1
                                    }
                                }
                                self?.insertPages(insertIndexSet, pageAt: index-1)
                            }
                        })
                    } else {
                        if let data = pdf {
                            self.model.insertedDocumentSet.insert(data)
                        }
                        for i in 0 ..< (pdf?.pageCount ?? 0) {
                            if let page = pdf?.page(at: i) {
                                self.pdfDocument()?.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.pdfDocument()?.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.isLocked() == false) {
                let page = self.pdfDocument()?.page(at: UInt(idx))
                var fileExt: String?
                let tiffData = page?.PDFListViewTIFFData(for: page?.bounds(for: self.displayBox()) ?? .zero)
                if self.allowsPrinting() {
//                    NSData *pdfData = [page dataRepresentation];
                    fileExt = "pdf"
                    // filenames
                    pboard.declareTypes([.pdf, .tiff, .fileURL, .filePromise, .localDraggedTypes, KPDFThumbnailDoucumentURLForDraggedTypes], owner: self)
                    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.pdfDocument()?.documentURL?.absoluteString)
//                    NSString *docmentName = [[[pdfView.document.documentURL path] lastPathComponent] stringByDeletingPathExtension];
                    var docmentName = self.pdfDocument()?.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.pdfDocument()?.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) {
            guard let last = rowIndexes.last, last < self.pageCount() else {
                return
            }
            //删除page时移除选中状态的annotation
            self.listView?.activeAnnotations = []
            for idx in rowIndexes {
                if let page = self.pdfDocument()?.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.pdfDocument()?.removePage(at: IndexSet(integer: idx))
                    self.deletePages_.insert(page)
                }
            }
            self.pdfDocument()?.removePage(at: rowIndexes)
            self.layoutDocumentView()
            self.resetThumbnails()
            let idx = rowIndexes.first ?? -1
            let index = min(idx, self.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.isLocked() == false {
                for idx in rowIndexes {
                    if let page = self.pdfDocument()?.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.isLocked() == false {
                        let copyPage = self.pdfDocument()?.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.pdfDocument()?.page(at: UInt(indexSet.first ?? 0)) {
                    var fileURL = dropDestination.appendingPathComponent(self.draggedFileName(for: page))
                    if self.allowsPrinting() {
                        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.displayBox()))
                        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.pdfDocument()?.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) {
            if self.isLocked() {
                return 0
            }
            if item == nil { // 第一层
//                let bks = self.bookmarks() ?? []
//                let bkNum = bks.isEmpty ? 0 : 1
                let bks: [CPDFBookmark] = []
                let bkNum = 0
                
                guard let rootOL = self.outlineRoot() else {
                    if bkNum > 0 {
                        
                    } else {
                        self.showOutlineEmptyView()
                    }
                    return 0 + bkNum
                }
                // 搜索按钮
                self.outlineSearchButton.isEnabled = rootOL.numberOfChildren > 0
                
                if self.isSearchOutlineMode { // 是否为搜索模块
                    if self.hasContainString(self.outlineSearchField.stringValue, rootOutline: rootOL) == false {
                        self.showSearchOutlineBlankState(true)
                        return 0
                    }
                    self.showSearchOutlineBlankState(false)
                    let ols = self.fetchOutlines(for: rootOL, searchString: self.outlineSearchField.stringValue)
                    return ols.count
                }
                
                if rootOL.numberOfChildren == 0 { // 没有数据
                    if bkNum > 0 {
                        
                    } else {
                        self.showOutlineEmptyView()
                    }
                    return 0 + bkNum
                }
                
                // 有数据
                self.hideOutlineEmptyView()
                return Int(rootOL.numberOfChildren) + bkNum
            } else { // 第二层 +
                if item is CPDFBookmark { // 书签
                    return 0
                }
                if let data = item as? String, data == Self.kOutlineRootBookmarkItem { // 书签group
                    if self.isSearchOutlineMode {
                        return 0
                    }
                    return (self.bookmarks()?.count) ?? 0
                }
                
                guard let ol = item as? CPDFOutline, ol.numberOfChildren > 0 else {
                    return 0
                }
                if self.isSearchOutlineMode == false {
                    return Int(ol.numberOfChildren)
                }
                // 搜索模式
                let ols = self.fetchOutlines(for: ol, searchString: self.outlineSearchField.stringValue)
                return ols.count
            }
        } else if outlineView.isEqual(to: self.noteOutlineView) {
            if self.noteSearchMode {
//                return self.noteSearchArray.count
                var cnt = 0
                for model in self.noteSearchArray {
                    cnt += 2
                    guard let data = model as? KMBotaAnnotationModel else {
                        continue
                    }
                    if data.isExpand == false {
                        continue
                    }
                    for replyI in data.replyAnnos {
                        cnt += 1
                    }
                }
                return cnt
            }
//            let count = self.annoListModel?.datas.count ?? 0
            var cnt = 0
            for sectionM in self.annoListModel?.datas ?? [] {
                if sectionM.items.count > 0 {
                    cnt += 1
                    if sectionM.isExpand == false {
                        continue
                    }
                    for item in sectionM.items {
                        cnt += 1
                        if let annoItem = item as? KMBotaAnnotationModel {
                            if annoItem.isExpand == false {
                                continue
                            }
                            for replyItem in annoItem.replyAnnos {
                                cnt += 1
                            }
                        }
                    }
                }
            }
                
            let hasAnno = self.allAnnotations.count >= 1
            self.noteSearchButton.isEnabled = hasAnno
            self.noteFilterButton.isEnabled = hasAnno
            self.noteOutlineView.usesAlternatingRowBackgroundColors = false
            if cnt < 1 {
                self.showNoteEmptyView()
            } else {
                self.hideNoteEmptyView()
            }
                
            return cnt
        }
        return 0
    }
    
    func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
        if outlineView.isEqual(to: self.tocOutlineView) {
            if self.isLocked() {
                return ""
            }
            
//            let bks = self.bookmarks() ?? []
//            let bkNum = bks.isEmpty ? 0 : 1
            let bks: [CPDFBookmark] = []
            let bkNum = 0
            if item == nil {
                if self.isSearchOutlineMode {
                    guard let rootOL = self.outlineRoot(), rootOL.numberOfChildren > 0 else {
                        return ""
                    }
                    let ols = self.fetchOutlines(for: rootOL, searchString: self.outlineSearchField.stringValue)
                    return ols.safe_element(for: index) as Any
                }
                if index == 0 && bkNum == 1 {
                    return Self.kOutlineRootBookmarkItem
                }
                let idx = bkNum == 1 ? index-1 : index
                let rootOL = self.outlineRoot()
                return rootOL?.child(at: UInt(idx)) as Any
            } else {
                if let data = item as? String, data == Self.kOutlineRootBookmarkItem {
                    return bks[index]
                }
                if item is CPDFBookmark {
                    return ""
                }
                if let ol = item as? CPDFOutline {
                    if self.isSearchOutlineMode == false {
                        return ol.child(at: UInt(index)) as Any
                    }
                    let ols = self.fetchOutlines(for: ol, searchString: self.outlineSearchField.stringValue)
                    return ols.safe_element(for: index) as Any
                }
            }
        } else if outlineView.isEqual(to: self.noteOutlineView) {
            if self.noteSearchMode {
//                return self.noteSearchArray[index]
                var idx = 0
                var models: [KMBotaAnnotationBaseModel] = []
                for model in self.noteSearchArray {
                    models.append(model)
                    if let data = (model as? KMBotaAnnotationModel)?.footerModel {
                        models.append(data)
                    }
                }
                for model in models {
                    idx += 1
                    if index + 1 == idx {
                        return model
                    }
//                    if index + 2 == idx {
//                        return (model as? KMBotaAnnotationModel)?.footerModel
//                    }
                    guard let data = model as? KMBotaAnnotationModel else {
                        continue
                    }
                    if data.isExpand == false {
                        continue
                    }
                    for replyI in data.replyAnnos {
                        idx += 1
                        if index + 1 == idx {
                            return replyI
                        }
                    }
                }
                return 0
            }
            
            var idx = 0
            var flagSectionM: KMBotaAnnotationSectionModel?
            var flagItem: KMBotaAnnotationBaseModel?
            var replyIten: KMBotaAnnotationReplyModel?
            var isSection = false
            var isReply = false
            for sectionM in self.annoListModel?.datas ?? [] {
                idx += 1
                if index + 1 == idx {
                    flagSectionM = sectionM
                    isSection = true
                    break
                }
                if sectionM.isExpand == false {
                    continue
                }
                for item in sectionM.items {
                    idx += 1
                    if index + 1 == idx {
                        flagSectionM = sectionM
                        flagItem = item
                        break
                    }
                    guard let annoItem = item as? KMBotaAnnotationModel else {
                        continue
                    }
                    if annoItem.isExpand == false {
//                        flagItem = annoItem
                        continue
                    }
                    for replyI in annoItem.replyAnnos {
                        idx += 1
                        if index + 1 == idx {
                            flagSectionM = sectionM
                            flagItem = item
                            replyIten = replyI
                            isReply = true
                            break
                        }
                    }
                }
            }
            if isSection {
                return flagSectionM
            }
            if isReply {
                return replyIten
            }
            return flagItem
        }
        return item as Any
    }
    
    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 columnId = tableColumn?.identifier.rawValue
            
            var title = ""
            var pageLabel = ""
            if let data = item as? String, data == Self.kOutlineRootBookmarkItem {
                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)"
            }
            
            cell.pageLabel.stringValue = pageLabel
            if columnId == kLabelColumnId.rawValue {
                if self.isSearchOutlineMode {
                    let attriString = NSMutableAttributedString(string: title)
                    var searchString = self.outlineSearchField.stringValue
                    var roughString = title
                    if self.outlineIgnoreCaseFlag {
                        roughString = roughString.lowercased()
                        searchString = searchString.lowercased()
                    }
                    
                    let ranges = roughString.ranges(of: searchString)
                    for range in ranges.nsRnage {
                        attriString.addAttribute(.font, value: NSFont.boldSystemFont(ofSize: 13), range: range)
                    }
                    cell.tocLabel.attributedStringValue = attriString
                } else {
                    cell.tocLabel.stringValue = title
                }
            }
            cell.tocLabel.textColor = KMAppearance.titleColor()
            return cell
        } else if outlineView.isEqual(to: self.noteOutlineView) {
            if let data = item as? KMBotaAnnotationSectionModel {
                var cell = outlineView.makeView(withIdentifier: KMSectionCellView.km_identifier, owner: nil) as? KMSectionCellView
                if cell == nil {
                    cell = KMSectionCellView.createFromNib()
                }
                cell?.titleLabel.textColor = KMAppearance.titleColor()
                cell?.countLabel.textColor = KMAppearance.titleColor()
                
                cell?.isExpand = data.isExpand
                cell?.contentInset = NSEdgeInsets(top: 0, left: 8, bottom: 0, right: 0)
                if self.noteSortType == .page {
                    let pageIndex = data.items.first?.anno?.page.pageIndex() ?? 0
                    cell?.titleLabel.stringValue = NSLocalizedString("Page", comment: "") + " \(pageIndex + 1)"
                } else {
                    if let date = data.items.first?.anno?.modificationDate() {
                        let string = KMTools.timeString(timeDate: date, formatString: "yyyy-MM-dd")
                        cell?.titleLabel.stringValue = string
                    } else {
                        cell?.titleLabel.stringValue = ""
                    }
                }
                cell?.countLabel.stringValue = "\(data.itemCount / 2)"
                
                cell?.itemClick = { idx, _ in
                    if idx == 1 { // 收取 & 展开
                        data.isExpand = !data.isExpand
                        
                        self.noteOutlineView.reloadData()
                    }
                }
                return cell
            }
            if let data = item as? KMBotaAnnotationFooterModel {
                var cell = outlineView.makeView(withIdentifier: KMNoteFooterCellView.km_identifier, owner: self) as? KMNoteFooterCellView
                if cell == nil {
                    cell = KMNoteFooterCellView.createFromNib()
                }
                cell?.model = data
                
                let cnt = self.noteReplyHanddler.fetchReplyAnnotations(data.anno)?.count ?? 0
                if cnt == 0 {
                    cell?.commentNumberLabel.stringValue = ""
                } else {
                    cell?.commentNumberLabel.stringValue = "\(cnt)"
                }
                
                let state = self.noteReplyHanddler.fetchReviewState(data.anno) ?? .none
                if state == .none {
                    cell?.operationIv.image = NSImage(named: "KMImageNameBotaNoteStateNone")
                } else if state == .completed {
                    cell?.operationIv.image = NSImage(named: "KMImageNameBotaNoteStateCompleted")
                } else if state == .canceled {
                    cell?.operationIv.image = NSImage(named: "KMImageNameBotaNoteStateCancelled")
                } else if state == .accepted {
                    cell?.operationIv.image = NSImage(named: "KMImageNameBotaNoteStateAccepted")
                } else if state == .rejected {
                    cell?.operationIv.image = NSImage(named: "KMImageNameBotaNoteStateRejected")
                }
                cell?.operationBox.toolTip = KMPDFAnnotationStateGetString(state: state)
                
                if let con = data.replyModel?.replyAnno?.contents, con.isEmpty == false {
                    cell?.inputTextF.stringValue = con
                    DispatchQueue.main.async {
                        self.view.window?.makeFirstResponder(cell?.inputTextF)
                    }
                } else {
                    if let cont = data.inputContent {
                        cell?.inputTextF.stringValue = cont
                    } else {
                        cell?.inputTextF.stringValue = ""
                    }
                }
                
                cell?.updateUI(expand: data.isExpand, animated: data.animated)
                data.animated = false
                
                if data.isFirstResp {
                    DispatchQueue.main.async {
                        self.view.window?.makeFirstResponder(cell?.inputTextF)
                    }
                    data.isFirstResp = false
                }
                
                cell?.inputDidChanged = { value, _ in
                    data.inputContent = value as? String ?? ""
                }
                
                cell?.itemClick = { [weak self] idx, param in
                    if idx == 1 { // comment
                        data.isExpand = !data.isExpand
                        data.animated = true
                        data.isFirstResp = true
//                        data.annoModel?.isExpand = data.isExpand
                        self?.noteOutlineView.reloadItem(data)
//                        self?.noteOutlineView.reloadData()
                    } else if idx == 2 { //
                        self?.noteReplyHanddler.showStatePopView(sender: param.first as! NSView, anno: data.anno)
                    } else if idx == 3 { // reply
                        let content = param.first as? String ?? ""
                        if content.isEmpty {
//                            Task {
//                                _ = await KMAlertTool.runModel(message: NSLocalizedString("Error", comment: ""))
//                            }
                            return
                        }
                        
                        if let con = data.replyModel?.replyAnno?.contents, con.isEmpty == false { // 编辑
                            let model = data.replyModel
                            model?.replyAnno?.contents = content
                            model?.replyAnno?.setUserName(KMPreference.shared.author)
                            model?.replyAnno?.setModificationDate(Date())
                            
                            // 置空编辑状态
                            data.replyModel = nil
                            data.inputContent = nil
                            self?.noteOutlineView.reloadData()
                            if let row = self?.noteOutlineView.row(forItem: data) {
                                self?.noteOutlineView.scrollRowToVisible(row)
                            }
                            return
                        }
                        if let replyAnno = self?.noteReplyHanddler.createReplyAnnotation(data.anno, content: content, userName: KMPreference.shared.author) {
//                            self?.reloadAnnotation()
                            let model = KMBotaAnnotationReplyModel()
                            model.anno = data.anno
                            model.replyAnno = replyAnno
                            model.annoModel = data.annoModel
                            data.annoModel?.replyAnnos.append(model)
                            data.inputContent = nil
                            self?.noteOutlineView.reloadData()
                            if let row = self?.noteOutlineView.row(forItem: data) {
                                self?.noteOutlineView.scrollRowToVisible(row)
                            }
                        }
                    } else if idx == 4 { // cancel
                        data.isExpand = false
                        data.replyModel = nil
                        data.inputContent = nil
                        self?.noteOutlineView.reloadItem(data)
                    }
                }
                
                return cell
            }
            if let data = item as? KMBotaAnnotationReplyModel {
                var cell = outlineView.makeView(withIdentifier: KMNoteReplyCellView.km_identifier, owner: self) as? KMNoteReplyCellView
                if cell == nil {
                    cell = KMNoteReplyCellView.createFromNib()
                }
                cell?.model = data
                cell?.titleLabel.stringValue = data.replyAnno?.userName() ?? ""
                if let data = data.replyAnno?.modificationDate() {
                    cell?.timeLabel.stringValue = KMTools.timeString(timeDate: data)
                } else {
                    cell?.timeLabel.stringValue = ""
                }
                cell?.contentLabel.stringValue = data.replyAnno?.contents ?? ""
                cell?.itemClick = { [weak self] idx, params in
                    if idx == 1 { // 更多
                        self?.noteReplyHanddler.showReplyMorePopView(sender: params.first as! NSView, replyModel: data)
                    }
                }
                return cell
            }
            let model = item as? KMBotaAnnotationModel
            let note = (item as? KMBotaAnnotationModel)?.anno
            let cell = outlineView.makeView(withIdentifier: KMNoteTableViewCell.km_identifier, owner: self) as! KMNoteTableViewCell
            cell.cellNote = note
            cell.model = model
            let state = self.noteReplyHanddler.fetchAnnoState(note) ?? .unMarked
            cell.state = state
            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.itemClick = { [weak self] idx , _ in
                if idx == 1 { // Markup
                    let anno = model?.anno
                    guard let state = self?.noteReplyHanddler.fetchAnnoState(anno), state == .marked else {
                        self?.noteReplyHanddler.markAnnotation(anno)
                        
//                        if let theA = anno {
//                            self?.note_reloadDataForAnnoIfNeed(anno: theA)
//                        }
                        self?.noteOutlineView.reloadItem(model)
                        return
                    }
                    self?.noteReplyHanddler.unMarkAnnotation(anno)
                    
//                    if let theA = anno {
//                        self?.note_reloadDataForAnnoIfNeed(anno: theA)
//                    }
                    self?.noteOutlineView.reloadItem(model)
                }
            }
            cell.isUnFoldNote = { [weak self] cellNote, isUnfold in
                // 将折叠状态记录到模型
                model?.foldType = isUnfold  ? .unfold : .fold
                
//                self?.note_reloadDataForAnnoIfNeed(anno: <#T##CPDFAnnotation#>)
                model?.footerModel?.isExpand = isUnfold
                model?.isExpand = isUnfold
//                self?.noteOutlineView.reloadItem(model?.footerModel)
                self?.noteOutlineView.reloadData()
                DispatchQueue.main.async {
                    if let row = self?.noteOutlineView.row(forItem: item) {
                        self?.noteOutlineView.scrollRowToVisible(row)
                    }
                }
            }
            return cell
        }
        return nil
    }
    
    func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
        if outlineView.isEqual(self.noteOutlineView) {
            if let model = item as? KMBotaAnnotationModel {
                if model.isExpand == false {
//                if model.foldType == .fold {
                    return model.foldH
                }
                if let anno = model.anno {
                    return KMBOTAAnnotationTool.fetchCellHeight(annotation: anno, maxSize: CGSize(width: 260+40 - 16, height: 1000))
                }
            }
            return 30
        } else if outlineView.isEqual(self.tocOutlineView) {
            if let ol = item as? CPDFOutline {
                let string: NSString = ol.label as NSString
                let ps = NSMutableParagraphStyle()
                ps.lineHeightMultiple = 1.32
                ps.alignment = .left
                let attris = [NSAttributedString.Key.paragraphStyle : ps,
                                  NSAttributedString.Key.font           : NSFont.SFProTextRegularFont(14.0)]
                let size = string.boundingRect(with: NSMakeSize(outlineView.frame.size.width - 30, 200), options: NSString.DrawingOptions(rawValue: 3), attributes: attris)
                return max(40, size.height + 16)
            }
            return 40
        }
        return outlineView.rowHeight
    }
    
    func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
        if outlineView.isEqual(self.tocOutlineView) {
            if let data = item as? String, data == Self.kOutlineRootBookmarkItem {
                return true
            }
            if item is CPDFBookmark {
                return false
            }
            guard let ol = item as? CPDFOutline else {
                return false
            }
            if self.isSearchOutlineMode == false {
                return ol.numberOfChildren > 0
            }
            let ols = self.fetchOutlines(for: ol, searchString: self.outlineSearchField.stringValue)
            return ols.count > 0
        } else if outlineView.isEqual(to: self.noteOutlineView) {
            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()
            if let data = item as? KMBotaAnnotationBaseModel {
                itemView.isSelected = data.isSelected
                
                
            }
            itemView.selectCallback = { theView in
                let isSelected = theView.isSelected
                
                if theView.numberOfColumns > 0 {
                    let cellView = theView.view(atColumn: 0)
                    if let data = cellView as? KMSectionCellView {
                        return
                    }
                    var model: KMBotaAnnotationModel?
                    var indexs = IndexSet()
                    if let data = cellView as? KMNoteFooterCellView {
                        model = data.model?.annoModel
                        model?.isSelected = isSelected
                        
                        self.view.window?.makeFirstResponder(data.inputTextF) 
                    }
                    if let data = cellView as? KMNoteReplyCellView {
                        model = data.model?.annoModel
                        
                        model?.isSelected = isSelected
                    }
                    if let data = cellView as? KMNoteTableViewCell {
                        model = data.model
                        model?.isSelected = isSelected
                    }
//
                    if let data = model {
//                        let row = self.noteOutlineView.row(for: cellView as! NSView)
                        let row = self.noteOutlineView.row(forItem: data)
                        indexs.insert(row)
                        var i = 1
                        for item in model?.replyAnnos ?? [] {
                            indexs.insert(row+i)
                            i += 1
                        }
                        indexs.insert(row+i)
//                        self.noteOutlineView.km_safe_selectRowIndexes(indexs, byExtendingSelection: false)
//                        self.noteOutlineView.selectRowIndexes(indexs, byExtendingSelection: false)
//                        self.noteOutlineView.km_selectRow(indexs.first!)
//                        self.noteOutlineView.selectedItem()
                        
                    }
                    KMPrint("")
                }
               
            }
            return itemView;
        }
        return nil
    }
    
    func outlineView(_ outlineView: NSOutlineView, selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet) -> IndexSet {
        if outlineView.isEqual(to: self.noteOutlineView) {
            var indexs = proposedSelectionIndexes
            for i in proposedSelectionIndexes {
                let item = self.noteOutlineView.item(atRow: i)
                var model: KMBotaAnnotationModel?
                if let data = item as? KMBotaAnnotationSectionModel {
                    
                }
                if let data = item as? KMBotaAnnotationModel {
                    model = data
                }
                if let data = item as? KMBotaAnnotationReplyModel {
                    model = data.annoModel
                }
                if let data = item as? KMBotaAnnotationFooterModel {
                    model = data.annoModel
                }
                
                if let data = model {
//                        let row = self.noteOutlineView.row(for: cellView as! NSView)
                    let row = self.noteOutlineView.row(forItem: data)
                    indexs.insert(row)
                    var i = 1
                    if data.isExpand {
                        for item in data.replyAnnos {
                            indexs.insert(row+i)
                            i += 1
                        }
                    }
                    indexs.insert(row+i)
                }
            }
            return indexs
        }
        return proposedSelectionIndexes
    }
    
    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
            }
            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 {
//                        if let bks = self.bookmarks(), bks.count > 0 {
//                            if let data = item as? String, data == Self.kOutlineRootBookmarkItem {
//
//                            } else {
//                                if index > 1 {
//                                    self.dragPDFOutline(self._dragPDFOutline, toIndex: index-2, 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) {
            var note: CPDFAnnotation?
            if let data = item as? CPDFAnnotation {
                note = data
            } else if let data = item as? KMBotaAnnotationModel {
                note = data.anno
            }
            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.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? KMBotaAnnotationModel)?.anno 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.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
            }
            self.dataUpdating = true
            for item in self.noteItems(items as NSArray) {
                guard let anno = item as? CPDFAnnotation else {
                    continue
                }
                if let data = anno as? CPDFFreeTextAnnotation {
                    self.listView?.commitEditAnnotationFreeText(data)
                }
                self.listView?.remove(anno)
            }
            for item in items {
                if let data = item as? KMBotaAnnotationModel {
                    data.sectionModel?.items.removeObject(data)
                    if let footerModel = data.footerModel {
                        data.sectionModel?.items.removeObject(footerModel)
                    }
                }
            }
            self.dataUpdating = false
            self.listView?.undoManager?.setActionName(KMLocalizedString("Remove Note", "Undo action name"))
            
            self.note_refrshUIIfNeed()
        } 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()
    }
}

// MARK: - Other

extension KMLeftSideViewController {
    func tableView(_ tv: NSTableView, extractRowsWithIndexes rowIndexes: IndexSet) {
        if tv.isEqual(to: self.thumbnailTableView) {
            guard let document = self.pdfDocument() else {
                return
            }
            let pages = NSMutableArray()
            for idx in self.thumbnailTableView.selectedRowIndexes {
                if (idx < document.pageCount) {
                    if let page = self.pdfDocument()?.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.pdfDocument()?.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 = 1
        } else if type == .Outline {
            self.leftView.segmentedControl.selectedSegment = 2
        } else if type == .Annotation {
            self.leftView.segmentedControl.selectedSegment = 3
        } else if type == .snapshot {
            self.leftView.segmentedControl.selectedSegment = 4
        } else if type == .Search {
            self.leftView.segmentedControl.selectedSegment = 0
        }
    }
    
    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
    }
}