KMLeftSideViewController.swift 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664
  1. //
  2. // KMLeftSideViewController.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by lxy on 2022/10/10.
  6. //
  7. import Cocoa
  8. import KMComponentLibrary
  9. // MARK: - KMBotaAnnotationHeaderView
  10. class KMBotaAnnotationHeaderView: NSView {
  11. private lazy var titleLabel_: NSTextField = {
  12. let view = NSTextField(labelWithString: "")
  13. view.font = ComponentLibrary.shared.getFontFromKey("mac/body-m-bold")
  14. view.textColor = KMNColorTools.colorText_2()
  15. return view
  16. }()
  17. private lazy var searchButton_: ComponentButton = {
  18. let view = ComponentButton()
  19. let prop = ComponentButtonProperty()
  20. prop.type = .text_gray
  21. prop.size = .xxs
  22. prop.onlyIcon = true
  23. prop.icon = NSImage(named: "KMImageNameOutlineSearch")
  24. view.properties = prop
  25. return view
  26. }()
  27. private lazy var sortButton_: ComponentButton = {
  28. let view = ComponentButton()
  29. let prop = ComponentButtonProperty()
  30. prop.type = .text_gray
  31. prop.size = .xxs
  32. prop.onlyIcon = true
  33. prop.icon = NSImage(named: "KMImageNameBotaSearch")
  34. view.properties = prop
  35. return view
  36. }()
  37. private lazy var moreButton_: ComponentButton = {
  38. let view = ComponentButton()
  39. let prop = ComponentButtonProperty()
  40. prop.type = .text_gray
  41. prop.size = .xxs
  42. prop.onlyIcon = true
  43. prop.icon = NSImage(named: "KMImageNameOutlineMore")
  44. view.properties = prop
  45. return view
  46. }()
  47. private lazy var bottomLine_: NSView = {
  48. let view = NSView()
  49. view.wantsLayer = true
  50. return view
  51. }()
  52. var titleLabel: NSTextField {
  53. get {
  54. return titleLabel_
  55. }
  56. }
  57. var searchButton: ComponentButton {
  58. get {
  59. return searchButton_
  60. }
  61. }
  62. var sortButton: ComponentButton {
  63. get {
  64. return sortButton_
  65. }
  66. }
  67. var moreButton: ComponentButton {
  68. get {
  69. return moreButton_
  70. }
  71. }
  72. var bottomLine: NSView {
  73. get {
  74. return bottomLine_
  75. }
  76. }
  77. var itemClick: KMCommonClickBlock?
  78. convenience init() {
  79. self.init(frame: .init(x: 0, y: 0, width: 300, height: 40))
  80. initSubviews()
  81. }
  82. override func awakeFromNib() {
  83. super.awakeFromNib()
  84. initSubviews()
  85. }
  86. func initSubviews() {
  87. let margin: CGFloat = 12
  88. addSubview(titleLabel_)
  89. titleLabel_.km_add_leading_constraint(constant: margin)
  90. titleLabel_.km_add_centerY_constraint()
  91. let vspace: CGFloat = 8
  92. let wh: CGFloat = 24
  93. addSubview(moreButton_)
  94. moreButton_.km_add_size_constraint(size: .init(width: wh, height: wh))
  95. moreButton_.km_add_trailing_constraint(constant: -margin)
  96. moreButton_.km_add_centerY_constraint()
  97. moreButton_.setTarget(self, action: #selector(moreAction))
  98. addSubview(sortButton_)
  99. sortButton_.km_add_size_constraint(size: .init(width: wh, height: wh))
  100. sortButton_.km_add_trailing_constraint(equalTo: moreButton_, attribute: .leading, constant: -vspace)
  101. sortButton_.km_add_centerY_constraint()
  102. sortButton_.setTarget(self, action: #selector(sortAction))
  103. addSubview(searchButton_)
  104. searchButton_.km_add_size_constraint(size: .init(width: wh, height: wh))
  105. searchButton_.km_add_trailing_constraint(equalTo: sortButton_, attribute: .leading, constant: -vspace)
  106. searchButton_.km_add_centerY_constraint()
  107. searchButton_.setTarget(self, action: #selector(searchAction))
  108. addSubview(bottomLine_)
  109. bottomLine_.km_add_leading_constraint()
  110. bottomLine_.km_add_trailing_constraint()
  111. bottomLine_.km_add_bottom_constraint()
  112. bottomLine_.km_add_height_constraint(constant: 1)
  113. }
  114. @objc func searchAction() {
  115. itemClick?(1)
  116. }
  117. @objc func sortAction() {
  118. itemClick?(2)
  119. }
  120. @objc func moreAction() {
  121. itemClick?(3)
  122. }
  123. }
  124. // MARK: - KMLeftSideViewControllerDelegate
  125. @objc protocol KMLeftSideViewControllerDelegate {
  126. @objc optional func controlStateChange(_ obj: KMLeftSideViewController,show:Bool)
  127. @objc optional func enterEditMode(_ obj: KMLeftSideViewController, _ pages: [Int])
  128. @objc optional func searchAction(searchString:String, isCase:Bool)
  129. @objc optional func controller(controller: KMLeftSideViewController, itemClick item: Any?, itemKey: KMItemKey, params: Any?)
  130. @objc optional func controller(controller: KMLeftSideViewController, rotateType: KMRotateType)
  131. @objc optional func controller(controller: KMLeftSideViewController, listViewSelectionDidChange object: Any?, info: [String : Any]?)
  132. @objc optional func listViewThumPageIndeDidChange(controller: KMLeftSideViewController, pageIndexs: IndexSet)
  133. }
  134. extension KMLeftSideViewController.Key {
  135. static let disableTableToolTipsKey = "SKDisableTableToolTips"
  136. }
  137. class KMLeftSideViewController: KMSideViewController {
  138. @IBOutlet var searchViewController: KMBotaSearchViewController!
  139. @IBOutlet weak var toolButtonBox: NSBox!
  140. @IBOutlet weak var toolButtonBoxLayoutConstraint: NSLayoutConstraint!
  141. @IBOutlet var noteOutlineView: KMNoteOutlineView!
  142. var bookmarkViewC: KMBookMarkViewController?
  143. var noteFilterButtonHoverView = KMHoverView()
  144. var noteFilterSelected = false
  145. var stopRepeatLoad: Bool = false
  146. var dataUpdating = false
  147. @IBOutlet weak var leftListView: NSView!
  148. @IBOutlet weak var toolHeaderBox: NSBox!
  149. var noteFilterMenu:NSMenu?
  150. var isFirst = false //打开阅读页初始化
  151. var isShowPanel : Bool = false
  152. weak var delegate: KMLeftSideViewControllerDelegate?
  153. let noteColumnId = NSUserInterfaceItemIdentifier(rawValue: "note")
  154. let authorColumnId = NSUserInterfaceItemIdentifier(rawValue: "author")
  155. struct Key {}
  156. var mwcFlags: MwcFlags = MwcFlags()
  157. let scalingIncrement: Float = 0.1
  158. var filterButtonLayer: NSView?
  159. var moreButtonLayer: KMButtonLayer?
  160. // 注释
  161. var noteTypeDict: [String : Any] = [:]
  162. private var _noteSortType: KMNoteSortType = .none
  163. var noteSortType: KMNoteSortType {
  164. get {
  165. return self._noteSortType
  166. }
  167. set {
  168. self._noteSortType = newValue
  169. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMAnnotationSortTypeKeyNotification"), object: self)
  170. }
  171. }
  172. var isAscendSort = false {
  173. didSet {
  174. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "KMAnnotationSortTypeKeyNotification"), object: self)
  175. }
  176. }
  177. var headerView: KMNBotaAnnotationHeaderView = {
  178. let view = KMNBotaAnnotationHeaderView()
  179. return view
  180. }()
  181. var isRenameNoteOutline = false
  182. var currentItem: Any?
  183. // 所有注释
  184. var allAnnotations: [CPDFAnnotation] = []
  185. // 注释搜索模式标记
  186. var noteSearchMode = false
  187. // 注释搜索数组
  188. var noteSearchArray: [KMBotaAnnotationBaseModel] = []
  189. // 注释搜索 忽略大小写标识
  190. var caseInsensitiveNoteSearch = false
  191. // 注释列表数据源
  192. var annoListModel: KMAnnotationListModel?
  193. var editNoteTextField: NSTextField?
  194. weak var editNote: CPDFAnnotation?
  195. var leftMargin: CGFloat = 0
  196. var dragIn = false
  197. private var deletePages_ = Set<CPDFPage>()
  198. let userFbHanddler = KMUserFeekbackHanddler()
  199. let noteReplyHanddler = KMNoteReplyHanddler()
  200. var searchGroupView: ComponentGroup?
  201. var searchGroupTarget: ComponentButton?
  202. var moreGroupView: ComponentGroup?
  203. var rightGroupView: ComponentGroup?
  204. var emptyView: ComponentEmpty = {
  205. let view = ComponentEmpty()
  206. view.properties = ComponentEmptyProperty(emptyType: .noAnnotation, state: .normal, image: NSImage(named: "KMImageNameEmptyAnnotate"), text: KMLocalizedString("No Annotations"), subText: "")
  207. return view
  208. }()
  209. private lazy var headerSearchView_: KMNBotaHeaderSearchView? = {
  210. let view = KMNBotaHeaderSearchView.createFromNib()
  211. return view
  212. }()
  213. var headerSearchView: KMNBotaHeaderSearchView? {
  214. get {
  215. return headerSearchView_
  216. }
  217. }
  218. deinit {
  219. KMPrint("KMLeftSideViewController deinit.")
  220. NotificationCenter.default.removeObserver(self)
  221. DistributedNotificationCenter.default().removeObserver(self)
  222. }
  223. override var nibName: NSNib.Name? {
  224. return "LeftSideView"
  225. }
  226. override func viewDidLoad() {
  227. super.viewDidLoad()
  228. noteReplyHanddler.viewC = self
  229. }
  230. override func loadView() {
  231. super.loadView()
  232. searchViewController.loadView()
  233. note_initSubViews()
  234. note_initDefalutValue()
  235. displayEmptyViewAnimating(false)
  236. displayNoteViewAnimating(false)
  237. hideHeaderSearch()
  238. toolHeaderBox.borderWidth = 0
  239. toolHeaderBox.contentView = headerView
  240. headerView.titleLabel.textColor = KMNColorTools.colorText_2()
  241. headerView.itemClick = { [weak self] idx, _ in
  242. if idx == 1 {
  243. self?.showHeaderSearch()
  244. } else if idx == 2 {
  245. self?.showFilterController()
  246. } else if idx == 3 {
  247. self?.showMoreGroupView()
  248. }
  249. }
  250. headerSearchView?.itemClick = { [weak self] idx, params in
  251. if idx == 1 { // 显示搜索限制条件
  252. guard let button = params.first as? ComponentButton else {
  253. return
  254. }
  255. self?.showSearchGroupView(sender: button)
  256. } else if idx == 2 { // 关闭搜索
  257. self?.hideHeaderSearch()
  258. self?.noteSearchMode = false
  259. self?.reloadAnnotation()
  260. }
  261. }
  262. headerSearchView?.valueDidChange = { [weak self] sender, info in
  263. let value = info?[.newKey] as? String ?? ""
  264. self?.noteSearchMode = !value.isEmpty
  265. self?.updateNoteFilterPredicate(searchString: value)
  266. }
  267. }
  268. override func updateUILanguage() {
  269. super.updateUILanguage()
  270. KMMainThreadExecute {
  271. self.headerView.titleLabel.stringValue = KMLocalizedString("Annotation")
  272. }
  273. }
  274. override func updateUIThemeColor() {
  275. super.updateUIThemeColor()
  276. KMMainThreadExecute {
  277. self.headerView.searchButton.properties.icon = NSImage(named: "KMImageNameOutlineSearch")
  278. self.headerView.searchButton.reloadData()
  279. self.headerView.sortButton.properties.icon = NSImage(named: "KMImagenameBotaAnnotationFilter")
  280. self.headerView.sortButton.reloadData()
  281. self.headerView.moreButton.properties.icon = NSImage(named: "KMImageNameOutlineMore")
  282. self.headerView.moreButton.reloadData()
  283. self.headerView.bottomLine.layer?.backgroundColor = KMNColorTools.colorBorder_divider().cgColor
  284. }
  285. }
  286. func showHeaderSearch() {
  287. toolHeaderBox.contentView = headerSearchView
  288. headerView.searchButton.properties.state = .normal
  289. headerView.searchButton.reloadData()
  290. }
  291. func hideHeaderSearch() {
  292. toolHeaderBox.contentView = headerView
  293. }
  294. func updateMarkState(model:KMBotaAnnotationModel){ // Markup
  295. let anno = model.anno
  296. guard let state = self.noteReplyHanddler.fetchAnnoState(anno), state == .marked else {
  297. self.noteReplyHanddler.markAnnotation(anno)
  298. self.noteOutlineView.reloadItem(model)
  299. return
  300. }
  301. self.noteReplyHanddler.unMarkAnnotation(anno)
  302. self.noteOutlineView.reloadItem(model)
  303. }
  304. func updateReplaState(model:KMBotaAnnotationModel,state:CPDFAnnotationState){
  305. let anno = model.anno
  306. noteReplyHanddler.updateAnnoState(anno: anno, state: state)
  307. self.noteOutlineView.reloadData()
  308. }
  309. func updateExpand (model:KMBotaAnnotationModel,isExpand:Bool,item: Any) {
  310. // 将折叠状态记录到模型
  311. model.foldType = isExpand ? .unfold : .fold
  312. model.footerModel?.isExpand = isExpand
  313. model.isExpand = isExpand
  314. self.noteOutlineView.reloadData()
  315. DispatchQueue.main.async {
  316. let row = self.noteOutlineView.row(forItem: item)
  317. self.noteOutlineView.scrollRowToVisible(row)
  318. }
  319. }
  320. // MARK: - Filter
  321. func showFilterController() {
  322. noteFilterAction(nil)
  323. }
  324. // MARK: - Group View
  325. func showMoreGroupView() {
  326. let groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle())
  327. var expand_MenuString = KMLocalizedString("Expand All")
  328. for secM in self.annoListModel?.datas ?? [] {
  329. if secM.isExpand == false {
  330. expand_MenuString = KMLocalizedString("Collapse All")
  331. break
  332. }
  333. }
  334. let expand_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: expand_MenuString,
  335. identifier: BOTAMenuIdentifier_Annotation_Expand)
  336. let sort_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Sort"),
  337. identifier: BOTAMenuIdentifier_Annotation_Sort)
  338. var subMenuItemArr: [ComponentMenuitemProperty] = []
  339. let pageProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("By Page"),identifier: BOTAMenuIdentifier_Annotation_SortPage)
  340. let timeAscProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("By Time - Ascending"),identifier: BOTAMenuIdentifier_Annotation_SortAscTime)
  341. let timeDecProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(
  342. text: KMLocalizedString("By Time - Descending"),identifier: BOTAMenuIdentifier_Annotation_SortDesTime)
  343. subMenuItemArr.append(pageProperty)
  344. subMenuItemArr.append(timeAscProperty)
  345. subMenuItemArr.append(timeDecProperty)
  346. sort_Menuitem.subPropertys = subMenuItemArr
  347. let improt_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Import Annotations"),
  348. identifier: BOTAMenuIdentifier_Annotation_Improt)
  349. let exprot_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Import Annotations"),
  350. identifier: BOTAMenuIdentifier_Annotation_Improt)
  351. let rem_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Remove All Annotations"),
  352. identifier: BOTAMenuIdentifier_Annotation_RemoveAll)
  353. let delete_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Delete All Reply"),
  354. identifier: BOTAMenuIdentifier_Annotation_DeleteRep)
  355. var menuItemArr: [ComponentMenuitemProperty] = []
  356. menuItemArr.append(expand_Menuitem)
  357. menuItemArr.append(sort_Menuitem)
  358. menuItemArr.append(improt_Menuitem)
  359. menuItemArr.append(exprot_Menuitem)
  360. menuItemArr.append(rem_Menuitem)
  361. menuItemArr.append(delete_Menuitem)
  362. moreGroupView = groupView
  363. groupView?.groupDelegate = self
  364. groupView?.frame = CGRectMake(310, 0, 200, 36.0 * CGFloat(menuItemArr.count))
  365. groupView?.updateGroupInfo(menuItemArr)
  366. var point = NSPoint(x: CGRectGetMaxX(headerView.moreButton.frame), y: CGRectGetMidY(headerView.moreButton.frame))
  367. point = headerView.convert(point, to: nil)
  368. point.y -= (groupView?.frame.size.height ?? 0)/2
  369. groupView?.showWithPoint(point, relativeTo: headerView.moreButton)
  370. }
  371. func showRightGropView(raw: Int,event:NSEvent,view:NSView?,item:Any) {
  372. let groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle())
  373. currentItem = item
  374. var menuItemArr: [ComponentMenuitemProperty] = []
  375. let rowIndexes = self.selectedObjcNotes()
  376. var viewHeight = 0.0
  377. if(rowIndexes.count == 1) {
  378. let dataItem = rowIndexes.first
  379. if let data = dataItem as? KMBotaAnnotationModel {
  380. if let an = data.anno as? CPDFAnnotation {
  381. if an.isKind(of: CPDFFreeTextAnnotation.self) == true ||
  382. an.isKind(of: CPDFTextAnnotation.self) == true {
  383. } else {
  384. let note_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Notes"),
  385. identifier: BOTAMenuIdentifier_Annotation_EditNote,representedObject: [data])
  386. menuItemArr.append(note_Menuitem)
  387. viewHeight += 36.0
  388. }
  389. let reply_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Reply"),
  390. identifier: BOTAMenuIdentifier_Annotation_AddRep,representedObject: [data])
  391. menuItemArr.append(reply_Menuitem)
  392. viewHeight += 36.0
  393. let state = self.noteReplyHanddler.fetchAnnoState(an) ?? .unMarked
  394. var markString = ""
  395. if state == .unMarked {
  396. markString = KMLocalizedString("Marked")
  397. } else {
  398. markString = KMLocalizedString("Unmarked")
  399. }
  400. let mark_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: markString,
  401. identifier: BOTAMenuIdentifier_Annotation_AddMark,representedObject: [data])
  402. menuItemArr.append(mark_Menuitem)
  403. viewHeight += 36.0
  404. let state_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Status"),
  405. identifier: BOTAMenuIdentifier_Annotation_RepState)
  406. var subMenuItemArr: [ComponentMenuitemProperty] = []
  407. let property0: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("None"),identifier: BOTAMenuIdentifier_Annotation_RepStateNone,representedObject: [data])
  408. let property1: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Accepted"),identifier: BOTAMenuIdentifier_Annotation_RepStateAccepted,representedObject: [data])
  409. let property2: ComponentMenuitemProperty = ComponentMenuitemProperty(
  410. text: KMLocalizedString("Rejected"),identifier: BOTAMenuIdentifier_Annotation_RepStateRejected,representedObject: [data])
  411. let property3: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Cancelled"),identifier: BOTAMenuIdentifier_Annotation_RepStateCancelled,representedObject: [data])
  412. let property4: ComponentMenuitemProperty = ComponentMenuitemProperty(
  413. text: KMLocalizedString("Completed"),identifier: BOTAMenuIdentifier_Annotation_RepStateCompleted,representedObject: [data])
  414. subMenuItemArr.append(property0)
  415. subMenuItemArr.append(property1)
  416. subMenuItemArr.append(property2)
  417. subMenuItemArr.append(property3)
  418. subMenuItemArr.append(property4)
  419. let reviewState = noteReplyHanddler.fetchReviewState(an) ?? .none
  420. if reviewState == .none {
  421. property0.righticon = NSImage(named: "KMNImageNameMenuSelect")
  422. } else if reviewState == .completed {
  423. property4.righticon = NSImage(named: "KMNImageNameMenuSelect")
  424. } else if reviewState == .canceled {
  425. property3.righticon = NSImage(named: "KMNImageNameMenuSelect")
  426. } else if reviewState == .accepted {
  427. property1.righticon = NSImage(named: "KMNImageNameMenuSelect")
  428. } else if reviewState == .rejected {
  429. property2.righticon = NSImage(named: "KMNImageNameMenuSelect")
  430. }
  431. state_Menuitem.subPropertys = subMenuItemArr
  432. menuItemArr.append(state_Menuitem)
  433. viewHeight += 36.0
  434. menuItemArr.append(ComponentMenuitemProperty.divider())
  435. viewHeight += 8.0
  436. var showString = ""
  437. if data.isExpand {
  438. showString = KMLocalizedString("Collapse")
  439. } else {
  440. showString = KMLocalizedString("Expand")
  441. }
  442. let show_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: showString,
  443. identifier: PDFViewMenuIdentifier_Normal_ShowPopUI,representedObject: [data])
  444. menuItemArr.append(show_Menuitem)
  445. viewHeight += 36.0
  446. menuItemArr.append(ComponentMenuitemProperty.divider())
  447. viewHeight += 8.0
  448. if an.isKind(of: CPDFFreeTextAnnotation.self) == true ||
  449. an.isKind(of: CPDFTextAnnotation.self) == true ||
  450. an.isKind(of: CPDFMarkupAnnotation.self) == true {
  451. let copy_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Copy Text"),
  452. identifier: PDFViewMenuIdentifier_Normal_Copy,representedObject: [data])
  453. copy_Menuitem.keyEquivalent = "⌘ C"
  454. menuItemArr.append(copy_Menuitem)
  455. viewHeight += 36.0
  456. }
  457. let delete_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Delete"),
  458. identifier: PDFViewMenuIdentifier_Normal_Delete,representedObject: [data])
  459. delete_Menuitem.keyEquivalent = "⌘ " + "⌫"
  460. menuItemArr.append(delete_Menuitem)
  461. viewHeight += 36.0
  462. }
  463. } else if let replyModel = dataItem as? KMBotaAnnotationReplyModel {
  464. let note_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Notes"),
  465. identifier: BOTAMenuIdentifier_Annotation_EditNote,representedObject: [replyModel])
  466. menuItemArr.append(note_Menuitem)
  467. viewHeight += 36.0
  468. let copy_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Copy Text"),
  469. identifier: PDFViewMenuIdentifier_Normal_CopyText,representedObject: [replyModel])
  470. copy_Menuitem.keyEquivalent = "⌘ C"
  471. menuItemArr.append(copy_Menuitem)
  472. viewHeight += 36.0
  473. let delete_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Delete"),
  474. identifier: BOTAMenuIdentifier_Annotation_DeleteSignRep,representedObject: [replyModel])
  475. delete_Menuitem.keyEquivalent = "⌘ " + "⌫"
  476. menuItemArr.append(delete_Menuitem)
  477. viewHeight += 36.0
  478. }
  479. } else if rowIndexes.count > 1 {
  480. let delete_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(text: KMLocalizedString("Delete"),
  481. identifier: BOTAMenuIdentifier_Annotation_DeleteMuteRep,representedObject: rowIndexes)
  482. delete_Menuitem.keyEquivalent = "⌘ " + "⌫"
  483. menuItemArr.append(delete_Menuitem)
  484. viewHeight += 36.0
  485. }
  486. rightGroupView = groupView
  487. groupView?.groupDelegate = self
  488. groupView?.frame = CGRectMake(310, 0, 200, viewHeight)
  489. groupView?.updateGroupInfo(menuItemArr)
  490. var point = NSPoint(x: CGRectGetMaxX(view?.frame ?? CGRectZero), y: CGRectGetMidY(view?.frame ?? CGRectZero))
  491. point = noteOutlineView.convert(point, to: nil)
  492. point.y -= (groupView?.frame.size.height ?? 0)/2
  493. groupView?.showWithPoint(point, relativeTo: view)
  494. }
  495. func showSearchGroupView(sender: ComponentButton) {
  496. var viewHeight: CGFloat = 8
  497. var menuItemArr: [ComponentMenuitemProperty] = []
  498. let titles = ["Whole Words","Case Sensitive"]
  499. for i in titles {
  500. let menuI = ComponentMenuitemProperty(text: KMLocalizedString(i))
  501. menuItemArr.append(menuI)
  502. viewHeight += 36
  503. }
  504. if let info = menuItemArr.first {
  505. if KMDataManager.ud_bool(forKey: KMNSearchKey.wholeWords.annotation) {
  506. info.righticon = NSImage(named: "KMNImageNameMenuSelect")
  507. }
  508. }
  509. if let info = menuItemArr.last {
  510. if KMDataManager.ud_bool(forKey: KMNSearchKey.caseSensitive.annotation) {
  511. info.righticon = NSImage(named: "KMNImageNameMenuSelect")
  512. }
  513. }
  514. let groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle())
  515. searchGroupView = groupView
  516. groupView?.groupDelegate = self
  517. groupView?.frame = CGRectMake(310, 0, 200, viewHeight)
  518. groupView?.updateGroupInfo(menuItemArr)
  519. var point = sender.convert(sender.frame.origin, to: nil)
  520. point.y -= viewHeight
  521. groupView?.showWithPoint(point, relativeTo: sender)
  522. searchGroupTarget = sender
  523. }
  524. func displayNoteViewAnimating(_ animate: Bool) {
  525. searchViewController.contentView = nil
  526. if let data = noteOutlineView.enclosingScrollView {
  527. replaceSideView(data, animate: animate)
  528. }
  529. var frame = noteOutlineView.enclosingScrollView?.frame ?? .zero
  530. frame.origin.y = 0
  531. frame.size.height = noteOutlineView.enclosingScrollView?.superview?.frame.size.height ?? 0
  532. noteOutlineView.enclosingScrollView?.frame = frame
  533. reloadAnnotation()
  534. }
  535. func displayEmptyViewAnimating(_ animate: Bool) {
  536. leftListView.addSubview(emptyView)
  537. emptyView.km_add_top_constraint(constant: 232)
  538. emptyView.km_add_bottom_constraint()
  539. emptyView.km_add_leading_constraint()
  540. emptyView.km_add_trailing_constraint()
  541. emptyView.isHidden = true
  542. }
  543. }
  544. //MARK: Cache
  545. extension KMLeftSideViewController {
  546. func clearNotification() {}
  547. }
  548. // MARK: - Private Methods
  549. extension KMLeftSideViewController {
  550. func updateThumbnail(at index: Int) {
  551. var indexs = IndexSet()
  552. indexs.insert(index)
  553. self.delegate?.listViewThumPageIndeDidChange?(controller: self, pageIndexs: indexs)
  554. }
  555. }
  556. // MARK: - NSOutlineViewDelegate, NSOutlineViewDataSource
  557. extension KMLeftSideViewController: NSOutlineViewDelegate, NSOutlineViewDataSource {
  558. func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
  559. if outlineView.isEqual(to: noteOutlineView) {
  560. if self.noteSearchMode {
  561. var cnt = 0
  562. for model in noteSearchArray {
  563. cnt += 2
  564. guard let data = model as? KMBotaAnnotationModel else {
  565. continue
  566. }
  567. if data.isExpand == false {
  568. continue
  569. }
  570. for replyI in data.replyAnnos {
  571. cnt += 1
  572. }
  573. }
  574. return cnt
  575. }
  576. var cnt = 0
  577. for sectionM in annoListModel?.datas ?? [] {
  578. if sectionM.items.count > 0 {
  579. cnt += 1
  580. if sectionM.isExpand == false {
  581. continue
  582. }
  583. for item in sectionM.items {
  584. cnt += 1
  585. if let annoItem = item as? KMBotaAnnotationModel {
  586. if annoItem.isExpand == false {
  587. continue
  588. }
  589. for replyItem in annoItem.replyAnnos {
  590. cnt += 1
  591. }
  592. }
  593. }
  594. }
  595. }
  596. self.noteOutlineView.usesAlternatingRowBackgroundColors = false
  597. let hasAnno = self.allAnnotations.count >= 1
  598. if(!hasAnno) {
  599. headerView.sortButton.properties.isDisabled = true
  600. headerView.searchButton.properties.isDisabled = true
  601. headerView.moreButton.properties.isDisabled = true
  602. } else {
  603. headerView.sortButton.properties.isDisabled = false
  604. headerView.searchButton.properties.isDisabled = false
  605. headerView.moreButton.properties.isDisabled = false
  606. }
  607. headerView.sortButton.reloadData()
  608. headerView.searchButton.reloadData()
  609. headerView.moreButton.reloadData()
  610. if cnt < 1 {
  611. self.showNoteEmptyView()
  612. } else {
  613. self.hideNoteEmptyView()
  614. }
  615. return cnt
  616. }
  617. return 0
  618. }
  619. func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
  620. if outlineView.isEqual(to: self.noteOutlineView) {
  621. if self.noteSearchMode {
  622. var idx = 0
  623. var models: [KMBotaAnnotationBaseModel] = []
  624. for model in self.noteSearchArray {
  625. models.append(model)
  626. if let data = (model as? KMBotaAnnotationModel)?.footerModel {
  627. models.append(data)
  628. }
  629. }
  630. for model in models {
  631. idx += 1
  632. if index + 1 == idx {
  633. return model
  634. }
  635. guard let data = model as? KMBotaAnnotationModel else {
  636. continue
  637. }
  638. if data.isExpand == false {
  639. continue
  640. }
  641. for replyI in data.replyAnnos {
  642. idx += 1
  643. if index + 1 == idx {
  644. return replyI
  645. }
  646. }
  647. }
  648. return 0
  649. }
  650. var idx = 0
  651. var flagSectionM: KMBotaAnnotationSectionModel?
  652. var flagItem: KMBotaAnnotationBaseModel?
  653. var replyIten: KMBotaAnnotationReplyModel?
  654. var isSection = false
  655. var isReply = false
  656. for sectionM in self.annoListModel?.datas ?? [] {
  657. idx += 1
  658. if index + 1 == idx {
  659. flagSectionM = sectionM
  660. isSection = true
  661. break
  662. }
  663. if sectionM.isExpand == false {
  664. continue
  665. }
  666. for item in sectionM.items {
  667. idx += 1
  668. if index + 1 == idx {
  669. flagSectionM = sectionM
  670. flagItem = item
  671. break
  672. }
  673. guard let annoItem = item as? KMBotaAnnotationModel else {
  674. continue
  675. }
  676. if annoItem.isExpand == false {
  677. continue
  678. }
  679. for replyI in annoItem.replyAnnos {
  680. idx += 1
  681. if index + 1 == idx {
  682. flagSectionM = sectionM
  683. flagItem = item
  684. replyIten = replyI
  685. isReply = true
  686. break
  687. }
  688. }
  689. }
  690. }
  691. if isSection {
  692. return flagSectionM
  693. }
  694. if isReply {
  695. return replyIten
  696. }
  697. return flagItem
  698. }
  699. return item as Any
  700. }
  701. func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
  702. if outlineView.isEqual(to: noteOutlineView) {
  703. if let data = item as? KMBotaAnnotationSectionModel {
  704. var cell = outlineView.makeView(withIdentifier: KMAnnotationSectionCellView.km_identifier, owner: nil) as? KMAnnotationSectionCellView
  705. if cell == nil {
  706. cell = KMAnnotationSectionCellView.createFromNib()
  707. }
  708. cell?.isExpand = data.isExpand
  709. if self.noteSortType == .page {
  710. let pageIndex = data.items.first?.anno?.page.pageIndex() ?? 0
  711. cell?.titleLabel.stringValue = KMLocalizedString("Page") + " \(pageIndex + 1)"
  712. } else {
  713. if let date = data.items.first?.anno?.modificationDate() {
  714. let string = KMTools.timeString(timeDate: date, formatString: "yyyy-MM-dd")
  715. cell?.titleLabel.stringValue = string
  716. } else {
  717. cell?.titleLabel.stringValue = ""
  718. }
  719. }
  720. cell?.countLabel.stringValue = "\(data.itemCount / 2)"
  721. cell?.itemClick = {[weak self] idx, _ in
  722. if idx == 1 { // 收取 & 展开
  723. data.isExpand = !data.isExpand
  724. self?.noteOutlineView.reloadData()
  725. }
  726. }
  727. return cell
  728. }
  729. if let data = item as? KMBotaAnnotationFooterModel {
  730. var cell = outlineView.makeView(withIdentifier: KMNoteFooterCellView.km_identifier, owner: self) as? KMNoteFooterCellView
  731. if cell == nil {
  732. cell = KMNoteFooterCellView.createFromNib()
  733. }
  734. cell?.model = data
  735. let state = noteReplyHanddler.fetchReviewState(data.anno) ?? .none
  736. if state == .none {
  737. cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateNone")
  738. } else if state == .completed {
  739. cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateCompleted")
  740. } else if state == .canceled {
  741. cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateCancelled")
  742. } else if state == .accepted {
  743. cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateAccepted")
  744. } else if state == .rejected {
  745. cell?.operationIv.image = NSImage(named: "KMNImageNameBotaNoteStateRejected")
  746. }
  747. cell?.operationBox.toolTip = KMPDFAnnotationStateGetString(state: state)
  748. if let con = data.replyModel?.replyAnno?.contents, con.isEmpty == false {
  749. cell?.inputTextF.stringValue = con
  750. DispatchQueue.main.async {
  751. self.view.window?.makeFirstResponder(cell?.inputTextF)
  752. }
  753. } else if let con = data.editAnnoModel?.anno?.contents, con.isEmpty == false {
  754. cell?.inputTextF.stringValue = con
  755. DispatchQueue.main.async {
  756. self.view.window?.makeFirstResponder(cell?.inputTextF)
  757. }
  758. } else {
  759. if let cont = data.inputContent {
  760. cell?.inputTextF.stringValue = cont
  761. } else {
  762. cell?.inputTextF.stringValue = ""
  763. }
  764. }
  765. cell?.updateUI(expand: data.isExpand, animated: data.animated)
  766. data.animated = false
  767. if data.isFirstResp {
  768. DispatchQueue.main.async {
  769. self.view.window?.makeFirstResponder(cell?.inputTextF)
  770. }
  771. data.isFirstResp = false
  772. }
  773. cell?.inputDidChanged = { value, _ in
  774. data.inputContent = value as? String ?? ""
  775. }
  776. cell?.itemClick = { [weak self] idx, param in
  777. if idx == 1 { // comment
  778. data.isExpand = !data.isExpand
  779. data.animated = true
  780. data.isFirstResp = true
  781. self?.noteOutlineView.reloadItem(data)
  782. } else if idx == 2 { //
  783. self?.noteReplyHanddler.showStatePopView(sender: param.first as! NSView, anno: data.anno)
  784. } else if idx == 3 { // reply
  785. let content = param.first as? String ?? ""
  786. if content.isEmpty {
  787. return
  788. }
  789. if let con = data.replyModel?.replyAnno?.contents, con.isEmpty == false { // 编辑
  790. let model = data.replyModel
  791. model?.replyAnno?.contents = content
  792. model?.replyAnno?.setUserName(KMPreference.shared.author)
  793. model?.replyAnno?.setModificationDate(Date())
  794. // 置空编辑状态
  795. data.replyModel = nil
  796. data.inputContent = nil
  797. self?.noteOutlineView.reloadData()
  798. if let row = self?.noteOutlineView.row(forItem: data) {
  799. self?.noteOutlineView.scrollRowToVisible(row)
  800. }
  801. return
  802. }
  803. if let con = data.editAnnoModel?.anno?.contents, con.isEmpty == false { // 编辑
  804. let model = data.editAnnoModel
  805. model?.anno?.contents = content
  806. model?.anno?.setUserName(KMPreference.shared.author)
  807. // 置空编辑状态
  808. data.replyModel = nil
  809. data.inputContent = nil
  810. data.editAnnoModel = nil
  811. self?.noteOutlineView.reloadData()
  812. if let row = self?.noteOutlineView.row(forItem: data) {
  813. self?.noteOutlineView.scrollRowToVisible(row)
  814. }
  815. return
  816. }
  817. if let replyAnno = self?.noteReplyHanddler.createReplyAnnotation(data.anno, content: content, userName: KMPreference.shared.author) {
  818. let model = KMBotaAnnotationReplyModel()
  819. model.anno = data.anno
  820. model.replyAnno = replyAnno
  821. model.annoModel = data.annoModel
  822. data.annoModel?.replyAnnos.append(model)
  823. data.inputContent = nil
  824. self?.noteOutlineView.reloadData()
  825. if let row = self?.noteOutlineView.row(forItem: data) {
  826. self?.noteOutlineView.scrollRowToVisible(row)
  827. }
  828. }
  829. } else if idx == 4 { // cancel
  830. data.isExpand = false
  831. data.replyModel = nil
  832. data.inputContent = nil
  833. self?.noteOutlineView.reloadItem(data)
  834. }
  835. }
  836. return cell
  837. }
  838. if let data = item as? KMBotaAnnotationReplyModel {
  839. var cell = outlineView.makeView(withIdentifier: KMNoteReplyCellView.km_identifier, owner: self) as? KMNoteReplyCellView
  840. if cell == nil {
  841. cell = KMNoteReplyCellView.createFromNib()
  842. }
  843. cell?.model = data
  844. cell?.titleLabel.stringValue = data.replyAnno?.userName() ?? ""
  845. if let data = data.replyAnno?.modificationDate() {
  846. cell?.timeLabel.stringValue = KMTools.timeString(timeDate: data)
  847. } else {
  848. cell?.timeLabel.stringValue = ""
  849. }
  850. cell?.contentLabel.stringValue = data.replyAnno?.contents ?? ""
  851. cell?.itemClick = { [weak self] idx, params in
  852. if idx == 1 { // 更多
  853. self?.noteReplyHanddler.showReplyMorePopView(sender: params.first as! NSView, replyModel: data)
  854. }
  855. }
  856. return cell
  857. }
  858. let model = item as? KMBotaAnnotationModel
  859. let note = (item as? KMBotaAnnotationModel)?.anno
  860. let cell = outlineView.makeView(withIdentifier: KMNoteTableViewCell.km_identifier, owner: self) as! KMNoteTableViewCell
  861. cell.cellNote = note
  862. cell.model = model
  863. let state = self.noteReplyHanddler.fetchAnnoState(note) ?? .unMarked
  864. cell.state = state
  865. if let data = note {
  866. if data.isKind(of: CPDFStampAnnotation.self) &&
  867. data.isKind(of: CSelfSignAnnotation.self) == false {
  868. } else {
  869. if data.isKind(of: CPDFMarkupAnnotation.self) {
  870. if (!cell.isFold) {
  871. } else {
  872. cell.imageViewHeightConstraint.constant = 18.0 + 8
  873. }
  874. } else {
  875. cell.imageViewHeightConstraint.constant = 18.0 + 8
  876. }
  877. }
  878. }
  879. cell.itemClick = { [weak self] idx , _ in
  880. if idx == 1 { // Markup
  881. if model != nil {
  882. self?.updateMarkState(model: model!)
  883. }
  884. }
  885. }
  886. cell.isUnFoldNote = { [weak self] cellNote, isUnfold in
  887. if model != nil {
  888. self?.updateExpand(model: model!, isExpand: isUnfold, item: item)
  889. }
  890. }
  891. return cell
  892. }
  893. return nil
  894. }
  895. func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat {
  896. if outlineView.isEqual(self.noteOutlineView) {
  897. if let model = item as? KMBotaAnnotationModel {
  898. if model.isExpand == false {
  899. return model.foldH
  900. }
  901. if let anno = model.anno {
  902. return KMBOTAAnnotationTool.fetchCellHeight(annotation: anno, maxSize: CGSize(width: 260+40 - 16, height: 1000))
  903. }
  904. }
  905. return 30
  906. }
  907. return outlineView.rowHeight
  908. }
  909. func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
  910. if outlineView.isEqual(to: self.noteOutlineView) {
  911. return false
  912. }
  913. return false
  914. }
  915. func outlineView(_ outlineView: NSOutlineView, rowViewForItem item: Any) -> NSTableRowView? {
  916. if outlineView.isEqual(self.noteOutlineView) {
  917. let itemView = KMBotaTableRowView()
  918. if let data = item as? KMBotaAnnotationBaseModel {
  919. itemView.isSelected = data.isSelected
  920. }
  921. let rowIndexes = self.noteOutlineView.selectedRowIndexes
  922. itemView.rightMouseCallback = { [weak self] (view, event) in
  923. if let data = item as? KMBotaAnnotationFooterModel {
  924. } else {
  925. if !KMOCToolClass.arrayContains(array: self?.selectedObjcNotes(), annotation: item) ||
  926. self?.selectedObjcNotes().count == 1 {
  927. let index = self?.noteOutlineView.row(forItem: item)
  928. self?.noteOutlineView.selectRowIndexes(IndexSet(integer: IndexSet.Element(index ?? 0)), byExtendingSelection: false)
  929. }
  930. let row = outlineView.row(forItem: item)
  931. if outlineView.rowView(atRow: row, makeIfNecessary: false) != nil {
  932. let rowView = outlineView.rowView(atRow: row, makeIfNecessary: false)
  933. self?.showRightGropView(raw: row, event: event, view: rowView,item: item)
  934. }
  935. }
  936. }
  937. itemView.selectCallback = { theView in
  938. let isSelected = theView.isSelected
  939. if theView.numberOfColumns > 0 {
  940. let cellView = theView.view(atColumn: 0)
  941. if let data = cellView as? KMAnnotationSectionCellView {
  942. return
  943. }
  944. var model: KMBotaAnnotationModel?
  945. var indexs = IndexSet()
  946. if let data = cellView as? KMNoteFooterCellView {
  947. model = data.model?.annoModel
  948. model?.isSelected = isSelected
  949. self.view.window?.makeFirstResponder(data.inputTextF)
  950. }
  951. if let data = cellView as? KMNoteReplyCellView {
  952. model = data.model?.annoModel
  953. model?.isSelected = isSelected
  954. }
  955. if let data = cellView as? KMNoteTableViewCell {
  956. model = data.model
  957. model?.isSelected = isSelected
  958. }
  959. if let data = model {
  960. let row = self.noteOutlineView.row(forItem: data)
  961. indexs.insert(row)
  962. var i = 1
  963. for item in model?.replyAnnos ?? [] {
  964. indexs.insert(row+i)
  965. i += 1
  966. }
  967. indexs.insert(row+i)
  968. }
  969. }
  970. }
  971. return itemView;
  972. }
  973. return nil
  974. }
  975. func outlineView(_ outlineView: NSOutlineView, selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet) -> IndexSet {
  976. if outlineView.isEqual(to: self.noteOutlineView) {
  977. for i in proposedSelectionIndexes {
  978. let item = self.noteOutlineView.item(atRow: i)
  979. if let data = item as? KMBotaAnnotationFooterModel {
  980. return IndexSet()
  981. }
  982. }
  983. }
  984. return proposedSelectionIndexes
  985. }
  986. func outlineViewSelectionDidChange(_ notification: Notification) {
  987. if self.hideNotes() == false {
  988. let selectedNotes = self.selectedNotes()
  989. if selectedNotes.count == 1 {
  990. let annotation = selectedNotes.last!
  991. self.listView?.go(to: annotation.bounds, on: annotation.page, animated: true)
  992. self.listView?.updateActiveAnnotations([annotation])
  993. self.listView?.setNeedsDisplayAnnotationViewForVisiblePages()
  994. if annotation is CPDFPolygonAnnotation || annotation is CPDFPolylineAnnotation {
  995. self.listView?.pdfListViewDelegate.pdfListViewAnnotationMeasureInfoChange?(self.listView, with: annotation)
  996. } else if let anno = annotation as? CPDFLineAnnotation {
  997. if anno.isMeasure {
  998. self.listView?.pdfListViewDelegate.pdfListViewAnnotationMeasureInfoChange?(self.listView, with: annotation)
  999. }
  1000. }
  1001. } else if(selectedNotes.count > 1){
  1002. let lastSelectedNote = selectedNotes.last!
  1003. let firstSelectedNote = selectedNotes.first!
  1004. self.listView?.go(to: lastSelectedNote.bounds, on: lastSelectedNote.page, animated: true)
  1005. if lastSelectedNote.page == firstSelectedNote.page {//同页的话支持多选选中
  1006. var activeAnnotations:[CPDFAnnotation] = listView?.activeAnnotations as! [CPDFAnnotation]
  1007. activeAnnotations.append(lastSelectedNote)
  1008. self.listView?.updateActiveAnnotations(activeAnnotations)
  1009. } else {
  1010. self.listView?.updateActiveAnnotations([lastSelectedNote])
  1011. let rowIndexes = self.noteOutlineView.selectedRowIndexes
  1012. for i in 0 ..< rowIndexes.count - 1 {
  1013. noteOutlineView.deselectRow(i)
  1014. }
  1015. }
  1016. self.listView?.setNeedsDisplayAnnotationViewForVisiblePages()
  1017. if lastSelectedNote is CPDFPolygonAnnotation || lastSelectedNote is CPDFPolylineAnnotation {
  1018. self.listView?.pdfListViewDelegate.pdfListViewAnnotationMeasureInfoChange?(self.listView, with: lastSelectedNote)
  1019. } else if let anno = lastSelectedNote as? CPDFLineAnnotation {
  1020. if anno.isMeasure {
  1021. self.listView?.pdfListViewDelegate.pdfListViewAnnotationMeasureInfoChange?(self.listView, with: lastSelectedNote)
  1022. }
  1023. }
  1024. }
  1025. }
  1026. }
  1027. func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
  1028. var dragOp = NSDragOperation(rawValue: 0)
  1029. if outlineView.isEqual(to: self.noteOutlineView) {
  1030. let pboard = info.draggingPasteboard
  1031. if pboard.canReadObject(forClasses: [NSColor.self], options: [:]) && index == NSOutlineViewDropOnItemIndex {
  1032. if let note = item as? CPDFAnnotation, note.type != nil {
  1033. dragOp = .every
  1034. }
  1035. }
  1036. }
  1037. return dragOp
  1038. }
  1039. func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
  1040. if outlineView.isEqual(to: noteOutlineView) {
  1041. let pboard = info.draggingPasteboard
  1042. if pboard.canReadObject(forClasses: [NSColor.self], options: [:]) {
  1043. let isShift = NSEvent.modifierFlags.contains(.shift)
  1044. let isAlt = NSEvent.modifierFlags.contains(.option)
  1045. guard let note = item as? CPDFAnnotation else {
  1046. NSSound.beep()
  1047. return false
  1048. }
  1049. if let color = NSColor(from: pboard) {
  1050. note.setColor(color, alternate: isAlt, updateDefaults: isShift)
  1051. return true
  1052. }
  1053. return false
  1054. }
  1055. }
  1056. return false
  1057. }
  1058. func outlineView(_ outlineView: NSOutlineView, setObjectValue object: Any?, for tableColumn: NSTableColumn?, byItem item: Any?) {
  1059. if outlineView.isEqual(to: noteOutlineView) {
  1060. var note: CPDFAnnotation?
  1061. if let data = item as? CPDFAnnotation {
  1062. note = data
  1063. } else if let data = item as? KMBotaAnnotationModel {
  1064. note = data.anno
  1065. }
  1066. if let data = note?.type, data.isEmpty == false {
  1067. if tableColumn?.identifier.rawValue == self.noteColumnId.rawValue {
  1068. let string1 = (object as? String) ?? ""
  1069. let string2 = note?.string() ?? ""
  1070. if string1 != string2 {
  1071. note?.setString(string1)
  1072. }
  1073. } else if tableColumn?.identifier.rawValue == self.authorColumnId.rawValue {
  1074. let string1 = (object as? String) ?? ""
  1075. let string2 = note?.userName() ?? ""
  1076. if string1 != string2 {
  1077. note?.setUserName(string1)
  1078. }
  1079. }
  1080. }
  1081. }
  1082. }
  1083. func outlineView(_ outlineView: NSOutlineView, dataCellFor tableColumn: NSTableColumn?, item: Any) -> NSCell? {
  1084. if noteOutlineView.isEqual(to: outlineView) {
  1085. if tableColumn == nil {
  1086. if let anno = item as? CPDFAnnotation, anno.type == nil {
  1087. return outlineView.tableColumn(withIdentifier: self.noteColumnId)?.dataCell(forRow: outlineView.row(forItem: item)) as? NSCell
  1088. }
  1089. }
  1090. }
  1091. return tableColumn?.dataCell(forRow: outlineView.row(forItem: item)) as? NSCell
  1092. }
  1093. func outlineView(_ outlineView: NSOutlineView, shouldEdit tableColumn: NSTableColumn?, item: Any) -> Bool {
  1094. if outlineView.isEqual(to: noteOutlineView) {
  1095. if (tableColumn == nil) {
  1096. if self.hideNotes() == false {
  1097. if let anno = item as? CPDFAnnotation, anno.isNote() {
  1098. self.listView?.scrollAnnotationToVisible(anno)
  1099. self.listView?.updateActiveAnnotations([anno])
  1100. }
  1101. }
  1102. return false
  1103. } else if tableColumn?.identifier.rawValue == self.noteColumnId.rawValue || tableColumn?.identifier.rawValue == self.authorColumnId.rawValue {
  1104. return true
  1105. }
  1106. }
  1107. return false
  1108. }
  1109. func outlineView(_ outlineView: NSOutlineView, toolTipFor cell: NSCell, rect: NSRectPointer, tableColumn: NSTableColumn?, item: Any, mouseLocation: NSPoint) -> String {
  1110. if outlineView.isEqual(to: self.noteOutlineView) {
  1111. if tableColumn == nil || tableColumn?.identifier.rawValue == self.noteColumnId.rawValue {
  1112. return (item as? CPDFAnnotation)?.string() ?? ""
  1113. }
  1114. }
  1115. return ""
  1116. }
  1117. func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, willBeginAt screenPoint: NSPoint, forItems draggedItems: [Any]) {
  1118. self.dragIn = true
  1119. }
  1120. func outlineView(_ outlineView: NSOutlineView, draggingSession session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {
  1121. self.dragIn = false
  1122. }
  1123. func noteItems(_ items: NSArray) -> NSArray {
  1124. let noteItems = NSMutableArray()
  1125. for item in items {
  1126. guard let anno = (item as? KMBotaAnnotationModel)?.anno else {
  1127. continue
  1128. }
  1129. if anno.type == nil {
  1130. }
  1131. if noteItems.contains(anno) == false {
  1132. noteItems.add(anno)
  1133. }
  1134. }
  1135. return noteItems
  1136. }
  1137. }
  1138. // MARK: - KMCustomOutlineViewDelegate, KMCustomOutlineViewDataSource
  1139. extension KMLeftSideViewController: KMCustomOutlineViewDelegate, KMCustomOutlineViewDataSource {
  1140. func outlineView(_ anOutlineView: NSOutlineView, canDeleteItems items: [Any]) -> Bool {
  1141. if anOutlineView.isEqual(to: noteOutlineView) {
  1142. return self.hideNotes() == false && items.count > 0
  1143. }
  1144. return false
  1145. }
  1146. func outlineView(_ anOutlineView: NSOutlineView, deleteItems items: [Any]) {
  1147. if anOutlineView.isEqual(to: noteOutlineView) {
  1148. if (items.isEmpty) {
  1149. return
  1150. }
  1151. self.dataUpdating = true
  1152. for item in self.noteItems(items as NSArray) {
  1153. guard let anno = item as? CPDFAnnotation else {
  1154. continue
  1155. }
  1156. if let data = anno as? CPDFFreeTextAnnotation {
  1157. self.listView?.commitEditAnnotationFreeText(data)
  1158. }
  1159. self.listView?.remove(anno)
  1160. }
  1161. for item in items {
  1162. if let data = item as? KMBotaAnnotationModel {
  1163. data.sectionModel?.items.removeObject(data)
  1164. if let footerModel = data.footerModel {
  1165. data.sectionModel?.items.removeObject(footerModel)
  1166. }
  1167. }
  1168. }
  1169. self.dataUpdating = false
  1170. self.listView?.undoManager?.setActionName(KMLocalizedString("Remove Note", comment: "Undo action name"))
  1171. self.note_refrshUIIfNeed()
  1172. }
  1173. }
  1174. func outlineView(_ anOutlineView: NSOutlineView, canCopyItems items: [Any]) -> Bool {
  1175. if anOutlineView.isEqual(to: noteOutlineView) {
  1176. if (items.count == 1) {
  1177. }
  1178. return items.count > 0
  1179. }
  1180. return false
  1181. }
  1182. func outlineView(_ anOutlineView: NSOutlineView, copyItems items: [Any]) {
  1183. if anOutlineView.isEqual(to: self.noteOutlineView) && items.isEmpty == false {
  1184. }
  1185. }
  1186. func outlineView(_ anOutlineView: NSOutlineView, typeSelectHelperSelectionStrings aTypeSelectHelper: SKTypeSelectHelper) -> NSArray {
  1187. if noteOutlineView.isEqual(to: anOutlineView) {
  1188. let count = self.noteOutlineView.numberOfRows
  1189. let texts = NSMutableArray(capacity: count)
  1190. for i in 0 ..< count {
  1191. let item = self.noteOutlineView.item(atRow: i)
  1192. let string = (item as? CPDFAnnotation)?.string() ?? ""
  1193. texts.add(string)
  1194. }
  1195. return texts
  1196. }
  1197. return []
  1198. }
  1199. }
  1200. // MARK: - Other
  1201. extension KMLeftSideViewController {
  1202. func fileNameWithSelectedPages(_ itemIndexes: IndexSet) -> String {
  1203. var pagesName = ""
  1204. if (itemIndexes.count > 1) {
  1205. pagesName.append(" pages")
  1206. } else {
  1207. pagesName.append(" page")
  1208. }
  1209. let docmentName = self.pdfDocument()?.documentURL.deletingPathExtension().lastPathComponent ?? ""
  1210. let tFileName = String(format: "%@ %@", pagesName,KMTools.parseIndexSet(indexSet: itemIndexes))
  1211. return String(format: "%@%@", docmentName,tFileName)
  1212. }
  1213. func draggedFileName(for page: CPDFPage) -> String {
  1214. let pageIndex = "\(page.pageIndex() + 1)"
  1215. var fileName = ""
  1216. if let doc = self.view.window?.windowController?.document as? NSDocument {
  1217. fileName = doc.fileURL?.deletingPathExtension().lastPathComponent ?? (MainBundle.nameSpaceName ?? "")
  1218. }
  1219. return "\(fileName)-Page \(pageIndex)"
  1220. }
  1221. func updateTableFont() {
  1222. }
  1223. }
  1224. // MARK: - ComponentGroupDelegate
  1225. extension KMLeftSideViewController: ComponentGroupDelegate {
  1226. func componentGroupDidDismiss(group: ComponentGroup?) {
  1227. if group == searchGroupView {
  1228. searchGroupTarget?.properties.state = .normal
  1229. searchGroupTarget?.reloadData()
  1230. searchGroupTarget = nil
  1231. } else if group == moreGroupView {
  1232. headerView.moreButton.properties.state = .normal
  1233. headerView.moreButton.reloadData()
  1234. }
  1235. }
  1236. func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) {
  1237. if group == searchGroupView {
  1238. guard let menuI = menuItemProperty else {
  1239. return
  1240. }
  1241. let idx = group?.menuItemArr.firstIndex(of: menuI)
  1242. if idx == 0 {
  1243. let key = KMNSearchKey.wholeWords.annotation
  1244. let value = KMDataManager.ud_bool(forKey: key)
  1245. KMDataManager.ud_set(!value, forKey: key)
  1246. } else if idx == 1 {
  1247. let key = KMNSearchKey.caseSensitive.annotation
  1248. let value = KMDataManager.ud_bool(forKey: key)
  1249. KMDataManager.ud_set(!value, forKey: key)
  1250. }
  1251. } else {
  1252. if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_Expand {
  1253. var isExpandAllItem = true
  1254. for secM in self.annoListModel?.datas ?? [] {
  1255. if secM.isExpand == false {
  1256. isExpandAllItem = false
  1257. break
  1258. }
  1259. }
  1260. if isExpandAllItem == true {
  1261. note_foldAllComments(nil)
  1262. } else {
  1263. note_expandAllComments(nil)
  1264. }
  1265. } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_SortPage {
  1266. self.noteSortType = .page
  1267. if let data = self.listView?.document?.documentURL.path {
  1268. KMDataManager.udExtension_set(self.noteSortType.rawValue, forKey: Self.Key.noteSortTypeKey + data)
  1269. }
  1270. if self.noteSearchMode {
  1271. self.reloadNoteForSearchMode()
  1272. } else {
  1273. self.reloadAnnotation()
  1274. }
  1275. } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_SortAscTime {
  1276. self.noteSortType = .time
  1277. self.isAscendSort = true
  1278. if let data = self.listView?.document?.documentURL.path {
  1279. KMDataManager.udExtension_set(self.noteSortType.rawValue, forKey: Self.Key.noteSortTypeKey + data)
  1280. }
  1281. if self.noteSearchMode {
  1282. self.reloadNoteForSearchMode()
  1283. } else {
  1284. self.reloadAnnotation()
  1285. }
  1286. } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_SortDesTime {
  1287. self.noteSortType = .time
  1288. self.isAscendSort = false
  1289. if let data = self.listView?.document?.documentURL.path {
  1290. KMDataManager.udExtension_set(self.noteSortType.rawValue, forKey: Self.Key.noteSortTypeKey + data)
  1291. }
  1292. if self.noteSearchMode {
  1293. self.reloadNoteForSearchMode()
  1294. } else {
  1295. self.reloadAnnotation()
  1296. }
  1297. } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_Improt {
  1298. importNotes(nil)
  1299. } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_Export {
  1300. exportNotes(nil)
  1301. } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RemoveAll {
  1302. removeAllAnnotations(nil)
  1303. } else if menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_DeleteRep {
  1304. removeAllReplyAnnotations(nil)
  1305. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_EditNote) {
  1306. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1307. if models.first != nil {
  1308. let model = models.first
  1309. if model != nil {
  1310. updateExpand(model: model!, isExpand: true, item: currentItem)
  1311. noteReplyHanddler.editAnnotation(annotationModel: model!)
  1312. DispatchQueue.main.async {
  1313. let row = self.noteOutlineView.row(forItem: model?.footerModel)
  1314. self.noteOutlineView.scrollRowToVisible(row)
  1315. }
  1316. self.noteOutlineView.reloadData()
  1317. }
  1318. }
  1319. } else if let anModels = menuItemProperty?.representedObject as? [KMBotaAnnotationReplyModel] {
  1320. if anModels.first != nil {
  1321. let model = anModels.first
  1322. noteReplyHanddler.editReplyAnnotation(replyModel: model)
  1323. DispatchQueue.main.async {
  1324. let row = self.noteOutlineView.row(forItem: model?.annoModel?.footerModel)
  1325. self.noteOutlineView.scrollRowToVisible(row)
  1326. }
  1327. self.noteOutlineView.reloadData()
  1328. }
  1329. }
  1330. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_AddRep) {
  1331. if let anModels = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1332. if anModels.first != nil {
  1333. if let model = anModels.first {
  1334. self.updateExpand(model: model, isExpand: true, item: currentItem)
  1335. if let footMode = model.footerModel {
  1336. let row = self.noteOutlineView.row(forItem: footMode)
  1337. let rowView = self.noteOutlineView.rowView(atRow: row, makeIfNecessary: true)
  1338. if(row != nil) {
  1339. for subview in rowView!.subviews {
  1340. if let cell = subview as? KMNoteFooterCellView {
  1341. cell.inputTextF.becomeFirstResponder()
  1342. }
  1343. }
  1344. }
  1345. }
  1346. }
  1347. }
  1348. }
  1349. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_AddMark) {
  1350. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1351. if models.first != nil {
  1352. updateMarkState(model: models.first!)
  1353. }
  1354. }
  1355. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateNone) {
  1356. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1357. if models.first != nil {
  1358. updateReplaState(model:models.first! , state:.none)
  1359. }
  1360. }
  1361. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateAccepted) {
  1362. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1363. if models.first != nil {
  1364. updateReplaState(model:models.first! , state:.accepted)
  1365. }
  1366. }
  1367. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateRejected) {
  1368. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1369. if models.first != nil {
  1370. updateReplaState(model:models.first! , state:.rejected)
  1371. }
  1372. }
  1373. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateCancelled) {
  1374. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1375. if models.first != nil {
  1376. updateReplaState(model:models.first! , state:.canceled)
  1377. }
  1378. }
  1379. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_RepStateCompleted) {
  1380. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1381. if models.first != nil {
  1382. updateReplaState(model:models.first! , state:.completed)
  1383. }
  1384. }
  1385. } else if (menuItemProperty?.identifier == PDFViewMenuIdentifier_Normal_ShowPopUI) {
  1386. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1387. if models.first != nil {
  1388. let model = models.first
  1389. updateExpand(model: model!, isExpand: !(model?.isExpand == true), item: currentItem)
  1390. }
  1391. }
  1392. } else if (menuItemProperty?.identifier == PDFViewMenuIdentifier_Normal_Copy) {
  1393. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationReplyModel] {
  1394. if models.first != nil {
  1395. let model = models.first
  1396. let an = model?.anno
  1397. var copyText:String = ""
  1398. if an?.isKind(of: CPDFMarkupAnnotation.self) == true {
  1399. if let markupAn = an as? CPDFMarkupAnnotation {
  1400. copyText = markupAn.markupContent()
  1401. }
  1402. } else if an?.isKind(of: CPDFFreeTextAnnotation.self) == true ||
  1403. an?.isKind(of: CPDFTextAnnotation.self) == true {
  1404. copyText = an?.contents ?? ""
  1405. let pboard = NSPasteboard.general
  1406. if copyText.isEmpty == false {
  1407. pboard.clearContents()
  1408. pboard.writeObjects([copyText as NSPasteboardWriting])
  1409. }
  1410. }
  1411. }
  1412. }
  1413. } else if (menuItemProperty?.identifier == PDFViewMenuIdentifier_Normal_Delete) {
  1414. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationModel] {
  1415. if models.first != nil {
  1416. let model = models.first
  1417. self.outlineView(self.noteOutlineView, deleteItems:[model!])
  1418. }
  1419. }
  1420. } else if (menuItemProperty?.identifier == PDFViewMenuIdentifier_Normal_CopyText) {
  1421. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationReplyModel] {
  1422. if models.first != nil {
  1423. let model = models.first
  1424. var copyText:String = ""
  1425. copyText = model?.replyAnno?.contents ?? ""
  1426. let pboard = NSPasteboard.general
  1427. if copyText.isEmpty == false {
  1428. pboard.clearContents()
  1429. pboard.writeObjects([copyText as NSPasteboardWriting])
  1430. }
  1431. }
  1432. }
  1433. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_DeleteMuteRep) {
  1434. if let models = menuItemProperty?.representedObject as? [Any] {
  1435. for i in 0 ..< models.count {
  1436. let model = models[i]
  1437. if let replyModel = model as? KMBotaAnnotationReplyModel {
  1438. noteReplyHanddler.removeReplyAnnotation(replyModel.replyAnno)
  1439. replyModel.annoModel?.replyAnnos.removeObject(replyModel)
  1440. } else if let anModel = model as? KMBotaAnnotationModel {
  1441. self.outlineView(self.noteOutlineView, deleteItems:[anModel])
  1442. }
  1443. }
  1444. self.noteOutlineView.reloadData()
  1445. }
  1446. } else if (menuItemProperty?.identifier == BOTAMenuIdentifier_Annotation_DeleteSignRep) {
  1447. if let models = menuItemProperty?.representedObject as? [KMBotaAnnotationReplyModel] {
  1448. if models.first != nil {
  1449. let model = models.first
  1450. if model != nil {
  1451. noteReplyHanddler.removeReplyAnnotation(model?.replyAnno)
  1452. model?.annoModel?.replyAnnos.removeObject(model!)
  1453. }
  1454. self.noteOutlineView.reloadData()
  1455. }
  1456. }
  1457. }
  1458. }
  1459. }
  1460. }