Skip to content

Commit 1037047

Browse files
committed
Add progressbar to multi-language detection GUI
Also increase the default window size.
1 parent d87a06d commit 1037047

File tree

4 files changed

+138
-68
lines changed

4 files changed

+138
-68
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.pemistahl.lingua.app.multilanguage
2+
3+
import com.github.pemistahl.lingua.api.LanguageDetector
4+
5+
internal interface DetectionProgressListener {
6+
fun detectionStarted() {
7+
// Do nothing by default
8+
}
9+
10+
fun detectionFinished(sections: List<LanguageDetector.LanguageSection>)
11+
}

src/main/kotlin/com/github/pemistahl/lingua/app/multilanguage/MultiLanguageGui.kt

Lines changed: 79 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
package com.github.pemistahl.lingua.app.multilanguage
22

33
import com.github.pemistahl.lingua.api.Language
4+
import com.github.pemistahl.lingua.api.LanguageDetector
45
import java.awt.Dimension
56
import java.awt.GridBagConstraints
67
import java.awt.GridBagLayout
8+
import java.awt.Insets
79
import java.awt.Toolkit
810
import javax.swing.BorderFactory
911
import javax.swing.JFrame
1012
import javax.swing.JLabel
13+
import javax.swing.JPanel
14+
import javax.swing.JProgressBar
1115
import javax.swing.JScrollPane
1216
import javax.swing.SwingUtilities
1317
import javax.swing.ToolTipManager
@@ -36,8 +40,9 @@ internal fun openMultiLanguageDetectionGui() {
3640
c.gridheight = 2 // To cover both text area and summary label below it
3741
frame.add(LanguageSelectionPanel(model, languages, colorMap), c)
3842
}
43+
44+
val textArea = MultiLanguageTextArea(model, colorMap)
3945
run {
40-
val textArea = MultiLanguageTextArea(model, colorMap)
4146
textArea.border = BorderFactory.createEmptyBorder(3, 3, 3, 3)
4247

4348
val scrollPane = JScrollPane(textArea)
@@ -51,44 +56,90 @@ internal fun openMultiLanguageDetectionGui() {
5156
c.weighty = 1.0
5257
frame.add(scrollPane, c)
5358
}
59+
5460
run {
5561
val noSectionsText = "Sections: 0"
5662
val summaryLabel = JLabel(noSectionsText)
57-
summaryLabel.border = BorderFactory.createEmptyBorder(3, 3, 3, 3)
58-
model.addListener {
59-
val sections = it.getSections()
60-
if (sections.isEmpty()) {
61-
summaryLabel.text = noSectionsText
62-
} else {
63-
val detectedLanguages =
64-
sections.map { s -> s.language }
65-
.groupingBy { l -> l }
66-
.eachCount()
67-
.entries
68-
.sortedWith { a, b ->
69-
// Sort highest count first
70-
val diff = b.value - a.value
71-
if (diff != 0) return@sortedWith diff
72-
73-
return@sortedWith a.key.compareTo(b.key)
74-
}
75-
.map { e -> "${e.key} (${e.value})" }
76-
summaryLabel.text = "Sections: ${sections.size}; Languages: ${detectedLanguages.joinToString(", ")}"
77-
}
63+
64+
val progressBar = JProgressBar()
65+
// Don't shrink progressbar when label becomes too large
66+
progressBar.minimumSize = progressBar.preferredSize
67+
68+
model.addListener(
69+
object : DetectionProgressListener {
70+
override fun detectionStarted() {
71+
progressBar.isIndeterminate = true
72+
progressBar.toolTipText = "Language detection in progress"
73+
74+
// Don't clear or change label text to avoid flashing text when user is currently typing
75+
}
76+
77+
override fun detectionFinished(sections: List<LanguageDetector.LanguageSection>) {
78+
progressBar.isIndeterminate = false
79+
progressBar.toolTipText = "Language detection finished"
80+
81+
if (sections.isEmpty()) {
82+
summaryLabel.text = noSectionsText
83+
} else {
84+
val detectedLanguages =
85+
sections.map { s -> s.language }
86+
.groupingBy { l -> l }
87+
.eachCount()
88+
.entries
89+
.sortedWith { a, b ->
90+
// Sort highest count first
91+
val diff = b.value - a.value
92+
if (diff != 0) return@sortedWith diff
93+
94+
return@sortedWith a.key.compareTo(b.key)
95+
}
96+
.map { e -> "${e.key} (${e.value})" }
97+
98+
summaryLabel.text =
99+
"Sections: ${sections.size}; Languages: ${detectedLanguages.joinToString(", ")}"
100+
}
101+
}
102+
},
103+
)
104+
105+
val footerPanel = JPanel(GridBagLayout())
106+
run {
107+
val c = GridBagConstraints()
108+
c.gridx = 0
109+
c.gridy = 0
110+
c.fill = GridBagConstraints.HORIZONTAL
111+
c.weightx = 1.0
112+
// Add spacing between label and progressbar
113+
c.insets = Insets(0, 0, 0, 3)
114+
footerPanel.add(summaryLabel, c)
115+
}
116+
run {
117+
val c = GridBagConstraints()
118+
c.gridx = 1
119+
c.gridy = 0
120+
c.fill = GridBagConstraints.HORIZONTAL
121+
footerPanel.add(progressBar, c)
78122
}
79123

80-
val c = GridBagConstraints()
81-
c.gridx = 1
82-
c.gridy = GridBagConstraints.RELATIVE
83-
c.fill = GridBagConstraints.HORIZONTAL
84-
frame.add(summaryLabel, c)
124+
run {
125+
val c = GridBagConstraints()
126+
c.gridx = 1
127+
c.gridy = GridBagConstraints.RELATIVE
128+
c.fill = GridBagConstraints.HORIZONTAL
129+
c.insets = Insets(3, 3, 3, 3)
130+
frame.add(footerPanel, c)
131+
}
85132
}
86133

87134
val screenSize: Dimension = Toolkit.getDefaultToolkit().screenSize
88-
frame.preferredSize = Dimension(screenSize.width / 3, screenSize.height / 3)
135+
frame.preferredSize = Dimension(screenSize.width / 2, screenSize.height / 2)
89136
frame.pack()
90137
// Center on screen
91138
frame.setLocationRelativeTo(null)
139+
140+
// Update initial state
141+
textArea.detectLanguages()
142+
92143
frame.isVisible = true
93144
}
94145
}

