Skip to content

Commit

Permalink
Created a Swing Realm and render image
Browse files Browse the repository at this point in the history
  • Loading branch information
Simon Scholz committed Oct 31, 2023
1 parent b066b8f commit a851c61
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 53 deletions.
2 changes: 2 additions & 0 deletions kotlin-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
64 changes: 50 additions & 14 deletions kotlin-app/src/main/kotlin/io/github/simonscholz/Main.kt
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
@@ -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<String> =
DocumentObservable(this)

fun JSpinner.toIntObservable(): IObservableValue<Int> = JSpinnerIntObservable(this)
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import java.awt.Color

class QrCodeConfigViewModel {
val qrCodeContent: WritableValue<String> = WritableValue("https://simonscholz.github.io/", String::class.java)
val size: WritableValue<Int> = WritableValue()
private val backgroundColor: WritableValue<Color> = WritableValue()
private val foregroundColor: WritableValue<Color> = WritableValue()
val size: WritableValue<Int> = WritableValue(200, Int::class.java)
private val backgroundColor: WritableValue<Color> = WritableValue(Color.WHITE, Color::class.java)
private val foregroundColor: WritableValue<Color> = WritableValue(Color.BLACK, Color::class.java)

private val logo: WritableValue<String> = WritableValue()
private val logo: WritableValue<String> = WritableValue("", String::class.java)

private val borderColor: WritableValue<Color> = WritableValue()
private val relativeBorderSize: WritableValue<String> = WritableValue()
private val borderRadius: WritableValue<String> = WritableValue()
private val borderColor: WritableValue<Color> = WritableValue(Color.BLACK, Color::class.java)
private val relativeBorderSize: WritableValue<Float> = WritableValue()
private val borderRadius: WritableValue<Float> = 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})"
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,49 @@
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<String>() {
private var documentValue = document.getText(0, document.length)

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)
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Int>() {

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
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import javax.imageio.ImageIO
import javax.swing.JPanel

object ImageUI {
fun createImagePanel(): JPanel {
fun createImagePanel(): Pair<JPanel, (BufferedImage) -> Unit> {
val imageContainer = JPanel()
imageContainer.background = Color.WHITE

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<JPanel, () -> Boolean> {
val propertiesPanel = JPanel(MigLayout())

propertiesPanel.add(JLabel("QR Code Content:"))
Expand All @@ -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:")
Expand All @@ -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) {
Expand Down

0 comments on commit a851c61

Please sign in to comment.