diff --git a/kotlin-app/build.gradle.kts b/kotlin-app/build.gradle.kts index 2453809..5176379 100644 --- a/kotlin-app/build.gradle.kts +++ b/kotlin-app/build.gradle.kts @@ -16,6 +16,8 @@ dependencies { implementation("com.miglayout:miglayout-swing:11.2") implementation("org.eclipse.platform:org.eclipse.core.databinding:1.13.100") + // Just for comparison with JFace implementation + //implementation("org.eclipse.platform:org.eclipse.jface.databinding:1.15.100") } detekt { 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 47688dd..0065ec8 100644 --- a/kotlin-app/src/main/kotlin/io/github/simonscholz/Main.kt +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/Main.kt @@ -1,30 +1,66 @@ package io.github.simonscholz import io.github.simonscholz.model.QrCodeConfigViewModel +import io.github.simonscholz.observables.SwingRealm +import io.github.simonscholz.qrcode.QrCodeConfig +import io.github.simonscholz.qrcode.QrCodeFactory import io.github.simonscholz.ui.ImageUI import io.github.simonscholz.ui.MainUI import io.github.simonscholz.ui.PropertiesUI +import java.awt.event.WindowAdapter +import java.awt.event.WindowEvent +import java.awt.image.BufferedImage import org.eclipse.core.databinding.DataBindingContext import javax.swing.JFrame +import javax.swing.SwingUtilities fun main() { - val frame = JFrame("QR Code AWT/Swing UI") - frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE - val dataBindingContext = DataBindingContext() - frame.addWindowListener(object : java.awt.event.WindowAdapter() { - override fun windowClosing(windowEvent: java.awt.event.WindowEvent?) { - dataBindingContext.dispose() + SwingUtilities.invokeLater { + val frame = JFrame("QR Code AWT/Swing UI") + frame.defaultCloseOperation = JFrame.EXIT_ON_CLOSE + val dataBindingContext = DataBindingContext(SwingRealm()) + frame.addWindowListener( + object : WindowAdapter() { + override fun windowClosing(windowEvent: WindowEvent) { + dataBindingContext.dispose() + } + }, + ) + + val qrCodeConfigViewModel = QrCodeConfigViewModel() + + val (imagePanel, setImage) = ImageUI.createImagePanel() + val (propertiesPanel, applyOnChange) = PropertiesUI.createPropertiesUI(qrCodeConfigViewModel, dataBindingContext) { + // TODO render new qr code image + if(qrCodeConfigViewModel.qrCodeContent.value.isNotBlank()) { + val qrCodeImage = renderImage(qrCodeConfigViewModel) + setImage(qrCodeImage) + } } - },) - val qrCodeConfigViewModel = QrCodeConfigViewModel() + val mainPanel = MainUI.createMainPanel(imagePanel, propertiesPanel) + frame.add(mainPanel) - val imagePanel = ImageUI.createImagePanel() - val propertiesPanel = PropertiesUI.createPropertiesUI(qrCodeConfigViewModel, dataBindingContext) + frame.pack() + frame.isVisible = true - val mainPanel = MainUI.createMainPanel(imagePanel, propertiesPanel) - frame.add(mainPanel) + dataBindingContext.bindings.forEach { + it.model.addChangeListener { + if(applyOnChange()) { + // TODO render new qr code image + if(qrCodeConfigViewModel.qrCodeContent.value.isNotBlank()) { + val qrCodeImage = renderImage(qrCodeConfigViewModel) + setImage(qrCodeImage) + } + } + } + } + } +} - frame.pack() - frame.isVisible = true +private fun renderImage(qrCodeConfigViewModel: QrCodeConfigViewModel): BufferedImage { + // TODO render new qr code image + println(qrCodeConfigViewModel) + val qrCodeConfig = QrCodeConfig.Builder(qrCodeConfigViewModel.qrCodeContent.value).qrCodeSize(qrCodeConfigViewModel.size.value).build() + return QrCodeFactory.createQrCodeApi().createQrCodeImage(qrCodeConfig) } diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/extension/BindingExtensions.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/extension/BindingExtensions.kt index 70d1978..1ca601b 100644 --- a/kotlin-app/src/main/kotlin/io/github/simonscholz/extension/BindingExtensions.kt +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/extension/BindingExtensions.kt @@ -1,8 +1,12 @@ package io.github.simonscholz.extension import io.github.simonscholz.observables.DocumentObservable +import io.github.simonscholz.observables.JSpinnerIntObservable import org.eclipse.core.databinding.observable.value.IObservableValue +import javax.swing.JSpinner import javax.swing.text.JTextComponent fun JTextComponent.toObservable(): IObservableValue = DocumentObservable(this) + +fun JSpinner.toIntObservable(): IObservableValue = JSpinnerIntObservable(this) 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 4cf1f62..cb41e20 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 @@ -5,13 +5,16 @@ import java.awt.Color class QrCodeConfigViewModel { val qrCodeContent: WritableValue = WritableValue("https://simonscholz.github.io/", String::class.java) - val size: WritableValue = WritableValue() - private val backgroundColor: WritableValue = WritableValue() - private val foregroundColor: WritableValue = WritableValue() + val size: WritableValue = WritableValue(200, Int::class.java) + private val backgroundColor: WritableValue = WritableValue(Color.WHITE, Color::class.java) + private val foregroundColor: WritableValue = WritableValue(Color.BLACK, Color::class.java) - private val logo: WritableValue = WritableValue() + private val logo: WritableValue = WritableValue("", String::class.java) - private val borderColor: WritableValue = WritableValue() - private val relativeBorderSize: WritableValue = WritableValue() - private val borderRadius: WritableValue = WritableValue() + private val borderColor: WritableValue = WritableValue(Color.BLACK, Color::class.java) + private val relativeBorderSize: WritableValue = WritableValue() + private val borderRadius: WritableValue = WritableValue() + + 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})" } diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/DocumentObservable.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/DocumentObservable.kt index 4a3a164..d798a87 100644 --- a/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/DocumentObservable.kt +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/DocumentObservable.kt @@ -1,11 +1,11 @@ package io.github.simonscholz.observables -import org.eclipse.core.databinding.observable.Diffs -import org.eclipse.core.databinding.observable.value.AbstractObservableValue import javax.swing.event.DocumentEvent import javax.swing.event.DocumentListener import javax.swing.text.Document import javax.swing.text.JTextComponent +import org.eclipse.core.databinding.observable.Diffs +import org.eclipse.core.databinding.observable.value.AbstractObservableValue class DocumentObservable(private val document: Document) : AbstractObservableValue() { private var documentValue = document.getText(0, document.length) @@ -13,28 +13,37 @@ class DocumentObservable(private val document: Document) : AbstractObservableVal constructor(jTextComponent: JTextComponent) : this(jTextComponent.document) init { - document.addDocumentListener(object : DocumentListener { - override fun changedUpdate(e: DocumentEvent) { - val oldValue = documentValue - documentValue = doGetValue() - fireValueChange(Diffs.createValueDiff(oldValue, documentValue)) - } - - override fun insertUpdate(e: DocumentEvent) { - val oldValue = documentValue - documentValue = doGetValue() - fireValueChange(Diffs.createValueDiff(oldValue, documentValue)) - } - - override fun removeUpdate(e: DocumentEvent) { - val oldValue = documentValue - documentValue = doGetValue() - fireValueChange(Diffs.createValueDiff(oldValue, documentValue)) - } - },) + document.addDocumentListener( + object : DocumentListener { + override fun changedUpdate(e: DocumentEvent) { + val oldValue = documentValue + documentValue = doGetValue() + fireValueChange(Diffs.createValueDiff(oldValue, documentValue)) + } + + override fun insertUpdate(e: DocumentEvent) { + val oldValue = documentValue + documentValue = doGetValue() + fireValueChange(Diffs.createValueDiff(oldValue, documentValue)) + } + + override fun removeUpdate(e: DocumentEvent) { + val oldValue = documentValue + documentValue = doGetValue() + fireValueChange(Diffs.createValueDiff(oldValue, documentValue)) + } + }, + ) } override fun getValueType(): Any = String::class.java override fun doGetValue(): String = document.getText(0, document.length) + + override fun doSetValue(value: String?) { + if (documentValue !== value) { + document.remove(0, document.length) + document.insertString(0, value, null) + } + } } diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/JSpinnerObservable.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/JSpinnerObservable.kt new file mode 100644 index 0000000..5ad5759 --- /dev/null +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/JSpinnerObservable.kt @@ -0,0 +1,29 @@ +package io.github.simonscholz.observables + +import org.eclipse.core.databinding.observable.Diffs +import org.eclipse.core.databinding.observable.value.AbstractObservableValue +import javax.swing.JSpinner + +class JSpinnerIntObservable( + private val jSpinner: JSpinner, +) : AbstractObservableValue() { + + private var value: Int = jSpinner.value as Int + + init { + jSpinner.addChangeListener { + val oldValue = value + value = doGetValue() + fireValueChange(Diffs.createValueDiff(oldValue, value)) + } + } + + override fun doGetValue(): Int = + jSpinner.value as Int + + override fun getValueType(): Any = Int::class.java + + override fun doSetValue(value: Int?) { + jSpinner.value = value + } +} diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/SwingRealm.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/SwingRealm.kt new file mode 100644 index 0000000..1883c14 --- /dev/null +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/observables/SwingRealm.kt @@ -0,0 +1,19 @@ +package io.github.simonscholz.observables + +import javax.swing.SwingUtilities +import org.eclipse.core.databinding.observable.Realm + +class SwingRealm: Realm() { + + init { + setDefault(this) + } + override fun isCurrent(): Boolean = + SwingUtilities.isEventDispatchThread() + + override fun syncExec(runnable: Runnable) = + SwingUtilities.invokeAndWait(runnable) + + override fun asyncExec(runnable: Runnable) = + SwingUtilities.invokeLater(runnable) +} diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/ImageUI.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/ImageUI.kt index e161cd4..cc4c584 100644 --- a/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/ImageUI.kt +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/ImageUI.kt @@ -11,7 +11,7 @@ import javax.imageio.ImageIO import javax.swing.JPanel object ImageUI { - fun createImagePanel(): JPanel { + fun createImagePanel(): Pair Unit> { val imageContainer = JPanel() imageContainer.background = Color.WHITE @@ -22,7 +22,9 @@ object ImageUI { } imageContainer.add(imageDrawPanel) - return imageContainer + val setImage = (imageDrawPanel::setImage as (BufferedImage) -> Unit) + + return Pair(imageContainer, setImage) } internal class ImagePanel : JPanel(true) { diff --git a/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/PropertiesUI.kt b/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/PropertiesUI.kt index e7cc3ad..ee8d5b4 100644 --- a/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/PropertiesUI.kt +++ b/kotlin-app/src/main/kotlin/io/github/simonscholz/ui/PropertiesUI.kt @@ -1,24 +1,29 @@ package io.github.simonscholz.ui +import io.github.simonscholz.extension.toIntObservable import io.github.simonscholz.extension.toObservable import io.github.simonscholz.model.QrCodeConfigViewModel import io.github.simonscholz.ui.properties.BorderPropertiesUI import net.miginfocom.swing.MigLayout import org.eclipse.core.databinding.DataBindingContext import java.awt.Color +import java.awt.event.ActionListener +import java.util.function.Supplier import javax.swing.JButton import javax.swing.JCheckBox import javax.swing.JColorChooser import javax.swing.JLabel import javax.swing.JPanel +import javax.swing.JSpinner import javax.swing.JTextArea import javax.swing.JTextField +import javax.swing.SpinnerNumberModel object PropertiesUI { private const val WIDTH = "width 200:220:300" - fun createPropertiesUI(qrCodeConfigViewModel: QrCodeConfigViewModel, dataBindingContext: DataBindingContext): JPanel { + fun createPropertiesUI(qrCodeConfigViewModel: QrCodeConfigViewModel, dataBindingContext: DataBindingContext, clickedApply: () -> Unit): Pair Boolean> { val propertiesPanel = JPanel(MigLayout()) propertiesPanel.add(JLabel("QR Code Content:")) @@ -28,10 +33,10 @@ object PropertiesUI { dataBindingContext.bindValue(contentTextArea.toObservable(), qrCodeConfigViewModel.qrCodeContent) propertiesPanel.add(JLabel("Size (px):")) - val textField = JTextField() - textField.text = "200" - propertiesPanel.add(textField, "wrap, growx, span 3, $WIDTH") - dataBindingContext.bindValue(textField.toObservable(), qrCodeConfigViewModel.size) + val sizeSpinnerModel = SpinnerNumberModel(200, 0, 20000, 1) + val sizeSpinner = JSpinner(sizeSpinnerModel) + propertiesPanel.add(sizeSpinner, "wrap, growx, span 3, $WIDTH") + dataBindingContext.bindValue(sizeSpinner.toIntObservable(), qrCodeConfigViewModel.size) createColorPickerFormItem(propertiesPanel, "Background Color:") createColorPickerFormItem(propertiesPanel, "Foreground Color:") @@ -50,14 +55,18 @@ object PropertiesUI { propertiesPanel.add(borderPropertiesUI, "wrap, growx, span 3") val applyOnChange = JCheckBox("Apply on change") - // TODO data binding propertiesPanel.add(applyOnChange, "wrap, growx, span 3, gaptop 25") - propertiesPanel.add(JButton("Apply"), "wrap, growx, span 3") + val applyButton = JButton("Apply") + propertiesPanel.add(applyButton, "wrap, growx, span 3") + applyButton.addActionListener { + clickedApply() + } - propertiesPanel.add(JButton("Save Config"), "wrap, growx, span 3, gaptop 25") + // TODO allow to save config + // propertiesPanel.add(JButton("Save Config"), "wrap, growx, span 3, gaptop 25") - return propertiesPanel + return Pair(propertiesPanel, applyOnChange::isSelected) } private fun createColorPickerFormItem(propertiesPanel: JPanel, labelText: String) {