[collab] Use toolbar for filter button
[idea/community.git] / platform / collaboration-tools / src / com / intellij / collaboration / ui / codereview / list / search / ReviewListSearchPanelFactory.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.collaboration.ui.codereview.list.search
3
4 import com.intellij.collaboration.messages.CollaborationToolsBundle
5 import com.intellij.collaboration.ui.codereview.list.search.ChooserPopupUtil.showAndAwaitListSubmission
6 import com.intellij.icons.AllIcons
7 import com.intellij.ide.DataManager
8 import com.intellij.openapi.actionSystem.*
9 import com.intellij.openapi.project.DumbAwareAction
10 import com.intellij.openapi.ui.popup.JBPopupFactory
11 import com.intellij.ui.*
12 import com.intellij.ui.components.GradientViewport
13 import com.intellij.ui.components.JBScrollPane
14 import com.intellij.ui.components.JBThinOverlappingScrollBar
15 import com.intellij.ui.components.panels.HorizontalLayout
16 import com.intellij.util.ui.JBUI
17 import kotlinx.coroutines.CoroutineScope
18 import kotlinx.coroutines.flow.update
19 import kotlinx.coroutines.launch
20 import org.jetbrains.annotations.Nls
21 import java.awt.Adjustable
22 import java.awt.BorderLayout
23 import javax.swing.JComponent
24 import javax.swing.JPanel
25 import javax.swing.ScrollPaneConstants
26
27 abstract class ReviewListSearchPanelFactory<S : ReviewListSearchValue, Q : ReviewListQuickFilter<S>, VM : ReviewListSearchPanelViewModel<S, Q>>(
28   protected val vm: VM
29 ) {
30
31   fun create(viewScope: CoroutineScope): JComponent {
32     val searchField = ReviewListSearchTextFieldFactory(vm.queryState).create(viewScope, chooseFromHistory = { point ->
33       val value = JBPopupFactory.getInstance()
34         .createPopupChooserBuilder(vm.getSearchHistory().reversed())
35         .setRenderer(SimpleListCellRenderer.create { label, value, _ ->
36           label.text = getShortText(value)
37         })
38         .createPopup()
39         .showAndAwaitListSubmission<S>(point)
40       if (value != null) {
41         vm.searchState.update { value }
42       }
43     })
44
45     val filters = createFilters(viewScope)
46
47     val filtersPanel = JPanel(HorizontalLayout(4)).apply {
48       isOpaque = false
49       filters.forEach { add(it, HorizontalLayout.LEFT) }
50     }.let {
51       ScrollPaneFactory.createScrollPane(it, true).apply {
52         viewport = GradientViewport(it, JBUI.insets(0, 10), false)
53
54         verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER
55         horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS
56         horizontalScrollBar = JBThinOverlappingScrollBar(Adjustable.HORIZONTAL)
57
58         ClientProperty.put(this, JBScrollPane.FORCE_HORIZONTAL_SCROLL, true)
59       }
60     }
61
62     val quickFilterButton = QuickFilterButtonFactory().create(viewScope, vm.quickFilters)
63
64     val filterPanel = JPanel(BorderLayout()).apply {
65       border = JBUI.Borders.emptyTop(10)
66       isOpaque = false
67       add(quickFilterButton, BorderLayout.WEST)
68       add(filtersPanel, BorderLayout.CENTER)
69     }
70
71     val searchPanel = JPanel(BorderLayout()).apply {
72       border = JBUI.Borders.compound(IdeBorderFactory.createBorder(SideBorder.BOTTOM), JBUI.Borders.empty(8, 10, 0, 10))
73       add(searchField, BorderLayout.CENTER)
74       add(filterPanel, BorderLayout.SOUTH)
75     }
76
77     return searchPanel
78   }
79
80   protected abstract fun getShortText(searchValue: S): @Nls String
81
82   protected abstract fun createFilters(viewScope: CoroutineScope): List<JComponent>
83
84   protected abstract fun Q.getQuickFilterTitle(): @Nls String
85
86   private inner class QuickFilterButtonFactory {
87
88     fun create(viewScope: CoroutineScope, quickFilters: List<Q>): JComponent {
89       val toolbar = ActionManager.getInstance().createActionToolbar(
90         "Review.FilterToolbar",
91         DefaultActionGroup(FilterPopupMenuAction(quickFilters)),
92         true
93       ).apply {
94         layoutPolicy = ActionToolbar.NOWRAP_LAYOUT_POLICY
95         component.isOpaque = false
96         component.border = null
97         targetComponent = null
98       }
99
100       viewScope.launch {
101         vm.searchState.collect {
102           toolbar.updateActionsImmediately()
103         }
104       }
105       return toolbar.component
106     }
107
108     private fun showQuickFiltersPopup(parentComponent: JComponent, quickFilters: List<Q>) {
109       val quickFiltersActions =
110         quickFilters.map { QuickFilterAction(it.getQuickFilterTitle(), it.filter) } +
111         Separator() +
112         ClearFiltersAction()
113
114
115       JBPopupFactory.getInstance()
116         .createActionGroupPopup(CollaborationToolsBundle.message("review.list.filter.quick.title"), DefaultActionGroup(quickFiltersActions),
117                                 DataManager.getInstance().getDataContext(parentComponent),
118                                 JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
119                                 false)
120         .showUnderneathOf(parentComponent)
121     }
122
123     private inner class FilterPopupMenuAction(private val quickFilters: List<Q>) : AnActionButton() {
124       override fun updateButton(e: AnActionEvent) {
125         e.presentation.icon = FILTER_ICON.getLiveIndicatorIcon(vm.searchState.value.filterCount != 0)
126       }
127
128       override fun actionPerformed(e: AnActionEvent) {
129         showQuickFiltersPopup(e.inputEvent.component as JComponent, quickFilters)
130       }
131     }
132
133     private inner class QuickFilterAction(name: @Nls String, private val search: S)
134       : DumbAwareAction(name), Toggleable {
135       override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
136       override fun update(e: AnActionEvent) = Toggleable.setSelected(e.presentation, vm.searchState.value == search)
137       override fun actionPerformed(e: AnActionEvent) = vm.searchState.update { search }
138     }
139
140     private inner class ClearFiltersAction
141       : DumbAwareAction(CollaborationToolsBundle.message("review.list.filter.quick.clear", vm.searchState.value.filterCount)) {
142
143       override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
144
145       override fun update(e: AnActionEvent) {
146         e.presentation.isEnabledAndVisible = vm.searchState.value.filterCount > 0
147       }
148
149       override fun actionPerformed(e: AnActionEvent) = vm.searchState.update { vm.emptySearch }
150     }
151   }
152
153   companion object {
154     private val FILTER_ICON: BadgeIconSupplier = BadgeIconSupplier(AllIcons.General.Filter)
155   }
156 }