diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/Main.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/Main.kt index 17b1aeb..4c92112 100644 --- a/kotlin-app/src/main/kotlin/io/github/simonscholz/Main.kt +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/Main.kt @@ -3,13 +3,16 @@ package io.github.simonscholz import io.github.simonscholz.model.QrCodeConfigViewModel import io.github.simonscholz.observables.SwingRealm import io.github.simonscholz.service.RenderImageService.renderImage +import io.github.simonscholz.ui.FrameMenu import io.github.simonscholz.ui.ImageUI import io.github.simonscholz.ui.MainUI import io.github.simonscholz.ui.PropertiesUI import org.eclipse.core.databinding.DataBindingContext import java.awt.event.WindowAdapter import java.awt.event.WindowEvent +import java.awt.image.BufferedImage import javax.swing.JFrame +import javax.swing.JPanel import javax.swing.SwingUtilities fun main() { @@ -25,15 +28,15 @@ fun main() { }, ) + var alreadyAppliedOnce = false + val alreadyAppliedOnceDelegate = { alreadyAppliedOnce } val qrCodeConfigViewModel = QrCodeConfigViewModel() + FrameMenu.createFrameMenu(frame, qrCodeConfigViewModel, alreadyAppliedOnceDelegate) val (imagePanel, setImage) = ImageUI.createImagePanel() val (propertiesPanel, applyOnChange) = PropertiesUI.createPropertiesUI(qrCodeConfigViewModel, dataBindingContext) { - if (qrCodeConfigViewModel.qrCodeContent.value.isNotBlank()) { - val qrCodeImage = renderImage(qrCodeConfigViewModel, frame) - setImage(qrCodeImage) - imagePanel.revalidate() - } + onPropertyApply(qrCodeConfigViewModel, frame, setImage, imagePanel) + alreadyAppliedOnce = true } val mainPanel = MainUI.createMainPanel(imagePanel, propertiesPanel) @@ -45,13 +48,18 @@ fun main() { dataBindingContext.bindings.forEach { it.model.addChangeListener { if (applyOnChange()) { - if (qrCodeConfigViewModel.qrCodeContent.value.isNotBlank()) { - val qrCodeImage = renderImage(qrCodeConfigViewModel, frame) - setImage(qrCodeImage) - imagePanel.revalidate() - } + onPropertyApply(qrCodeConfigViewModel, frame, setImage, imagePanel) + alreadyAppliedOnce = true } } } } } + +private fun onPropertyApply(qrCodeConfigViewModel: QrCodeConfigViewModel, frame: JFrame, setImage: (BufferedImage) -> Unit, imagePanel: JPanel) { + if (qrCodeConfigViewModel.qrCodeContent.value.isNotBlank()) { + val qrCodeImage = renderImage(qrCodeConfigViewModel, frame) + setImage(qrCodeImage) + imagePanel.revalidate() + } +} diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/model/QrCodeConfigViewModel.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/model/QrCodeConfigViewModel.kt index 8df9d0a..2bcc7d6 100644 --- a/kotlin-app/src/main/kotlin/io/github/simonscholz/model/QrCodeConfigViewModel.kt +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/model/QrCodeConfigViewModel.kt @@ -1,11 +1,12 @@ package io.github.simonscholz.model +import io.github.simonscholz.qrcode.DEFAULT_IMG_SIZE import org.eclipse.core.databinding.observable.value.WritableValue import java.awt.Color class QrCodeConfigViewModel { val qrCodeContent: WritableValue = WritableValue("https://simonscholz.github.io/", String::class.java) - val size: WritableValue = WritableValue(200, Int::class.java) + val size: WritableValue = WritableValue(DEFAULT_IMG_SIZE, Int::class.java) val backgroundColor: WritableValue = WritableValue(Color.WHITE, Color::class.java) val foregroundColor: WritableValue = WritableValue(Color.BLACK, Color::class.java) @@ -25,5 +26,5 @@ class QrCodeConfigViewModel { val positionalSquareOuterBorderColor: WritableValue = WritableValue(Color.WHITE, Color::class.java) override fun toString(): String = - "QrCodeConfigViewModel(qrCodeContent=${qrCodeContent.value}, size=${size.value}, backgroundColor=${backgroundColor.value}, foregroundColor=${foregroundColor.value}, logo=${logo.value}, borderColor=${borderColor.value}, relativeBorderSize=${relativeBorderSize.value}, borderRadius=${borderRadius.value})" + "QrCodeConfigViewModel(qrCodeContent=$qrCodeContent, size=$size, backgroundColor=$backgroundColor, foregroundColor=$foregroundColor, logo=$logo, logoRelativeSize=$logoRelativeSize, logoBackgroundColor=$logoBackgroundColor, borderColor=$borderColor, relativeBorderSize=$relativeBorderSize, borderRadius=$borderRadius, positionalSquareIsCircleShaped=$positionalSquareIsCircleShaped, positionalSquareRelativeBorderRound=$positionalSquareRelativeBorderRound, positionalSquareCenterColor=$positionalSquareCenterColor, positionalSquareInnerSquareColor=$positionalSquareInnerSquareColor, positionalSquareOuterSquareColor=$positionalSquareOuterSquareColor, positionalSquareOuterBorderColor=$positionalSquareOuterBorderColor)" } diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/FrameMenu.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/FrameMenu.kt new file mode 100644 index 0000000..e47ffd9 --- /dev/null +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/FrameMenu.kt @@ -0,0 +1,119 @@ +package io.github.simonscholz.ui + +import io.github.simonscholz.model.QrCodeConfigViewModel +import io.github.simonscholz.service.RenderImageService +import java.awt.Desktop +import java.awt.event.ActionEvent +import java.awt.event.InputEvent +import java.awt.event.KeyEvent +import java.net.URI +import javax.imageio.ImageIO +import javax.swing.AbstractAction +import javax.swing.JFileChooser +import javax.swing.JFrame +import javax.swing.JMenu +import javax.swing.JMenuBar +import javax.swing.JMenuItem +import javax.swing.JOptionPane +import javax.swing.KeyStroke +import javax.swing.filechooser.FileNameExtensionFilter +import kotlin.system.exitProcess + +object FrameMenu { + fun createFrameMenu(frame: JFrame, qrCodeConfigViewModel: QrCodeConfigViewModel, alreadyAppliedOnceDelegate: () -> Boolean) { + // Create and set the menu bar + val menuBar = JMenuBar() + frame.jMenuBar = menuBar + + // Create the File menu + val fileMenu = JMenu("File") + menuBar.add(fileMenu) + + // Create menu items for File menu + val saveMenuItem = JMenuItem("Save Qr Code Image") + // Add the keybinding for Save (Ctrl + S) + saveMenuItem.accelerator = KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK) + frame.rootPane.actionMap.put( + "SaveAction", + object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + saveFile(frame, qrCodeConfigViewModel, alreadyAppliedOnceDelegate) + } + }, + ) + val exitMenuItem = JMenuItem("Exit") + exitMenuItem.accelerator = KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.ALT_DOWN_MASK) + frame.rootPane.actionMap.put( + "ExitAction", + object : AbstractAction() { + override fun actionPerformed(e: ActionEvent) { + exitApplication() + } + }, + ) + + // Add ActionListeners to the menu items + saveMenuItem.addActionListener { saveFile(frame, qrCodeConfigViewModel, alreadyAppliedOnceDelegate) } + exitMenuItem.addActionListener { exitApplication() } + + // Add menu items to the File menu + fileMenu.add(saveMenuItem) + fileMenu.addSeparator() + fileMenu.add(exitMenuItem) + + // Create the File menu + val helpMenu = JMenu("Help") + menuBar.add(helpMenu) + + val gitHubRepoMenuItem = JMenuItem("GitHub Repository") + val readmeMenuItem = JMenuItem("README") + val issueMenuItem = JMenuItem("Open Issue") + + gitHubRepoMenuItem.addActionListener { openURL(frame, "https://github.com/SimonScholz/qr-code-with-logo") } + readmeMenuItem.addActionListener { openURL(frame, "https://github.com/SimonScholz/qr-code-with-logo/blob/main/README.adoc") } + issueMenuItem.addActionListener { openURL(frame, "https://github.com/SimonScholz/qr-code-with-logo/issues") } + + helpMenu.add(gitHubRepoMenuItem) + helpMenu.add(readmeMenuItem) + helpMenu.add(issueMenuItem) + } + + private fun openURL(frame: JFrame, url: String) { + try { + if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) { + Desktop.getDesktop().browse(URI(url)) + } else { + // Handle the case where the Desktop class is not supported + JOptionPane.showMessageDialog(frame, "Desktop is not supported on this platform.", "Error", JOptionPane.ERROR_MESSAGE) + } + } catch (e: Exception) { + e.printStackTrace() + // Handle any exceptions that may occur when opening the URL + JOptionPane.showMessageDialog(frame, "Error opening URL: ${e.message}", "Error", JOptionPane.ERROR_MESSAGE) + } + } + + private fun saveFile(frame: JFrame, qrCodeConfigViewModel: QrCodeConfigViewModel, alreadyAppliedOnceDelegate: () -> Boolean) { + val fileChooser = JFileChooser() + fileChooser.fileFilter = FileNameExtensionFilter("Png Image Files (*.png)", "png") + val result = fileChooser.showSaveDialog(null) + + if (result == JFileChooser.APPROVE_OPTION) { + if (fileChooser.selectedFile.extension.endsWith("png")) { + val selectedFile = fileChooser.selectedFile + val qrCodeImage = if (alreadyAppliedOnceDelegate()) { + RenderImageService.renderImage(qrCodeConfigViewModel = qrCodeConfigViewModel, component = frame) + } else { + RenderImageService.renderInitialImage() + } + ImageIO.write(qrCodeImage, "png", selectedFile) + } else { + JOptionPane.showMessageDialog(frame, "The file to be saved must have the png extension", "Image Saving Error", JOptionPane.ERROR_MESSAGE) + } + } + } + + private fun exitApplication() { + exitProcess(0) + } +}