@@ -5,8 +5,9 @@ import com.intellij.openapi.application.ApplicationManager
55import com.intellij.openapi.diagnostic.Logger
66import com.intellij.openapi.project.Project
77import com.intellij.openapi.rd.util.launchBackground
8- import com.intellij.ui.Gray
8+ import com.intellij.openapi. ui.ComboBox
99import com.intellij.ui.JBColor
10+ import com.intellij.ui.SimpleListCellRenderer
1011import com.intellij.util.Alarm
1112import com.intellij.util.AlarmFactory
1213import com.intellij.util.messages.MessageBusConnection
@@ -20,15 +21,21 @@ import org.digma.intellij.plugin.log.Log
2021import org.digma.intellij.plugin.model.rest.usage.UsageStatusResult
2122import org.digma.intellij.plugin.ui.model.environment.EnvironmentsSupplier
2223import org.digma.intellij.plugin.ui.panels.DigmaResettablePanel
23- import java.awt.*
24+ import java.awt.Cursor
25+ import java.awt.Dimension
26+ import java.awt.event.ComponentAdapter
27+ import java.awt.event.ComponentEvent
2428import java.awt.event.MouseAdapter
2529import java.awt.event.MouseEvent
2630import java.util.*
2731import java.util.concurrent.atomic.AtomicBoolean
2832import java.util.concurrent.locks.ReentrantLock
29- import javax.swing.*
33+ import javax.swing.BoxLayout
34+ import javax.swing.Icon
35+ import javax.swing.JList
3036import javax.swing.event.PopupMenuEvent
3137import javax.swing.event.PopupMenuListener
38+ import javax.swing.plaf.basic.BasicComboPopup
3239
3340class EnvironmentsDropdownPanel (
3441 project : Project ,
@@ -39,17 +46,14 @@ class EnvironmentsDropdownPanel(
3946 private val logger: Logger = Logger .getInstance(EnvironmentsDropdownPanel ::class .java)
4047
4148 private val usageStatusChangeConnection: MessageBusConnection = project.messageBus.connect()
42- private val usageStatusResult: UsageStatusResult
49+ private var usageStatusResult: UsageStatusResult
4350 private val project: Project
4451 private val rebuildPanelLock = ReentrantLock ()
4552 private var selectedItem: String
4653 private val localHostname: String
4754 private val changeEnvAlarm: Alarm
4855 private val popupMenuOpened: AtomicBoolean = AtomicBoolean (false )
49- // Determine the background color of the dark theme editor (example value used)
50- private val darkThemeEditorBackgroundColor = JBColor .namedColor(" Editor.background" , Gray ._50 )
51- // Create a new color object using the background color of the dark theme editor
52- private val menuItemBackgroundColor = JBColor (darkThemeEditorBackgroundColor, darkThemeEditorBackgroundColor)
56+ private val wasNotInitializedYet: AtomicBoolean = AtomicBoolean (true )
5357
5458 init {
5559 usageStatusChangeConnection.subscribe(
@@ -91,16 +95,23 @@ class EnvironmentsDropdownPanel(
9195 }
9296
9397 private fun rebuildInBackground (usageStatus : UsageStatusResult ) {
94- val lifetimeDefinition = LifetimeDefinition ()
95- lifetimeDefinition.lifetime.launchBackground {
96- rebuildPanelLock.lock()
97- Log .log(logger::debug, " Lock acquired for rebuild EnvironmentsDropdownPanel process." )
98- try {
99- rebuild(usageStatus)
100- } finally {
101- rebuildPanelLock.unlock()
102- Log .log(logger::debug, " Lock released for rebuild EnvironmentsDropdownPanel process." )
103- lifetimeDefinition.terminate()
98+ if (! popupMenuOpened.get()) {
99+ if (usageStatus.environmentStatuses.size != usageStatusResult.environmentStatuses.size
100+ || usageStatusResult.codeObjectStatuses.isEmpty() || wasNotInitializedYet.get()) {
101+ usageStatusResult = usageStatus
102+
103+ val lifetimeDefinition = LifetimeDefinition ()
104+ lifetimeDefinition.lifetime.launchBackground {
105+ rebuildPanelLock.lock()
106+ Log .log(logger::debug, " Lock acquired for rebuild EnvironmentsDropdownPanel process." )
107+ try {
108+ rebuild(usageStatus)
109+ } finally {
110+ rebuildPanelLock.unlock()
111+ Log .log(logger::debug, " Lock released for rebuild EnvironmentsDropdownPanel process." )
112+ lifetimeDefinition.terminate()
113+ }
114+ }
104115 }
105116 }
106117 }
@@ -180,74 +191,102 @@ class EnvironmentsDropdownPanel(
180191 environmentsInfo : MutableMap <String , MutableMap <String , Any >>,
181192 hasUsageFunction : (String ) -> Boolean
182193 ) {
183- val dropdownLabel = object : JLabel () {
184- override fun getPreferredSize (): Dimension {
185- val size = super .getPreferredSize()
186- size.width + = 20 // Add some extra space to fit the "Select an item" text
187- return size
188- }
189- }
190- dropdownLabel.icon = getDropDownIcon()
191- dropdownLabel.text = selectedItem
192- dropdownLabel.horizontalTextPosition = SwingConstants .LEFT
193- dropdownLabel.verticalTextPosition = SwingConstants .CENTER
194- val popupMenu = JPopupMenu ()
195- // Add header to the popupMenu
196- val header = createHeader(" Environment" , " Data" )
197- popupMenu.add(header)
194+ val items = mutableListOf<String >()
195+ val icons = mutableListOf<Icon >()
196+ val environmentsInfo = environmentsInfo
198197
199198 for (envInfo in environmentsInfo) {
200199 val buttonData = envInfo.value
201200 val currEnv = envInfo.key
202201 val linkText = buttonData.getValue(" linkText" ).toString()
203- // val isSelectedEnv = buttonData.getValue("isSelectedEnv") as Boolean
204-
205- // val icon: Icon = if (isSelectedEnv) Laf.Icons.Environment.ENVIRONMENT_HAS_USAGE else Laf.Icons.Environment.ENVIRONMENT_HAS_NO_USAGE
206202 val icon: Icon = if (hasUsageFunction(currEnv)) Laf .Icons .Environment .ENVIRONMENT_HAS_USAGE else Laf .Icons .Environment .ENVIRONMENT_HAS_NO_USAGE
203+ items.add(linkText)
204+ icons.add(icon)
205+ }
207206
208- val menuItem = createMenuItem(linkText, icon)
209-
210- menuItem.addActionListener { event ->
211- selectedItem = linkText // Update the selected item
212- dropdownLabel.text = linkText
213-
214- val currentSelected: String = getSelected()
215-
216- if (currentSelected == = event.source) {
217- return @addActionListener
218- }
219-
220- changeEnvAlarm.cancelAllRequests()
221- changeEnvAlarm.addRequest({
222- environmentsSupplier.setCurrent(currEnv)
223- }, 100 )
207+ if (items.size > 0 ) {
208+ // this flag fixes initial load issue
209+ wasNotInitializedYet.set(false )
210+ }
211+ val comboBox = ComboBox (items.toTypedArray())
212+ comboBox.renderer = object : SimpleListCellRenderer <String >() {
213+ override fun customize (list : JList <out String >, value : String , index : Int , selected : Boolean , hasFocus : Boolean ) {
214+ text = value
215+ icon = icons.getOrElse(index) { null }
216+ foreground = if (selected) JBColor .WHITE else JBColor .BLACK
217+ background = if (selected) JBColor .BLUE else JBColor .WHITE
224218 }
225- popupMenu.add(menuItem)
226219 }
227- popupMenu.addPopupMenuListener(object : PopupMenuListener {
220+
221+ comboBox.addPopupMenuListener(object : PopupMenuListener {
228222 override fun popupMenuWillBecomeVisible (e : PopupMenuEvent ? ) {
229- dropdownLabel.icon = Laf .Icons .General .ARROW_UP
230223 popupMenuOpened.set(true )
224+ // Set the width of the ComboBox to the width of the widest item when the popup menu is opened
225+ val popup = comboBox.getUI().getAccessibleChild(comboBox, 0 )
226+ val popupList = (popup as ? BasicComboPopup )?.list
227+ if (popupList != null ) {
228+ val popupWidth = items.maxOfOrNull { comboBox.getFontMetrics(comboBox.font).stringWidth(it) } ? : 0
229+ comboBox.preferredSize = Dimension (popupWidth + 40 , comboBox.preferredSize.height)
230+ }
231231 }
232+
232233 override fun popupMenuWillBecomeInvisible (e : PopupMenuEvent ? ) {
233- dropdownLabel.icon = Laf .Icons .General .ARROW_DOWN
234+ // Reset the width of the ComboBox to the fixed width when the popup menu is closed
235+ comboBox.preferredSize = Dimension (30 , comboBox.preferredSize.height)
234236 popupMenuOpened.set(false )
235237 }
238+
236239 override fun popupMenuCanceled (e : PopupMenuEvent ? ) {
237- // Do nothing
240+ // Reset the width of the ComboBox to the fixed width when the popup menu is canceled
241+ comboBox.preferredSize = Dimension (30 , comboBox.preferredSize.height)
238242 }
239243 })
240- dropdownLabel.addMouseListener(object : MouseAdapter () {
241- override fun mousePressed (e : MouseEvent ) {
242- popupMenu.show(dropdownLabel, 0 , dropdownLabel.height)
244+
245+ comboBox.addActionListener { event ->
246+ selectedItem = comboBox.selectedItem?.toString() ? : " " // Update the selected item
247+
248+ val currentSelected: String = getSelected()
249+
250+ if (currentSelected == = event.source) {
251+ return @addActionListener
252+ }
253+
254+ changeEnvAlarm.cancelAllRequests()
255+ changeEnvAlarm.addRequest({
256+ environmentsSupplier.setCurrent(adjustBackEnvNameIfNeeded(selectedItem))
257+ }, 100 )
258+ }
259+
260+ // Remove the border around the ComboBox
261+ comboBox.background = Laf .Colors .EDITOR_BACKGROUND
262+ comboBox.isOpaque = false
263+ // Set a fixed width for the closed ComboBox
264+ comboBox.preferredSize = Dimension (30 , comboBox.preferredSize.height)
265+ comboBox.selectedItem = getSelected()
266+
267+ this .add(comboBox)
268+
269+ // Add ComponentListener to detect resizing of parent component
270+ addComponentListener(object : ComponentAdapter () {
271+ override fun componentResized (e : ComponentEvent ) {
272+ comboBox.hidePopup()
243273 }
244274 })
245275
246- this .add(dropdownLabel)
276+ // Add MouseListener to detect click outside of ComboBox
277+ addMouseListener(object : MouseAdapter () {
278+ override fun mouseClicked (e : MouseEvent ) {
279+ if (! comboBox.bounds.contains(e.point)) {
280+ comboBox.hidePopup()
281+ }
282+ }
283+ })
247284 }
248285
249- private fun getDropDownIcon (): Icon {
250- return if (popupMenuOpened.get()) Laf .Icons .General .ARROW_UP else Laf .Icons .General .ARROW_DOWN
286+ private fun adjustBackEnvNameIfNeeded (environment : String ): String {
287+ return if (environment.equals(LOCAL_ENV , ignoreCase = true )) {
288+ (localHostname + SUFFIX_OF_LOCAL ).uppercase(Locale .getDefault())
289+ } else environment
251290 }
252291
253292 private fun removeExistingComponentsIfPresent () {
@@ -283,77 +322,4 @@ class EnvironmentsDropdownPanel(
283322 }
284323 return txtValue
285324 }
286-
287- private fun createMenuItem (text : String , icon : Icon ): JMenuItem {
288- val menuItem = JMenuItem ()
289- menuItem.layout = BorderLayout ()
290-
291- // Create a panel for the text and icon labels with a flexible horizontal gap
292- val panel = JPanel ()
293- panel.layout = BoxLayout (panel, BoxLayout .LINE_AXIS )
294-
295- val textLabel = JLabel (text)
296- textLabel.horizontalAlignment = SwingConstants .LEFT
297- textLabel.font = Font (" Helvetica" , Font .PLAIN , 12 )
298- textLabel.border = JBUI .Borders .empty(0 , 5 )
299-
300- val iconLabel = JLabel (icon)
301- iconLabel.horizontalAlignment = SwingConstants .RIGHT
302- iconLabel.border = JBUI .Borders .empty(0 , 5 )
303-
304- panel.add(textLabel)
305- panel.add(Box .createHorizontalGlue())
306- panel.add(iconLabel)
307-
308- menuItem.add(panel, BorderLayout .CENTER )
309-
310- // Set the background color of the menu item to the dark theme editor background color
311- menuItem.background = menuItemBackgroundColor
312- panel.background = menuItemBackgroundColor
313-
314- // Add a mouse listener to highlight the menu item on mouse over
315- menuItem.addMouseListener(object : MouseAdapter () {
316- override fun mouseEntered (e : MouseEvent ) {
317- menuItem.cursor = Cursor .getPredefinedCursor(Cursor .HAND_CURSOR )
318- menuItem.background = Color .DARK_GRAY
319- panel.background = Color .DARK_GRAY
320- }
321-
322- override fun mouseExited (e : MouseEvent ) {
323- menuItem.background = menuItemBackgroundColor
324- panel.background = menuItemBackgroundColor
325- }
326- })
327- // Remove the border around the focused menu item
328- menuItem.border = BorderFactory .createEmptyBorder()
329-
330- return menuItem
331- }
332-
333- private fun createHeader (leftText : String , rightText : String ): JComponent {
334- val headerPanel = JPanel ()
335- headerPanel.layout = BoxLayout (headerPanel, BoxLayout .LINE_AXIS )
336-
337- val leftLabel = JLabel (leftText)
338- leftLabel.font = Font (" SF Pro Text" , Font .TRUETYPE_FONT , 10 )
339- leftLabel.horizontalAlignment = SwingConstants .LEFT
340- leftLabel.border = JBUI .Borders .empty(0 , 5 )
341- leftLabel.foreground = Laf .Colors .DROP_DOWN_HEADER_TEXT_COLOR
342-
343- val minGapSize = maxOf(10 , SwingUtilities .computeStringWidth(leftLabel.getFontMetrics(leftLabel.font), leftText))
344-
345- headerPanel.add(leftLabel)
346-
347- headerPanel.add(Box .createHorizontalStrut(minGapSize)) // Add flexible horizontal gap
348-
349- val rightLabel = JLabel (rightText)
350- rightLabel.font = Font (" SF Pro Text" , Font .TRUETYPE_FONT , 10 )
351- rightLabel.horizontalAlignment = SwingConstants .RIGHT
352- rightLabel.border = JBUI .Borders .empty(0 , 5 )
353- rightLabel.foreground = Laf .Colors .DROP_DOWN_HEADER_TEXT_COLOR
354-
355- headerPanel.add(rightLabel)
356- headerPanel.background = menuItemBackgroundColor
357- return headerPanel
358- }
359325}
0 commit comments