mirror of
https://gitlab.com/2009scape/rt4-client.git
synced 2025-12-16 19:40:18 -07:00
841 lines
33 KiB
Kotlin
841 lines
33 KiB
Kotlin
package KondoKit.views
|
|
|
|
import KondoKit.Helpers
|
|
import KondoKit.components.*
|
|
import KondoKit.ViewConstants
|
|
import KondoKit.views.ViewLayoutHelpers.createSearchFieldSection
|
|
import KondoKit.setFixedSize
|
|
import KondoKit.attachPopupMenu
|
|
import KondoKit.ReflectiveEditorPlugin
|
|
import KondoKit.pluginmanager.GitLabPlugin
|
|
import KondoKit.pluginmanager.GitLabPluginFetcher
|
|
import KondoKit.pluginmanager.PluginDownloadManager
|
|
import KondoKit.pluginmanager.PluginProperties
|
|
import KondoKit.pluginmanager.PluginInfoWithStatus
|
|
import KondoKit.Helpers.showToast
|
|
import KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
|
|
import KondoKit.plugin.Companion.VIEW_BACKGROUND_COLOR
|
|
import KondoKit.plugin.Companion.WIDGET_COLOR
|
|
import KondoKit.plugin.Companion.WRENCH_ICON
|
|
import KondoKit.plugin.Companion.secondaryColor
|
|
import plugin.Plugin
|
|
import plugin.PluginInfo
|
|
import plugin.PluginRepository
|
|
import rt4.GlobalJsonConfig
|
|
import java.awt.*
|
|
import java.io.File
|
|
import java.util.*
|
|
import javax.swing.*
|
|
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
|
|
const val VIEW_NAME = "REFLECTIVE_EDITOR_VIEW"
|
|
const val PLUGIN_LIST_VIEW = "PLUGIN_LIST"
|
|
const val PLUGIN_DETAIL_VIEW = "PLUGIN_DETAIL"
|
|
|
|
private val reflectiveEditorPlugin = ReflectiveEditorPlugin()
|
|
|
|
private var searchField: SearchField? = null
|
|
|
|
// Helper function to check if a plugin is the KondoKit plugin
|
|
private fun isKondoKit(pluginName: String): Boolean {
|
|
return pluginName == "KondoKit"
|
|
}
|
|
|
|
// 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 = ""
|
|
private var searchFieldWrapper: JPanel? = null
|
|
private var pluginListContentPanel: JPanel? = null
|
|
private var pluginListScrollablePanel: ScrollablePanel? = null
|
|
|
|
override val name: String = VIEW_NAME
|
|
override val iconSpriteId: Int = WRENCH_ICON
|
|
override val panel: JPanel
|
|
get() = reflectiveEditorView ?: JPanel()
|
|
|
|
override fun createView() {
|
|
// Initialize the plugin
|
|
reflectiveEditorPlugin.Init()
|
|
createReflectiveEditorView()
|
|
}
|
|
|
|
override fun registerFunctions() {
|
|
}
|
|
|
|
fun createReflectiveEditorView() {
|
|
cardLayout = CardLayout()
|
|
mainPanel = JPanel(cardLayout)
|
|
mainPanel.background = VIEW_BACKGROUND_COLOR
|
|
mainPanel.border = BorderFactory.createEmptyBorder(0, 0, 0, 0)
|
|
mainPanel.isDoubleBuffered = true
|
|
|
|
val pluginListView = createPluginListView()
|
|
pluginListView.name = PLUGIN_LIST_VIEW
|
|
mainPanel.add(pluginListView, PLUGIN_LIST_VIEW)
|
|
|
|
val pluginDetailView = BaseView(PLUGIN_DETAIL_VIEW).apply {
|
|
layout = BorderLayout()
|
|
background = VIEW_BACKGROUND_COLOR
|
|
}
|
|
mainPanel.add(pluginDetailView, PLUGIN_DETAIL_VIEW)
|
|
|
|
reflectiveEditorView = mainPanel
|
|
|
|
cardLayout.show(mainPanel, PLUGIN_LIST_VIEW)
|
|
|
|
// Plugin initialization is handled by the plugin class, gitLabPlugins will be fetched automatically
|
|
}
|
|
|
|
private fun createPluginListView(): JPanel {
|
|
val panel = BaseView(PLUGIN_LIST_VIEW, addDefaultSpacing = false).apply {
|
|
background = VIEW_BACKGROUND_COLOR
|
|
}
|
|
|
|
val searchSection = createSearchFieldSection(
|
|
parent = panel,
|
|
placeholderText = "Search plugins...",
|
|
viewName = VIEW_NAME,
|
|
initialText = pluginSearchText
|
|
) { searchText ->
|
|
pluginSearchText = searchText
|
|
SwingUtilities.invokeLater {
|
|
addPlugins(reflectiveEditorView!!)
|
|
}
|
|
}
|
|
this.searchField = searchSection.searchField
|
|
searchFieldWrapper = searchSection.wrapper
|
|
|
|
panel.add(Box.createVerticalStrut(5))
|
|
panel.add(searchSection.wrapper)
|
|
panel.add(Box.createVerticalStrut(10))
|
|
|
|
pluginListContentPanel = JPanel().apply {
|
|
layout = BoxLayout(this, BoxLayout.Y_AXIS)
|
|
background = VIEW_BACKGROUND_COLOR
|
|
alignmentX = Component.CENTER_ALIGNMENT
|
|
}
|
|
panel.add(pluginListContentPanel)
|
|
|
|
val scrollablePanel = ScrollablePanel(panel)
|
|
pluginListScrollablePanel = scrollablePanel
|
|
|
|
val container = JPanel(BorderLayout())
|
|
container.background = VIEW_BACKGROUND_COLOR
|
|
container.add(scrollablePanel, BorderLayout.CENTER)
|
|
|
|
populatePluginListContent()
|
|
|
|
SwingUtilities.invokeLater {
|
|
resetScrollablePanel(scrollablePanel)
|
|
}
|
|
|
|
return container
|
|
}
|
|
|
|
private fun populatePluginListContent() {
|
|
val contentPanel = pluginListContentPanel ?: return
|
|
|
|
val treeLock = contentPanel.treeLock
|
|
synchronized(treeLock) {
|
|
contentPanel.removeAll()
|
|
|
|
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)
|
|
} else {
|
|
pluginsWithoutExposed.add(pluginInfo as PluginInfo to plugin)
|
|
}
|
|
}
|
|
}
|
|
|
|
pluginsWithExposed.sortWith(compareBy(
|
|
{ if (isKondoKit(it.second.javaClass.`package`.name)) 0 else 1 },
|
|
{ it.second.javaClass.`package`.name }
|
|
))
|
|
pluginsWithoutExposed.sortWith(compareBy(
|
|
{ if (isKondoKit(it.second.javaClass.`package`.name)) 0 else 1 },
|
|
{ it.second.javaClass.`package`.name }
|
|
))
|
|
|
|
for ((pluginInfo, plugin) in pluginsWithExposed) {
|
|
val pluginPanel = createPluginItemPanel(pluginInfo, plugin)
|
|
contentPanel.add(pluginPanel)
|
|
contentPanel.add(Box.createVerticalStrut(5))
|
|
}
|
|
|
|
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)
|
|
contentPanel.add(separator)
|
|
contentPanel.add(Box.createVerticalStrut(5))
|
|
}
|
|
|
|
for ((pluginInfo, plugin) in pluginsWithoutExposed) {
|
|
val pluginPanel = createPluginItemPanel(pluginInfo, plugin)
|
|
contentPanel.add(pluginPanel)
|
|
contentPanel.add(Box.createVerticalStrut(5))
|
|
}
|
|
} catch (e: Exception) {
|
|
e.printStackTrace()
|
|
}
|
|
|
|
val disabledDir = File(reflectiveEditorPlugin.getPluginsDirectory(), "disabled")
|
|
if (disabledDir.exists() && disabledDir.isDirectory) {
|
|
val disabledPlugins = disabledDir.listFiles { file -> file.isDirectory } ?: arrayOf()
|
|
|
|
for (pluginDir in disabledPlugins.sortedBy { it.name }) {
|
|
if (isKondoKit(pluginDir.name)) {
|
|
continue
|
|
}
|
|
if (pluginSearchText.isBlank() || pluginDir.name.contains(pluginSearchText, ignoreCase = true)) {
|
|
val pluginPanel = createDisabledPluginItemPanel(pluginDir.name)
|
|
contentPanel.add(pluginPanel)
|
|
contentPanel.add(Box.createVerticalStrut(5))
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pluginSearchText.isNotBlank()) {
|
|
val matchingPluginStatuses = reflectiveEditorPlugin.getPluginStatuses().filter { pluginStatus ->
|
|
!pluginStatus.isInstalled &&
|
|
!isKondoKit(pluginStatus.name) &&
|
|
(pluginStatus.name.contains(pluginSearchText, ignoreCase = true) ||
|
|
(pluginStatus.description?.contains(pluginSearchText, ignoreCase = true) ?: false))
|
|
}
|
|
|
|
if (matchingPluginStatuses.isNotEmpty()) {
|
|
val separator = JPanel().apply {
|
|
background = VIEW_BACKGROUND_COLOR
|
|
preferredSize = Dimension(Int.MAX_VALUE, 1)
|
|
maximumSize = preferredSize
|
|
}
|
|
contentPanel.add(Box.createVerticalStrut(10))
|
|
contentPanel.add(separator)
|
|
contentPanel.add(Box.createVerticalStrut(5))
|
|
|
|
val headerPanel = JPanel(FlowLayout(FlowLayout.LEFT)).apply {
|
|
background = VIEW_BACKGROUND_COLOR
|
|
add(JLabel("Available Plugins").apply {
|
|
foreground = secondaryColor
|
|
font = ViewConstants.FONT_RUNESCAPE_SMALL_BOLD_16
|
|
})
|
|
}
|
|
contentPanel.add(headerPanel)
|
|
contentPanel.add(Box.createVerticalStrut(5))
|
|
|
|
matchingPluginStatuses.forEach { pluginStatus ->
|
|
val pluginPanel = createPluginStatusItemPanel(pluginStatus)
|
|
contentPanel.add(pluginPanel)
|
|
contentPanel.add(Box.createVerticalStrut(5))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
contentPanel.revalidate()
|
|
contentPanel.repaint()
|
|
}
|
|
|
|
private fun createPluginItemPanel(pluginInfo: PluginInfo, plugin: Plugin): JPanel {
|
|
val panel = createPluginWidgetPanel().apply {
|
|
layout = BorderLayout()
|
|
}
|
|
|
|
// Plugin name and version (using package name as in original implementation)
|
|
val packageName = plugin.javaClass.`package`.name
|
|
val nameLabel = JLabel(packageName)
|
|
nameLabel.foreground = secondaryColor
|
|
nameLabel.font = ViewConstants.FONT_RUNESCAPE_SMALL_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 = exposedFields.takeIf { it.isNotEmpty() }?.let {
|
|
UiStyler.button(text = "Edit") {
|
|
showPluginDetails(pluginInfo, plugin)
|
|
}
|
|
}
|
|
|
|
// Plugin toggle switch (iOS style) - skip for KondoKit since it should always be on
|
|
val toggleSwitch = if (!isKondoKit(packageName)) {
|
|
createToggleSwitch(plugin, pluginInfo)
|
|
} else {
|
|
// Create a placeholder component that takes the same space but is invisible
|
|
val placeholder = JPanel()
|
|
placeholder.background = WIDGET_COLOR
|
|
placeholder.setFixedSize(ViewConstants.TOGGLE_PLACEHOLDER_SIZE)
|
|
placeholder.isVisible = false
|
|
placeholder
|
|
}
|
|
|
|
val infoPanel = JPanel(BorderLayout())
|
|
infoPanel.background = WIDGET_COLOR
|
|
infoPanel.add(nameLabel, BorderLayout.WEST)
|
|
|
|
val controlsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 5, -3))
|
|
controlsPanel.background = WIDGET_COLOR
|
|
|
|
editButton?.let { controlsPanel.add(it) }
|
|
|
|
if (!isKondoKit(packageName)) {
|
|
controlsPanel.add(toggleSwitch)
|
|
}
|
|
|
|
panel.add(infoPanel, BorderLayout.CENTER)
|
|
panel.add(controlsPanel, BorderLayout.EAST)
|
|
|
|
if (!isKondoKit(packageName)) {
|
|
val popupMenu = createPluginContextMenu(plugin, pluginInfo, packageName, false)
|
|
panel.attachPopupMenu(popupMenu)
|
|
}
|
|
|
|
return panel
|
|
}
|
|
|
|
private fun createDisabledPluginItemPanel(pluginName: String): JPanel {
|
|
val panel = createPluginWidgetPanel().apply {
|
|
layout = BorderLayout()
|
|
}
|
|
|
|
// Plugin name
|
|
val nameLabel = JLabel(pluginName)
|
|
nameLabel.foreground = secondaryColor
|
|
nameLabel.font = ViewConstants.FONT_RUNESCAPE_SMALL_PLAIN_16
|
|
|
|
// Plugin toggle switch (iOS style) - initially off for disabled plugins
|
|
val toggleSwitch = ToggleSwitch()
|
|
toggleSwitch.setActivated(false)
|
|
toggleSwitch.onToggleListener = { activated ->
|
|
if (activated) {
|
|
reflectiveEditorPlugin.enablePlugin(pluginName, mainPanel ?: JPanel())
|
|
}
|
|
// If trying to disable an already disabled plugin, reset the toggle
|
|
else {
|
|
toggleSwitch.setActivated(false)
|
|
}
|
|
}
|
|
|
|
// Layout
|
|
val infoPanel = JPanel(BorderLayout())
|
|
infoPanel.background = WIDGET_COLOR
|
|
infoPanel.add(nameLabel, BorderLayout.WEST)
|
|
|
|
val controlsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 0, 0))
|
|
controlsPanel.background = WIDGET_COLOR
|
|
controlsPanel.add(toggleSwitch)
|
|
|
|
panel.add(infoPanel, BorderLayout.CENTER)
|
|
panel.add(controlsPanel, BorderLayout.EAST)
|
|
|
|
// Add right-click context menu (except for KondoKit itself)
|
|
if (!isKondoKit(pluginName)) {
|
|
val popupMenu = createPluginContextMenu(null, null, pluginName, true)
|
|
panel.attachPopupMenu(popupMenu)
|
|
}
|
|
|
|
return panel
|
|
}
|
|
|
|
private fun createPluginStatusItemPanel(pluginStatus: PluginInfoWithStatus): JPanel {
|
|
val panel = createPluginWidgetPanel().apply {
|
|
layout = BorderLayout()
|
|
}
|
|
|
|
// Plugin name
|
|
val nameLabel = JLabel(pluginStatus.name)
|
|
nameLabel.foreground = secondaryColor
|
|
nameLabel.font = ViewConstants.FONT_RUNESCAPE_SMALL_PLAIN_16
|
|
|
|
// Action button based on plugin status
|
|
val actionButton = UiStyler.button()
|
|
|
|
// Progress bar for downloads
|
|
val progressBar = JProgressBar(0, 100)
|
|
progressBar.isStringPainted = true
|
|
progressBar.isVisible = false
|
|
progressBar.string = "Downloading..."
|
|
|
|
if (pluginStatus.isDownloading) {
|
|
actionButton.isVisible = false
|
|
progressBar.isVisible = true
|
|
progressBar.value = pluginStatus.downloadProgress
|
|
} else if (pluginStatus.isInstalled) {
|
|
if (pluginStatus.needsUpdate) {
|
|
actionButton.text = "Update"
|
|
actionButton.addActionListener {
|
|
// Start downloading the plugin to update it
|
|
startPluginDownload(pluginStatus)
|
|
}
|
|
} else {
|
|
actionButton.text = "Installed"
|
|
actionButton.isEnabled = false
|
|
}
|
|
} else {
|
|
actionButton.text = "Download"
|
|
actionButton.addActionListener {
|
|
// Start downloading the plugin
|
|
startPluginDownload(pluginStatus)
|
|
}
|
|
}
|
|
|
|
// Plugin toggle switch (iOS style)
|
|
val toggleSwitch = ToggleSwitch()
|
|
|
|
// Skip the toggle switch for KondoKit since it should always be enabled
|
|
if (isKondoKit(pluginStatus.name)) {
|
|
// Hide the toggle switch for KondoKit
|
|
toggleSwitch.isVisible = false
|
|
} else {
|
|
toggleSwitch.setActivated(pluginStatus.isInstalled && !pluginStatus.needsUpdate)
|
|
toggleSwitch.isEnabled = pluginStatus.isInstalled && !pluginStatus.needsUpdate
|
|
|
|
if (pluginStatus.isInstalled && !pluginStatus.needsUpdate) {
|
|
toggleSwitch.onToggleListener = { activated ->
|
|
// Find the corresponding loaded plugin to use toggle functionality
|
|
val loadedPluginsField = PluginRepository::class.java.getDeclaredField("loadedPlugins")
|
|
loadedPluginsField.isAccessible = true
|
|
val loadedPlugins = loadedPluginsField.get(null) as HashMap<*, *>
|
|
|
|
var foundPlugin: Plugin? = null
|
|
var foundPluginInfo: PluginInfo? = null
|
|
|
|
for ((pluginInfo, plugin) in loadedPlugins) {
|
|
if (getPluginDirName(plugin as Plugin) == pluginStatus.name) {
|
|
foundPlugin = plugin
|
|
foundPluginInfo = pluginInfo as PluginInfo
|
|
break
|
|
}
|
|
}
|
|
|
|
if (foundPlugin != null && foundPluginInfo != null) {
|
|
reflectiveEditorPlugin.togglePlugin(foundPlugin, foundPluginInfo, toggleSwitch, activated, mainPanel ?: JPanel())
|
|
} else {
|
|
// Fallback for plugins that don't have loaded instances available
|
|
val pluginDirName = pluginStatus.name
|
|
val sourceDir = if (activated) {
|
|
// Moving from disabled to enabled
|
|
File(File(reflectiveEditorPlugin.getPluginsDirectory(), "disabled"), pluginDirName)
|
|
} else {
|
|
// Moving from enabled to disabled
|
|
File(reflectiveEditorPlugin.getPluginsDirectory(), pluginDirName)
|
|
}
|
|
|
|
val destDir = if (activated) {
|
|
// Moving to main plugins directory
|
|
File(reflectiveEditorPlugin.getPluginsDirectory(), pluginDirName)
|
|
} else {
|
|
// Moving to disabled directory
|
|
val disabledDir = File(reflectiveEditorPlugin.getPluginsDirectory(), "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)
|
|
} else {
|
|
// Move the directory
|
|
if (sourceDir.renameTo(destDir)) {
|
|
showToast(mainPanel, if (activated) "Plugin enabled" else "Plugin disabled")
|
|
|
|
// Schedule plugin reload to avoid crashes
|
|
reflectiveEditorPlugin.setReloadPlugins(true)
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Hide the toggle switch for non-installed plugins
|
|
toggleSwitch.isVisible = false
|
|
}
|
|
}
|
|
|
|
val infoPanel = JPanel(BorderLayout())
|
|
infoPanel.background = WIDGET_COLOR
|
|
infoPanel.add(nameLabel, BorderLayout.WEST)
|
|
|
|
val controlsPanel = JPanel(FlowLayout(FlowLayout.RIGHT, 0, 0))
|
|
controlsPanel.background = WIDGET_COLOR
|
|
controlsPanel.add(actionButton)
|
|
controlsPanel.add(progressBar)
|
|
if (toggleSwitch.isVisible) {
|
|
controlsPanel.add(toggleSwitch)
|
|
}
|
|
|
|
if (!pluginStatus.isInstalled) {
|
|
val tooltipText = buildInstallableTooltip(pluginStatus)
|
|
panel.toolTipText = tooltipText
|
|
actionButton.toolTipText = tooltipText
|
|
progressBar.toolTipText = tooltipText
|
|
} else {
|
|
panel.toolTipText = null
|
|
actionButton.toolTipText = null
|
|
progressBar.toolTipText = null
|
|
}
|
|
|
|
panel.add(infoPanel, BorderLayout.CENTER)
|
|
panel.add(controlsPanel, BorderLayout.EAST)
|
|
|
|
if (!isKondoKit(pluginStatus.name) && pluginStatus.isInstalled) {
|
|
val popupMenu = createPluginContextMenu(null, null, pluginStatus.name, !pluginStatus.needsUpdate)
|
|
panel.attachPopupMenu(popupMenu)
|
|
}
|
|
|
|
return panel
|
|
}
|
|
|
|
private fun createPluginWidgetPanel(): WidgetPanel {
|
|
return WidgetPanel(
|
|
widgetWidth = ViewConstants.PLUGIN_LIST_ITEM_SIZE.width,
|
|
widgetHeight = ViewConstants.PLUGIN_LIST_ITEM_SIZE.height,
|
|
addDefaultPadding = false,
|
|
paddingTop = 10,
|
|
paddingLeft = 10,
|
|
paddingBottom = 10,
|
|
paddingRight = 10
|
|
)
|
|
}
|
|
|
|
private fun buildInstallableTooltip(pluginStatus: PluginInfoWithStatus): String {
|
|
val lines = mutableListOf<String>()
|
|
pluginStatus.remoteVersion?.takeIf { it.isNotBlank() }?.let {
|
|
lines += "<b>Version:</b> ${escapeHtml(it)}"
|
|
}
|
|
pluginStatus.author?.takeIf { it.isNotBlank() }?.let {
|
|
lines += "<b>Author:</b> ${escapeHtml(it)}"
|
|
}
|
|
pluginStatus.description?.takeIf { it.isNotBlank() }?.let {
|
|
lines += escapeHtml(it).replace("\n", "<br>")
|
|
}
|
|
|
|
if (lines.isEmpty()) {
|
|
lines += "Click Download to install this plugin."
|
|
}
|
|
|
|
return "<html>${lines.joinToString("<br>")}</html>"
|
|
}
|
|
|
|
private fun escapeHtml(value: String): String {
|
|
return value
|
|
.replace("&", "&")
|
|
.replace("<", "<")
|
|
.replace(">", ">")
|
|
}
|
|
|
|
|
|
|
|
private fun createToggleSwitch(plugin: Plugin, pluginInfo: PluginInfo): ToggleSwitch {
|
|
val toggleSwitch = ToggleSwitch()
|
|
|
|
// Set initial state
|
|
toggleSwitch.setActivated(reflectiveEditorPlugin.isPluginEnabled(plugin, pluginInfo))
|
|
|
|
// Add toggle listener
|
|
toggleSwitch.onToggleListener = { activated ->
|
|
reflectiveEditorPlugin.togglePlugin(plugin, pluginInfo, toggleSwitch, activated, mainPanel ?: JPanel())
|
|
}
|
|
|
|
return toggleSwitch
|
|
}
|
|
|
|
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 showPluginDetails(pluginInfo: PluginInfo, plugin: Plugin) {
|
|
currentPluginInfo = pluginInfo
|
|
currentPlugin = plugin
|
|
|
|
searchFieldWrapper?.isVisible = false
|
|
|
|
// 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
|
|
val packageName = plugin.javaClass.`package`.name
|
|
|
|
val headerPanel = ViewHeader("$packageName 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)
|
|
}
|
|
}
|
|
searchFieldWrapper?.isVisible = true
|
|
cardLayout.show(mainPanel, PLUGIN_LIST_VIEW)
|
|
}
|
|
|
|
|
|
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)
|
|
|
|
val infoPanel = JPanel(BorderLayout())
|
|
infoPanel.background = WIDGET_COLOR
|
|
infoPanel.border = BorderFactory.createEmptyBorder(10, 10, 10, 10)
|
|
|
|
|
|
val infoText = """
|
|
<html>
|
|
Version: ${(pluginInfo.version)}<br>
|
|
Author: ${(pluginInfo.author ?: "").trim('\'')}<br>
|
|
Description: ${(pluginInfo.description ?: "").trim('\'')}
|
|
</html>
|
|
""".trimIndent()
|
|
|
|
|
|
val infoFont = ViewConstants.FONT_RUNESCAPE_SMALL_14
|
|
val infoLabel = LabelComponent(infoText, isHtml = true).apply {
|
|
font = infoFont
|
|
}
|
|
infoPanel.add(infoLabel, BorderLayout.CENTER)
|
|
|
|
val fm = infoLabel.getFontMetrics(infoFont)
|
|
|
|
val avgCharWidth = fm.stringWidth("x")
|
|
val availableWidth = 200
|
|
val charsPerLine = availableWidth / avgCharWidth
|
|
|
|
val textContent = infoText.replace("<[^>]*>".toRegex(), "")
|
|
val lines = textContent.split('\n').filter { it.isNotBlank() }
|
|
|
|
var totalLines = 0
|
|
for (line in lines) {
|
|
val lineLength = line.length
|
|
totalLines += kotlin.math.ceil(lineLength.toDouble() / charsPerLine).toInt()
|
|
}
|
|
|
|
val lineHeight = fm.height
|
|
val estimatedHeight = (totalLines * lineHeight) + 20
|
|
|
|
val finalHeight = kotlin.math.max(60, kotlin.math.min(estimatedHeight, 150))
|
|
infoPanel.maximumSize = Dimension(Int.MAX_VALUE, finalHeight)
|
|
infoPanel.preferredSize = Dimension(ViewConstants.DEFAULT_WIDGET_SIZE.width, finalHeight)
|
|
|
|
|
|
|
|
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
|
|
}
|
|
|
|
// Check if we need to reload plugins
|
|
if (reflectiveEditorPlugin.shouldReloadPlugins()) {
|
|
PluginRepository.reloadPlugins()
|
|
reflectiveEditorPlugin.setReloadPlugins(false)
|
|
}
|
|
|
|
// Update plugin statuses to reflect current loaded plugins
|
|
reflectiveEditorPlugin.updatePluginStatuses()
|
|
|
|
val contentPanel = pluginListContentPanel
|
|
if (contentPanel == null) {
|
|
// Fallback path: rebuild the list view if it was not initialized yet
|
|
val existingListView = mainPanel.components.find { it.name == PLUGIN_LIST_VIEW }
|
|
val pluginListView = createPluginListView()
|
|
pluginListView.name = PLUGIN_LIST_VIEW
|
|
if (existingListView != null) {
|
|
mainPanel.remove(existingListView)
|
|
}
|
|
mainPanel.add(pluginListView, PLUGIN_LIST_VIEW)
|
|
} else {
|
|
contentPanel.isVisible = false
|
|
try {
|
|
populatePluginListContent()
|
|
} finally {
|
|
contentPanel.isVisible = true
|
|
}
|
|
}
|
|
|
|
searchFieldWrapper?.isVisible = true
|
|
|
|
cardLayout.show(mainPanel, PLUGIN_LIST_VIEW)
|
|
|
|
pluginListScrollablePanel?.let { scrollPanel ->
|
|
SwingUtilities.invokeLater {
|
|
resetScrollablePanel(scrollPanel)
|
|
}
|
|
}
|
|
|
|
mainPanel.revalidate()
|
|
mainPanel.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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Method to start downloading a plugin
|
|
private fun startPluginDownload(pluginStatus: PluginInfoWithStatus) {
|
|
reflectiveEditorPlugin.startPluginDownload(pluginStatus, mainPanel ?: JPanel())
|
|
}
|
|
|
|
// Helper method to create context menu for plugins
|
|
private fun createPluginContextMenu(plugin: Plugin?, pluginInfo: PluginInfo?, pluginName: String, isDisabled: Boolean): JPopupMenu {
|
|
val popupMenu = PopupMenuComponent()
|
|
|
|
// Add "Delete" menu item
|
|
popupMenu.addMenuItem("Delete Plugin") {
|
|
deletePlugin(pluginName, isDisabled)
|
|
}
|
|
|
|
return popupMenu
|
|
}
|
|
|
|
// Helper method to delete a plugin
|
|
private fun deletePlugin(pluginName: String, isDisabled: Boolean) {
|
|
reflectiveEditorPlugin.deletePlugin(pluginName, isDisabled, mainPanel ?: JPanel())
|
|
}
|
|
|
|
// Helper method to recursively delete a directory
|
|
private fun deleteRecursively(file: File): Boolean {
|
|
if (file.isDirectory) {
|
|
file.listFiles()?.forEach { child ->
|
|
if (!deleteRecursively(child)) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
return file.delete()
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
}
|
|
}
|