rt4-client/plugin-playground/src/main/kotlin/KondoKit/views/ReflectiveEditorView.kt

765 lines
No EOL
31 KiB
Kotlin

package KondoKit.views
import KondoKit.Helpers
import KondoKit.components.*
import KondoKit.components.ReflectiveEditorComponents.CustomSearchField
import KondoKit.components.ReflectiveEditorComponents.GitLabPlugin
import KondoKit.components.ReflectiveEditorComponents.GitLabPluginFetcher
import KondoKit.Helpers.showToast
import KondoKit.plugin
import KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
import KondoKit.plugin.Companion.secondaryColor
import plugin.Plugin
import plugin.PluginInfo
import plugin.PluginRepository
import rt4.GlobalJsonConfig
import java.awt.*
import java.awt.image.BufferedImage
import java.io.File
import java.util.*
import javax.imageio.ImageIO
import javax.swing.*
import javax.swing.border.AbstractBorder
import kotlin.math.ceil
/*
This is used for the runtime editing of plugin variables.
To expose fields add the @Exposed annotation.
When they are applied this will trigger an invoke of OnKondoValueUpdated()
if it is implemented. Check GroundItems plugin for an example.
*/
object ReflectiveEditorView : View {
var reflectiveEditorView: JPanel? = null
private val loadedPlugins: MutableList<String> = mutableListOf()
const val VIEW_NAME = "REFLECTIVE_EDITOR_VIEW"
const val PLUGIN_LIST_VIEW = "PLUGIN_LIST"
const val PLUGIN_DETAIL_VIEW = "PLUGIN_DETAIL"
val pluginsDirectory: File = File(GlobalJsonConfig.instance.pluginsFolder)
// GitLab API configuration
private const val GITLAB_ACCESS_TOKEN = "glpat-dE2Cs2e4b32-H7c9oGuS"
private const val GITLAB_PROJECT_ID = "38297322"
private const val GITLAB_BRANCH = "master"
// Store fetched GitLab plugins
private var gitLabPlugins: List<GitLabPlugin> = listOf()
// Store the cog icon to be reused
private var cogIcon: Icon? = null
private fun loadCogIcon() {
try {
val imageStream = this::class.java.getResourceAsStream("res/cog.png")
if (imageStream != null) {
val image: BufferedImage = ImageIO.read(imageStream)
val scaledImage = image.getScaledInstance(12, 12, Image.SCALE_SMOOTH)
cogIcon = ImageIcon(scaledImage)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
// Card layout for switching between views within the reflective editor view
private lateinit var cardLayout: CardLayout
private lateinit var mainPanel: JPanel
private var currentPluginInfo: PluginInfo? = null
private var currentPlugin: Plugin? = null
// Search text for filtering plugins
private var pluginSearchText: String = ""
override val name: String = VIEW_NAME
override val iconSpriteId: Int = KondoKit.plugin.WRENCH_ICON
override val panel: JPanel
get() = reflectiveEditorView ?: JPanel()
override fun createView() {
createReflectiveEditorView()
}
override fun registerFunctions() {
// Reflective editor functions are handled within the view itself
}
fun createReflectiveEditorView() {
// Load the cog icon once
loadCogIcon()
// Create the main panel with card layout
cardLayout = CardLayout()
mainPanel = JPanel(cardLayout)
mainPanel.background = VIEW_BACKGROUND_COLOR
mainPanel.border = BorderFactory.createEmptyBorder(0, 0, 0, 0)
// Help minimize flicker during dynamic swaps
mainPanel.isDoubleBuffered = true
// Create the plugin list view
val pluginListView = createPluginListView()
pluginListView.name = PLUGIN_LIST_VIEW
mainPanel.add(pluginListView, PLUGIN_LIST_VIEW)
// Create a placeholder for the plugin detail view
val pluginDetailView = BaseView(PLUGIN_DETAIL_VIEW).apply {
layout = BorderLayout()
background = VIEW_BACKGROUND_COLOR
}
mainPanel.add(pluginDetailView, PLUGIN_DETAIL_VIEW)
// Set the reflectiveEditorView to our main panel
reflectiveEditorView = mainPanel
// Show the plugin list view by default
cardLayout.show(mainPanel, PLUGIN_LIST_VIEW)
// Fetch GitLab plugins in the background
GitLabPluginFetcher.fetchGitLabPlugins { plugins ->
gitLabPlugins = plugins
// We'll update the UI when needed
}
}
private fun createPluginListView(): JPanel {
val panel = JPanel()
panel.layout = BoxLayout(panel, BoxLayout.Y_AXIS)
panel.background = VIEW_BACKGROUND_COLOR
// Add search field at the top
val searchField = CustomSearchField(panel) { searchText ->
pluginSearchText = searchText
// Refresh the plugin list to apply filtering
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
}
val searchFieldWrapper = JPanel().apply {
layout = BoxLayout(this, BoxLayout.X_AXIS)
background = VIEW_BACKGROUND_COLOR
preferredSize = Dimension(230, 30)
maximumSize = preferredSize
minimumSize = preferredSize
alignmentX = Component.CENTER_ALIGNMENT
add(searchField)
}
panel.add(Box.createVerticalStrut(10)) // Spacer
panel.add(searchFieldWrapper)
panel.add(Box.createVerticalStrut(10)) // Spacer
try {
// Get loaded plugins
val loadedPluginsField = PluginRepository::class.java.getDeclaredField("loadedPlugins")
loadedPluginsField.isAccessible = true
val loadedPlugins = loadedPluginsField.get(null) as HashMap<*, *>
// Separate plugins with and without exposed attributes
val pluginsWithExposed = mutableListOf<Pair<PluginInfo, Plugin>>()
val pluginsWithoutExposed = mutableListOf<Pair<PluginInfo, Plugin>>()
for ((pluginInfo, plugin) in loadedPlugins) {
val exposedFields = (plugin as Plugin).javaClass.declaredFields.filter { field ->
field.annotations.any { annotation ->
annotation.annotationClass.simpleName == "Exposed"
}
}
// Apply search filter
val pluginName = plugin.javaClass.`package`.name
if (pluginSearchText.isBlank() || pluginName.contains(pluginSearchText, ignoreCase = true)) {
if (exposedFields.isNotEmpty()) {
pluginsWithExposed.add(pluginInfo as PluginInfo to plugin as Plugin)
} else {
pluginsWithoutExposed.add(pluginInfo as PluginInfo to plugin as Plugin)
}
}
}
// Sort both lists by package name
pluginsWithExposed.sortBy { it.second.javaClass.`package`.name }
pluginsWithoutExposed.sortBy { it.second.javaClass.`package`.name }
// Add plugins with exposed attributes first
for ((pluginInfo, plugin) in pluginsWithExposed) {
val pluginPanel = createPluginItemPanel(pluginInfo, plugin)
panel.add(pluginPanel)
panel.add(Box.createVerticalStrut(5))
}
// Add a separator if we have plugins with exposed attributes
if (pluginsWithExposed.isNotEmpty() && pluginsWithoutExposed.isNotEmpty()) {
val separator = JPanel()
separator.background = VIEW_BACKGROUND_COLOR
separator.preferredSize = Dimension(Int.MAX_VALUE, 1)
separator.maximumSize = Dimension(Int.MAX_VALUE, 1)
panel.add(separator)
panel.add(Box.createVerticalStrut(5))
}
// Add plugins without exposed attributes
for ((pluginInfo, plugin) in pluginsWithoutExposed) {
val pluginPanel = createPluginItemPanel(pluginInfo, plugin)
panel.add(pluginPanel)
panel.add(Box.createVerticalStrut(5))
}
} catch (e: Exception) {
e.printStackTrace()
}
// Add disabled plugins to the list (filtered by search text)
val disabledDir = File(pluginsDirectory, "disabled")
if (disabledDir.exists() && disabledDir.isDirectory) {
val disabledPlugins = disabledDir.listFiles { file -> file.isDirectory } ?: arrayOf()
// Add disabled plugins to the list without exposed attributes (filtered by search text)
for (pluginDir in disabledPlugins.sortedBy { it.name }) {
// Apply search filter
if (pluginSearchText.isBlank() || pluginDir.name.contains(pluginSearchText, ignoreCase = true)) {
val pluginPanel = createDisabledPluginItemPanel(pluginDir.name)
panel.add(pluginPanel)
panel.add(Box.createVerticalStrut(5))
}
}
}
// Add a section for available plugins from GitLab that are not installed
// Only show this section when there's a search term
if (pluginSearchText.isNotBlank()) {
val matchingGitLabPlugins = gitLabPlugins.filter { plugin ->
plugin.pluginProperties != null &&
(plugin.path.contains(pluginSearchText, ignoreCase = true) ||
plugin.pluginProperties!!.description.contains(pluginSearchText, ignoreCase = true))
}
if (matchingGitLabPlugins.isNotEmpty()) {
// Add a separator
val separator = JPanel()
separator.background = VIEW_BACKGROUND_COLOR
separator.preferredSize = Dimension(Int.MAX_VALUE, 1)
separator.maximumSize = Dimension(Int.MAX_VALUE, 1)
panel.add(Box.createVerticalStrut(10))
panel.add(separator)
panel.add(Box.createVerticalStrut(5))
// Add a header for available plugins
val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT))
headerPanel.background = VIEW_BACKGROUND_COLOR
val headerLabel = JLabel("Available Plugins")
headerLabel.foreground = plugin.Companion.secondaryColor
headerLabel.font = Font("RuneScape Small", Font.BOLD, 16)
headerPanel.add(headerLabel)
panel.add(headerPanel)
panel.add(Box.createVerticalStrut(5))
// Add matching GitLab plugins
for (gitLabPlugin in matchingGitLabPlugins) {
val pluginPanel = createGitLabPluginItemPanel(gitLabPlugin)
panel.add(pluginPanel)
panel.add(Box.createVerticalStrut(5))
}
}
}
// Wrap the panel in a ScrollablePanel for custom scrolling
val scrollablePanel = ScrollablePanel(panel)
// Reset scroll position to top
SwingUtilities.invokeLater {
// For ScrollablePanel, we need to reset the internal offset
resetScrollablePanel(scrollablePanel)
}
val container = JPanel(BorderLayout())
container.background = VIEW_BACKGROUND_COLOR
container.add(scrollablePanel, BorderLayout.CENTER)
return container
}
private fun createPluginItemPanel(pluginInfo: PluginInfo, plugin: Plugin): JPanel {
val panel = JPanel(BorderLayout())
panel.background = KondoKit.plugin.Companion.WIDGET_COLOR
panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
panel.maximumSize = Dimension(220, 60)
// Plugin name and version (using package name as in original implementation)
val packageName = plugin.javaClass.`package`.name
val nameLabel = JLabel(packageName)
nameLabel.foreground = KondoKit.plugin.Companion.secondaryColor
nameLabel.font = Font("RuneScape Small", Font.PLAIN, 16)
// Check if plugin has exposed attributes
val exposedFields = plugin.javaClass.declaredFields.filter { field ->
field.annotations.any { annotation ->
annotation.annotationClass.simpleName == "Exposed"
}
}
// Edit button (only show if plugin has exposed attributes)
val editButton = if (exposedFields.isNotEmpty()) {
val button = JButton()
if (cogIcon != null) {
button.icon = cogIcon
} else {
button.text = "Edit"
}
button.background = KondoKit.plugin.Companion.TITLE_BAR_COLOR
button.foreground = KondoKit.plugin.Companion.secondaryColor
button.font = Font("RuneScape Small", Font.PLAIN, 14)
button.addActionListener {
showPluginDetails(pluginInfo, plugin)
}
button
} else {
null
}
// Plugin toggle switch (iOS style)
val toggleSwitch = createToggleSwitch(plugin, pluginInfo)
// Layout
val infoPanel = JPanel(BorderLayout())
infoPanel.background = KondoKit.plugin.Companion.WIDGET_COLOR
infoPanel.add(nameLabel, BorderLayout.WEST)
val controlsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
controlsPanel.background = KondoKit.plugin.Companion.WIDGET_COLOR
// Add edit button first (left side of controls)
editButton?.let { controlsPanel.add(it) }
// Add toggle switch second (right side of controls)
controlsPanel.add(toggleSwitch)
panel.add(infoPanel, BorderLayout.CENTER)
panel.add(controlsPanel, BorderLayout.EAST)
return panel
}
private fun createDisabledPluginItemPanel(pluginName: String): JPanel {
val panel = JPanel(BorderLayout())
panel.background = plugin.Companion.WIDGET_COLOR
panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
panel.maximumSize = Dimension(220, 60)
// Plugin name
val nameLabel = JLabel(pluginName)
nameLabel.foreground = plugin.Companion.secondaryColor
nameLabel.font = Font("RuneScape Small", Font.PLAIN, 16)
// Plugin toggle switch (iOS style) - initially off for disabled plugins
val toggleSwitch = ToggleSwitch()
toggleSwitch.setActivated(false)
toggleSwitch.onToggleListener = { activated ->
if (activated) {
enablePlugin(pluginName)
}
// If trying to disable an already disabled plugin, reset the toggle
else {
toggleSwitch.setActivated(false)
}
}
// Layout
val infoPanel = JPanel(BorderLayout())
infoPanel.background = plugin.Companion.WIDGET_COLOR
infoPanel.add(nameLabel, BorderLayout.WEST)
val controlsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
controlsPanel.background = plugin.Companion.WIDGET_COLOR
controlsPanel.add(toggleSwitch)
panel.add(infoPanel, BorderLayout.CENTER)
panel.add(controlsPanel, BorderLayout.EAST)
return panel
}
private fun createGitLabPluginItemPanel(gitLabPlugin: GitLabPlugin): JPanel {
val panel = JPanel(BorderLayout())
panel.background = plugin.Companion.WIDGET_COLOR
panel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
panel.maximumSize = Dimension(220, 60)
// Plugin name
val nameLabel = JLabel(gitLabPlugin.path)
nameLabel.foreground = plugin.Companion.secondaryColor
nameLabel.font = Font("RuneScape Small", Font.PLAIN, 16)
// Download button (placeholder for now)
val downloadButton = JButton("Download")
downloadButton.background = plugin.Companion.TITLE_BAR_COLOR
downloadButton.foreground = plugin.Companion.secondaryColor
downloadButton.font = Font("RuneScape Small", Font.PLAIN, 14)
downloadButton.addActionListener {
// TODO: Implement download functionality
showToast(mainPanel, "Download functionality not yet implemented", JOptionPane.INFORMATION_MESSAGE)
}
// Plugin toggle switch (iOS style) - initially off for GitLab plugins
val toggleSwitch = ToggleSwitch()
toggleSwitch.setActivated(false)
toggleSwitch.isEnabled = false // Disable toggle for GitLab plugins until downloaded
// Layout
val infoPanel = JPanel(BorderLayout())
infoPanel.background = plugin.Companion.WIDGET_COLOR
infoPanel.add(nameLabel, BorderLayout.WEST)
val controlsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 5, 0))
controlsPanel.background = plugin.Companion.WIDGET_COLOR
controlsPanel.add(downloadButton)
controlsPanel.add(toggleSwitch)
panel.add(infoPanel, BorderLayout.CENTER)
panel.add(controlsPanel, BorderLayout.EAST)
return panel
}
private fun enablePlugin(pluginName: String) {
try {
// Source and destination directories
val disabledDir = File(pluginsDirectory, "disabled")
val sourceDir = File(disabledDir, pluginName)
val destDir = File(pluginsDirectory, pluginName)
// Check if source directory exists
if (!sourceDir.exists()) {
showToast(mainPanel, "Plugin directory not found: ${sourceDir.absolutePath}", JOptionPane.ERROR_MESSAGE)
return
}
// Move the directory
if (sourceDir.renameTo(destDir)) {
showToast(mainPanel, "Plugin enabled")
// Reload plugins to apply the change
PluginRepository.reloadPlugins()
// Refresh the plugin list view
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
} else {
showToast(mainPanel, "Failed to enable plugin", JOptionPane.ERROR_MESSAGE)
}
} catch (e: Exception) {
e.printStackTrace()
showToast(mainPanel, "Error enabling plugin: ${e.message}", JOptionPane.ERROR_MESSAGE)
}
}
private fun createToggleSwitch(plugin: Plugin, pluginInfo: PluginInfo): ToggleSwitch {
val toggleSwitch = ToggleSwitch()
// Set initial state
toggleSwitch.setActivated(isPluginEnabled(plugin, pluginInfo))
// Add toggle listener
toggleSwitch.onToggleListener = { activated ->
togglePlugin(plugin, pluginInfo, toggleSwitch, activated)
}
return toggleSwitch
}
private fun isPluginEnabled(plugin: Plugin, pluginInfo: PluginInfo): Boolean {
// Get the plugin directory name from the plugin's class package
val pluginDirName = getPluginDirName(plugin)
val pluginDir = File(pluginsDirectory, pluginDirName)
return pluginDir.exists() && pluginDir.isDirectory
}
private fun getPluginDirName(plugin: Plugin): String {
// Extract the directory name from the plugin's package
// The package name is typically like "GroundItems.plugin" so we take the first part
val packageName = plugin.javaClass.`package`.name
return packageName.substringBefore(".")
}
private fun togglePlugin(plugin: Plugin, pluginInfo: PluginInfo, toggleSwitch: ToggleSwitch, activated: Boolean) {
try {
// Get the plugin directory name from the plugin's class package
val pluginDirName = getPluginDirName(plugin)
// Source and destination directories
val sourceDir = if (activated) {
// Moving from disabled to enabled
File(File(pluginsDirectory, "disabled"), pluginDirName)
} else {
// Moving from enabled to disabled
File(pluginsDirectory, pluginDirName)
}
val destDir = if (activated) {
// Moving to main plugins directory
File(pluginsDirectory, pluginDirName)
} else {
// Moving to disabled directory
val disabledDir = File(pluginsDirectory, "disabled")
if (!disabledDir.exists()) {
disabledDir.mkdirs()
}
File(disabledDir, pluginDirName)
}
// Check if source directory exists
if (!sourceDir.exists()) {
showToast(mainPanel, "Plugin directory not found: ${sourceDir.absolutePath}", JOptionPane.ERROR_MESSAGE)
// Reset toggle switch to previous state
toggleSwitch.setActivated(!activated)
return
}
// Move the directory
if (sourceDir.renameTo(destDir)) {
showToast(mainPanel, if (activated) "Plugin enabled" else "Plugin disabled")
// Reload plugins to apply the change
PluginRepository.reloadPlugins()
// Refresh the plugin list view
SwingUtilities.invokeLater {
addPlugins(reflectiveEditorView!!)
}
} else {
showToast(mainPanel, "Failed to ${if (activated) "enable" else "disable"} plugin", JOptionPane.ERROR_MESSAGE)
// Reset toggle switch to previous state
toggleSwitch.setActivated(!activated)
}
} catch (e: Exception) {
e.printStackTrace()
showToast(mainPanel, "Error toggling plugin: ${e.message}", JOptionPane.ERROR_MESSAGE)
// Reset toggle switch to previous state
toggleSwitch.setActivated(!activated)
}
}
private fun showPluginDetails(pluginInfo: PluginInfo, plugin: Plugin) {
currentPluginInfo = pluginInfo
currentPlugin = plugin
// Remove the existing detail view if it exists
val existingDetailView = mainPanel.components.find { it.name == PLUGIN_DETAIL_VIEW }
if (existingDetailView != null) {
mainPanel.remove(existingDetailView)
}
// Create a new detail view
val detailView = BaseView(PLUGIN_DETAIL_VIEW)
detailView.layout = BorderLayout()
detailView.background = VIEW_BACKGROUND_COLOR
// Header with back button
//$packageName v${pluginInfo.version} should be
val headerPanel = ViewHeader(" v${pluginInfo.version}", 40).apply {
border = BorderFactory.createEmptyBorder(5, 10, 5, 10)
}
val backButton = ButtonPanel(FlowLayout.LEFT).addButton("Back") {
// Reset scroll position to top when returning to the list view
SwingUtilities.invokeLater {
// Find the ScrollablePanel in the plugin list view and reset its scroll position
val listView = mainPanel.components.find { it.name == PLUGIN_LIST_VIEW }
if (listView != null && listView is Container) {
findAndResetScrollablePanel(listView)
}
}
cardLayout.show(mainPanel, PLUGIN_LIST_VIEW)
}
val packageName = plugin.javaClass.`package`.name
headerPanel.add(backButton, BorderLayout.WEST)
// Content panel for settings
val contentPanel = JPanel()
contentPanel.layout = BoxLayout(contentPanel, BoxLayout.Y_AXIS)
contentPanel.background = VIEW_BACKGROUND_COLOR
contentPanel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
// Add plugin info
val infoPanel = JPanel(BorderLayout())
infoPanel.background = KondoKit.plugin.Companion.WIDGET_COLOR
infoPanel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
infoPanel.maximumSize = Dimension(Int.MAX_VALUE, 80)
val infoText = """
Version: ${pluginInfo.version}
Author: ${pluginInfo.author}
Description: ${pluginInfo.description}
""".trimIndent()
val infoLabel = LabelComponent(infoText).apply {
font = Font("RuneScape Small", Font.PLAIN, 14)
}
infoPanel.add(infoLabel, BorderLayout.CENTER)
contentPanel.add(infoPanel)
contentPanel.add(Box.createVerticalStrut(10))
// Add exposed fields
addPluginSettings(contentPanel, plugin)
// Wrap the content panel in a ScrollablePanel for custom scrolling
val scrollablePanel = ScrollablePanel(contentPanel)
// Reset scroll position to top when entering the edit page
SwingUtilities.invokeLater {
resetScrollablePanel(scrollablePanel)
}
detailView.add(headerPanel, BorderLayout.NORTH)
detailView.add(scrollablePanel, BorderLayout.CENTER)
// Add the new detail view to the main panel
mainPanel.add(detailView, PLUGIN_DETAIL_VIEW)
// Revalidate and repaint the main panel
mainPanel.revalidate()
mainPanel.repaint()
// Show the detail view
cardLayout.show(mainPanel, PLUGIN_DETAIL_VIEW)
}
private fun addPluginSettings(contentPanel: JPanel, plugin: Plugin) {
val settingsPanel = SettingsPanel(plugin)
settingsPanel.addSettingsFromPlugin()
contentPanel.add(settingsPanel)
}
fun addPlugins(reflectiveEditorView: JPanel) {
// Ensure we run on the EDT; if not, reschedule and return
if (!SwingUtilities.isEventDispatchThread()) {
SwingUtilities.invokeLater { addPlugins(reflectiveEditorView) }
return
}
// Batch updates to avoid intermediate repaints/flicker
mainPanel.ignoreRepaint = true
try {
// Remove the existing plugin list view if present
val existingListView = mainPanel.components.find { it.name == PLUGIN_LIST_VIEW }
if (existingListView != null) {
mainPanel.remove(existingListView)
}
// Create a new plugin list view off-EDT (already on EDT here) and add it
val pluginListView = createPluginListView()
pluginListView.name = PLUGIN_LIST_VIEW
mainPanel.add(pluginListView, PLUGIN_LIST_VIEW)
// Switch card after the new component is in place
cardLayout.show(mainPanel, PLUGIN_LIST_VIEW)
// Revalidate/repaint once at the end
mainPanel.revalidate()
mainPanel.repaint()
} finally {
mainPanel.ignoreRepaint = false
}
}
var customToolTipWindow: JWindow? = null
fun showCustomToolTip(text: String, component: JComponent) {
val _font = Font("RuneScape Small", Font.PLAIN, 16)
val maxWidth = 150
val lineHeight = 16
// Create a dummy JLabel to get FontMetrics for the font used in the tooltip
val dummyLabel = JLabel()
dummyLabel.font = _font
val fontMetrics = dummyLabel.getFontMetrics(_font)
// 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 = 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(TOOLTIP_BACKGROUND)
val textColor = Helpers.colorToHex(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 = TOOLTIP_BACKGROUND
foreground = Color.WHITE
font = _font
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(TOOLTIP_BACKGROUND)
val textColor = Helpers.colorToHex(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
}
// Helper method to reset a ScrollablePanel to the top
private fun resetScrollablePanel(scrollablePanel: ScrollablePanel) {
// Access the content panel and reset its position
// Since we can't directly access the private fields, we'll use reflection
try {
val contentField = ScrollablePanel::class.java.getDeclaredField("content")
contentField.isAccessible = true
val contentPanel = contentField.get(scrollablePanel) as JPanel
// Reset the location to the top
contentPanel.setLocation(0, 0)
// Force a repaint
scrollablePanel.revalidate()
scrollablePanel.repaint()
} catch (e: Exception) {
// If reflection fails, at least try to repaint
scrollablePanel.revalidate()
scrollablePanel.repaint()
}
}
// Helper method to find and reset ScrollablePanel components within a container
private fun findAndResetScrollablePanel(container: Component) {
if (container is ScrollablePanel) {
resetScrollablePanel(container)
} else if (container is Container) {
for (child in container.components) {
findAndResetScrollablePanel(child)
}
}
}
// Custom border for rounded components
class RoundedBorder(radius: Int) : AbstractBorder() {
private val radius = radius
override fun paintBorder(c: Component, g: Graphics, x: Int, y: Int, width: Int, height: Int) {
val g2 = g as Graphics2D
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
g2.setColor(c.background)
g2.fillRoundRect(x, y, width - 1, height - 1, radius, radius)
}
}
}