KMEditImageController.swift 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. //
  2. // KMEditImageController.swift
  3. // PDF Reader Pro
  4. //
  5. // Created by Niehaoyu on 2024/11/14.
  6. //
  7. import Cocoa
  8. import KMComponentLibrary
  9. class KMEditImageController: NSViewController {
  10. @IBOutlet var scrollView: NSScrollView!
  11. @IBOutlet var contendView: NSView!
  12. @IBOutlet var sizeBGView: NSView!
  13. @IBOutlet var sizeLabel: NSTextField!
  14. @IBOutlet var sizeSyncButton: ComponentButton!
  15. @IBOutlet var sizeWidthInput: ComponentInputNumber!
  16. @IBOutlet var sizeHeightInput: ComponentInputNumber!
  17. @IBOutlet var rotateBGView: NSView!
  18. @IBOutlet var rotateLabel: NSTextField!
  19. @IBOutlet var rotateSelect: ComponentSelect!
  20. @IBOutlet var rotateLeftButton: ComponentButton!
  21. @IBOutlet var rotateRightButton: ComponentButton!
  22. @IBOutlet var flipVerticalButton: ComponentButton!
  23. @IBOutlet var flipHorizontalButton: ComponentButton!
  24. @IBOutlet var opacityBGView: NSView!
  25. @IBOutlet var opacityLabel: NSTextField!
  26. @IBOutlet var opacitySlider: ComponentSlider!
  27. @IBOutlet var opacitySelect: ComponentSelect!
  28. @IBOutlet var cropButton: ComponentButton!
  29. @IBOutlet var replaceButton: ComponentButton!
  30. @IBOutlet var extractButton: ComponentButton!
  31. @IBOutlet var extractBtnTopConst: NSLayoutConstraint!
  32. @IBOutlet var alignmentBGView: NSView!
  33. @IBOutlet var emptyView: ComponentEmpty!
  34. private var syncChangeBounds: Bool = true //同步修改宽高
  35. private var groupView: ComponentGroup!
  36. private var alignmentController: KMNAlignmentController?
  37. private var areas: [CPDFEditImageArea] = []
  38. var pdfView: CPDFListView?
  39. //MARK: - func
  40. override func viewDidAppear() {
  41. super.viewDidAppear()
  42. opacitySlider.reloadData()
  43. }
  44. override func viewDidLoad() {
  45. super.viewDidLoad()
  46. setupProperty()
  47. }
  48. func setupProperty() {
  49. self.scrollView.backgroundColor = ComponentLibrary.shared.backgroundColor(forToken: "colorBg/layout-middle") ?? NSColor.clear
  50. //Size
  51. sizeLabel.stringValue = KMLocalizedString("Size")
  52. sizeLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2")
  53. sizeLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium")
  54. sizeSyncButton.properties = ComponentButtonProperty(type: .text_gray, size: .xxs, onlyIcon: true, icon: NSImage(named: "sync_Change_unlock"), keepPressState: true)
  55. sizeSyncButton.properties.propertyInfo.leftIcon_press = NSImage(named: "sync_Change_lock")
  56. sizeSyncButton.toolTip = KMLocalizedString("Lock")
  57. sizeSyncButton.setTarget(self, action: #selector(sizeSyncButtonClicked(_:)))
  58. sizeSyncButton.reloadData()
  59. sizeWidthInput.properties = ComponentInputNumberProperty(alignment: .center,
  60. size: .s,
  61. state: .normal,
  62. showPrefix: true,
  63. leftIcon: NSImage(named: "w_icon"),
  64. showSuffix: false,
  65. minSize: 1,
  66. maxSize: 1000,
  67. text:"100",
  68. valueType: .floatType)
  69. sizeWidthInput.delegate = self
  70. sizeHeightInput.properties = ComponentInputNumberProperty(alignment: .center,
  71. size: .s,
  72. state: .normal,
  73. showPrefix: true,
  74. leftIcon: NSImage(named: "h_icon"),
  75. showSuffix: false,
  76. minSize: 1,
  77. maxSize: 1000,
  78. text:"100",
  79. valueType: .floatType)
  80. sizeHeightInput.delegate = self
  81. //Rotate
  82. rotateLabel.stringValue = KMLocalizedString("Rotate & Flip")
  83. rotateLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2")
  84. rotateLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium")
  85. rotateSelect.properties = ComponentSelectProperties(size: .s,
  86. state: .normal,
  87. creatable: true,
  88. text: "100",
  89. textUnit: "°",
  90. regexString: "0123456789-")
  91. if true {
  92. var opacityItems: [ComponentMenuitemProperty] = []
  93. for string in ["0°", "45°", "-45°", "90°", "-90°"] {
  94. let item = ComponentMenuitemProperty(type: .normal, text: string)
  95. opacityItems.append(item)
  96. }
  97. rotateSelect.updateMenuItemsArr(opacityItems)
  98. }
  99. rotateSelect.delegate = self
  100. rotateLeftButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "pageEdit_rotateRight"), keepPressState: false)
  101. rotateLeftButton.setTarget(self, action: #selector(buttonClicked(_:)))
  102. rotateRightButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "pageEdit_rotateLeft"), keepPressState: false)
  103. rotateRightButton.setTarget(self, action: #selector(buttonClicked(_:)))
  104. flipVerticalButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "flipVertical"), keepPressState: false)
  105. flipVerticalButton.setTarget(self, action: #selector(buttonClicked(_:)))
  106. flipHorizontalButton.properties = ComponentButtonProperty(type: .text_gray, size: .s, onlyIcon: true, icon: NSImage(named: "flipHorizontal"), keepPressState: false)
  107. flipHorizontalButton.setTarget(self, action: #selector(buttonClicked(_:)))
  108. //Opacity
  109. opacityLabel.stringValue = KMLocalizedString("Opacity")
  110. opacityLabel.textColor = ComponentLibrary.shared.getComponentColorFromKey("colorText/2")
  111. opacityLabel.font = ComponentLibrary.shared.getFontFromKey("mac/body-s-medium")
  112. opacitySlider.properties = ComponentSliderProperty(size: .m, percent: 1)
  113. opacitySlider.delegate = self
  114. opacitySelect.properties = ComponentSelectProperties(size: .s,
  115. state: .normal,
  116. creatable: true,
  117. text: "100",
  118. textUnit: "%",
  119. regexString: "0123456789%")
  120. if true {
  121. var opacityItems: [ComponentMenuitemProperty] = []
  122. for string in ["25%", "50%", "75%", "100%"] {
  123. let item = ComponentMenuitemProperty(type: .normal, text: string)
  124. opacityItems.append(item)
  125. }
  126. opacitySelect.updateMenuItemsArr(opacityItems)
  127. }
  128. opacitySelect.delegate = self
  129. cropButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Crop"), keepPressState: false)
  130. cropButton.setTarget(self, action: #selector(buttonClicked(_:)))
  131. replaceButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Replace"), keepPressState: false)
  132. replaceButton.setTarget(self, action: #selector(buttonClicked(_:)))
  133. extractButton.properties = ComponentButtonProperty(type: .default_tertiary, size: .s, buttonText: KMLocalizedString("Extract"), keepPressState: true)
  134. extractButton.setTarget(self, action: #selector(buttonClicked(_:)))
  135. if alignmentController == nil {
  136. alignmentController = KMNAlignmentController.init()
  137. }
  138. alignmentController?.view.frame = alignmentBGView.bounds
  139. alignmentController?.view.autoresizingMask = [.width, .height]
  140. alignmentController?.delegate = self
  141. alignmentBGView.addSubview(alignmentController!.view)
  142. emptyView.properties = ComponentEmptyProperty(emptyType: .addImage,
  143. text: KMLocalizedString("Add Image"),
  144. subText: KMLocalizedString("Select an area on the page to insert the image."))
  145. }
  146. func reloadData() {
  147. areas = pdfView?.km_editingImageAreas() ?? []
  148. emptyView.isHidden = true
  149. contendView.isHidden = false
  150. if areas.count == 0 {
  151. emptyView.isHidden = false
  152. contendView.isHidden = true
  153. } else if areas.count == 1 {
  154. cropButton.isHidden = false
  155. replaceButton.isHidden = false
  156. extractBtnTopConst.constant = 96
  157. alignmentBGView.isHidden = true
  158. } else if areas.count > 1 {
  159. cropButton.isHidden = true
  160. replaceButton.isHidden = true
  161. extractBtnTopConst.constant = 16
  162. alignmentBGView.isHidden = false
  163. if areas.count == 2 {
  164. alignmentController?.updateMulti(false)
  165. } else {
  166. alignmentController?.updateMulti(true)
  167. }
  168. }
  169. var first_area: CPDFEditImageArea?
  170. if areas.count > 0 {
  171. first_area = areas.first
  172. }
  173. //refreshData
  174. var boundsWidth: CGFloat?
  175. var boundsHeight: CGFloat?
  176. var rotateAngle: CGFloat?
  177. var opacity_value: CGFloat?
  178. if areas.count == 0 {
  179. } else if areas.count == 1, let area = first_area {
  180. boundsWidth = area.bounds.size.width
  181. boundsHeight = area.bounds.size.height
  182. if let rotates = pdfView?.km_editAreasRotates([area]) {
  183. rotateAngle = rotates.first ?? 1
  184. }
  185. if let opacitys = pdfView?.km_editAreasOpacitys([area]) {
  186. var opacity = opacitys.first ?? 1
  187. opacity = min(1, opacity)
  188. opacity = max(0, opacity)
  189. opacity_value = opacity
  190. }
  191. } else if areas.count > 1, let area = first_area {
  192. if CPDFEditArea.isMultiBoundsWidth(areas) == true {
  193. boundsWidth = nil
  194. } else {
  195. boundsWidth = area.bounds.size.width
  196. }
  197. if CPDFEditArea.isMultiBoundsHeight(areas) == true {
  198. boundsHeight = nil
  199. } else {
  200. boundsHeight = area.bounds.size.height
  201. }
  202. if CPDFEditArea.isMultiAngle(areas, inPDFView: pdfView) == true {
  203. rotateAngle = nil
  204. } else {
  205. rotateAngle = pdfView?.getRotateWith(area)
  206. }
  207. if CPDFEditArea.isMultiOpacity(areas, inPDFView: pdfView) == true {
  208. opacity_value = nil
  209. } else {
  210. var firstValue = pdfView?.opacityByRange(for: area) ?? 0
  211. if firstValue < 0 {
  212. firstValue = pdfView?.opacity(for: area) ?? 0
  213. }
  214. opacity_value = firstValue
  215. }
  216. }
  217. if let value = boundsWidth {
  218. sizeWidthInput.properties.text = String(format: "%.1f", value)
  219. sizeWidthInput.properties.multiState = false
  220. } else {
  221. sizeWidthInput.properties.text = "-"
  222. sizeWidthInput.properties.multiState = true
  223. }
  224. if let value = boundsHeight {
  225. sizeHeightInput.properties.text = String(format: "%.1f", value)
  226. sizeHeightInput.properties.multiState = false
  227. } else {
  228. sizeHeightInput.properties.text = "-"
  229. sizeHeightInput.properties.multiState = true
  230. }
  231. sizeWidthInput.properties.isDisabled = pdfView?.isEditImage == true
  232. sizeWidthInput.reloadData()
  233. sizeHeightInput.properties.isDisabled = pdfView?.isEditImage == true
  234. sizeHeightInput.reloadData()
  235. sizeSyncButton.properties.isDisabled = pdfView?.isEditImage == true
  236. if syncChangeBounds {
  237. sizeSyncButton.properties.state = .pressed
  238. } else {
  239. sizeSyncButton.properties.state = .normal
  240. }
  241. sizeSyncButton.reloadData()
  242. rotateSelect.properties.isDisabled = pdfView?.isEditImage == true
  243. rotateLeftButton.properties.isDisabled = pdfView?.isEditImage == true
  244. rotateLeftButton.reloadData()
  245. rotateRightButton.properties.isDisabled = pdfView?.isEditImage == true
  246. rotateRightButton.reloadData()
  247. flipVerticalButton.properties.isDisabled = pdfView?.isEditImage == true
  248. flipVerticalButton.reloadData()
  249. flipHorizontalButton.properties.isDisabled = pdfView?.isEditImage == true
  250. flipHorizontalButton.reloadData()
  251. opacitySlider.properties.isDisabled = pdfView?.isEditImage == true
  252. opacitySlider.reloadData()
  253. opacitySelect.properties.isDisabled = pdfView?.isEditImage == true
  254. replaceButton.properties.isDisabled = pdfView?.isEditImage == true
  255. replaceButton.reloadData()
  256. extractButton.properties.isDisabled = pdfView?.isEditImage == true
  257. extractButton.reloadData()
  258. if let value = rotateAngle {
  259. rotateSelect.properties.text = String(format: "%.0f", value)
  260. } else {
  261. rotateSelect.properties.text = "-"
  262. }
  263. rotateSelect.reloadData()
  264. if let value = opacity_value {
  265. opacitySelect.properties.text = String(format: "%.0f", value*100)
  266. opacitySlider.properties.percent = value
  267. } else {
  268. opacitySelect.properties.text = "-"
  269. opacitySlider.properties.percent = 0
  270. }
  271. opacitySelect.reloadData()
  272. opacitySlider.reloadData()
  273. if pdfView?.isEditImage == true {
  274. cropButton.properties.buttonText = KMLocalizedString("Cancel Crop")
  275. } else {
  276. cropButton.properties.buttonText = KMLocalizedString("Crop")
  277. }
  278. cropButton.reloadData()
  279. }
  280. //MARK: - Action
  281. @objc func sizeSyncButtonClicked(_ sender: ComponentButton) {
  282. syncChangeBounds = !syncChangeBounds
  283. if syncChangeBounds {
  284. sizeSyncButton.properties.state = .pressed
  285. } else {
  286. sizeSyncButton.properties.state = .normal
  287. }
  288. sizeSyncButton.reloadData()
  289. }
  290. @objc func buttonClicked(_ sender: ComponentButton) {
  291. if sender == rotateLeftButton {
  292. self.pdfView?.leftRotateAction()
  293. } else if sender == rotateRightButton {
  294. self.pdfView?.rightRotateAction()
  295. } else if sender == flipVerticalButton {
  296. self.pdfView?.reverseYAction()
  297. } else if sender == flipHorizontalButton {
  298. self.pdfView?.reverseXAction()
  299. } else if sender == cropButton {
  300. if pdfView?.isEditImage == true {
  301. pdfView?.cropCancelAction()
  302. } else {
  303. pdfView?.cropAction()
  304. }
  305. } else if sender == replaceButton {
  306. if let areas = self.pdfView?.km_editingImageAreas() {
  307. let panel = NSOpenPanel()
  308. panel.allowsMultipleSelection = false
  309. panel.allowedFileTypes = ["png","jpg"]
  310. panel.beginSheetModal(for: NSApp.mainWindow!) { response in
  311. if response == .OK {
  312. let openPath = panel.url?.path
  313. for area in areas {
  314. self.pdfView?.replace(area, imagePath: openPath!)
  315. }
  316. }
  317. }
  318. }
  319. } else if sender == extractButton {
  320. extractAction()
  321. }
  322. reloadData()
  323. }
  324. func extractAction() {
  325. var menuItemArr: [ComponentMenuitemProperty] = []
  326. let items: [(String, String)] = [("jpg", "export_jpg_Key"),
  327. ("png", "export_png_Key"),
  328. ("pdf", "export_pdf_Key")]
  329. for (i, value) in items {
  330. let properties_Menuitem: ComponentMenuitemProperty = ComponentMenuitemProperty(multipleSelect: false,
  331. itemSelected: false,
  332. keyEquivalent: nil,
  333. text: KMLocalizedString(i),
  334. identifier: value)
  335. menuItemArr.append(properties_Menuitem)
  336. }
  337. if groupView == nil {
  338. groupView = ComponentGroup.createFromNib(in: ComponentLibrary.shared.componentBundle())
  339. }
  340. groupView.groupDelegate = self
  341. groupView?.frame = CGRectMake(0, 0, extractButton.frame.size.width, 36*3+8)
  342. groupView.updateGroupInfo(menuItemArr)
  343. if let point: CGPoint = extractButton.superview?.convert(extractButton.frame.origin, to: self.view.window?.contentView) {
  344. groupView.showWithPoint(CGPoint(x: point.x, y: point.y - CGRectGetHeight(groupView.frame)-4), relativeTo: nil)
  345. }
  346. }
  347. //MARK: - MouseEvent
  348. override func mouseDown(with event: NSEvent) {
  349. super.mouseDown(with: event)
  350. view.window?.makeFirstResponder(nil)
  351. }
  352. }
  353. //MARK: - ComponentInputNumberDelegate
  354. extension KMEditImageController: ComponentInputNumberDelegate {
  355. func componentInputNumberDidFinishEditing(inputNumber: ComponentInputNumber?) {
  356. if areas.count == 0 {
  357. return
  358. }
  359. var textValue = inputNumber?.properties.text.stringToCGFloat() ?? 0
  360. if inputNumber == sizeWidthInput {
  361. let areas = self.pdfView?.km_editingImageAreas() ?? []
  362. for area in areas {
  363. pdfView?.updateArea(area, newWidth: textValue, syncChangeBounds)
  364. }
  365. } else if inputNumber == sizeHeightInput {
  366. let areas = self.pdfView?.km_editingImageAreas() ?? []
  367. for area in areas {
  368. pdfView?.updateArea(area, newHeight: textValue, syncChangeBounds)
  369. }
  370. }
  371. reloadData()
  372. }
  373. func componentInputNumberDidIncrease(inputNumber: ComponentInputNumber?) {
  374. if areas.count == 0 {
  375. return
  376. }
  377. //只处理多选状态
  378. var textValue = inputNumber?.properties.text.stringToCGFloat() ?? 0
  379. if inputNumber == sizeWidthInput {
  380. let areas = self.pdfView?.km_editingImageAreas() ?? []
  381. for area in areas {
  382. textValue = area.bounds.size.width + 1
  383. pdfView?.updateArea(area, newWidth: textValue, syncChangeBounds)
  384. }
  385. } else if inputNumber == sizeHeightInput {
  386. let areas = self.pdfView?.km_editingImageAreas() ?? []
  387. for area in areas {
  388. textValue = area.bounds.size.height + 1
  389. pdfView?.updateArea(area, newHeight: textValue, syncChangeBounds)
  390. }
  391. }
  392. reloadData()
  393. }
  394. func componentInputNumberDidDecrease(inputNumber: ComponentInputNumber?) {
  395. if areas.count == 0 {
  396. return
  397. }
  398. //只处理多选状态
  399. var textValue = inputNumber?.properties.text.stringToCGFloat() ?? 0
  400. if inputNumber == sizeWidthInput {
  401. let areas = self.pdfView?.km_editingImageAreas() ?? []
  402. for area in areas {
  403. textValue = area.bounds.size.width - 1
  404. pdfView?.updateArea(area, newWidth: textValue, syncChangeBounds)
  405. }
  406. } else if inputNumber == sizeHeightInput {
  407. let areas = self.pdfView?.km_editingImageAreas() ?? []
  408. for area in areas {
  409. textValue = area.bounds.size.height - 1
  410. pdfView?.updateArea(area, newHeight: textValue, syncChangeBounds)
  411. }
  412. }
  413. reloadData()
  414. }
  415. }
  416. //MARK: - ComponentSelectDelegate
  417. extension KMEditImageController: ComponentSelectDelegate {
  418. func componentSelectDidSelect(view: ComponentSelect?, menuItemProperty: ComponentMenuitemProperty?) {
  419. if view == opacitySelect {
  420. if let text = opacitySelect.properties.text {
  421. let result = text.stringByDeleteCharString("%")
  422. let opacity = result.stringToCGFloat()/100
  423. pdfView?.setEditingAreasOpacity(opacity)
  424. reloadData()
  425. }
  426. } else if view == rotateSelect {
  427. if let text = rotateSelect.properties.text {
  428. let result = text.stringByDeleteCharString("°")
  429. let value = result.stringToCGFloat()
  430. pdfView?.rotateEditingAreas(-value)
  431. reloadData()
  432. }
  433. }
  434. }
  435. func componentSelectTextDidEndEditing(_ view: ComponentSelect) {
  436. if view == opacitySelect {
  437. if let text = opacitySelect.properties.text {
  438. let result = text.stringByDeleteCharString("%")
  439. if result != "-" {
  440. let opacity = result.stringToCGFloat()/100
  441. pdfView?.setEditingAreasOpacity(opacity)
  442. }
  443. reloadData()
  444. }
  445. } else if view == rotateSelect {
  446. if let text = rotateSelect.properties.text {
  447. let result = text.stringByDeleteCharString("°")
  448. if result != "-" {
  449. let value = result.stringToCGFloat()
  450. pdfView?.rotateEditingAreas(-value)
  451. }
  452. reloadData()
  453. }
  454. }
  455. }
  456. }
  457. //MARK: - ComponentCColorDelegate
  458. extension KMEditImageController: ComponentCColorDelegate {
  459. }
  460. //MARK: - ComponentSliderDelegate
  461. extension KMEditImageController: ComponentSliderDelegate {
  462. func componentSliderDidUpdate(_ view: ComponentSlider) {
  463. let percent = view.properties.percent
  464. self.pdfView?.setEditingAreasOpacity(percent)
  465. reloadData()
  466. }
  467. }
  468. //MARK: - ComponentGroupDelegate
  469. extension KMEditImageController: ComponentGroupDelegate {
  470. func componentGroupDidSelect(group: ComponentGroup?, menuItemProperty: ComponentMenuitemProperty?) {
  471. if menuItemProperty?.identifier == "export_jpg_Key" {
  472. pdfView?.exportEditingImageAreasAction(format: "jpg")
  473. } else if menuItemProperty?.identifier == "export_png_Key" {
  474. pdfView?.exportEditingImageAreasAction(format: "png")
  475. } else if menuItemProperty?.identifier == "export_pdf_Key" {
  476. pdfView?.exportEditingImageAreasAction(format: "pdf")
  477. }
  478. }
  479. func componentGroupDidDismiss(group: ComponentGroup?) {
  480. extractButton.properties.state = .normal
  481. extractButton.reloadData()
  482. }
  483. }
  484. //MARK: - KMNAlignmentControllerDelegate
  485. extension KMEditImageController: KMNAlignmentControllerDelegate {
  486. func alignmentControllerDidClick(_ controller: KMNAlignmentController, _ alignmentType: KMPDFActiveFormsAlignType) {
  487. pdfView?.changeEditingAreas(alignmentType)
  488. }
  489. }