SettingsDisplayView.swift 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. //
  2. // SettingsDisplayView.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by Niehaoyu on 2024/9/26.
  6. //
  7. import Cocoa
  8. import KMComponentLibrary
  9. class SettingsDisplayView: BaseXibView {
  10. @IBOutlet weak var contendBox: NSBox!
  11. @IBOutlet weak var layoutZoomView: NSView!
  12. @IBOutlet weak var layoutZoomLabel: NSTextField!
  13. @IBOutlet weak var layoutLabel: NSTextField!
  14. @IBOutlet weak var layoutSelectView: ComponentSelect!
  15. @IBOutlet weak var zoomLabel: NSTextField!
  16. @IBOutlet weak var zoomSelectView: ComponentSelect!
  17. @IBOutlet weak var leftSideView: NSView!
  18. @IBOutlet weak var leftSideLabel: NSTextField!
  19. @IBOutlet weak var defaultOpenRadio: ComponentRadio!
  20. @IBOutlet weak var defaultOpenSelectView: ComponentSelect!
  21. @IBOutlet weak var remeberLastRadio: ComponentRadio!
  22. @IBOutlet weak var hideLeftSideRadio: ComponentRadio!
  23. @IBOutlet weak var prioritizeOutlineRadio: ComponentRadio!
  24. @IBOutlet weak var defaultOpenRadioWidthConst: NSLayoutConstraint!
  25. @IBOutlet weak var remeberLastRadioWidthConst: NSLayoutConstraint!
  26. @IBOutlet weak var hideLeftSideRadioWidthConst: NSLayoutConstraint!
  27. @IBOutlet weak var prioritizeOutlineCheckboxWidthConst: NSLayoutConstraint!
  28. @IBOutlet weak var propertyPanelView: NSView!
  29. @IBOutlet weak var propertyPanelLabel: NSTextField!
  30. @IBOutlet var autoExpandPropertyPanelCheckbox: ComponentCheckBox!
  31. @IBOutlet weak var showQuickCheckbox: ComponentCheckBox!
  32. @IBOutlet weak var showQuickCheckboxWidthConst: NSLayoutConstraint!
  33. @IBOutlet var propertyPanelWidthConst: NSLayoutConstraint!
  34. @IBOutlet weak var highlightView: NSView!
  35. @IBOutlet weak var highlightLabel: NSTextField!
  36. @IBOutlet weak var highlightLinkCheckbox: ComponentCheckBox!
  37. @IBOutlet weak var highlightFormCheckbox: ComponentCheckBox!
  38. @IBOutlet weak var highlightLinkBoxWidthConst: NSLayoutConstraint!
  39. @IBOutlet weak var highglightFormboxWidthConst: NSLayoutConstraint!
  40. @IBOutlet var autoScrollView: NSView!
  41. @IBOutlet var autoScrolllabel: NSTextField!
  42. @IBOutlet var timeLabel: NSTextField!
  43. @IBOutlet var autoTimeSlider: ComponentSlider!
  44. @IBOutlet var autoTimeSelect: ComponentSelect!
  45. @IBOutlet var timeValueLabel: NSTextField!
  46. @IBOutlet var jumpSpaceLabel: NSTextField!
  47. @IBOutlet var jumpSpaceSlider: ComponentSlider!
  48. @IBOutlet var jumpSpaceSelect: ComponentSelect!
  49. @IBOutlet var jumpSpaceValueLabel: NSTextField!
  50. deinit {
  51. NotificationCenter.default.removeObserver(self)
  52. }
  53. override func draw(_ dirtyRect: NSRect) {
  54. super.draw(dirtyRect)
  55. // Drawing code here.
  56. self.setTitleUI()
  57. self.setUpLayoutAndZoom()
  58. self.setUpLeftSidePanel()
  59. self.setUpPropertyPanel()
  60. self.setUpHighlight()
  61. setUpAutoScroll()
  62. self.reloadData()
  63. }
  64. public required init?(coder decoder: NSCoder) {
  65. super.init(coder: decoder)
  66. }
  67. override init(frame frameRect: NSRect) {
  68. super.init(frame: frameRect)
  69. }
  70. public override func awakeFromNib() {
  71. super.awakeFromNib()
  72. }
  73. override func updateUIThemeColor() {
  74. super.updateUIThemeColor()
  75. self.setTitleUI()
  76. }
  77. func setTitleUI () {
  78. //获取颜色
  79. let titleLabelColor: NSColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2")
  80. let subtitleLabelColor: NSColor = ComponentLibrary.shared.getComponentColorFromKey("comp-field/colorText-filled-nor")
  81. //获取字体
  82. let titleLabelFont: NSFont = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium")
  83. let subtitleLabelFont: NSFont = ComponentLibrary.shared.getFontFromKey("mac/body-s-regular")
  84. //Layout and Zoom
  85. layoutZoomLabel.stringValue = KMLocalizedString("Default Layout and Zoom")
  86. layoutZoomLabel.textColor = titleLabelColor
  87. layoutZoomLabel.font = titleLabelFont
  88. layoutLabel.stringValue = KMLocalizedString("Page Layout:")
  89. layoutLabel.textColor = subtitleLabelColor
  90. layoutLabel.font = subtitleLabelFont
  91. zoomLabel.stringValue = KMLocalizedString("Zoom:")
  92. zoomLabel.textColor = subtitleLabelColor
  93. zoomLabel.font = subtitleLabelFont
  94. leftSideLabel.stringValue = KMLocalizedString("Left Side Panel")
  95. leftSideLabel.textColor = titleLabelColor
  96. leftSideLabel.font = titleLabelFont
  97. propertyPanelLabel.stringValue = KMLocalizedString("Property Panel")
  98. propertyPanelLabel.textColor = titleLabelColor
  99. propertyPanelLabel.font = titleLabelFont
  100. highlightLabel.stringValue = KMLocalizedString("Highlight")
  101. highlightLabel.textColor = titleLabelColor
  102. highlightLabel.font = titleLabelFont
  103. autoScrolllabel.stringValue = KMLocalizedString("Auto Scroll Options…")
  104. autoScrolllabel.textColor = titleLabelColor
  105. autoScrolllabel.font = titleLabelFont
  106. timeLabel.stringValue = KMLocalizedString("Time Interval") + ":"
  107. timeLabel.textColor = subtitleLabelColor
  108. timeLabel.font = subtitleLabelFont
  109. timeValueLabel.stringValue = KMLocalizedString("sec (1~100)")
  110. timeValueLabel.textColor = subtitleLabelColor
  111. timeValueLabel.font = subtitleLabelFont
  112. jumpSpaceLabel.stringValue = KMLocalizedString("Jump Space") + ":"
  113. jumpSpaceLabel.textColor = subtitleLabelColor
  114. jumpSpaceLabel.font = subtitleLabelFont
  115. jumpSpaceValueLabel.stringValue = KMLocalizedString("px (10~1000)")
  116. jumpSpaceValueLabel.textColor = subtitleLabelColor
  117. jumpSpaceValueLabel.font = subtitleLabelFont
  118. }
  119. func setUpLayoutAndZoom() {
  120. layoutSelectView.properties = ComponentSelectProperties(size: .s,
  121. state: .normal,
  122. text: KMLocalizedString("Single Page Continuous"))
  123. if true {
  124. var menuItemArr: [ComponentMenuitemProperty] = []
  125. for string in ["Single Page", "Single Page Continuous", "Two Pages", "Two Pages Continuous",
  126. "Book Mode", "Book Mode Continuous"] {
  127. var itemProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false,
  128. itemSelected: false,
  129. isDisabled: false,
  130. keyEquivalent: nil,
  131. text: KMLocalizedString(string))
  132. if string == " " {
  133. itemProperty = ComponentMenuitemProperty.divider()
  134. }
  135. menuItemArr.append(itemProperty)
  136. }
  137. layoutSelectView.updateMenuItemsArr(menuItemArr)
  138. layoutSelectView.delegate = self
  139. }
  140. zoomSelectView.properties = ComponentSelectProperties(size: .s,
  141. state: .normal,
  142. text: KMLocalizedString("Adaptation Width"))
  143. if true {
  144. zoomSelectView.updateMenuItemsArr(KMPDFToolbarConfig.scaleZoomItems())
  145. zoomSelectView.delegate = self
  146. }
  147. }
  148. func setUpLeftSidePanel() {
  149. //Left Side Panel
  150. defaultOpenRadio.properties = ComponentCheckBoxProperty(size: .s,
  151. state: .normal,
  152. isDisabled: false,
  153. showhelp: false,
  154. text: KMLocalizedString("Default Open"),
  155. checkboxType: .normal)
  156. defaultOpenRadioWidthConst.constant = defaultOpenRadio.properties.propertyInfo.viewWidth
  157. defaultOpenSelectView.properties = ComponentSelectProperties(size: .s,
  158. state: .normal,
  159. isDisabled: false,
  160. isError: false,
  161. leftIcon: false,
  162. placeholder: nil,
  163. errorText: nil,
  164. creatable: false,
  165. text: KMLocalizedString("Thumbnail"))
  166. if true {
  167. var menuItemArr: [ComponentMenuitemProperty] = []
  168. let ThumbnailProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: KMLocalizedString("Thumbnails"))
  169. menuItemArr.append(ThumbnailProperty)
  170. let OutlineProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: KMLocalizedString("Outline"))
  171. menuItemArr.append(OutlineProperty)
  172. let BookmarkProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: KMLocalizedString("Bookmarks"))
  173. menuItemArr.append(BookmarkProperty)
  174. let AnnotationProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false, itemSelected: false, isDisabled: false, keyEquivalent: nil, text: KMLocalizedString("Annotations"))
  175. menuItemArr.append(AnnotationProperty)
  176. defaultOpenSelectView.updateMenuItemsArr(menuItemArr)
  177. defaultOpenSelectView.delegate = self
  178. }
  179. remeberLastRadio.properties = ComponentCheckBoxProperty(size: .s,
  180. state: .normal,
  181. isDisabled: false,
  182. showhelp: false,
  183. text: KMLocalizedString("Same as the last open"),
  184. checkboxType: .normal)
  185. remeberLastRadioWidthConst.constant = remeberLastRadio.properties.propertyInfo.viewWidth
  186. hideLeftSideRadio.properties = ComponentCheckBoxProperty(size: .s,
  187. state: .normal,
  188. isDisabled: false,
  189. showhelp: false,
  190. text: KMLocalizedString("Hide left side panel"),
  191. checkboxType: .normal)
  192. hideLeftSideRadioWidthConst.constant = hideLeftSideRadio.properties.propertyInfo.viewWidth
  193. prioritizeOutlineRadio.properties = ComponentCheckBoxProperty(size: .s,
  194. state: .normal,
  195. isDisabled: false,
  196. showhelp: false,
  197. text: KMLocalizedString("Prioritize open the outline list when outlines are available"),
  198. checkboxType: .normal)
  199. prioritizeOutlineCheckboxWidthConst.constant = prioritizeOutlineRadio.properties.propertyInfo.viewWidth
  200. defaultOpenRadio.setTarget(self, action: #selector(leftSidePanelAction(_:)))
  201. remeberLastRadio.setTarget(self, action: #selector(leftSidePanelAction(_:)))
  202. hideLeftSideRadio.setTarget(self, action: #selector(leftSidePanelAction(_:)))
  203. prioritizeOutlineRadio.setTarget(self, action: #selector(leftSidePanelAction(_:)))
  204. }
  205. func setUpPropertyPanel() {
  206. //Property Panel
  207. autoExpandPropertyPanelCheckbox.properties = ComponentCheckBoxProperty(size: .s,
  208. state: .normal,
  209. isDisabled: false,
  210. showhelp: true,
  211. text: KMLocalizedString("Automatically expand the properties panel"),
  212. checkboxType: SettingsManager.sharedInstance.showQuickActionBar ? .selected : .normal)
  213. autoExpandPropertyPanelCheckbox.setTarget(self, action: #selector(propertyPanelAction(_:)))
  214. autoExpandPropertyPanelCheckbox.toolTip = KMLocalizedString("If you don't want the automatic expansion mode to interfere with your immersive reading experience, you can turn this option off. You can still click the \"Properties\" icon to expand the panel when you need it (⌥⌘0).")
  215. propertyPanelWidthConst.constant = autoExpandPropertyPanelCheckbox.properties.propertyInfo.viewWidth
  216. showQuickCheckbox.properties = ComponentCheckBoxProperty(size: .s,
  217. state: .normal,
  218. isDisabled: false,
  219. showhelp: false,
  220. text: KMLocalizedString("Always show quick action bar"),
  221. checkboxType: SettingsManager.sharedInstance.showQuickActionBar ? .selected : .normal)
  222. showQuickCheckbox.setTarget(self, action: #selector(propertyPanelAction(_:)))
  223. showQuickCheckboxWidthConst.constant = showQuickCheckbox.properties.propertyInfo.viewWidth
  224. }
  225. func setUpHighlight() {
  226. //Highlight
  227. highlightLinkCheckbox.properties = ComponentCheckBoxProperty(size: .s,
  228. state: .normal,
  229. isDisabled: false,
  230. showhelp: false,
  231. text: KMLocalizedString("Highlight Links"),
  232. checkboxType: .normal)
  233. highlightLinkBoxWidthConst.constant = highlightLinkCheckbox.properties.propertyInfo.viewWidth
  234. highlightFormCheckbox.properties = ComponentCheckBoxProperty(size: .s,
  235. state: .normal,
  236. isDisabled: false,
  237. showhelp: false,
  238. text: KMLocalizedString("Highlight Form Fields"),
  239. checkboxType: .normal)
  240. highglightFormboxWidthConst.constant = highlightFormCheckbox.properties.propertyInfo.viewWidth
  241. highlightLinkCheckbox.setTarget(self, action: #selector(highlightAction(_:)))
  242. highlightFormCheckbox.setTarget(self, action: #selector(highlightAction(_:)))
  243. }
  244. func setUpAutoScroll() {
  245. autoTimeSlider.properties = ComponentSliderProperty(size: .m, percent: 1)
  246. autoTimeSlider.delegate = self
  247. autoTimeSelect.properties = ComponentSelectProperties(size: .s,
  248. state: .normal,
  249. text: KMLocalizedString("5"))
  250. if true {
  251. var menuItemArr: [ComponentMenuitemProperty] = []
  252. for string in ["5", "10", "20", "40", "50", "100"] {
  253. var itemProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false,
  254. itemSelected: false,
  255. isDisabled: false,
  256. keyEquivalent: nil,
  257. text: KMLocalizedString(string))
  258. if string == " " {
  259. itemProperty = ComponentMenuitemProperty.divider()
  260. }
  261. menuItemArr.append(itemProperty)
  262. }
  263. autoTimeSelect.updateMenuItemsArr(menuItemArr)
  264. autoTimeSelect.delegate = self
  265. }
  266. jumpSpaceSlider.properties = ComponentSliderProperty(size: .m, percent: 1)
  267. jumpSpaceSlider.delegate = self
  268. jumpSpaceSelect.properties = ComponentSelectProperties(size: .s,
  269. state: .normal,
  270. text: KMLocalizedString("20"))
  271. if true {
  272. var menuItemArr: [ComponentMenuitemProperty] = []
  273. for string in ["10", "50", "100", "200", "500", "1000"] {
  274. var itemProperty: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false,
  275. itemSelected: false,
  276. isDisabled: false,
  277. keyEquivalent: nil,
  278. text: KMLocalizedString(string))
  279. if string == " " {
  280. itemProperty = ComponentMenuitemProperty.divider()
  281. }
  282. menuItemArr.append(itemProperty)
  283. }
  284. jumpSpaceSelect.updateMenuItemsArr(menuItemArr)
  285. jumpSpaceSelect.delegate = self
  286. }
  287. }
  288. func reloadData() {
  289. //Default Layout and Zoom
  290. layoutSelectView.selectItemAtIndex(SettingsManager.sharedInstance.layoutType.rawValue)
  291. zoomSelectView.selectItemAtIndex(SettingsManager.sharedInstance.zoomType.rawValue)
  292. //Left Side Panel
  293. defaultOpenRadio.properties.checkboxType = .normal
  294. remeberLastRadio.properties.checkboxType = .normal
  295. hideLeftSideRadio.properties.checkboxType = .normal
  296. prioritizeOutlineRadio.properties.checkboxType = .normal
  297. defaultOpenSelectView.properties.isDisabled = true
  298. if SettingsManager.sharedInstance.leftPanelType == .defaultOpen {
  299. defaultOpenSelectView.properties.isDisabled = false
  300. defaultOpenRadio.properties.checkboxType = .selected
  301. } else if SettingsManager.sharedInstance.leftPanelType == .sameAsLastOpen {
  302. remeberLastRadio.properties.checkboxType = .selected
  303. } else if SettingsManager.sharedInstance.leftPanelType == .hideLeftSide {
  304. hideLeftSideRadio.properties.checkboxType = .selected
  305. } else if SettingsManager.sharedInstance.leftPanelType == .prioritizeOutline {
  306. prioritizeOutlineRadio.properties.checkboxType = .selected
  307. }
  308. defaultOpenRadio.reloadData()
  309. remeberLastRadio.reloadData()
  310. hideLeftSideRadio.reloadData()
  311. prioritizeOutlineRadio.reloadData()
  312. defaultOpenSelectView.selectItemAtIndex(SettingsManager.sharedInstance.defaultOpen.rawValue)
  313. defaultOpenSelectView.reloadData()
  314. //Property Panel
  315. autoExpandPropertyPanelCheckbox.properties.checkboxType = SettingsManager.sharedInstance.autoExpandPropertyPanel ? .selected : .normal
  316. autoExpandPropertyPanelCheckbox.reloadData()
  317. showQuickCheckbox.properties.checkboxType = SettingsManager.sharedInstance.showQuickActionBar ? .selected : .normal
  318. showQuickCheckbox.reloadData()
  319. //Highlight
  320. highlightLinkCheckbox.properties.checkboxType = CPDFKitConfig.sharedInstance().enableLinkFieldHighlight() ? .selected : .normal
  321. highlightLinkCheckbox.reloadData()
  322. highlightFormCheckbox.properties.checkboxType = CPDFKitConfig.sharedInstance().enableFormFieldHighlight() ? .selected : .normal
  323. highlightFormCheckbox.reloadData()
  324. //Auto Scroll
  325. let autoScrollTime: CGFloat = SettingsManager.sharedInstance.autoScrollTimeInterval
  326. autoTimeSlider.properties.percent = (autoScrollTime - 1) / (100 - 1)
  327. autoTimeSlider.reloadData()
  328. autoTimeSelect.properties.text = String(format: "%.0f", autoScrollTime, "%")
  329. autoTimeSelect.reloadData()
  330. let autoScrollJump: CGFloat = SettingsManager.sharedInstance.autoScrollJumpSpace
  331. jumpSpaceSlider.properties.percent = (autoScrollJump - 10) / (1000 - 10)
  332. jumpSpaceSlider.reloadData()
  333. jumpSpaceSelect.properties.text = String(format: "%.0f", autoScrollJump, "%")
  334. jumpSpaceSelect.reloadData()
  335. }
  336. //MARK: - Action
  337. @objc func leftSidePanelAction(_ sender: NSView) {
  338. if sender == defaultOpenRadio {
  339. SettingsManager.sharedInstance.leftPanelType = .defaultOpen
  340. } else if sender == remeberLastRadio {
  341. SettingsManager.sharedInstance.leftPanelType = .sameAsLastOpen
  342. } else if sender == hideLeftSideRadio {
  343. SettingsManager.sharedInstance.leftPanelType = .hideLeftSide
  344. } else if sender == prioritizeOutlineRadio {
  345. SettingsManager.sharedInstance.leftPanelType = .prioritizeOutline
  346. }
  347. self.reloadData()
  348. }
  349. @objc func propertyPanelAction(_ sender: NSView) {
  350. if sender == showQuickCheckbox {
  351. SettingsManager.sharedInstance.showQuickActionBar = showQuickCheckbox.properties.checkboxType == .selected ? true : false
  352. } else if sender == autoExpandPropertyPanelCheckbox {
  353. SettingsManager.sharedInstance.autoExpandPropertyPanel = autoExpandPropertyPanelCheckbox.properties.checkboxType == .selected ? true : false
  354. }
  355. self.reloadData()
  356. }
  357. @objc func highlightAction(_ sender: NSView) {
  358. if sender == highlightLinkCheckbox {
  359. let value = highlightLinkCheckbox.properties.checkboxType == .selected ? true : false
  360. CPDFAnnotation.updateLinkFieldHighlight(nil, linkFieldHighlight: value)
  361. NotificationCenter.default.post(name: kPDFViewHighlightLinkUpdateNotiName, object: nil)
  362. } else if sender == highlightFormCheckbox {
  363. let value = highlightFormCheckbox.properties.checkboxType == .selected ? true : false
  364. CPDFAnnotation.updateHighlightFormFiled(nil, highlightFormFiled: value)
  365. NotificationCenter.default.post(name: kPDFViewHighlightFormFiledUpdateNotiName, object: nil)
  366. }
  367. self.reloadData()
  368. }
  369. }
  370. //MARK: - ComponentSelectDelegate
  371. extension SettingsDisplayView: ComponentSelectDelegate {
  372. func componentSelectDidSelect(view: ComponentSelect?, menuItemProperty: ComponentMenuitemProperty?) {
  373. if view == layoutSelectView {
  374. if layoutSelectView.indexOfSelect() >= 0 {
  375. SettingsManager.sharedInstance.layoutType = pageLayoutType(rawValue: layoutSelectView.indexOfSelect())!
  376. }
  377. } else if view == zoomSelectView {
  378. if zoomSelectView.indexOfSelect() >= 0 {
  379. SettingsManager.sharedInstance.zoomType = zoomInfoType(rawValue: zoomSelectView.indexOfSelect())!
  380. }
  381. } else if view == defaultOpenSelectView {
  382. if defaultOpenSelectView.indexOfSelect() >= 0 {
  383. SettingsManager.sharedInstance.defaultOpen = defaultOpenType(rawValue: defaultOpenSelectView.indexOfSelect())!
  384. }
  385. }
  386. SettingsManager.sharedInstance.saveData()
  387. self.reloadData()
  388. }
  389. }
  390. //MARK: - ComponentSliderDelegate
  391. extension SettingsDisplayView: ComponentSliderDelegate {
  392. func componentSliderDidUpdate(_ view: ComponentSlider) {
  393. let value = view.properties.percent
  394. if view == autoTimeSlider {
  395. SettingsManager.sharedInstance.autoScrollTimeInterval = 1 + (100 - 1)*value
  396. } else if view == jumpSpaceSlider {
  397. SettingsManager.sharedInstance.autoScrollJumpSpace = 10 + (1000 - 10)*value
  398. }
  399. reloadData()
  400. }
  401. }