KMToolbarConfigModel.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. //
  2. // KMToolbarConfigModel.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by tangchao on 2024/5/28.
  6. //
  7. import Cocoa
  8. class KMToolbarConfigModel: NSObject {
  9. var leftCellIdentifiers: [String]? {
  10. didSet {
  11. let ids = self.leftCellIdentifiers ?? []
  12. self.leftWidth = 0
  13. if ids.isEmpty == false {
  14. self.leftWidth += self.leftMargin + 5
  15. }
  16. for itemId in ids {
  17. let item = KMToolbarItemView(itemIdentifier: itemId)
  18. self.setupMainItem(item)
  19. self.leftWidth += (item.itemWidth + 5)
  20. }
  21. }
  22. }
  23. var leftCount: Int {
  24. get {
  25. return self.leftCellIdentifiers?.count ?? 0
  26. }
  27. }
  28. var centerCellIdentifiers: [String]? {
  29. didSet {
  30. let ids = self.centerCellIdentifiers ?? []
  31. self.centerWidth = 0
  32. for itemId in ids {
  33. let item = KMToolbarItemView(itemIdentifier: itemId)
  34. self.setupMainItem(item)
  35. self.centerWidth += (item.itemWidth + 5)
  36. }
  37. }
  38. }
  39. var centerCount: Int {
  40. get {
  41. return self.centerCellIdentifiers?.count ?? 0
  42. }
  43. }
  44. var rightCellIdentifiers: [String]? {
  45. didSet {
  46. let ids = self.rightCellIdentifiers ?? []
  47. self.rightWidth = 0
  48. if ids.isEmpty == false {
  49. self.rightWidth += self.rightMargin + 5
  50. }
  51. for itemId in ids {
  52. let item = KMToolbarItemView(itemIdentifier: itemId)
  53. self.setupMainItem(item)
  54. self.rightWidth += (item.itemWidth + 5)
  55. }
  56. }
  57. }
  58. var rightCount: Int {
  59. get {
  60. return self.rightCellIdentifiers?.count ?? 0
  61. }
  62. }
  63. var defaultCellIdentifiers: [String]?
  64. var allowedCellIdentifiers: [String]? {
  65. get {
  66. let left = self.leftCellIdentifiers ?? []
  67. let center = self.centerCellIdentifiers ?? []
  68. let right = self.rightCellIdentifiers ?? []
  69. return left + center + right
  70. }
  71. }
  72. var removedCellIdentifiers: [String]? {
  73. get {
  74. let ids = self.defaultCellIdentifiers ?? []
  75. if ids.isEmpty {
  76. return [KMToolbarFixedSpaceItemIdentifier]
  77. }
  78. var removedIds: [String] = []
  79. for itemId in ids {
  80. if self.allowedCellIdentifiers?.contains(itemId) == false {
  81. removedIds.append(itemId)
  82. }
  83. }
  84. removedIds.insert(KMToolbarFixedSpaceItemIdentifier, at: 0)
  85. return removedIds
  86. }
  87. }
  88. var leftWidth: CGFloat = 0
  89. var centerWidth: CGFloat = 0
  90. var rightWidth: CGFloat = 0
  91. var leftMargin: CGFloat = 10
  92. var rightMargin: CGFloat = 10
  93. var itemH: CGFloat = 48
  94. var contentWidth: CGFloat {
  95. get {
  96. return self.leftWidth + self.centerWidth + self.rightWidth
  97. }
  98. }
  99. var windowWidth: CGFloat = 1480
  100. var segItemWidth: CGFloat {
  101. get {
  102. return max((self.windowWidth - self.contentWidth - 26 * 2) * 0.5, 20)
  103. }
  104. }
  105. func isLeft(at idx: Int) -> Bool {
  106. if self.leftCount <= 0 {
  107. return false
  108. }
  109. let stardIdx = 0
  110. let endIdx = self.leftCount
  111. if idx >= stardIdx && idx < endIdx {
  112. return true
  113. }
  114. return false
  115. }
  116. func isCenter(at idx: Int) -> Bool {
  117. if self.centerCount <= 0 {
  118. return false
  119. }
  120. let stardIdx = self.leftCount+1
  121. let endIdx = stardIdx+self.centerCount
  122. if idx >= stardIdx && idx < endIdx {
  123. return true
  124. }
  125. return false
  126. }
  127. func isRight(at idx: Int) -> Bool {
  128. if self.rightCount <= 0 {
  129. return false
  130. }
  131. let stardIdx = self.leftCount+self.centerCount+2
  132. let endIdx = stardIdx+self.rightCount
  133. if idx >= stardIdx && idx < endIdx {
  134. return true
  135. }
  136. return false
  137. }
  138. func isFirstSegI(at idx: Int) -> Bool {
  139. return idx == self.leftCount
  140. }
  141. func isSecondSegI(at idx: Int) -> Bool {
  142. return idx == (self.leftCount+self.centerCount+1)
  143. }
  144. func isSeg(at idx: Int) -> Bool {
  145. if self.isFirstSegI(at: idx) || self.isSecondSegI(at: idx) {
  146. return true
  147. }
  148. return false
  149. }
  150. func findAllowedItem(at idx: Int) -> String? {
  151. if self.isLeft(at: idx) {
  152. return self.leftCellIdentifiers?[idx]
  153. }
  154. if self.isCenter(at: idx) {
  155. let theIdx = idx-self.leftCount-1
  156. return self.centerCellIdentifiers?[theIdx]
  157. }
  158. if self.isRight(at: idx) {
  159. let theIdx = idx-self.leftCount-self.centerCount-2
  160. return self.rightCellIdentifiers?[theIdx]
  161. }
  162. return nil
  163. }
  164. func removeItem(at idx: Int) -> Bool {
  165. if self.isLeft(at: idx) {
  166. self.leftCellIdentifiers?.remove(at: idx)
  167. return true
  168. }
  169. if self.isCenter(at: idx) {
  170. let theIdx = idx-self.leftCount-1
  171. self.centerCellIdentifiers?.remove(at: theIdx)
  172. return true
  173. }
  174. if self.isRight(at: idx) {
  175. let theIdx = idx-self.leftCount-self.centerCount-2
  176. self.rightCellIdentifiers?.remove(at: theIdx)
  177. return true
  178. }
  179. return false
  180. }
  181. func addItem(itemId: String, at idx: Int) -> Bool {
  182. let isFixedSpaceItem = itemId == KMToolbarFixedSpaceItemIdentifier
  183. if self.isLeft(at: idx) || idx == self.leftCount {
  184. let contains = self.leftCellIdentifiers?.contains(itemId) ?? false
  185. if contains == false || isFixedSpaceItem {
  186. self.leftCellIdentifiers?.insert(itemId, at: idx)
  187. }
  188. return true
  189. }
  190. if self.isCenter(at: idx) || idx == (self.leftCount+self.centerCount+1) {
  191. let theIdx = idx-self.leftCount-1
  192. let contains = self.centerCellIdentifiers?.contains(itemId) ?? false
  193. if contains == false || isFixedSpaceItem {
  194. self.centerCellIdentifiers?.insert(itemId, at: theIdx)
  195. }
  196. return true
  197. }
  198. if self.isRight(at: idx) || idx == (self.leftCount+self.centerCount+self.rightCount+2) {
  199. let theIdx = idx-self.leftCount-self.centerCount-2
  200. let contains = self.rightCellIdentifiers?.contains(itemId) ?? false
  201. if contains == false || isFixedSpaceItem {
  202. self.rightCellIdentifiers?.insert(itemId, at: theIdx)
  203. }
  204. return true
  205. }
  206. return false
  207. }
  208. func moveItem(itemIdx: Int, to idx: Int) -> Bool {
  209. if self.isLeft(at: itemIdx) { // left
  210. if self.isLeft(at: idx) { // left -> left [ || idx == self.leftCount]
  211. self.leftCellIdentifiers?.swapAt(itemIdx, idx)
  212. return true
  213. } else if self.isCenter(at: idx) || idx == (self.leftCount+self.centerCount+1) { // left -> center
  214. if let itemId = self.findAllowedItem(at: itemIdx) {
  215. _ = self.addItem(itemId: itemId, at: idx)
  216. self.leftCellIdentifiers?.remove(at: itemIdx)
  217. return true
  218. }
  219. } else if self.isRight(at: idx) || idx == self.leftCount+self.centerCount+self.rightCount+2 { // left -> right
  220. if let itemId = self.findAllowedItem(at: itemIdx) {
  221. _ = self.addItem(itemId: itemId, at: idx)
  222. self.leftCellIdentifiers?.remove(at: itemIdx)
  223. return true
  224. }
  225. }
  226. } else if self.isCenter(at: itemIdx) { // center
  227. if self.isLeft(at: idx) || idx == self.leftCount { // center -> left
  228. if let itemId = self.findAllowedItem(at: itemIdx) {
  229. _ = self.removeItem(at: itemIdx)
  230. _ = self.addItem(itemId: itemId, at: idx)
  231. return true
  232. }
  233. } else if self.isCenter(at: idx) { // center -> center [|| idx == (self.leftCount+self.centerCount+1]
  234. let offset = self.leftCount+1
  235. self.centerCellIdentifiers?.swapAt(itemIdx-offset, idx-offset)
  236. return true
  237. } else if self.isRight(at: idx) || idx == self.leftCount+self.centerCount+self.rightCount+2 { // center -> right
  238. if let itemId = self.findAllowedItem(at: itemIdx) {
  239. _ = self.addItem(itemId: itemId, at: idx)
  240. _ = self.removeItem(at: itemIdx)
  241. return true
  242. }
  243. }
  244. } else if self.isRight(at: itemIdx) { // right
  245. if self.isLeft(at: idx) || idx == self.leftCount { // right -> left
  246. if let itemId = self.findAllowedItem(at: itemIdx) {
  247. _ = self.removeItem(at: itemIdx)
  248. _ = self.addItem(itemId: itemId, at: idx)
  249. return true
  250. }
  251. } else if self.isCenter(at: idx) || idx == (self.leftCount+self.centerCount+1) { // right -> center
  252. if let itemId = self.findAllowedItem(at: itemIdx) {
  253. _ = self.removeItem(at: itemIdx)
  254. _ = self.addItem(itemId: itemId, at: idx)
  255. return true
  256. }
  257. } else if self.isRight(at: idx) { // right -> right [|| idx == self.leftCount+self.centerCount+self.rightCount+2]
  258. let offset = self.leftCount+self.centerCount+2
  259. self.rightCellIdentifiers?.swapAt(itemIdx-offset, idx-offset)
  260. return true
  261. }
  262. }
  263. return false
  264. }
  265. }
  266. extension KMToolbarConfigModel {
  267. func setupMainItem(_ item: KMToolbarItemView?) {
  268. let identifier = item?.itemIdentifier
  269. if identifier == KMLeftControlToolbarItemIdentifier {
  270. item?.image = NSImage(named: "KMImageNameUXIconBtnTriLeftNor")
  271. item?.titleName = NSLocalizedString("Panel", comment: "")
  272. // item?.toolTip = NSLocalizedString("View Settings", comment: "")
  273. item?.boxImagePosition = .imageAbove
  274. item?.selectBackgroundType = .imageBox
  275. } else if identifier == KMDocumentZoomViewToolbarItemIdentifier{
  276. item?.titleName = NSLocalizedString("Zoom", comment: "")
  277. let view = KMToolbarZoomItemView(zoomView: nil)
  278. item?.customizeView = view
  279. } else if identifier == KMDocumentPreviousPageToolbarItemIdentifier {
  280. item?.titleName = NSLocalizedString("Zoom", comment: "")
  281. let view = KMToolbarPreviousNextItemView()
  282. item?.customizeView = view
  283. } else if identifier == KMDocumentHomeToolbarItemIdentifier {
  284. item?.image = NSImage(named: "KMImageNameToolbarHomeNor")
  285. item?.titleName = NSLocalizedString("Home", comment: "")
  286. // item?.toolTip = NSLocalizedString("A Welcome Gift from Us", comment: "")
  287. item?.boxImagePosition = .imageAbove
  288. item?.selectBackgroundType = .imageBox
  289. } else if identifier == KMDocumentAnnotationToolbarItemIdentifier {
  290. item?.titleName = NSLocalizedString("Tools", comment: "")
  291. item?.image = NSImage(named: "KMImageNameUXIconToolbarMytoolsNor")
  292. // item?.toolTip = String(format: "%@: %@, %@, %@, %@", KMLocalizedString("Tool Mode"),KMLocalizedString("Annotate"),KMLocalizedString("Scroll"),KMLocalizedString("Magnify"),KMLocalizedString("Select"))
  293. item?.boxImagePosition = .imageAbove
  294. item?.selectBackgroundType = .imageBox
  295. } else if identifier == KMDocumentPageToolbarItemIdentifier {
  296. item?.titleName = NSLocalizedString("Page Edit", comment: "")
  297. item?.image = NSImage(named: "KMImageNameUXIconToolbarPageeditNor")
  298. // item?.toolTip = NSLocalizedString("PDF page editor: insert, delete, extract, rotate, reposition, and replace pages in a PDF", comment: "")
  299. item?.boxImagePosition = .imageAbove
  300. item?.selectBackgroundType = .imageBox
  301. } else if identifier == KMDocumentConversonToolbarItemIdentifier {
  302. item?.titleName = NSLocalizedString("Converter", comment: "")
  303. item?.image = NSImage(named: "KMImageNameUXIconToolbarConvertNor")
  304. // item?.toolTip = NSLocalizedString("Convert PDFs to Microsoft Word, PowerPoint, Excel, RTF, Text, Image, CSV, and more Offline", comment: "")
  305. item?.boxImagePosition = .imageAbove
  306. item?.selectBackgroundType = .imageBox
  307. } else if identifier == KMDocumentScanOCRToolbarItemIdentifier {
  308. item?.titleName = NSLocalizedString("OCR", comment: "")
  309. item?.image = NSImage(named: "KMImageNameToolbarOCRNor")
  310. item?.boxImagePosition = .imageAbove
  311. // item?.toolTip = NSLocalizedString("Recognize text from Image-based or Scanned PDF with OCR", comment: "")
  312. item?.selectBackgroundType = .imageBox
  313. } else if identifier == KMToolbarToolEnhancedScanIdentifier {
  314. item?.image = NSImage(named: "KMImageNameMainToolEnhancedScan")
  315. // item?.toolTip = NSLocalizedString("Enhanced Scan", comment: "")
  316. item?.titleName = NSLocalizedString("Enhanced Scan", comment: "")
  317. item?.boxImagePosition = .imageLeft
  318. item?.selectBackgroundType = .imageBox
  319. } else if identifier == KMToolbarToolOCRTextIdentifier {
  320. item?.image = NSImage(named: "KMImageNameMainToolOCRText")
  321. // item?.toolTip = NSLocalizedString("OCR Text Recognition", comment: "")
  322. item?.titleName = NSLocalizedString("OCR Text Recognition", comment: "")
  323. item?.boxImagePosition = .imageLeft
  324. item?.selectBackgroundType = .imageBox
  325. } else if identifier == KMDocumentEditToolbarItemIdentifier {
  326. item?.titleName = NSLocalizedString("Edit PDF", comment: "")
  327. item?.image = NSImage(named: "KMImageNameUXIconToolbarEditNor")
  328. item?.boxImagePosition = .imageAbove
  329. // item?.toolTip = NSLocalizedString("Edit text and image in PDF ", comment: "")
  330. item?.selectBackgroundType = .imageBox
  331. } else if identifier == KMDocumentFormToolbarItemIdentifier {
  332. item?.titleName = NSLocalizedString("Forms", comment: "")
  333. item?.image = NSImage(named: "KMImageNameUXIconToolbarFormNor")
  334. item?.boxImagePosition = .imageAbove
  335. // item?.toolTip = NSLocalizedString("Edit PDF Form", comment: "")
  336. item?.selectBackgroundType = .imageBox
  337. } else if identifier == KMDocumentFillSginToolbarItemIdentifier {
  338. item?.titleName = NSLocalizedString("Fill & Sign", comment: "")
  339. item?.image = NSImage(named: "KMImageNameUXIconToolbarFillsignNor")
  340. item?.boxImagePosition = .imageAbove
  341. // item?.toolTip = NSLocalizedString("Fill and sign forms", comment: "")
  342. item?.selectBackgroundType = .imageBox
  343. } else if identifier == KMDocumentToolToolbarItemIdentifier {
  344. item?.titleName = NSLocalizedString("Editor", comment: "")
  345. item?.image = NSImage(named: "KMImageNameUXIconToolbarEdittoolNor")
  346. item?.boxImagePosition = .imageAbove
  347. // item?.toolTip = NSLocalizedString("Edit, delete, cut, copy, paste, and insert text in PDFs", comment: "")
  348. item?.selectBackgroundType = .imageBox
  349. } else if identifier == KMDocumentRedactToolbarItemIdentifier {
  350. item?.titleName = NSLocalizedString("Redact Text", comment: "")
  351. item?.image = NSImage(named: "KMImageNameUXIconToolbarRedactNor")
  352. item?.boxImagePosition = .imageAbove
  353. // item?.toolTip = NSLocalizedString("Mark for redaction", comment: "")
  354. item?.selectBackgroundType = .imageBox
  355. } else if identifier == KMDocumentAITranslationToolbarItemIdentifier {
  356. item?.image = NSImage(named: "ic_function_other_AITranslation")
  357. item?.titleName = "AI Translation"
  358. // item?.toolTip = NSLocalizedString("AI Translation", comment: "")
  359. item?.boxImagePosition = .imageOnly
  360. } else if identifier == KMDocumentPrintToolbarItemIdentifier {
  361. item?.image = NSImage(named: "KMImageNameToolbarPrintNor")
  362. item?.titleName = "Print"
  363. // item?.toolTip = NSLocalizedString("Print", comment: "")
  364. item?.boxImagePosition = .imageOnly
  365. } else if identifier == KMDocumentViewDisplayToolbarItemIdentifier {
  366. item?.image = NSImage(named: "KMImageNameUXIconToolbarPageviewNor")
  367. item?.titleName = NSLocalizedString("Page Display", comment: "")
  368. // item?.toolTip = NSLocalizedString("Page Display", comment: "")
  369. item?.boxImagePosition = .imageAbove
  370. item?.selectBackgroundType = .imageBox
  371. } else if identifier == KMDocumentAIToolsToolbarItemIdentifier {
  372. item?.image = NSImage(named: "KMImageNameUXIconAINor")
  373. item?.titleName = NSLocalizedString("AI Tools", comment: "")
  374. // item?.toolTip = NSLocalizedString("AI Tools", comment: "")
  375. item?.boxImagePosition = .imageAbove
  376. item?.selectBackgroundType = .imageBox
  377. } else if identifier == KMDocumentShareToolbarItemIdentifier {
  378. item?.image = NSImage(named: "KMImageNameUXIconToolbarShareNor")
  379. item?.titleName = NSLocalizedString("Share", comment: "")
  380. // item?.toolTip = NSLocalizedString("Share the file with others", comment: "")
  381. item?.boxImagePosition = .imageAbove
  382. item?.selectBackgroundType = .imageBox
  383. } else if identifier == KMDocumentSearchToolbarItemIdentifier {
  384. item?.titleName = NSLocalizedString("Search", comment: "")
  385. let view = NSView()
  386. view.frame = NSMakeRect(0, 0, 150, 40)
  387. let boxView = NSView()
  388. boxView.frame = NSMakeRect(0, 16, 150, 22)
  389. view.addSubview(boxView)
  390. let searchView = NSSearchField()
  391. searchView.frame = NSMakeRect(0, 0, 150, 22)
  392. searchView.placeholderString = NSLocalizedString("Search", comment: "")
  393. searchView.sendsWholeSearchString = true
  394. searchView.sendsSearchStringImmediately = true
  395. searchView.drawsBackground = false
  396. boxView.addSubview(searchView)
  397. let titleLabel = NSTextField(labelWithString: NSLocalizedString("Search", comment: ""))
  398. view.addSubview(titleLabel)
  399. titleLabel.frame = NSMakeRect(0, 0, 130, 14)
  400. titleLabel.alignment = .center
  401. titleLabel.textColor = KMAppearance.subtitleColor()
  402. titleLabel.font = KMToolbarMainItemView.textFont
  403. item?.customizeView = view
  404. } else if identifier == KMDocumentPageInputToolbarItemIdentifier {
  405. item?.titleName = NSLocalizedString("Page", comment: "")
  406. let view = KMToolbarPageInputItemView()
  407. item?.customizeView = view
  408. view.totalNumber = 0
  409. } else if identifier == KMRightControlToolbarItemIdentifier {
  410. item?.image = NSImage(named: "KMImageNameUXIconBtnTriRightNor")
  411. item?.titleName = NSLocalizedString("Properties", comment: "")
  412. // item?.toolTip = NSLocalizedString("Show/Hide Annotation Properties Panel", comment: "")
  413. item?.boxImagePosition = .imageAbove
  414. item?.selectBackgroundType = .imageBox
  415. } else if identifier == KMToolbarToolRedactItemIdentifier {
  416. item?.image = NSImage(named: "KMImageNameMainToolsRedact")
  417. // item?.toolTip = NSLocalizedString("Redact", comment: "")
  418. item?.titleName = NSLocalizedString("Redact", comment: "")
  419. item?.selectBackgroundType = .imageBox
  420. }
  421. // else if identifier == KMDocumentDigitalSignToolbarItemIdentifier {
  422. // item?.image = NSImage(named: "DigitalSign_icon")
  423. //// item?.toolTip = NSLocalizedString("Digital signature ensures the authenticity and integrity of digital files. Click and drag the cursor to create a signature field on the page.", comment: "")
  424. // item?.titleName = NSLocalizedString("Digital Sign", comment: "")
  425. // item?.selectBackgroundType = .imageBox
  426. // item?.boxImagePosition = .imageAbove
  427. // }
  428. else if identifier == KMDocumentSignToolbarItemIdentifier {
  429. item?.image = NSImage(named: "DigitalSign_icon")
  430. // item?.toolTip = NSLocalizedString("Digital signature ensures the authenticity and integrity of digital files. Click and drag the cursor to create a signature field on the page.", comment: "")
  431. item?.titleName = NSLocalizedString("Signature", comment: "")
  432. item?.selectBackgroundType = .imageBox
  433. item?.boxImagePosition = .imageAbove
  434. } else if identifier == KMDocumentPreviousBackToolbarItemIdentifier {
  435. item?.titleName = NSLocalizedString("Zoom", comment: "")
  436. let view = KMToolbarPreviousBackItemView()
  437. item?.customizeView = view
  438. } else if identifier == KMDocumentFirstLastToolbarItemIdentifier {
  439. item?.titleName = NSLocalizedString("Zoom", comment: "")
  440. let view = KMToolbarFirstLastItemView()
  441. item?.customizeView = view
  442. } else if identifier == KMDocumentPageIndicatorToolbarItemIdentifier {
  443. item?.titleName = NSLocalizedString("Zoom", comment: "")
  444. let view = KMToolbarPageIndicatorItemView(zoomView: nil)
  445. item?.customizeView = view
  446. } else if identifier == KMDocumentPresentationToolbarItemIdentifier {
  447. item?.image = NSImage(named: "KMImageNameToolbarSlideshowNor")
  448. item?.titleName = NSLocalizedString("Presentation", comment: "")
  449. item?.selectBackgroundType = .imageBox
  450. item?.boxImagePosition = .imageAbove
  451. } else if identifier == KMToolbarFixedSpaceItemIdentifier {
  452. let view = NSView()
  453. view.frame = NSMakeRect(0, 0, 36, 36)
  454. view.wantsLayer = true
  455. view.layer?.borderWidth = 1
  456. item?.customizeView = view
  457. }
  458. }
  459. }