Fix tooltip labels

This commit is contained in:
downthecrop 2025-11-17 11:28:23 -08:00
parent 5bb81c7bd3
commit 5240963743
7 changed files with 135 additions and 156 deletions

View file

@ -4,105 +4,85 @@ import KondoKit.pluginmanager.GitLabPlugin
import KondoKit.pluginmanager.PluginProperties
import KondoKit.util.HttpFetcher
import KondoKit.pluginmanager.GitLabConfig
import KondoKit.util.HttpStatusException
import KondoKit.util.JsonParser
import com.google.gson.JsonObject
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.util.concurrent.*
import javax.swing.SwingUtilities
object GitLabPluginFetcher {
private const val TAG = "GitLabPluginFetcher"
// Thread pool for concurrent plugin property fetching
private val executorService = Executors.newFixedThreadPool(5)
// Debug logging function
private fun debugLog(message: String) {
if (GitLabConfig.DEBUG) {
println("[GitLabPluginFetcher] $message")
}
}
private fun debugLog(message: String) = PluginLogger.debug(TAG, message)
// Function to fetch plugins from GitLab
fun fetchGitLabPlugins(onComplete: (List<GitLabPlugin>) -> Unit) {
Thread {
val plugins = mutableListOf<GitLabPlugin>()
try {
debugLog("Starting to fetch GitLab plugins...")
val plugins = mutableListOf<GitLabPlugin>()
val apiUrl = GitLabConfig.getRepositoryTreeUrl()
debugLog("API URL: $apiUrl")
val connection = HttpFetcher.openGetConnection(apiUrl)
// Read response
val responseCode = connection.responseCode
debugLog("Response code: $responseCode")
if (responseCode == HttpURLConnection.HTTP_OK) {
val reader = BufferedReader(InputStreamReader(connection.inputStream))
val response = reader.use { it.readText() }
debugLog("Response length: ${response.length} characters")
debugLog("Response preview: ${response.take(200)}...")
// Parse JSON response
val treeItems = JsonParser.fromJson(response, Array<JsonObject>::class.java)
debugLog("Parsed ${treeItems.size} items from JSON")
// Filter for directories (trees) with mode "040000"
val pluginDirectories = mutableListOf<Pair<String, String>>()
for (jsonObject in treeItems) {
if (jsonObject["type"].asString == "tree" && jsonObject["mode"].asString == "040000") {
val folderId = jsonObject["id"].asString
val folderPath = jsonObject["path"].asString
pluginDirectories.add(folderId to folderPath)
debugLog("Found directory: $folderPath (ID: $folderId)")
}
val response = HttpFetcher.fetchString(apiUrl)
debugLog("Response length: ${response.length} characters")
debugLog("Response preview: ${response.take(200)}...")
// Parse JSON response
val treeItems = JsonParser.fromJson(response, Array<JsonObject>::class.java)
debugLog("Parsed ${treeItems.size} items from JSON")
// Filter for directories (trees) with mode "040000"
val pluginDirectories = mutableListOf<Pair<String, String>>()
for (jsonObject in treeItems) {
if (jsonObject["type"].asString == "tree" && jsonObject["mode"].asString == "040000") {
val folderId = jsonObject["id"].asString
val folderPath = jsonObject["path"].asString
pluginDirectories.add(folderId to folderPath)
debugLog("Found directory: $folderPath (ID: $folderId)")
}
debugLog("Found ${pluginDirectories.size} plugin directories")
// Fetch plugin properties in parallel
val pluginFutures = mutableListOf<Future<GitLabPlugin>>()
for ((folderId, folderPath) in pluginDirectories) {
val future = executorService.submit(Callable<GitLabPlugin> {
try {
val pluginProperties = fetchPluginProperties(folderPath)
debugLog("Successfully fetched properties for: $folderPath")
GitLabPlugin(folderId, folderPath, pluginProperties, null)
} catch (e: Exception) {
debugLog("Error fetching plugin.properties for $folderPath: ${e.message}")
GitLabPlugin(folderId, folderPath, null, "Error fetching plugin.properties")
}
})
pluginFutures.add(future)
}
// Collect results
for (future in pluginFutures) {
}
debugLog("Found ${pluginDirectories.size} plugin directories")
// Fetch plugin properties in parallel
val pluginFutures = mutableListOf<Future<GitLabPlugin>>()
for ((folderId, folderPath) in pluginDirectories) {
val future = executorService.submit(Callable<GitLabPlugin> {
try {
plugins.add(future.get())
val pluginProperties = fetchPluginProperties(folderPath)
debugLog("Successfully fetched properties for: $folderPath")
GitLabPlugin(folderId, folderPath, pluginProperties, null)
} catch (e: Exception) {
debugLog("Error getting plugin result: ${e.message}")
debugLog("Error fetching plugin.properties for $folderPath: ${e.message}")
GitLabPlugin(folderId, folderPath, null, "Error fetching plugin.properties")
}
})
pluginFutures.add(future)
}
// Collect results
for (future in pluginFutures) {
try {
plugins.add(future.get())
} catch (e: Exception) {
debugLog("Error getting plugin result: ${e.message}")
}
} else {
debugLog("HTTP error: $responseCode")
val errorReader = BufferedReader(InputStreamReader(connection.errorStream))
val errorResponse = errorReader.use { it.readText() }
debugLog("Error response: $errorResponse")
}
debugLog("Completed fetching plugins. Total plugins: ${plugins.size}")
// Update on UI thread
SwingUtilities.invokeLater {
onComplete(plugins)
}
} catch (e: HttpStatusException) {
debugLog("HTTP error ${e.statusCode} fetching plugin tree: ${e.body.take(200)}")
} catch (e: Exception) {
debugLog("Exception occurred: ${e.message}")
e.printStackTrace()
SwingUtilities.invokeLater {
onComplete(emptyList())
}
}
debugLog("Completed fetching plugins. Total plugins: ${plugins.size}")
// Update on UI thread
SwingUtilities.invokeLater {
onComplete(plugins)
}
}.start()
}
@ -113,25 +93,16 @@ object GitLabPluginFetcher {
val pluginUrl = GitLabConfig.getRawFileUrl(pluginFilePath)
debugLog("Fetching plugin.properties from: $pluginUrl")
val connection = HttpFetcher.openGetConnection(pluginUrl)
// Read response
val responseCode = connection.responseCode
debugLog("plugin.properties response code: $responseCode")
if (responseCode == HttpURLConnection.HTTP_OK) {
val reader = BufferedReader(InputStreamReader(connection.inputStream))
val rawContent = reader.use { it.readText() }
try {
val rawContent = HttpFetcher.fetchString(pluginUrl)
debugLog("plugin.properties content length: ${rawContent.length} characters")
debugLog("plugin.properties content preview: ${rawContent.take(200)}...")
return parseProperties(rawContent)
} else {
debugLog("Failed to fetch plugin.properties. Response code: $responseCode")
val errorReader = BufferedReader(InputStreamReader(connection.errorStream))
val errorResponse = errorReader.use { it.readText() }
debugLog("Error response for plugin.properties: $errorResponse")
throw Exception("Plugin file not found for folder: $folderPath")
} catch (e: HttpStatusException) {
debugLog("Failed to fetch plugin.properties. Response code: ${e.statusCode}")
debugLog("Error response for plugin.properties: ${e.body.take(200)}")
throw Exception("Plugin file not found for folder: $folderPath", e)
}
}

View file

@ -18,6 +18,7 @@ import javax.swing.SwingUtilities
* Manages downloading and installing plugins from GitLab with concurrent support
*/
object PluginDownloadManager {
private const val TAG = "PluginDownloadManager"
private const val MAX_CONCURRENT_DOWNLOADS = 3
// Thread pool for concurrent downloads
@ -30,12 +31,8 @@ object PluginDownloadManager {
}
// Debug logging function
private fun debugLog(message: String) {
if (GitLabConfig.DEBUG) {
println("[PluginDownloadManager] $message")
}
}
private fun debugLog(message: String) = PluginLogger.debug(TAG, message)
// Get download URL for debugging/logging purposes
fun getDownloadUrlForLogging(plugin: GitLabPlugin): String {
return GitLabConfig.getArchiveUrl(plugin.path)

View file

@ -0,0 +1,13 @@
package KondoKit.pluginmanager
/**
* Shared lightweight logger so pluginmanager classes don't each redefine
* their own debug helper tied to GitLabConfig.DEBUG.
*/
object PluginLogger {
fun debug(tag: String, message: String) {
if (GitLabConfig.DEBUG) {
println("[$tag] $message")
}
}
}

View file

@ -435,50 +435,50 @@ class SettingsPanel(private val plugin: Plugin) : JPanel() {
companion object {
var customToolTipWindow: JWindow? = null
private var customToolTipLabel: JLabel? = 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()
val bgColor = Helpers.colorToHex(KondoKit.plugin.Companion.TOOLTIP_BACKGROUND)
val textColor = Helpers.colorToHex(KondoKit.plugin.Companion.secondaryColor)
// Constrain width via HTML so Swing's renderer wraps the text naturally.
val safeText = text
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
val html = """
<html>
<body style="margin:0; padding:6px; width:${maxWidth}px; color:$textColor; background-color:$bgColor; word-wrap:break-word;">
$safeText
</body>
</html>
""".trimIndent()
val (window, label) = if (customToolTipWindow == null || customToolTipLabel == null) {
val lbl = JLabel().apply {
border = BorderFactory.createLineBorder(Color.BLACK)
isOpaque = true
background = KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
foreground = Color.WHITE
font = tooltipFont
horizontalAlignment = SwingConstants.LEFT
verticalAlignment = SwingConstants.TOP
}
val win = JWindow().apply { contentPane.add(lbl) }
customToolTipWindow = win
customToolTipLabel = lbl
win to lbl
} 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()
customToolTipWindow!! to customToolTipLabel!!
}
label.text = html
label.font = tooltipFont
label.preferredSize = null // let HTML + pack decide size from width constraint
window.pack()
// Position the tooltip near the component
val locationOnScreen = component.locationOnScreen
customToolTipWindow!!.setLocation(locationOnScreen.x, locationOnScreen.y + 15)

View file

@ -18,15 +18,23 @@ object GEPriceService {
val grand_exchange_price: String?
)
private fun <T> mapToPriceMap(
items: Array<T>,
idSelector: (T) -> String?,
priceSelector: (T) -> String?
): Map<String, String> {
return items.mapNotNull { item ->
val id = idSelector(item)
val price = priceSelector(item)
if (id != null && price != null) id to price else null
}.toMap()
}
fun loadRemotePrices(): Map<String, String> {
return try {
val payload = HttpFetcher.fetchString(REMOTE_GE_URL)
val items = JsonParser.fromJson<Array<RemoteGEItem>>(payload)
items.mapNotNull { item ->
val id = item.item_id
val value = item.value
if (id != null && value != null) id to value else null
}.toMap()
mapToPriceMap(items, { it.item_id }, { it.value })
} catch (e: Exception) {
emptyMap()
}
@ -36,11 +44,7 @@ object GEPriceService {
return try {
val json = jsonProvider() ?: return emptyMap()
val items = JsonParser.fromJson<Array<LocalGEItem>>(json)
items.mapNotNull { item ->
val id = item.id
val price = item.grand_exchange_price
if (id != null && price != null) id to price else null
}.toMap()
mapToPriceMap(items, { it.id }, { it.grand_exchange_price })
} catch (e: Exception) {
emptyMap()
}

View file

@ -15,6 +15,7 @@ import KondoKit.util.setFixedSize
import KondoKit.views.XPTrackerView.wrappedWidget
import KondoKit.ui.components.PopupMenuComponent
import KondoKit.ui.components.ProgressBar
import KondoKit.ui.components.LabelComponent
import KondoKit.ui.components.WidgetPanel
import KondoKit.plugin.Companion.TITLE_BAR_COLOR
import KondoKit.plugin.Companion.TOOLTIP_BACKGROUND
@ -171,12 +172,8 @@ object LootTrackerView : View, OnPostClientTickCallback, OnKillingBlowNPCCallbac
}
}
private fun createLabel(text: String): JLabel {
return JLabel(text).apply {
font = ViewConstants.FONT_RUNESCAPE_SMALL_16
horizontalAlignment = JLabel.LEFT
}
}
private fun createLabel(text: String): LabelComponent =
LabelComponent(text, alignment = JLabel.LEFT)
private fun addItemToLootPanel(lootTrackerView: JPanel, drop: Item, npcName: String) {
findLootItemsPanel(lootTrackerView, npcName)?.let { lootPanel ->

View file

@ -16,6 +16,7 @@ import KondoKit.util.attachPopupMenu
import KondoKit.util.XPTable
import KondoKit.ui.components.PopupMenuComponent
import KondoKit.ui.components.ProgressBar
import KondoKit.ui.components.LabelComponent
import KondoKit.ui.components.WidgetPanel
import KondoKit.ui.components.XPWidget
import KondoKit.plugin.Companion.IMAGE_SIZE
@ -91,17 +92,13 @@ object XPTrackerView : View, OnUpdateCallback, OnXPUpdateCallback {
}
}
private fun createMetricLabel(title: String, initialValue: String = "0", topPadding: Int = 0): JLabel {
return JLabel(formatHtmlLabelText("$title ", primaryColor, initialValue, secondaryColor)).apply {
horizontalAlignment = JLabel.LEFT
font = widgetFont
private fun createMetricLabel(title: String, initialValue: String = "0", topPadding: Int = 0): LabelComponent =
LabelComponent(alignment = JLabel.LEFT).apply {
updateHtmlText("$title ", primaryColor, initialValue, secondaryColor)
if (topPadding > 0) {
border = BorderFactory.createEmptyBorder(topPadding, 0, 0, 0)
} else {
border = BorderFactory.createEmptyBorder(0, 0, 0, 0)
}
}
}
private fun getSkillIcon(skillId: Int): BufferedImage {
return skillIconCache[skillId] ?: getBufferedImageFromSprite(API.GetSprite(getSpriteId(skillId))).also {