From 5803710c021ebbe3db8f12c4c7a14fc18810a79b Mon Sep 17 00:00:00 2001 From: Aleksandr Krasilnikov Date: Mon, 1 Jul 2019 20:01:51 +0300 Subject: [PATCH] [clonedialog] Introduce new clone dialog and its API New API is designed to provide two abilities for implementators: 1. Extending clone dialog with custom external components (e.g. for some cloud VCS hosting providers, like Github, Bitbucket, GitLab, etc). For each new extensions it is required to implement `VcsCloneDialogExtension` and `VcsCloneDialogExtensionComponent` interfaces. 2. Extending the existing "Repository URL" clone dialog component with UI for clone process initiating for any VCS based on `CheckoutProvider`. It is required to implement `com.intellij.openapi.vcs.CheckoutProvider#buildVcsCloneComponent`. GitOrigin-RevId: 997db50dca810cdf802c0c7bf6aabc3a9c523a6b --- platform/icons/src/vcs/clone.svg | 6 + platform/icons/src/vcs/clone_dark.svg | 6 + .../src/META-INF/VcsExtensionPoints.xml | 2 + .../src/META-INF/VcsExtensions.xml | 5 + .../src/idea/VcsActions.xml | 5 + .../util/resources/misc/registry.properties | 4 + .../util/src/com/intellij/icons/AllIcons.java | 1 + .../openapi/vcs/CheckoutProvider.java | 7 + .../openapi/vcs/ui/VcsCloneComponent.kt | 25 ++++ .../openapi/vcs/ui/VcsCloneComponentStub.kt | 27 ++++ .../ui/cloneDialog/VcsCloneDialogExtension.kt | 32 +++++ .../VcsCloneDialogExtensionComponent.kt | 48 +++++++ .../impl/welcomeScreen/GetFromVcsAction.java | 8 +- .../welcomeScreen/RunVcsCloneDialogAction.kt | 25 ++++ .../RepositoryUrlCloneDialogExtension.kt | 98 ++++++++++++++ .../util/ui/cloneDialog/VcsCloneDialog.kt | 123 ++++++++++++++++++ .../VcsCloneDialogExtensionList.kt | 47 +++++++ .../VcsCloneDialogExtensionListItem.kt | 48 +++++++ 18 files changed, 516 insertions(+), 1 deletion(-) create mode 100644 platform/icons/src/vcs/clone.svg create mode 100644 platform/icons/src/vcs/clone_dark.svg create mode 100644 platform/vcs-api/src/com/intellij/openapi/vcs/ui/VcsCloneComponent.kt create mode 100644 platform/vcs-api/src/com/intellij/openapi/vcs/ui/VcsCloneComponentStub.kt create mode 100644 platform/vcs-api/src/com/intellij/openapi/vcs/ui/cloneDialog/VcsCloneDialogExtension.kt create mode 100644 platform/vcs-api/src/com/intellij/openapi/vcs/ui/cloneDialog/VcsCloneDialogExtensionComponent.kt create mode 100644 platform/vcs-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/RunVcsCloneDialogAction.kt create mode 100644 platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/RepositoryUrlCloneDialogExtension.kt create mode 100644 platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialog.kt create mode 100644 platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialogExtensionList.kt create mode 100644 platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialogExtensionListItem.kt diff --git a/platform/icons/src/vcs/clone.svg b/platform/icons/src/vcs/clone.svg new file mode 100644 index 000000000000..285121cfa7f6 --- /dev/null +++ b/platform/icons/src/vcs/clone.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/platform/icons/src/vcs/clone_dark.svg b/platform/icons/src/vcs/clone_dark.svg new file mode 100644 index 000000000000..592210d05523 --- /dev/null +++ b/platform/icons/src/vcs/clone_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml b/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml index feccdea73f5b..e4b7b61354f2 100644 --- a/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml @@ -105,6 +105,8 @@ + diff --git a/platform/platform-resources/src/META-INF/VcsExtensions.xml b/platform/platform-resources/src/META-INF/VcsExtensions.xml index 3d284e904322..fa94a453efaa 100644 --- a/platform/platform-resources/src/META-INF/VcsExtensions.xml +++ b/platform/platform-resources/src/META-INF/VcsExtensions.xml @@ -140,6 +140,11 @@ + + diff --git a/platform/platform-resources/src/idea/VcsActions.xml b/platform/platform-resources/src/idea/VcsActions.xml index 7ef52030259c..18c827e08899 100644 --- a/platform/platform-resources/src/idea/VcsActions.xml +++ b/platform/platform-resources/src/idea/VcsActions.xml @@ -346,6 +346,11 @@ icon="AllIcons.Welcome.FromVCS"> + + + + + fun getOkButtonText(): String = "Clone" +} diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ui/VcsCloneComponentStub.kt b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/VcsCloneComponentStub.kt new file mode 100644 index 000000000000..1fb0b99c13bc --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/VcsCloneComponentStub.kt @@ -0,0 +1,27 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.vcs.ui + +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.openapi.vcs.CheckoutProvider +import java.awt.FlowLayout +import javax.swing.JComponent +import javax.swing.JLabel +import javax.swing.JPanel + +class VcsCloneComponentStub(private val checkoutProvider: CheckoutProvider, + private val primaryActionText: String = "Clone") : VcsCloneComponent { + override fun getView(): JComponent { + val panel = JPanel(FlowLayout(FlowLayout.LEADING)) + // todo: replace with better help text + // todo: or add additional button closer to vcs combo + panel.add(JLabel("Click \"$primaryActionText\" to continue")) + return panel + } + + override fun doClone(project: Project, listener: CheckoutProvider.Listener) = checkoutProvider.doCheckout(project, listener) + + override fun isOkEnabled() = true + + override fun doValidateAll() = emptyList() +} \ No newline at end of file diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ui/cloneDialog/VcsCloneDialogExtension.kt b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/cloneDialog/VcsCloneDialogExtension.kt new file mode 100644 index 000000000000..617dc0ba4a17 --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/cloneDialog/VcsCloneDialogExtension.kt @@ -0,0 +1,32 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.vcs.ui.cloneDialog + +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import javax.swing.Icon + +/** + * Extension point that provide an ability to add integration with specific cloud repository hosting/service (e.g "GitHub", "BitBucket", etc) + */ +interface VcsCloneDialogExtension { + companion object { + val EP_NAME = + ExtensionPointName.create("com.intellij.openapi.vcs.ui.cloneDialog.VcsCloneDialogExtension") + } + + /** + * Visible name of extension (e.g. "Repository URL", "GitHub", etc) + */ + fun getName(): String + + /** + * Visible icon of extension + */ + fun getIcon(): Icon + + /** + * Builds [VcsCloneDialogExtensionComponent] that would be displayed on center of get-from-vcs dialog when extension is selected. + * Will be called lazily and once on first choosing of extension. + */ + fun createMainComponent(project: Project): VcsCloneDialogExtensionComponent +} \ No newline at end of file diff --git a/platform/vcs-api/src/com/intellij/openapi/vcs/ui/cloneDialog/VcsCloneDialogExtensionComponent.kt b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/cloneDialog/VcsCloneDialogExtensionComponent.kt new file mode 100644 index 000000000000..305e7ab6e3fa --- /dev/null +++ b/platform/vcs-api/src/com/intellij/openapi/vcs/ui/cloneDialog/VcsCloneDialogExtensionComponent.kt @@ -0,0 +1,48 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.vcs.ui.cloneDialog + +import com.intellij.openapi.ui.ValidationInfo +import javax.swing.JComponent + +/** + * Main component for get-from-vcs extensions. + * It's responsible for: + * 1. Providing extension-specific UI for get-from-vcs dialog by [getView] + * 2. handling commands that would be provided from top-level [com.intellij.openapi.ui.DialogWrapper] + */ +interface VcsCloneDialogExtensionComponent { + + /** + * Return main [JComponent] that will be displayed in center of get-from-vcs dialog when extension is selected. + * + * It is called once when extension is selected first time + */ + fun getView(): JComponent + + /** + * Checks that is possible to perform ok action in current state of component + * + * Would be called form [com.intellij.openapi.ui.DialogWrapper.isOKActionEnabled] when extension is selected + */ + fun isOkEnabled(): Boolean + + /** + * Perform primary clone/checkout action. + * would be called from [com.intellij.openapi.ui.DialogWrapper.doOKAction] when extension is selected + * + * @see com.intellij.openapi.ui.DialogWrapper.doOKAction + */ + fun doOk() + + /** + * would be called from [com.intellij.openapi.ui.DialogWrapper.doValidateAll] when extension is selected + * + * @see com.intellij.openapi.ui.DialogWrapper.doValidateAll + */ + fun doValidateAll(): List + + /** + * Text that represents dialog primary action + */ + fun getOkButtonText(): String = "Clone" +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/GetFromVcsAction.java b/platform/vcs-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/GetFromVcsAction.java index 99eebe2fd174..bd8a356bf8d9 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/GetFromVcsAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/GetFromVcsAction.java @@ -3,12 +3,17 @@ package com.intellij.openapi.wm.impl.welcomeScreen; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vcs.CheckoutProvider; import com.intellij.openapi.vcs.checkout.CheckoutActionGroup; import com.intellij.ui.UIBundle; import org.jetbrains.annotations.NotNull; -public class GetFromVcsAction extends WelcomePopupAction{ +/** + * @deprecated superseded by {@link RunVcsCloneDialogAction} + */ +@Deprecated +public class GetFromVcsAction extends WelcomePopupAction { @Override protected void fillActions(DefaultActionGroup group) { @@ -32,6 +37,7 @@ public class GetFromVcsAction extends WelcomePopupAction{ @Override public void update(@NotNull AnActionEvent e) { + e.getPresentation().setVisible(!Registry.is("vcs.use.new.clone.dialog")); e.getPresentation().setEnabled(CheckoutProvider.EXTENSION_POINT_NAME.hasAnyExtensions()); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/RunVcsCloneDialogAction.kt b/platform/vcs-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/RunVcsCloneDialogAction.kt new file mode 100644 index 000000000000..526d6ce2e565 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/RunVcsCloneDialogAction.kt @@ -0,0 +1,25 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.wm.impl.welcomeScreen + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.util.registry.Registry +import com.intellij.openapi.vcs.CheckoutProvider +import com.intellij.util.ui.cloneDialog.VcsCloneDialog + +class RunVcsCloneDialogAction : DumbAwareAction("Get from Version Control") { + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = CheckoutProvider.EXTENSION_POINT_NAME.hasAnyExtensions() + e.presentation.isVisible = Registry.`is`("vcs.use.new.clone.dialog") + } + + override fun actionPerformed(e: AnActionEvent) { + val project = e.getData(CommonDataKeys.PROJECT) ?: ProjectManager.getInstance().defaultProject + VcsCloneDialog.Builder(project) + .forExtension() + .showAndGet() + } +} diff --git a/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/RepositoryUrlCloneDialogExtension.kt b/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/RepositoryUrlCloneDialogExtension.kt new file mode 100644 index 000000000000..4a15fcc402d5 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/RepositoryUrlCloneDialogExtension.kt @@ -0,0 +1,98 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.util.ui.cloneDialog + +import com.intellij.icons.AllIcons +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.openapi.vcs.CheckoutProvider +import com.intellij.openapi.vcs.ProjectLevelVcsManager +import com.intellij.openapi.vcs.ui.VcsCloneComponent +import com.intellij.openapi.vcs.ui.cloneDialog.VcsCloneDialogExtension +import com.intellij.openapi.vcs.ui.cloneDialog.VcsCloneDialogExtensionComponent +import com.intellij.ui.SimpleListCellRenderer +import com.intellij.ui.layout.* +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import java.awt.BorderLayout +import java.awt.CardLayout +import java.awt.event.ItemEvent +import java.util.* +import javax.swing.Icon +import javax.swing.JPanel + +class RepositoryUrlCloneDialogExtension : VcsCloneDialogExtension { + override fun getIcon(): Icon = AllIcons.Welcome.FromVCS + + override fun getName() = "Repository URL" + + override fun createMainComponent(project: Project): RepositoryUrlMainExtensionComponent { + return RepositoryUrlMainExtensionComponent(project) + } + + class RepositoryUrlMainExtensionComponent(private val project: Project) : VcsCloneDialogExtensionComponent { + private val vcsComponents = HashMap() + private val cardLayout = CardLayout() + private val mainPanel = JPanel(BorderLayout()) + private val comboBox: ComboBox = ComboBox().apply { + renderer = SimpleListCellRenderer.create("") { it.vcsName.removePrefix("_") } + } + + init { + mainPanel.border = JBUI.Borders.emptyRight(UIUtil.DEFAULT_VGAP) + val northPanel = panel { + row("Version control:") { comboBox() } + } + mainPanel.add(northPanel, BorderLayout.NORTH) + + val centerPanel = JPanel(cardLayout) + mainPanel.add(centerPanel, BorderLayout.CENTER) + + comboBox.addItemListener { e: ItemEvent -> + if (e.stateChange == ItemEvent.SELECTED) { + val provider = e.item as CheckoutProvider + cardLayout.show(centerPanel, provider.vcsName) + } + } + + for (checkoutProvider in CheckoutProvider.EXTENSION_POINT_NAME.extensions) { + comboBox.addItem(checkoutProvider) + val vcsComponent = checkoutProvider.buildVcsCloneComponent(project) + vcsComponents[checkoutProvider.vcsName] = vcsComponent + val view = vcsComponent.getView() + centerPanel.add(view, checkoutProvider.vcsName) + } + } + + override fun getView() = mainPanel + + override fun getOkButtonText(): String { + return getCurrentVcsComponent()?.getOkButtonText() ?: "Clone" + } + + fun openForVcs(clazz: Class): RepositoryUrlMainExtensionComponent { + comboBox.selectedItem = CheckoutProvider.EXTENSION_POINT_NAME.findExtension(clazz) + return this + } + + override fun doOk() { + val listener = ProjectLevelVcsManager.getInstance(project).compositeCheckoutListener + getCurrentVcsComponent()?.doClone(project, listener) + } + + override fun isOkEnabled(): Boolean { + return getCurrentVcsComponent()?.isOkEnabled() ?: true + } + + override fun doValidateAll(): List { + return getCurrentVcsComponent()?.doValidateAll() ?: emptyList() + } + + private fun getCurrentVcsComponent() = vcsComponents[getCurrentVcsName()] + + private fun getCurrentVcsName(): String { + val checkoutProvider = comboBox.selectedItem as CheckoutProvider + return checkoutProvider.vcsName + } + } +} diff --git a/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialog.kt b/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialog.kt new file mode 100644 index 000000000000..18804b96f934 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialog.kt @@ -0,0 +1,123 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.util.ui.cloneDialog + +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.openapi.vcs.CheckoutProvider +import com.intellij.openapi.vcs.ui.cloneDialog.VcsCloneDialogExtension +import com.intellij.openapi.vcs.ui.cloneDialog.VcsCloneDialogExtensionComponent +import com.intellij.openapi.wm.impl.welcomeScreen.FlatWelcomeFrame +import com.intellij.openapi.wm.impl.welcomeScreen.FlatWelcomeFrame.getSeparatorColor +import com.intellij.ui.CollectionListModel +import com.intellij.ui.ScrollPaneFactory +import com.intellij.ui.ScrollingUtil +import com.intellij.ui.border.CustomLineBorder +import com.intellij.util.ui.JBEmptyBorder +import com.intellij.util.ui.JBInsets +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import com.intellij.util.ui.cloneDialog.RepositoryUrlCloneDialogExtension.RepositoryUrlMainExtensionComponent +import java.awt.CardLayout +import java.util.* +import javax.swing.JComponent +import javax.swing.JPanel +import javax.swing.ScrollPaneConstants +import javax.swing.event.ListSelectionListener + +/** + * Top-level UI-component for new clone/checkout dialog + */ +internal class VcsCloneDialog private constructor(private val project: Project, + initialExtensionClass: Class, + private var initialVcs: Class? = null) : DialogWrapper(project) { + private lateinit var extensionList: VcsCloneDialogExtensionList + private val cardLayout = CardLayout() + private val mainPanel = JPanel(cardLayout) + private val extensionComponents: MutableMap = HashMap() + + init { + init() + title = "Get From Version Control" + JBUI.size(FlatWelcomeFrame.MAX_DEFAULT_WIDTH, FlatWelcomeFrame.DEFAULT_HEIGHT).let { + rootPane.minimumSize = it + rootPane.preferredSize = it + } + + val withoutRightInset = UIUtil.PANEL_REGULAR_INSETS.let { + // use empty right inset to align the scroll bar to the edge of panel + JBInsets(it.top, it.left, it.bottom, 0) + } + mainPanel.border = JBEmptyBorder(withoutRightInset) + + VcsCloneDialogExtension.EP_NAME.findExtension(initialExtensionClass)?.let { + ScrollingUtil.selectItem(extensionList, it) + } + } + + override fun getStyle() = DialogStyle.COMPACT + + override fun createCenterPanel(): JComponent { + val extensions = VcsCloneDialogExtension.EP_NAME.extensionList + val listModel = CollectionListModel(extensions) + + extensionList = VcsCloneDialogExtensionList(listModel).apply { + addListSelectionListener(ListSelectionListener { e -> + val source = e.source as VcsCloneDialogExtensionList + switchComponent(source.selectedValue) + }) + } + val scrollableList = ScrollPaneFactory.createScrollPane(extensionList, true).apply { + border = CustomLineBorder(getSeparatorColor(), JBUI.insetsRight(1)) + } + return JBUI.Panels.simplePanel() + .addToCenter(mainPanel) + .addToLeft(scrollableList).apply { + border = CustomLineBorder(getSeparatorColor(), JBUI.insetsBottom(1)) + } + } + + override fun isOKActionEnabled(): Boolean { + return getSelectedComponent()?.isOkEnabled() ?: false + } + + override fun doOKAction() { + getSelectedComponent()?.doOk() + } + + override fun doValidateAll(): List { + return getSelectedComponent()?.doValidateAll() ?: emptyList() + } + + private fun switchComponent(extension: VcsCloneDialogExtension) { + val extensionId = extension.javaClass.name + val mainComponent = extensionComponents.getOrPut(extensionId, { + val component = extension.createMainComponent(project) + val scrollableMainPanel = ScrollPaneFactory.createScrollPane(component.getView(), true) + scrollableMainPanel.verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED + scrollableMainPanel.horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER + mainPanel.add(scrollableMainPanel, extensionId) + component + }) + + setOKButtonText(mainComponent.getOkButtonText()) + if (mainComponent is RepositoryUrlMainExtensionComponent) { + initialVcs?.let { mainComponent.openForVcs(it) } + } + cardLayout.show(mainPanel, extensionId) + } + + private fun getSelectedComponent(): VcsCloneDialogExtensionComponent? { + return extensionComponents[extensionList.selectedValue.javaClass.name] + } + + class Builder(private val project: Project) { + fun forExtension(clazz: Class = RepositoryUrlCloneDialogExtension::class.java): VcsCloneDialog { + return VcsCloneDialog(project, clazz, null) + } + + fun forVcs(clazz: Class): VcsCloneDialog { + return VcsCloneDialog(project, RepositoryUrlCloneDialogExtension::class.java, clazz) + } + } +} \ No newline at end of file diff --git a/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialogExtensionList.kt b/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialogExtensionList.kt new file mode 100644 index 000000000000..6417d035e5aa --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialogExtensionList.kt @@ -0,0 +1,47 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.util.ui.cloneDialog + +import com.intellij.openapi.vcs.ui.cloneDialog.VcsCloneDialogExtension +import com.intellij.ui.ScrollingUtil +import com.intellij.ui.SingleSelectionModel +import com.intellij.ui.components.JBList +import com.intellij.util.ui.ListUiUtil +import com.intellij.util.ui.UIUtil +import java.awt.Component +import java.awt.FlowLayout +import javax.swing.* +import javax.swing.border.EmptyBorder + +class VcsCloneDialogExtensionList(listModel: ListModel) : JBList(listModel) { + init { + selectionModel = SingleSelectionModel() + cellRenderer = Renderer() + + ScrollingUtil.installActions(this) + } + + class Renderer : ListCellRenderer { + val component = VcsCloneDialogExtensionListItem + val wrapper: JComponent + + init { + wrapper = JPanel(FlowLayout(FlowLayout.LEADING)).apply { + border = EmptyBorder(UIUtil.PANEL_REGULAR_INSETS) + add(component) + } + } + + override fun getListCellRendererComponent(list: JList, + value: VcsCloneDialogExtension, + index: Int, + isSelected: Boolean, + cellHasFocus: Boolean): Component { + component.setTitle(value.getName()) + component.setTitleForeground(ListUiUtil.WithTallRow.foreground(list, isSelected)) + component.setIcon(value.getIcon()) + UIUtil.setBackgroundRecursively(wrapper, ListUiUtil.WithTallRow.background(list, isSelected)) + return wrapper + } + } +} + diff --git a/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialogExtensionListItem.kt b/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialogExtensionListItem.kt new file mode 100644 index 000000000000..3598996f7fa3 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/util/ui/cloneDialog/VcsCloneDialogExtensionListItem.kt @@ -0,0 +1,48 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.util.ui.cloneDialog + +import com.intellij.util.IconUtil +import com.intellij.util.ui.GridBag +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import java.awt.Color +import java.awt.GridBagLayout +import javax.swing.Icon +import javax.swing.JComponent +import javax.swing.JLabel + + +object VcsCloneDialogExtensionListItem : JComponent() { + + private val iconLabel: JLabel = JLabel() + private val titleLabel: JLabel = JLabel() + + init { + layout = GridBagLayout() + + var gbc = GridBag().next() + .insetRight(UIUtil.DEFAULT_VGAP) + .anchor(GridBag.LINE_START) + .fillCellNone() + add(iconLabel, gbc) + + gbc = gbc.next() + .insets(JBUI.emptyInsets()) + .anchor(GridBag.BELOW_BASELINE_LEADING) + .fillCellHorizontally() + titleLabel.font = JBUI.Fonts.label().asBold() + add(titleLabel, gbc) + } + + fun setTitle(title: String) { + titleLabel.text = title + } + + fun setIcon(icon: Icon) { + iconLabel.icon = IconUtil.scale(icon, null, 22.0f/icon.iconHeight) // scale is chosen so that the size of icon corresponds the design + } + + fun setTitleForeground(foreground: Color) { + titleLabel.foreground = foreground + } +} \ No newline at end of file