src/main/kotlin/com/github/pemistahl/lingua/app/multilanguage/MultiLanguageModel.kt

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ internal class MultiLanguageModel(
3131
private var sections = emptyList<LanguageDetector.LanguageSection>()
3232
private var maxSectionLength: Int = 0
3333

34-
private val listeners: MutableList<(MultiLanguageModel) -> Unit> = mutableListOf()
34+
private val listeners: MutableList<DetectionProgressListener> = mutableListOf()
3535

3636
private var currentWorker: SwingWorker<*, *>? = null
3737

@@ -42,7 +42,7 @@ internal class MultiLanguageModel(
4242
setLanguages(initialLanguages)
4343
}
4444

45-
fun addListener(listener: (MultiLanguageModel) -> Unit) {
45+
fun addListener(listener: DetectionProgressListener) {
4646
listeners.add(listener)
4747
}
4848

@@ -81,40 +81,43 @@ internal class MultiLanguageModel(
8181
currentWorker?.cancel(true)
8282
currentWorkerCancelled?.set(true)
8383

84-
if (languageDetector != null) {
85-
val currentWorkerCancelled = AtomicBoolean(false)
86-
this.currentWorkerCancelled = currentWorkerCancelled
87-
// Store property value in local variable to safely access it from worker thread
88-
val text = text
89-
currentWorker =
90-
object : SwingWorker<List<LanguageDetector.LanguageSection>, Unit>() {
91-
override fun doInBackground(): List<LanguageDetector.LanguageSection> {
92-
return languageDetector.detectMultiLanguageOf(text)
93-
}
84+
// Store `text` property value in local variable to safely access it from worker thread
85+
val text = text
86+
if (text.isEmpty() || languageDetector == null) {
87+
sections = emptyList()
88+
listeners.forEach { it.detectionFinished(sections) }
89+
return
90+
}
9491

95-
override fun done() {
96-
if (!isCancelled && !currentWorkerCancelled.get()) {
97-
val sections = get()
98-
sections.forEach {
99-
this@MultiLanguageModel.sectionsMap[it.start] =
100-
DetectionEntry(
101-
it.start,
102-
it.end,
103-
it.language,
104-
it.confidenceValues,
105-
)
106-
}
107-
maxSectionLength = sections.maxOfOrNull { it.end - it.start } ?: 0
108-
this@MultiLanguageModel.sections = sections
109-
110-
listeners.forEach { it(this@MultiLanguageModel) }
92+
listeners.forEach { it.detectionStarted() }
93+
94+
val currentWorkerCancelled = AtomicBoolean(false)
95+
this.currentWorkerCancelled = currentWorkerCancelled
96+
currentWorker =
97+
object : SwingWorker<List<LanguageDetector.LanguageSection>, Unit>() {
98+
override fun doInBackground(): List<LanguageDetector.LanguageSection> {
99+
return languageDetector.detectMultiLanguageOf(text)
100+
}
101+
102+
override fun done() {
103+
if (!isCancelled && !currentWorkerCancelled.get()) {
104+
val sections = get()
105+
sections.forEach {
106+
this@MultiLanguageModel.sectionsMap[it.start] =
107+
DetectionEntry(
108+
it.start,
109+
it.end,
110+
it.language,
111+
it.confidenceValues,
112+
)
111113
}
114+
maxSectionLength = sections.maxOfOrNull { it.end - it.start } ?: 0
115+
this@MultiLanguageModel.sections = sections
116+
117+
listeners.forEach { it.detectionFinished(sections) }
112118
}
113-
}.also { it.execute() }
114-
} else {
115-
sections = emptyList()
116-
listeners.forEach { it(this) }
117-
}
119+
}
120+
}.also { it.execute() }
118121
}
119122

120123
/**

src/main/kotlin/com/github/pemistahl/lingua/app/multilanguage/MultiLanguageTextArea.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.pemistahl.lingua.app.multilanguage
22

3+
import com.github.pemistahl.lingua.api.LanguageDetector
34
import java.awt.Font
45
import java.awt.event.MouseEvent
56
import java.util.Locale
@@ -37,13 +38,17 @@ internal class MultiLanguageTextArea(
3738
)
3839
ToolTipManager.sharedInstance().registerComponent(this)
3940

40-
multiLanguageModel.addListener {
41-
updateHighlightedSections()
42-
}
41+
multiLanguageModel.addListener(
42+
object : DetectionProgressListener {
43+
override fun detectionFinished(sections: List<LanguageDetector.LanguageSection>) {
44+
updateHighlightedSections(sections)
45+
}
46+
},
47+
)
4348
languageColorMap.addListener { changedColors ->
4449
if (changedColors.keys.any(multiLanguageModel::hasEntryForLanguage)) {
4550
// Update highlighted sections because their color has changed
46-
updateHighlightedSections()
51+
updateHighlightedSections(multiLanguageModel.getSections())
4752
}
4853
}
4954
}
@@ -68,13 +73,13 @@ internal class MultiLanguageTextArea(
6873
return multiLanguageModel.getEntryForIndex(offset)?.getRenderedString()
6974
}
7075

71-
private fun detectLanguages() {
76+
fun detectLanguages() {
7277
multiLanguageModel.updateText(text)
7378
}
7479

75-
private fun updateHighlightedSections() {
80+
private fun updateHighlightedSections(sections: List<LanguageDetector.LanguageSection>) {
7681
highlighter.removeAllHighlights()
77-
multiLanguageModel.getSections().forEach { section ->
82+
sections.forEach { section ->
7883
val painter = BorderedHighlightPainter(languageColorMap.getColor(section.language))
7984
highlighter.addHighlight(section.start, section.end, painter)
8085
}

0 commit comments

Comments
 (0)