From 55d986297988b66ba14c75e4fd1239c371332062 Mon Sep 17 00:00:00 2001 From: TheBlob42 Date: Sat, 27 Feb 2021 21:24:38 +0100 Subject: [PATCH] Refactor maximum width calculation Refactor the FormatConfig object to use JLabel HTML formatting instead of separate JLabels. This will ensure that the calculated width is exactly the same as in the popup instead of being just an approximation. By refactor this we can also remove a lot of special "calculation functions" that we had implemented before. This way we have no width related code in the FormatConfig object. --- .../idea/whichkey/config/FormatConfig.kt | 100 ++++-------------- .../idea/whichkey/config/PopupConfig.kt | 16 +-- 2 files changed, 31 insertions(+), 85 deletions(-) diff --git a/src/main/kotlin/eu/theblob42/idea/whichkey/config/FormatConfig.kt b/src/main/kotlin/eu/theblob42/idea/whichkey/config/FormatConfig.kt index f739856..c2995a3 100644 --- a/src/main/kotlin/eu/theblob42/idea/whichkey/config/FormatConfig.kt +++ b/src/main/kotlin/eu/theblob42/idea/whichkey/config/FormatConfig.kt @@ -5,7 +5,6 @@ import com.intellij.openapi.editor.colors.TextAttributesKey import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment import eu.theblob42.idea.whichkey.model.Mapping import java.awt.Color -import java.awt.Font import javax.swing.JLabel import javax.swing.KeyStroke @@ -101,77 +100,32 @@ object FormatConfig { } /** - * Format all given mappings with the appropriate HTML tags and font colors - * @param mappings The mappings which should be formatted - * @return A [List] of the [String]s representing the formatted mappings - */ - fun formatMappings(mappings: List>): List { - return mappings - .map { (key, mapping) -> - val formattedKey = String.format( - buildHtmlFormatString(keyStyle, keyColor), - escapeForHtml(key)) - val formattedDivider = String.format( - buildHtmlFormatString("span", defaultForegroundColor), - divider) - val formattedDescription = String.format( - buildHtmlFormatString( - if (mapping.prefix) descPrefixStyle else descCommandStyle, - if (mapping.prefix) descPrefixColor else descCommandColor - ), - escapeForHtml(mapping.description)) - - "$formattedKey$formattedDivider$formattedDescription" - } - } - - /** - * Calculate the string width (number of characters) without any HTML tags or style attributes for the given [entry] + * Format the given mapping with the appropriate HTML tags and font attributes * - * @param entry A [Pair] of the next key to press and the corresponding [Mapping] that should be used for the calculation - * @return The raw string width (number of characters) + * @param entry A [Pair] of the next key to press and the corresponding [Mapping] + * @return An HTML [String] representation of the formatted mapping */ - fun calcRawMappingWidth(entry: Pair): Int { + fun formatMappingEntry(entry: Pair): String { val (key, mapping) = entry - return "${key}$divider${mapping.description}".length - } - - /** - * Calculate the pixel width of the given [entry] after formatting it according to the configured - * font-family, font-size and font-style for each individual part (key, divider & description). - * - * The calculated width is not 100% exact but its approximation is very close and from testing only a few pixels of, - * which is good enough for our use case. - * - * @param entry A [Pair] of the next key to press and the corresponding [Mapping] that should be used for the calculation - * @return The approximate width of the formatted entry in pixels - */ - fun calcFormattedMappingWidth(entry: Pair): Int { - val (key, mapping) = entry - val keyFont = Font(fontFamily, toFontStyle(keyStyle), fontSize) - val dividerFont = Font(fontFamily, Font.PLAIN, fontSize) - val descriptionFont = Font( - fontFamily, - toFontStyle(if (mapping.prefix) descPrefixStyle else descCommandStyle), - fontSize) - - val keyLabel = JLabel(key) - keyLabel.font = keyFont - val keyWidth = keyLabel.preferredSize.width - - val dividerLabel = JLabel(divider) - dividerLabel.font = dividerFont - val dividerWidth = dividerLabel.preferredSize.width - - val descriptionLabel = JLabel(mapping.description) - descriptionLabel.font = descriptionFont - val descriptionWidth = descriptionLabel.preferredSize.width - - return keyWidth + dividerWidth + descriptionWidth + val formattedKey = String.format( + buildHtmlFormatString(keyStyle, keyColor), + escapeForHtml(key)) + val formattedDivider = String.format( + buildHtmlFormatString("span", defaultForegroundColor), + divider) + val formattedDescription = String.format( + buildHtmlFormatString( + if (mapping.prefix) descPrefixStyle else descCommandStyle, + if (mapping.prefix) descPrefixColor else descCommandColor + ), + escapeForHtml(mapping.description)) + + return "$formattedKey$formattedDivider$formattedDescription" } /** * Format the typed keys sequence as paragraph with appropriate HTML tags and font colors + * * @param keyStrokes The key strokes which should be formatted * @return The formatted sequence as HTML paragraph */ @@ -190,21 +144,9 @@ object FormatConfig { // ***** UTILITY FUNCTIONS // ***************************************************************************************************************** - /** - * Convert the given HTML font [tag] into an appropriate [Font] style value - * @param tag The HTML tag to convert (e.g. "i", "b") - * @return The appropriate constant (e.g. [Font.ITALIC], [Font.BOLD]) - */ - private fun toFontStyle(tag: String): Int { - return when (tag) { - "i" -> Font.ITALIC - "b" -> Font.BOLD - else -> Font.PLAIN - } - } - /** * Build an HTML string for the usage with [String.format] to add tags and CSS colors + * * @param tagName The HTML tag to use * @param color The font color * @return The built format string @@ -215,6 +157,7 @@ object FormatConfig { /** * Convert [Color] to hex color code + * * @param color The AWT [Color] object to convert * @return The appropriate hex code for the given [Color] */ @@ -224,6 +167,7 @@ object FormatConfig { /** * Replace any problematic HTML characters with the appropriate escaped ones + * * @param text The text to escape for the usage in HTML * @return The escaped [String] */ diff --git a/src/main/kotlin/eu/theblob42/idea/whichkey/config/PopupConfig.kt b/src/main/kotlin/eu/theblob42/idea/whichkey/config/PopupConfig.kt index 2f24417..eb742cf 100644 --- a/src/main/kotlin/eu/theblob42/idea/whichkey/config/PopupConfig.kt +++ b/src/main/kotlin/eu/theblob42/idea/whichkey/config/PopupConfig.kt @@ -9,6 +9,7 @@ import com.maddyhome.idea.vim.option.OptionsManager import eu.theblob42.idea.whichkey.model.Mapping import kotlinx.coroutines.* import javax.swing.JFrame +import javax.swing.JLabel import javax.swing.KeyStroke import kotlin.math.ceil @@ -52,10 +53,10 @@ object PopupConfig { * it might be erroneous and could change in the future */ val frameWidth = (ideFrame.width * 0.65).toInt() - // check for the longest string without HTML tags or styling (we have manually checked that 'nestedMappings' is not empty) - val maxMapping = nestedMappings.maxByOrNull { FormatConfig.calcRawMappingWidth(it) }!! - // calculate the pixel width of the longest mapping string (with styling) - val maxStringWidth = FormatConfig.calcFormattedMappingWidth(maxMapping) + // check for the longest string as this will most probably be the widest mapping + val maxMapping = nestedMappings.maxByOrNull { (key, mapping) -> key.length + mapping.description.length }!! // (we have manually checked that 'nestedMappings' is not empty) + // calculate the pixel width of the longest mapping string (with HTML formatting & styling) + val maxStringWidth = JLabel("${FormatConfig.formatMappingEntry(maxMapping)}").preferredSize.width val possibleColumns = (frameWidth / maxStringWidth).let { when { // ensure a minimum value of 1 to avoid dividing by zero @@ -69,10 +70,11 @@ object PopupConfig { val columnWidth = frameWidth / possibleColumns val elementsPerColumn = ceil(nestedMappings.size / possibleColumns.toDouble()).toInt() - val windowedMappings = FormatConfig.formatMappings( + val windowedMappings = nestedMappings // TODO implement other sort options - nestedMappings.sortedBy { it.first } - ).windowed(elementsPerColumn, elementsPerColumn, true) + .sortedBy { it.first } + .map(FormatConfig::formatMappingEntry) + .windowed(elementsPerColumn, elementsPerColumn, true) // to properly align the columns within HTML use a table with fixed with cells val mappingsStringBuilder = StringBuilder()