mirror of
https://gitlab.com/2009scape/rt4-client.git
synced 2025-12-17 03:50:24 -07:00
fix up remaining spacing issues
This commit is contained in:
parent
3392d61d9e
commit
5bb81c7bd3
39 changed files with 244 additions and 271 deletions
|
|
@ -1,490 +0,0 @@
|
|||
package KondoKit.components
|
||||
|
||||
import KondoKit.Helpers
|
||||
import KondoKit.Helpers.FieldNotifier
|
||||
import KondoKit.ViewConstants
|
||||
import KondoKit.components.UiStyler
|
||||
import KondoKit.setFixedSize
|
||||
import KondoKit.plugin.Companion.WIDGET_COLOR
|
||||
import KondoKit.plugin.Companion.primaryColor
|
||||
import KondoKit.plugin.Companion.secondaryColor
|
||||
import plugin.Plugin
|
||||
import java.awt.*
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.lang.reflect.Field
|
||||
import java.util.*
|
||||
import java.util.Timer
|
||||
import javax.swing.*
|
||||
import javax.swing.table.DefaultTableModel
|
||||
|
||||
class SettingsPanel(private val plugin: Plugin) : JPanel() {
|
||||
|
||||
init {
|
||||
layout = BoxLayout(this, BoxLayout.Y_AXIS)
|
||||
background = WIDGET_COLOR
|
||||
border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
|
||||
}
|
||||
|
||||
fun addSettingsFromPlugin() {
|
||||
val fieldNotifier = FieldNotifier(plugin)
|
||||
val exposedFields = plugin.javaClass.declaredFields.filter { field ->
|
||||
field.annotations.any { annotation ->
|
||||
annotation.annotationClass.simpleName == "Exposed"
|
||||
}
|
||||
}
|
||||
|
||||
if (exposedFields.isNotEmpty()) {
|
||||
for (field in exposedFields) {
|
||||
field.isAccessible = true
|
||||
|
||||
// Get the "Exposed" annotation specifically and retrieve its description, if available
|
||||
val description = field.exposedDescription()
|
||||
|
||||
val fieldPanel = JPanel().apply {
|
||||
layout = GridBagLayout()
|
||||
background = WIDGET_COLOR
|
||||
foreground = secondaryColor
|
||||
border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
|
||||
maximumSize = Dimension(Int.MAX_VALUE, 50)
|
||||
}
|
||||
|
||||
val gbc = GridBagConstraints().apply {
|
||||
insets = Insets(0, 5, 0, 5)
|
||||
}
|
||||
|
||||
val label = JLabel(field.name.capitalize()).apply {
|
||||
foreground = secondaryColor
|
||||
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
|
||||
}
|
||||
gbc.gridx = 0
|
||||
gbc.gridy = 0
|
||||
gbc.weightx = 0.0
|
||||
gbc.anchor = GridBagConstraints.WEST
|
||||
fieldPanel.add(label, gbc)
|
||||
|
||||
// Create appropriate input component based on field type
|
||||
val inputComponent: JComponent
|
||||
val isHashMapEditor: Boolean
|
||||
when {
|
||||
field.type == Boolean::class.javaPrimitiveType || field.type == java.lang.Boolean::class.java -> {
|
||||
inputComponent = JCheckBox().apply {
|
||||
isSelected = field.get(plugin) as Boolean
|
||||
}
|
||||
isHashMapEditor = false
|
||||
}
|
||||
|
||||
field.type.isEnum -> {
|
||||
val enumConstants = field.type.enumConstants
|
||||
inputComponent = JComboBox(enumConstants as Array<out Enum<*>>).apply {
|
||||
selectedItem = field.get(plugin)
|
||||
}
|
||||
isHashMapEditor = false
|
||||
}
|
||||
|
||||
field.type == Int::class.javaPrimitiveType || field.type == Integer::class.java -> {
|
||||
inputComponent = JSpinner(SpinnerNumberModel(field.get(plugin) as Int, Int.MIN_VALUE, Int.MAX_VALUE, 1))
|
||||
isHashMapEditor = false
|
||||
}
|
||||
|
||||
field.type == Float::class.javaPrimitiveType ||
|
||||
field.type == Double::class.javaPrimitiveType ||
|
||||
field.type == java.lang.Float::class.java ||
|
||||
field.type == java.lang.Double::class.java -> {
|
||||
inputComponent = JSpinner(SpinnerNumberModel((field.get(plugin) as Number).toDouble(), -Double.MAX_VALUE, Double.MAX_VALUE, 0.1))
|
||||
isHashMapEditor = false
|
||||
}
|
||||
|
||||
// Check if the field is a HashMap
|
||||
field.isHashMapType() -> {
|
||||
inputComponent = createHashMapEditor(field, plugin, fieldNotifier)
|
||||
isHashMapEditor = true
|
||||
}
|
||||
|
||||
else -> {
|
||||
inputComponent = JTextField(field.get(plugin)?.toString() ?: "")
|
||||
isHashMapEditor = false
|
||||
}
|
||||
}
|
||||
|
||||
// Handle HashMap editors differently - they don't use the fieldPanel layout
|
||||
if (isHashMapEditor) {
|
||||
// Add the HashMap editor directly to the settings panel
|
||||
add(inputComponent)
|
||||
} else {
|
||||
// Add mouse listener to the label only if a description is available
|
||||
if (description.isNotBlank()) {
|
||||
label.cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
label.addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseEntered(e: MouseEvent) {
|
||||
showCustomToolTip(description, label)
|
||||
}
|
||||
|
||||
override fun mouseExited(e: MouseEvent) {
|
||||
customToolTipWindow?.isVisible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
gbc.gridx = 1
|
||||
gbc.gridy = 0
|
||||
gbc.weightx = 1.0
|
||||
gbc.fill = GridBagConstraints.HORIZONTAL
|
||||
fieldPanel.add(inputComponent, gbc)
|
||||
|
||||
// Add apply button for non-HashMap editors
|
||||
val applyButton = UiStyler.button(
|
||||
text = "\u2714",
|
||||
onClick = {
|
||||
try {
|
||||
val newValue = when (inputComponent) {
|
||||
is JCheckBox -> inputComponent.isSelected
|
||||
is JComboBox<*> -> inputComponent.selectedItem
|
||||
is JSpinner -> inputComponent.value
|
||||
is JTextField -> Helpers.convertValue(field.type, field.genericType, inputComponent.text)
|
||||
else -> throw IllegalArgumentException("Unsupported input component type")
|
||||
}
|
||||
fieldNotifier.setFieldValue(field, newValue)
|
||||
Helpers.showToast(
|
||||
this@SettingsPanel,
|
||||
"${field.name} updated successfully!"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Helpers.showToast(
|
||||
this@SettingsPanel,
|
||||
"Failed to update ${field.name}: ${e.message}",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
)
|
||||
}
|
||||
}
|
||||
).also {
|
||||
it.maximumSize = Dimension(Int.MAX_VALUE, 8)
|
||||
}
|
||||
|
||||
gbc.gridx = 2
|
||||
gbc.gridy = 0
|
||||
gbc.weightx = 0.0
|
||||
gbc.fill = GridBagConstraints.NONE
|
||||
|
||||
fieldPanel.add(applyButton, gbc)
|
||||
add(fieldPanel)
|
||||
}
|
||||
|
||||
// Track field changes in real-time and update UI (skip HashMap fields)
|
||||
if (!field.isHashMapType()) {
|
||||
var previousValue = field.get(plugin)?.toString()
|
||||
val timer = Timer()
|
||||
timer.schedule(object : TimerTask() {
|
||||
override fun run() {
|
||||
val currentValue = field.get(plugin)?.toString()
|
||||
if (currentValue != previousValue) {
|
||||
previousValue = currentValue
|
||||
SwingUtilities.invokeLater {
|
||||
// Update the inputComponent based on the new value
|
||||
when (inputComponent) {
|
||||
is JCheckBox -> inputComponent.isSelected = field.get(plugin) as Boolean
|
||||
is JComboBox<*> -> inputComponent.selectedItem = field.get(plugin)
|
||||
is JSpinner -> inputComponent.value = field.get(plugin)
|
||||
is JTextField -> inputComponent.text = field.get(plugin)?.toString() ?: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0, 1000) // Poll every 1000 milliseconds (1 second)
|
||||
}
|
||||
}
|
||||
|
||||
if (exposedFields.isNotEmpty()) {
|
||||
add(Box.createVerticalStrut(5))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createHashMapEditor(field: Field, plugin: Plugin, fieldNotifier: FieldNotifier): JComponent {
|
||||
// Create a panel to hold the table and buttons
|
||||
val editorPanel = JPanel()
|
||||
editorPanel.layout = BoxLayout(editorPanel, BoxLayout.Y_AXIS)
|
||||
editorPanel.background = WIDGET_COLOR
|
||||
editorPanel.border = BorderFactory.createEmptyBorder(5, 0, 5, 0)
|
||||
editorPanel.maximumSize = Dimension(Int.MAX_VALUE, 250)
|
||||
|
||||
val description = field.exposedDescription()
|
||||
|
||||
// Create title label
|
||||
val titleLabel = JLabel("${field.name} (Key-Value Pairs)").apply {
|
||||
foreground = secondaryColor
|
||||
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
|
||||
alignmentX = Component.LEFT_ALIGNMENT
|
||||
|
||||
// Add mouse listener to the label only if a description is available
|
||||
if (description.isNotBlank()) {
|
||||
cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
|
||||
addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseEntered(e: MouseEvent) {
|
||||
showCustomToolTip(description, this@apply)
|
||||
}
|
||||
|
||||
override fun mouseExited(e: MouseEvent) {
|
||||
SettingsPanel.customToolTipWindow?.isVisible = false
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get the current HashMap value
|
||||
val hashMap = field.get(plugin) as? HashMap<*, *> ?: HashMap<Any, Any>()
|
||||
|
||||
// Create a table model for the HashMap
|
||||
val tableModel = object : DefaultTableModel() {
|
||||
init {
|
||||
val columnVector = java.util.Vector<String>()
|
||||
columnVector.add("Key")
|
||||
columnVector.add("Value")
|
||||
columnIdentifiers = columnVector
|
||||
hashMap.forEach { (key, value) ->
|
||||
val vector = java.util.Vector<Any>()
|
||||
vector.add(key.toString())
|
||||
vector.add(value.toString())
|
||||
addRow(vector)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isCellEditable(row: Int, column: Int): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getColumnClass(columnIndex: Int): Class<*> {
|
||||
return String::class.java
|
||||
}
|
||||
}
|
||||
|
||||
// Create the table
|
||||
val table = JTable(tableModel).apply {
|
||||
background = WIDGET_COLOR
|
||||
foreground = secondaryColor
|
||||
gridColor = primaryColor
|
||||
tableHeader.background = WIDGET_COLOR
|
||||
tableHeader.foreground = secondaryColor
|
||||
preferredScrollableViewportSize = Dimension(Int.MAX_VALUE, 150)
|
||||
setFillsViewportHeight(true)
|
||||
}
|
||||
|
||||
// Create a scroll pane for the table with fixed size
|
||||
val scrollPane = JScrollPane(table).apply {
|
||||
setFixedSize(Dimension(Int.MAX_VALUE, 150))
|
||||
background = WIDGET_COLOR
|
||||
verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED
|
||||
horizontalScrollBarPolicy = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER
|
||||
alignmentX = Component.LEFT_ALIGNMENT
|
||||
}
|
||||
|
||||
// Create buttons panel
|
||||
val buttonsPanel = JPanel(FlowLayout(FlowLayout.LEFT, 5, 5)).apply {
|
||||
background = WIDGET_COLOR
|
||||
alignmentX = Component.LEFT_ALIGNMENT
|
||||
}
|
||||
|
||||
// Add button
|
||||
val addButton = UiStyler.button(
|
||||
text = "+",
|
||||
onClick = {
|
||||
val vector = java.util.Vector<Any>()
|
||||
vector.add("")
|
||||
vector.add("")
|
||||
tableModel.addRow(vector)
|
||||
// Select the new row for editing
|
||||
val newRow = tableModel.rowCount - 1
|
||||
table.setRowSelectionInterval(newRow, newRow)
|
||||
table.editCellAt(newRow, 0)
|
||||
table.editorComponent?.requestFocus()
|
||||
}
|
||||
).apply {
|
||||
setFixedSize(40, 30)
|
||||
}
|
||||
|
||||
// Remove button
|
||||
val removeButton = UiStyler.button(
|
||||
text = "-",
|
||||
onClick = {
|
||||
val selectedRow = table.selectedRow
|
||||
if (selectedRow >= 0) {
|
||||
tableModel.removeRow(selectedRow)
|
||||
} else {
|
||||
Helpers.showToast(
|
||||
this@SettingsPanel,
|
||||
"Please select a row to remove",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
)
|
||||
}
|
||||
}
|
||||
).apply {
|
||||
setFixedSize(40, 30)
|
||||
}
|
||||
|
||||
// Apply button
|
||||
val applyButton = UiStyler.button(
|
||||
text = "Apply Changes",
|
||||
onClick = {
|
||||
try {
|
||||
// Commit any active cell editing before reading values
|
||||
if (table.isEditing) {
|
||||
table.cellEditor.stopCellEditing()
|
||||
}
|
||||
|
||||
// Get the current HashMap from the field and modify it in place
|
||||
val currentHashMap = field.get(plugin) as? HashMap<Any, Any> ?: HashMap<Any, Any>()
|
||||
|
||||
// Clear the current HashMap
|
||||
currentHashMap.clear()
|
||||
|
||||
// Add the new entries from the table to the existing HashMap
|
||||
for (i in 0 until tableModel.rowCount) {
|
||||
val key = tableModel.getValueAt(i, 0).toString()
|
||||
val value = tableModel.getValueAt(i, 1).toString()
|
||||
|
||||
if (key.isBlank()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (value.isBlank()) {
|
||||
Helpers.showToast(
|
||||
this@SettingsPanel,
|
||||
"Skipping entry with empty value for key '$key'",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
val convertedValue = try {
|
||||
val fieldTypeName = field.genericType.toString()
|
||||
when {
|
||||
fieldTypeName.contains("java.util.HashMap<java.lang.String, java.lang.Integer>") ||
|
||||
fieldTypeName.contains("HashMap<String, Int>") ||
|
||||
fieldTypeName.contains("HashMap<String, Integer>") -> {
|
||||
try {
|
||||
value.toInt()
|
||||
} catch (numberFormat: NumberFormatException) {
|
||||
Helpers.showToast(
|
||||
this@SettingsPanel,
|
||||
"Invalid number format for key '$key': '$value'. Using 0 as default.",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
)
|
||||
0
|
||||
}
|
||||
}
|
||||
fieldTypeName.contains("java.lang.Integer") || fieldTypeName.contains("int") -> value.toInt()
|
||||
fieldTypeName.contains("java.lang.Double") || fieldTypeName.contains("double") -> value.toDouble()
|
||||
fieldTypeName.contains("java.lang.Float") || fieldTypeName.contains("float") -> value.toFloat()
|
||||
fieldTypeName.contains("java.lang.Boolean") || fieldTypeName.contains("boolean") -> value.toBoolean()
|
||||
else -> value
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
value
|
||||
}
|
||||
|
||||
currentHashMap[key] = convertedValue
|
||||
}
|
||||
|
||||
fieldNotifier.setFieldValue(field, currentHashMap)
|
||||
Helpers.showToast(
|
||||
this@SettingsPanel,
|
||||
"${field.name} updated successfully!"
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Helpers.showToast(
|
||||
this@SettingsPanel,
|
||||
"Failed to update ${field.name}: ${e.message}",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
)
|
||||
}
|
||||
}
|
||||
).apply {
|
||||
setFixedSize(120, 30)
|
||||
}
|
||||
|
||||
buttonsPanel.add(addButton)
|
||||
buttonsPanel.add(removeButton)
|
||||
buttonsPanel.add(Box.createHorizontalStrut(20))
|
||||
buttonsPanel.add(applyButton)
|
||||
|
||||
// Add components to the editor panel in vertical stack
|
||||
editorPanel.add(titleLabel)
|
||||
editorPanel.add(Box.createVerticalStrut(5))
|
||||
editorPanel.add(scrollPane)
|
||||
editorPanel.add(Box.createVerticalStrut(5))
|
||||
editorPanel.add(buttonsPanel)
|
||||
|
||||
return editorPanel
|
||||
}
|
||||
|
||||
private fun Field.exposedDescription(): String {
|
||||
val exposedAnnotation = annotations.firstOrNull {
|
||||
it.annotationClass.simpleName == "Exposed"
|
||||
} ?: return ""
|
||||
|
||||
return try {
|
||||
val descriptionMethod = exposedAnnotation.annotationClass.java.getMethod("description")
|
||||
descriptionMethod.invoke(exposedAnnotation) as? String ?: ""
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
private fun Field.isHashMapType(): Boolean {
|
||||
return type == HashMap::class.java || type.simpleName == "HashMap"
|
||||
}
|
||||
|
||||
companion object {
|
||||
var customToolTipWindow: JWindow? = null
|
||||
|
||||
fun showCustomToolTip(text: String, component: JComponent) {
|
||||
val tooltipFont = ViewConstants.FONT_RUNESCAPE_SMALL_PLAIN_16
|
||||
val maxWidth = 150
|
||||
|
||||
// Create a dummy JLabel to get FontMetrics for the font used in the tooltip
|
||||
val dummyLabel = JLabel()
|
||||
dummyLabel.font = tooltipFont
|
||||
val fontMetrics = dummyLabel.getFontMetrics(tooltipFont)
|
||||
val lineHeight = fontMetrics.height
|
||||
|
||||
// Calculate the approximate width of the text
|
||||
val textWidth = fontMetrics.stringWidth(text)
|
||||
|
||||
// Calculate the number of lines required based on the text width and max tooltip width
|
||||
val numberOfLines = Math.ceil(textWidth.toDouble() / maxWidth).toInt()
|
||||
|
||||
// Calculate the required height of the tooltip
|
||||
val requiredHeight = numberOfLines * lineHeight + 6 // Adding some padding
|
||||
|
||||
if (customToolTipWindow == null) {
|
||||
customToolTipWindow = JWindow().apply {
|
||||
val bgColor = Helpers.colorToHex(KondoKit.plugin.Companion.TOOLTIP_BACKGROUND)
|
||||
val textColor = Helpers.colorToHex(KondoKit.plugin.Companion.secondaryColor)
|
||||
contentPane = JLabel("<html><div style='color: $textColor; background-color: $bgColor; padding: 3px; word-break: break-all;'>$text</div></html>").apply {
|
||||
border = BorderFactory.createLineBorder(Color.BLACK)
|
||||
isOpaque = true
|
||||
background = KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
|
||||
foreground = Color.WHITE
|
||||
font = tooltipFont
|
||||
maximumSize = Dimension(maxWidth, Int.MAX_VALUE)
|
||||
preferredSize = Dimension(maxWidth, requiredHeight)
|
||||
}
|
||||
pack()
|
||||
}
|
||||
} else {
|
||||
// Update the tooltip text
|
||||
val label = customToolTipWindow!!.contentPane as JLabel
|
||||
val bgColor = Helpers.colorToHex(KondoKit.plugin.Companion.TOOLTIP_BACKGROUND)
|
||||
val textColor = Helpers.colorToHex(KondoKit.plugin.Companion.secondaryColor)
|
||||
label.text = "<html><div style='color: $textColor; background-color: $bgColor; padding: 3px; word-break: break-all;'>$text</div></html>"
|
||||
label.preferredSize = Dimension(maxWidth, requiredHeight)
|
||||
customToolTipWindow!!.pack()
|
||||
}
|
||||
|
||||
// Position the tooltip near the component
|
||||
val locationOnScreen = component.locationOnScreen
|
||||
customToolTipWindow!!.setLocation(locationOnScreen.x, locationOnScreen.y + 15)
|
||||
customToolTipWindow!!.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue