3afada9bc9bd5d011292414e1fb09824c0bdb6ff
[idea/community.git] / platform / execution-impl / src / com / intellij / execution / runToolbar / RunToolbarExtraSlotPane.kt
1 // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2 package com.intellij.execution.runToolbar
3
4 import com.intellij.execution.runToolbar.data.RWActiveListener
5 import com.intellij.execution.runToolbar.data.RWSlotListener
6 import com.intellij.icons.AllIcons
7 import com.intellij.ide.DataManager
8 import com.intellij.lang.LangBundle
9 import com.intellij.openapi.actionSystem.*
10 import com.intellij.openapi.actionSystem.impl.segmentedActionBar.SegmentedActionToolbarComponent
11 import com.intellij.openapi.options.ShowSettingsUtil
12 import com.intellij.openapi.project.Project
13 import com.intellij.ui.ComponentUtil
14 import com.intellij.ui.Gray
15 import com.intellij.ui.JBColor
16 import com.intellij.ui.components.labels.LinkLabel
17 import com.intellij.ui.components.panels.VerticalLayout
18 import com.intellij.util.ui.JBUI
19 import net.miginfocom.swing.MigLayout
20 import java.awt.Dimension
21 import java.awt.Font
22 import java.awt.event.MouseAdapter
23 import java.awt.event.MouseEvent
24 import javax.swing.JComponent
25 import javax.swing.JLabel
26 import javax.swing.JPanel
27 import javax.swing.SwingUtilities
28
29 class RunToolbarExtraSlotPane(val project: Project, val baseWidth: () -> Int?): RWActiveListener {
30   private val manager = RunToolbarSlotManager.getInstance(project)
31   val slotPane = JPanel(VerticalLayout(JBUI.scale(3))).apply {
32     isOpaque = false
33     border = JBUI.Borders.empty()
34   }
35
36   private val components = mutableListOf<SlotComponent>()
37
38   private val managerListener = object : RWSlotListener {
39     override fun slotAdded() {
40       addSingleSlot()
41     }
42
43     override fun slotRemoved(index: Int) {
44       if (index >= 0 && index < components.size) {
45         removeSingleComponent(components[index])
46       }
47       else {
48         rebuild()
49       }
50     }
51
52     override fun rebuildPopup() {
53       rebuild()
54     }
55   }
56
57   init {
58     manager.activeListener.addListener(this)
59   }
60
61   fun clear() {
62     manager.activeListener.removeListener(this)
63   }
64
65   override fun enabled() {
66     manager.slotListeners.addListener(managerListener)
67   }
68
69   override fun disabled() {
70     manager.slotListeners.removeListener(managerListener)
71   }
72
73   private var added = false
74   val newSlotDetails = object : JLabel(LangBundle.message("run.toolbar.add.slot.details")){
75     override fun getFont(): Font {
76       return JBUI.Fonts.toolbarFont()
77     }
78   }.apply {
79     border = JBUI.Borders.empty()
80     isEnabled = false
81   }
82
83   private val pane = object : JPanel(VerticalLayout(JBUI.scale(2))) {
84     override fun addNotify() {
85       build()
86       added = true
87       super.addNotify()
88       SwingUtilities.invokeLater {
89         pack()
90       }
91     }
92
93     override fun removeNotify() {
94       added = false
95       super.removeNotify()
96     }
97
98 /*    override fun getPreferredSize(): Dimension {
99       val d = super.getPreferredSize()
100       return baseWidth()?.let {
101         val w = it + insets.left + insets.right
102         println("getPreferredSize: $it ${w}")
103         return Dimension(w, d.height)
104       } ?: d
105
106     }*/
107   }.apply {
108     border = JBUI.Borders.empty(3, 0, 0, 3)
109     background = JBColor.namedColor("Panel.background", Gray.xCD)
110
111     add(slotPane)
112
113     val bottomPane = object : JPanel(MigLayout("fillx, ins 0, novisualpadding, gap 0, hidemode 2, wrap 3", "[][]push[]")) {
114       override fun getPreferredSize(): Dimension {
115         val preferredSize = super.getPreferredSize()
116         baseWidth()?.let {
117           preferredSize.width = it
118         }
119         return preferredSize
120       }
121     }.apply {
122       isOpaque = false
123       border = JBUI.Borders.empty(5, 0, 7, 5)
124
125       add(JLabel(AllIcons.Toolbar.AddSlot).apply {
126         val d = preferredSize
127         d.width = FixWidthSegmentedActionToolbarComponent.ARROW_WIDTH
128         preferredSize = d
129
130         addMouseListener(object : MouseAdapter() {
131           override fun mouseClicked(e: MouseEvent) {
132             manager.addAndSaveSlot()
133           }
134         })
135       })
136       add(LinkLabel<Unit>(LangBundle.message("run.toolbar.add.slot"), null).apply {
137         setListener(
138           {_, _ ->
139             manager.addAndSaveSlot()
140           }, null)
141       })
142
143       add(JLabel(AllIcons.General.GearPlain).apply {
144         addMouseListener(object : MouseAdapter() {
145           override fun mouseClicked(e: MouseEvent) {
146             ShowSettingsUtil.getInstance().showSettingsDialog(project, RunToolbarSettingsConfigurable::class.java)
147           }
148         })
149
150         isVisible = RunToolbarProcess.isSettingsAvailable
151       })
152
153       add(newSlotDetails, "skip")
154     }
155     add(bottomPane)
156   }
157
158   internal fun getView(): JComponent = pane
159
160   private fun rebuild() {
161     build()
162     pack()
163   }
164
165   private fun build() {
166     val count = manager.slotsCount()
167     slotPane.removeAll()
168     components.clear()
169     repeat(count) { addNewSlot() }
170   }
171
172   private fun addSingleSlot() {
173     addNewSlot()
174     pack()
175   }
176
177   private fun removeSingleComponent(component: SlotComponent) {
178     removeComponent(component)
179     pack()
180   }
181
182   private fun addNewSlot() {
183     val slot = createComponent()
184     slot.minus.addMouseListener(object : MouseAdapter() {
185       override fun mouseClicked(e: MouseEvent) {
186         getData(slot)?.let {
187           manager.removeSlot(it.id)
188         }
189       }
190     })
191
192     components.add(slot)
193     slotPane.add(slot.view)
194
195   }
196
197   fun pack() {
198     newSlotDetails.isVisible = manager.slotsCount() == 0
199
200     slotPane.revalidate()
201     pane.revalidate()
202
203     slotPane.repaint()
204     pane.repaint()
205     ComponentUtil.getWindow(pane)?.let {
206       if (it.isShowing) {
207         it.pack()
208       }
209     }
210   }
211
212   private fun removeComponent(component: SlotComponent) {
213     slotPane.remove(component.view)
214     components.remove(component)
215   }
216
217   private fun getData(component: SlotComponent): SlotDate? {
218     val index = components.indexOf(component)
219     if(index < 0) return null
220     return manager.getData(index)
221   }
222
223   private fun createComponent(): SlotComponent {
224     val group = DefaultActionGroup()
225     val bar = FixWidthSegmentedActionToolbarComponent(ActionPlaces.MAIN_TOOLBAR, group)
226
227     val component = SlotComponent(bar, JLabel(AllIcons.Toolbar.RemoveSlot).apply {
228       val d = preferredSize
229       d.width = FixWidthSegmentedActionToolbarComponent.ARROW_WIDTH
230       preferredSize = d
231      })
232
233     bar.targetComponent = bar
234     DataManager.registerDataProvider(bar, DataProvider {
235       key ->
236       if(RunToolbarData.RUN_TOOLBAR_DATA_KEY.`is`(key)) {
237         getData(component)
238       }
239       else
240         null
241     })
242
243     val runToolbarActionsGroup = ActionManager.getInstance().getAction(
244       "RunToolbarActionsGroup") as DefaultActionGroup
245
246     for (action in runToolbarActionsGroup.getChildren(null)) {
247       if (action is ActionGroup && !action.isPopup) {
248         group.addAll(*action.getChildren(null))
249       }
250       else {
251         group.addAction(action)
252       }
253     }
254
255     return component
256   }
257
258   internal data class SlotComponent(val bar: SegmentedActionToolbarComponent, val minus: JComponent) {
259     val view = JPanel(MigLayout("ins 0, gap 0, novisualpadding")).apply {
260       add(minus)
261       add(bar)
262     }
263   }
264 }