diff --git a/platform/core-api/src/com/intellij/openapi/application/ApplicationInfo.java b/platform/core-api/src/com/intellij/openapi/application/ApplicationInfo.java index 970190a6fd21..1a574a0d3356 100644 --- a/platform/core-api/src/com/intellij/openapi/application/ApplicationInfo.java +++ b/platform/core-api/src/com/intellij/openapi/application/ApplicationInfo.java @@ -6,6 +6,7 @@ import com.intellij.openapi.util.NlsSafe; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Calendar; @@ -68,6 +69,11 @@ public abstract class ApplicationInfo { public abstract String getKeyConversionUrl(); + @Nullable + public String getGettingStartedUrl() { + return null; + } + public abstract boolean hasHelp(); public abstract boolean hasContextHelp(); diff --git a/platform/platform-api/resources/messages/IdeBundle.properties b/platform/platform-api/resources/messages/IdeBundle.properties index 18e57b90221e..eda510ad684a 100644 --- a/platform/platform-api/resources/messages/IdeBundle.properties +++ b/platform/platform-api/resources/messages/IdeBundle.properties @@ -1916,6 +1916,12 @@ welcome.screen.action.docs.how.tos.action.text=Docs and How-Tos welcome.screen.plugins.title=Plugins welcome.screen.projects.title=Projects welcome.screen.customize.title=Customize +welcome.screen.learnIde.title=Learn IDE + +welcome.screen.learnIde.interactive.courses.text=Interactive Courses +welcome.screen.learnIde.help.and.resources.text=Help and Resources + + welcome.screen.color.theme.header=Color theme welcome.screen.ide.font.size.label=IDE font: diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java index 9a1a2bea1b73..a38720be7586 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java @@ -289,6 +289,7 @@ public interface IdeActions { @NonNls String GROUP_WELCOME_SCREEN_DOC = "WelcomeScreen.Documentation"; @NonNls String GROUP_WELCOME_SCREEN_CONFIGURE = "WelcomeScreen.Configure"; @NonNls String GROUP_WELCOME_SCREEN_HELP = "WelcomeScreen.Help"; + @NonNls String GROUP_WELCOME_SCREEN_LEARN_IDE = "WelcomeScreen.LearnIdeHelp"; @NonNls String ACTION_KEYMAP_REFERENCE="Help.KeymapReference"; @NonNls String ACTION_MOVE = "Move"; @NonNls String ACTION_RENAME = "RenameElement"; diff --git a/platform/platform-api/src/com/intellij/openapi/wm/InteractiveCourseData.kt b/platform/platform-api/src/com/intellij/openapi/wm/InteractiveCourseData.kt new file mode 100644 index 000000000000..6b4a84cafcb0 --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/wm/InteractiveCourseData.kt @@ -0,0 +1,22 @@ +// Copyright 2000-2020 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 + +import javax.swing.Action +import javax.swing.Icon +import javax.swing.JComponent + +interface InteractiveCourseData { + + fun getName(): String + + fun getDescription(): String + + fun getIcon(): Icon + + fun getActionButtonName(): String + + fun getAction(): Action + + fun getExpandContent(): JComponent + +} \ No newline at end of file diff --git a/platform/platform-api/src/com/intellij/openapi/wm/InteractiveCourseFactory.kt b/platform/platform-api/src/com/intellij/openapi/wm/InteractiveCourseFactory.kt new file mode 100644 index 000000000000..ee9249a1e536 --- /dev/null +++ b/platform/platform-api/src/com/intellij/openapi/wm/InteractiveCourseFactory.kt @@ -0,0 +1,13 @@ +// Copyright 2000-2020 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 + +import com.intellij.openapi.extensions.ExtensionPointName + +interface InteractiveCourseFactory { + + companion object { + val INTERACTIVE_COURSE_FACTORY_EP = ExtensionPointName("com.intellij.interactiveCourseFactory") + } + + fun getInteractiveCourseData(): InteractiveCourseData +} diff --git a/platform/platform-impl/src/com/intellij/ide/actions/GettingStartedAction.java b/platform/platform-impl/src/com/intellij/ide/actions/GettingStartedAction.java new file mode 100644 index 000000000000..673c8418734e --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/actions/GettingStartedAction.java @@ -0,0 +1,31 @@ +// Copyright 2000-2020 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.ide.actions; + +import com.intellij.ide.BrowserUtil; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.ApplicationInfo; +import com.intellij.openapi.project.DumbAware; +import org.jetbrains.annotations.NotNull; + +public class GettingStartedAction extends AnAction implements DumbAware { + private final String myUrl; + + public GettingStartedAction() { + myUrl = ApplicationInfo.getInstance().getGettingStartedUrl(); + } + + @Override + public void update(@NotNull AnActionEvent e) { + //boolean enabled = myUrl != null; + //e.getPresentation().setEnabledAndVisible(enabled); + e.getPresentation().setEnabledAndVisible(true); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + if (myUrl != null) { + BrowserUtil.browse(myUrl); + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/LearnIdeTabFactory.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/LearnIdeTabFactory.kt new file mode 100644 index 000000000000..ba68242fc7fa --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/LearnIdeTabFactory.kt @@ -0,0 +1,19 @@ +// Copyright 2000-2020 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.ide.IdeBundle +import com.intellij.openapi.Disposable +import com.intellij.openapi.wm.WelcomeScreenTab +import com.intellij.openapi.wm.WelcomeTabFactory +import com.intellij.openapi.wm.impl.welcomeScreen.learnIde.LearnIdeContentPanel +import javax.swing.JComponent + +class LearnIdeTabFactory: WelcomeTabFactory { + override fun createWelcomeTab(parentDisposable: Disposable): WelcomeScreenTab { + return object : TabbedWelcomeScreen.DefaultWelcomeScreenTab(IdeBundle.message("welcome.screen.learnIde.title")) { + override fun buildComponent(): JComponent { + return LearnIdeContentPanel() + } + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/HeightLimitedPane.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/HeightLimitedPane.kt new file mode 100644 index 000000000000..e596bc15dd77 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/HeightLimitedPane.kt @@ -0,0 +1,75 @@ +// Copyright 2000-2020 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.learnIde + +import com.intellij.ui.scale.JBUIScale +import java.awt.Dimension +import java.awt.event.MouseEvent +import javax.swing.JTextPane +import javax.swing.plaf.ComponentUI +import javax.swing.plaf.FontUIResource +import javax.swing.plaf.TextUI +import javax.swing.text.DefaultCaret +import javax.swing.text.SimpleAttributeSet +import javax.swing.text.StyleConstants + +class HeightLimitedPane(text: String, val relativeFontSize: Int, val style: SimpleAttributeSet, val _width: Int? = null) : JTextPane() { + init { + isEditable = false + document.insertString(0, text, style) + //ensure that style has been applied + StyleConstants.setFontSize(style, (font.size2D + JBUIScale.scale(relativeFontSize)).toInt()) + styledDocument.setCharacterAttributes(0, text.length, style, true) + styledDocument.setParagraphAttributes(0, text.length, style, true) + isOpaque = false + isEditable = false + alignmentX = LEFT_ALIGNMENT + highlighter = null + //make JTextPane transparent for mouse actions + caret = object : DefaultCaret() { + override fun mousePressed(e: MouseEvent?) { + this@HeightLimitedPane.parent.mouseListeners.forEach { it.mousePressed(e) } + } + + override fun mouseReleased(e: MouseEvent?) { + this@HeightLimitedPane.parent.mouseListeners.forEach { it.mouseReleased(e) } + } + + override fun mouseEntered(e: MouseEvent?) { + this@HeightLimitedPane.parent.mouseListeners.forEach { it.mouseEntered(e) } + } + + override fun mouseExited(e: MouseEvent?) { + this@HeightLimitedPane.parent.mouseListeners.forEach { it.mouseExited(e) } + } + } + } + + override fun getMaximumSize(): Dimension { + if (_width == null) { + return this.preferredSize + } + else { + return Dimension(width, this.preferredSize.height) + } + } + + override fun setUI(ui: TextUI?) { + super.setUI(ui) + if (font != null) { + font = FontUIResource(font.deriveFont(font.size2D + JBUIScale.scale(relativeFontSize))) + } + } + + override fun setUI(newUI: ComponentUI?) { + super.setUI(newUI) + } + + override fun updateUI() { + super.updateUI() + if (font != null && style != null) { + StyleConstants.setFontSize(style, font.size) + styledDocument.setCharacterAttributes(0, text.length, style, true) + styledDocument.setParagraphAttributes(0, text.length, style, true) + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/InteractiveCoursePanel.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/InteractiveCoursePanel.kt new file mode 100644 index 000000000000..e99f2e53c88e --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/InteractiveCoursePanel.kt @@ -0,0 +1,226 @@ +// Copyright 2000-2020 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.learnIde + +import com.intellij.icons.AllIcons +import com.intellij.openapi.wm.InteractiveCourseData +import com.intellij.ui.RoundedLineBorder +import com.intellij.ui.components.JBLabel +import com.intellij.util.ui.JBUI +import java.awt.* +import java.awt.event.MouseAdapter +import java.awt.event.MouseEvent +import java.awt.event.MouseListener +import javax.swing.* +import javax.swing.border.CompoundBorder +import javax.swing.border.EmptyBorder +import javax.swing.plaf.FontUIResource +import javax.swing.plaf.LabelUI + +class InteractiveCoursePanel(private val data: InteractiveCourseData) : JPanel() { + + private enum class ContentState { COLLAPSED, EXPANDED } + + private val pluginPanelWidth = 72 + private val chevronPanelWidth = 55 + + val startLearningButton = JButton() + + private val interactiveCourseDescription = HeightLimitedPane(data.getDescription(), -1, LearnIdeContentColorsAndFonts.REGULAR) + private val interactiveCourseContent = createInteractiveCourseContent() + + private var contentState: ContentState = ContentState.COLLAPSED + private val expandCollapseListener: MouseListener = createExpandCollapseListener() + + private val expandedCourseContent = data.getExpandContent() + private val chevronPanel = JPanel() + private val chevronLabel = JLabel(AllIcons.General.ChevronDown) + val pluginPanel = JPanel() + val pluginLabel = JLabel(data.getIcon()) + + private val roundBorder1px = CompoundBorder(RoundedLineBorder(LearnIdeContentColorsAndFonts.InteractiveCoursesBorder, 4, 1), + JBUI.Borders.emptyRight(5)) + + private val calculateInnerComponentHeight: () -> Int = { preferredSize.height } + + init { + initPluginPanel() + initChevronPanel() + + layout = BoxLayout(this, BoxLayout.LINE_AXIS) + isOpaque = false + border = roundBorder1px + alignmentX = LEFT_ALIGNMENT + + add(pluginPanel) + add(interactiveCourseContent) + add(chevronPanel) + + addMouseListener(expandCollapseListener) + interactiveCourseDescription.addMouseListener(expandCollapseListener) + } + + private fun initChevronPanel() { + chevronPanel.apply { + layout = BorderLayout() + isOpaque = false + alignmentY = TOP_ALIGNMENT + add(chevronLabel, BorderLayout.CENTER) + add(JPanel() + .apply { preferredSize = Dimension(chevronPanelWidth, 1); minimumSize = Dimension(chevronPanelWidth, 1); isOpaque = false }, + BorderLayout.NORTH) + } + } + + private fun initPluginPanel() { + pluginPanel.apply { + layout = BorderLayout() + border = EmptyBorder(12, 0, 0, 0) + isOpaque = false + add(pluginLabel, BorderLayout.NORTH) + alignmentY = TOP_ALIGNMENT + add(JPanel() + .apply { preferredSize = Dimension(pluginPanelWidth, 1); minimumSize = Dimension(pluginPanelWidth, 1); isOpaque = false }, + BorderLayout.CENTER) + } + } + + override fun getMaximumSize(): Dimension { + return Dimension(this.preferredSize.width, calculateInnerComponentHeight()) + } + + private fun createInteractiveCourseContent(): JPanel { + val panel = JPanel() + panel.layout = BoxLayout(panel, BoxLayout.PAGE_AXIS) + panel.isOpaque = false + panel.alignmentY = TOP_ALIGNMENT + + panel.add(rigid(12, 10)) + + val learnIdeFeaturesHeader = object : JBLabel(data.getName()) { + override fun setUI(ui: LabelUI?) { + super.setUI(ui) + if (font != null) { + font = FontUIResource(font.deriveFont(font.size2D).deriveFont(Font.BOLD)) + } + } + } + + learnIdeFeaturesHeader.alignmentX = LEFT_ALIGNMENT + panel.add(learnIdeFeaturesHeader) + + panel.add(rigid(1, 4)) + panel.add(interactiveCourseDescription) + panel.add(rigid(4, 9)) + + startLearningButton.action = data.getAction() + startLearningButton.margin = Insets(0, 0, 0, 0) + startLearningButton.isSelected = true + startLearningButton.isOpaque = false + startLearningButton.alignmentX = LEFT_ALIGNMENT + + val buttonPlace = buttonPixelHunting(startLearningButton) + panel.add(buttonPlace) + + panel.add(rigid(18, 21)) + + return panel + } + + private fun createExpandCollapseListener(): MouseAdapter { + return object : MouseAdapter() { + override fun mouseEntered(e: MouseEvent?) { + if (contentState == ContentState.EXPANDED) return + activateLearnIdeFeaturesPanel() + } + + override fun mouseExited(e: MouseEvent?) { + if (e == null) return + deactivateLearnIdeFeaturesPanel(e.locationOnScreen) + } + + override fun mousePressed(e: MouseEvent?) { + onLearnIdeFeaturesPanelClick() + } + } + } + + private fun buttonPixelHunting(button: JButton): JPanel { + + val buttonSizeWithoutInsets = Dimension(button.preferredSize.width - button.insets.left - button.insets.right, + button.preferredSize.height - button.insets.top - button.insets.bottom) + + val buttonPlace = JPanel().apply { + layout = null + maximumSize = buttonSizeWithoutInsets + preferredSize = buttonSizeWithoutInsets + minimumSize = buttonSizeWithoutInsets + isOpaque = false + alignmentX = LEFT_ALIGNMENT + } + + buttonPlace.add(button) + button.bounds = Rectangle(-button.insets.left, -button.insets.top, button.preferredSize.width, button.preferredSize.height) + + return buttonPlace + } + + private fun activateLearnIdeFeaturesPanel() { + background = LearnIdeContentColorsAndFonts.HoveredColor + isOpaque = true + repaint() + cursor = Cursor(Cursor.HAND_CURSOR); + } + + private fun deactivateLearnIdeFeaturesPanel(mouseLocationOnScreen: Point) { + if (pointerInLearnIdeFeaturesPanel(mouseLocationOnScreen)) return + isOpaque = false + repaint() + cursor = Cursor(Cursor.DEFAULT_CURSOR) + } + + private fun onLearnIdeFeaturesPanelClick() { + if (contentState == ContentState.COLLAPSED) { + expandContent() + chevronLabel.icon = AllIcons.General.ChevronUp + contentState = ContentState.EXPANDED + } + else { + collapseContent() + chevronLabel.icon = AllIcons.General.ChevronDown + contentState = ContentState.COLLAPSED + } + chevronLabel.repaint() + } + + private fun expandContent() { + this.isOpaque = false + interactiveCourseDescription.maximumSize = interactiveCourseDescription.preferredSize + interactiveCourseContent.add(expandedCourseContent) + chevronPanel.maximumSize = chevronPanel.size + interactiveCourseContent.revalidate() + interactiveCourseContent.repaint() + repaint() + + } + + private fun collapseContent() { + val pointerLocation = MouseInfo.getPointerInfo().location + if (pointerInLearnIdeFeaturesPanel(pointerLocation)) activateLearnIdeFeaturesPanel() + interactiveCourseContent.remove(expandedCourseContent) + interactiveCourseContent.revalidate() + interactiveCourseContent.repaint() + repaint() + } + + private fun pointerInLearnIdeFeaturesPanel(mouseLocationOnScreen: Point) = + Rectangle(Point(locationOnScreen.x + 1, locationOnScreen.y + 1), + Dimension(bounds.size.width - 2, bounds.size.height - 2)).contains( + mouseLocationOnScreen) + + + private fun rigid(_width: Int, _height: Int): Component { + return Box.createRigidArea( + Dimension(JBUI.scale(_width), JBUI.scale(_height))).apply { (this as JComponent).alignmentX = LEFT_ALIGNMENT } + } + +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/LearnIdeContentColorsAndFonts.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/LearnIdeContentColorsAndFonts.kt new file mode 100644 index 000000000000..48d45aa87777 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/LearnIdeContentColorsAndFonts.kt @@ -0,0 +1,83 @@ +// Copyright 2000-2020 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.learnIde + +import com.intellij.ide.ui.UISettings +import com.intellij.ui.JBColor +import com.intellij.util.ui.JBUI +import java.awt.Font +import java.awt.font.TextAttribute +import javax.swing.JLabel +import javax.swing.text.SimpleAttributeSet +import javax.swing.text.StyleConstants + +object LearnIdeContentColorsAndFonts { + + val InteractiveCoursesBorder = JBColor.namedColor("Button.startBorderColor", JBColor(0x87AFDA, 0x5E6060)) + private val fontSize: Int by lazy { UISettings.instance.fontSize.ifZero(JBUI.scale(13)) } + private val fontFace: String by lazy { UISettings.instance.fontFace ?: JLabel().font.fontName } + + val HeaderFont: Font by lazy { Font(fontFace, Font.BOLD, fontSize + 5) } + val HeaderColor = JBColor.namedColor("ParameterInfo.foreground", JBColor(0x1D1D1D, 0xBBBBBB)) + + + val HoveredColor = JBColor.namedColor("Plugins.lightSelectionBackground", JBColor(0xEDF6FE, 0x464A4D)) + val REGULAR = SimpleAttributeSet() + + val ModuleHeaderColor = JBColor.namedColor("link", JBColor(0x2470B3, 0x589DF6)) + val ModuleDescriptionColor = JBColor.namedColor("disabledText", JBColor(0x8C8C8C, 0x777777)) + val MODULE_HEADER = SimpleAttributeSet() + val MODULE_DESCRIPTION = SimpleAttributeSet() + + val PARAGRAPH_STYLE = SimpleAttributeSet() + val HEADER = SimpleAttributeSet() + + init { + StyleConstants.setFontSize(REGULAR, UISettings.instance.fontSize - 1) + StyleConstants.setFontFamily(REGULAR, UISettings.instance.fontFace) + StyleConstants.setForeground(REGULAR, HeaderColor) + + StyleConstants.setLeftIndent(REGULAR, 0.0f) + StyleConstants.setRightIndent(REGULAR, 0f) + StyleConstants.setSpaceAbove(REGULAR, 0.0f) + StyleConstants.setSpaceBelow(REGULAR, 0.0f) + StyleConstants.setLineSpacing(REGULAR, 0.0f) + + StyleConstants.setFontSize(HEADER, HeaderFont.size) + StyleConstants.setFontFamily(HEADER, UISettings.instance.fontFace) + StyleConstants.setForeground(HEADER, HeaderColor) + StyleConstants.setBold(HEADER, true) + HEADER.addAttribute(TextAttribute.TRACKING, -0.1) + + StyleConstants.setLeftIndent(PARAGRAPH_STYLE, 0.0f) + StyleConstants.setRightIndent(PARAGRAPH_STYLE, 0f) + StyleConstants.setSpaceAbove(PARAGRAPH_STYLE, 0.0f) + StyleConstants.setSpaceBelow(PARAGRAPH_STYLE, 0.0f) + StyleConstants.setLineSpacing(PARAGRAPH_STYLE, 0.0f) + + StyleConstants.setFontFamily(MODULE_HEADER, UISettings.instance.fontFace) + StyleConstants.setFontSize(MODULE_HEADER, UISettings.instance.fontSize - 1) + StyleConstants.setForeground(MODULE_HEADER, ModuleHeaderColor) + StyleConstants.setLeftIndent(MODULE_HEADER, 0.0f) + StyleConstants.setRightIndent(MODULE_HEADER, 0f) + StyleConstants.setSpaceAbove(MODULE_HEADER, 0.0f) + StyleConstants.setSpaceBelow(MODULE_HEADER, 0.0f) + StyleConstants.setLineSpacing(MODULE_HEADER, 0.0f) + + StyleConstants.setFontFamily(MODULE_DESCRIPTION, UISettings.instance.fontFace) + StyleConstants.setFontSize(MODULE_DESCRIPTION, UISettings.instance.fontSize - 2) + StyleConstants.setForeground(MODULE_DESCRIPTION, ModuleDescriptionColor) + StyleConstants.setLeftIndent(MODULE_DESCRIPTION, 0.0f) + StyleConstants.setRightIndent(MODULE_DESCRIPTION, 0f) + StyleConstants.setSpaceAbove(MODULE_DESCRIPTION, 0.0f) + StyleConstants.setSpaceBelow(MODULE_DESCRIPTION, 0.0f) + StyleConstants.setLineSpacing(MODULE_DESCRIPTION, 0.0f) + + } + + + private fun Int.ifZero(nonZeroValue: Int): Int = + if (this == 0) nonZeroValue else this + +} + + diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/LearnIdeContentPanel.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/LearnIdeContentPanel.kt new file mode 100644 index 000000000000..de8f1e3df035 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/learnIde/LearnIdeContentPanel.kt @@ -0,0 +1,153 @@ +// Copyright 2000-2020 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.learnIde + +import com.intellij.icons.AllIcons.Ide.External_link_arrow +import com.intellij.ide.IdeBundle +import com.intellij.ide.actions.GettingStartedAction +import com.intellij.ide.actions.HelpTopicsAction +import com.intellij.ide.actions.JetBrainsTvAction +import com.intellij.ide.actions.WhatsNewAction +import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.actionSystem.ex.ActionUtil +import com.intellij.openapi.wm.InteractiveCourseFactory +import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager +import com.intellij.openapi.wm.impl.welcomeScreen.learnIde.LearnIdeContentColorsAndFonts.HEADER +import com.intellij.ui.AncestorListenerAdapter +import com.intellij.ui.components.JBScrollPane +import com.intellij.ui.components.labels.LinkLabel +import com.intellij.util.ui.JBUI +import java.awt.* +import javax.swing.* +import javax.swing.border.EmptyBorder +import javax.swing.event.AncestorEvent + +class LearnIdeContentPanel : JPanel() { + + private val interactiveCoursesPanel: JPanel = JPanel() + private val helpAndResourcesPanel: JPanel = JPanel() + + private val viewComponent: JPanel = JPanel().apply { layout = BorderLayout(); background = WelcomeScreenUIManager.getProjectsBackground() } + private val myScrollPane: JBScrollPane = JBScrollPane(viewComponent, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER).apply { border = JBUI.Borders.empty()} + + private val interactiveCoursesHeader: JTextPane = HeightLimitedPane(IdeBundle.message("welcome.screen.learnIde.interactive.courses.text"), + 5, HEADER) + + private val helpAndResourcesHeader: JTextPane = HeightLimitedPane(IdeBundle.message("welcome.screen.learnIde.help.and.resources.text"), 5, HEADER) + + init { + layout = BorderLayout() + isFocusable = false + isOpaque = true + background = WelcomeScreenUIManager.getProjectsBackground() + + val contentPanel = JPanel() + contentPanel.layout = BoxLayout(contentPanel, BoxLayout.PAGE_AXIS) + + //get all Interactive Courses + contentPanel.addInteractiveCoursesPanel() + contentPanel.addHelpAndResourcesPanel() + contentPanel.add(Box.createVerticalGlue()) + + //set LearnPanel UI + contentPanel.border = EmptyBorder(24, 24, 24, 24) + contentPanel.isOpaque = false + viewComponent.add(contentPanel, BorderLayout.CENTER) + add(myScrollPane, BorderLayout.CENTER) + + contentPanel.bounds = Rectangle(contentPanel.location, contentPanel.preferredSize) + revalidate() + repaint() + } + + private fun Container.addHelpAndResourcesPanel() { + helpAndResourcesPanel.apply { + layout = BoxLayout(this, BoxLayout.PAGE_AXIS) + isOpaque = false + add(helpAndResourcesHeader) + add(rigid(0, 1)) + addHelpActions() + } + this.add(helpAndResourcesPanel) + } + + private fun Container.addInteractiveCoursesPanel() { + val interactiveCourses = InteractiveCourseFactory.INTERACTIVE_COURSE_FACTORY_EP.extensions.map {it.getInteractiveCourseData()} + interactiveCoursesPanel.layout = BoxLayout(interactiveCoursesPanel, BoxLayout.PAGE_AXIS) + interactiveCoursesPanel.isOpaque = false + + if (interactiveCourses.isNotEmpty()) { + interactiveCoursesPanel.add(interactiveCoursesHeader) + var actionButton: JButton? = null + for (interactiveCourse in interactiveCourses) { + interactiveCoursesPanel.add(rigid(0, 12)) + val interactiveCoursePanel = InteractiveCoursePanel(interactiveCourse) + interactiveCoursesPanel.add(interactiveCoursePanel) + if (actionButton == null) actionButton = interactiveCoursePanel.startLearningButton + } + addAncestorListener(object : AncestorListenerAdapter() { + override fun ancestorAdded(event: AncestorEvent?) { + rootPane?.defaultButton = actionButton + } + }) + this.add(interactiveCoursesPanel) + this.add(rigid(1, 32)) + } + } + + + private fun addHelpActions() { + val whatsNewAction = WhatsNewAction() + if (emptyWelcomeScreenEventFromAction(whatsNewAction).presentation.isEnabled) { + helpAndResourcesPanel.add(linkLabelByAction(WhatsNewAction())) + helpAndResourcesPanel.add(rigid(1, 16)) + } + val helpActions = ActionManager.getInstance().getAction(IdeActions.GROUP_WELCOME_SCREEN_LEARN_IDE) as ActionGroup + val anActionEvent = emptyWelcomeScreenEventFromAction(helpActions) + helpActions.getChildren(anActionEvent).forEach { + if (setOf(HelpTopicsAction::class.java.simpleName, GettingStartedAction::class.java.simpleName, + JetBrainsTvAction::class.java.simpleName).any { simpleName -> simpleName == it.javaClass.simpleName }) { + helpAndResourcesPanel.add(linkLabelByAction(it).wrapWithUrlPanel()) + } + else { + helpAndResourcesPanel.add(linkLabelByAction(it)) + + } + helpAndResourcesPanel.add(rigid(1, 6)) + } + } + + private fun LinkLabel.wrapWithUrlPanel(): JPanel { + val jPanel = JPanel() + jPanel.isOpaque = false + jPanel.layout = BoxLayout(jPanel, BoxLayout.LINE_AXIS) + jPanel.add(this, BorderLayout.CENTER) + jPanel.add(JLabel(External_link_arrow), BorderLayout.EAST) + jPanel.maximumSize = jPanel.preferredSize + jPanel.alignmentX = LEFT_ALIGNMENT + return jPanel + } + + private fun emptyWelcomeScreenEventFromAction(action: AnAction) = + AnActionEvent.createFromAnAction(action, null, ActionPlaces.WELCOME_SCREEN, DataContext.EMPTY_CONTEXT) + + private fun linkLabelByAction(it: AnAction): LinkLabel { + return LinkLabel(it.templateText, null).apply { + alignmentX = LEFT_ALIGNMENT + setListener({ _, _ -> performActionOnWelcomeScreen(it) }, null) + } + } + + private fun rigid(_width: Int, _height: Int): Component { + return Box.createRigidArea( + Dimension(JBUI.scale(_width), JBUI.scale(_height))).apply { (this as JComponent).alignmentX = LEFT_ALIGNMENT } + } + + private fun performActionOnWelcomeScreen(action: AnAction) { + val anActionEvent = AnActionEvent.createFromAnAction(action, null, ActionPlaces.WELCOME_SCREEN, DataContext.EMPTY_CONTEXT) + ActionUtil.performActionDumbAware(action, anActionEvent) + } + +} + + diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index fa3f71161f94..ff71f7ac54ca 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -1136,6 +1136,8 @@ action.Help.KeymapReference.text=_Keymap Reference action.Help.KeymapReference.description=Open PDF file with the default keymap reference card. action.Help.JetBrainsTV.text=_Demos and Screencasts action.Help.JetBrainsTV.description=View short live demos introducing features of JetBrains products. +action.Help.GettingStarted.text=Getting Started +action.Help.GettingStarted.description=Knowledge base and learning materials. action.CheckForUpdate.text=_Check for Updates... action.CheckForUpdate.description=Checks for available IDE and plugin updates action.CheckForUpdate.WelcomeScreen.text=Check for Updates diff --git a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml index bacbcfa9734a..4542ee90aa41 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml @@ -177,6 +177,7 @@ + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 1db6e99bbc19..27acaa6f20b5 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -435,6 +435,8 @@ order="after ProjectsWelcomeTab"/> + diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index e6eb3a03c0f5..6488d4b1d84a 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -771,6 +771,7 @@ + @@ -939,6 +940,15 @@ + + + + + + + + +