From 2aa76b0407b7bb7e028c2cc9c0f23cbbd4f8674b Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Wed, 28 Dec 2011 12:42:58 +0400 Subject: [PATCH] IDEA-76142 Gradle support - cannot update IDEA projects once one of build.gradle files changes 1. Added basic infrastructure for the gradle tool window GUI controls; 2. Minor refactoring; --- .../openapi/actionSystem/Presentation.java | 2 +- .../openapi/util/text/StringUtil.java | 2 +- .../resources/i18n/GradleBundle.properties | 7 +- plugins/gradle/src/META-INF/plugin.xml | 7 + .../config/GradleLinkToProjectAction.java | 28 ++++ .../gradle/config/GradleToolWindowPanel.java | 141 ++++++++++++++++++ .../gradle/config/MultiRowFlowPanel.java | 83 +++++++++++ .../GradleProjectStructureChangesPanel.java | 14 +- .../plugins/gradle/util/GradleConstants.java | 7 +- 9 files changed, 284 insertions(+), 7 deletions(-) create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleLinkToProjectAction.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java create mode 100644 plugins/gradle/src/org/jetbrains/plugins/gradle/config/MultiRowFlowPanel.java diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/Presentation.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/Presentation.java index 274bf3e4d1b1..17c9efe571b6 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/Presentation.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/Presentation.java @@ -285,7 +285,7 @@ public final class Presentation implements Cloneable { myChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } - public Object clone(){ + public Presentation clone(){ try{ Presentation presentation = (Presentation)super.clone(); presentation.myChangeSupport = new PropertyChangeSupport(presentation); diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index c71ed0094d4a..da5b331fde9e 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -1104,7 +1104,7 @@ public class StringUtil { } @NotNull - public static Iterable tokenize(@NotNull String s, final StringTokenizer tokenizer) { + public static Iterable tokenize(final StringTokenizer tokenizer) { return new Iterable() { public Iterator iterator() { return new Iterator() { diff --git a/plugins/gradle/resources/i18n/GradleBundle.properties b/plugins/gradle/resources/i18n/GradleBundle.properties index 35ec79d873bb..c7f0cb14e8ff 100644 --- a/plugins/gradle/resources/i18n/GradleBundle.properties +++ b/plugins/gradle/resources/i18n/GradleBundle.properties @@ -55,4 +55,9 @@ gradle.generic.text.error.sdk.undefined=Gradle installation is unknown gradle.home.setting.type.deduced=Gradle location is deduced gradle.home.setting.type.unknown=Gradle location is unknown gradle.home.setting.type.explicit.correct=Gradle location is defined -gradle.home.setting.type.explicit.incorrect=Gradle location is incorrect \ No newline at end of file +gradle.home.setting.type.explicit.incorrect=Gradle location is incorrect + +gradle.toolwindow.text.no.linked.project=\nThere is no linked Gradle project\nYou may use {link} to add the one. + +gradle.action.link.project.text=Link gradle project +gradle.action.link.project.description=Allows to link any gradle project to the current IDE project \ No newline at end of file diff --git a/plugins/gradle/src/META-INF/plugin.xml b/plugins/gradle/src/META-INF/plugin.xml index d317fcd0aece..78023504c47d 100644 --- a/plugins/gradle/src/META-INF/plugin.xml +++ b/plugins/gradle/src/META-INF/plugin.xml @@ -58,4 +58,11 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleLinkToProjectAction.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleLinkToProjectAction.java new file mode 100644 index 000000000000..adccca227adf --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleLinkToProjectAction.java @@ -0,0 +1,28 @@ +package org.jetbrains.plugins.gradle.config; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAware; +import org.jetbrains.plugins.gradle.util.GradleBundle; + +/** + * Allows to link gradle project to the current IntelliJ IDEA project. + *

+ * Not thread-safe. + * + * @author Denis Zhdanov + * @since 12/26/11 5:09 PM + */ +public class GradleLinkToProjectAction extends AnAction implements DumbAware { + + public GradleLinkToProjectAction() { + getTemplatePresentation().setText(GradleBundle.message("gradle.action.link.project.text")); + getTemplatePresentation().setDescription(GradleBundle.message("gradle.action.link.project.description")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + // TODO den implement + System.out.println("action performed"); + } +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java new file mode 100644 index 000000000000..a640f9683293 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/GradleToolWindowPanel.java @@ -0,0 +1,141 @@ +package org.jetbrains.plugins.gradle.config; + +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionToolbar; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.ex.ActionButtonLook; +import com.intellij.openapi.actionSystem.impl.ActionButton; +import com.intellij.openapi.ui.SimpleToolWindowPanel; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.ScrollPaneFactory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.util.GradleBundle; +import org.jetbrains.plugins.gradle.util.GradleConstants; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Base class for high-level Gradle GUI controls used at the Gradle tool window. The basic idea is to encapsulate the same features in + * this class and allow to extend it via Template Method pattern. The shared features are listed below: + *

+ * 
    + *
  • provide common actions at the toolbar;
  • + *
  • show info control when no gradle project is linked to the current IntelliJ IDEA project;
  • + *
+ *
+ *

+ * Not thread-safe. + * + * @author Denis Zhdanov + * @since 12/26/11 5:19 PM + */ +public abstract class GradleToolWindowPanel extends SimpleToolWindowPanel { + + private static final String NON_LINKED_CARD_NAME = "NON_LINKED"; + private static final String CONTENT_CARD_NAME = "CONTENT"; + private static final String LINK_BUTTON_MARKER = "{link}"; + private static final String TOOL_WINDOW_TOOLBAR_ID = "Gradle.ChangeActionsToolbar"; + private static final String LINK_PROJECT_ACTION_ID = "Gradle.LinkToProject"; + + /** Show info control when no gradle project is linked, using {@link CardLayout} for that. */ + private final CardLayout myLayout = new CardLayout(); + /** Top-level container, managed by the card layout. */ + private final JPanel myContent = new JPanel(myLayout); + /** Control to show when no gradle project is linked to the current IntelliJ IDEA project. */ + private final JPanel myNonLinkedInfoPanel = new JPanel(); + + protected GradleToolWindowPanel(@NotNull String place) { + super(true); + final ActionManager actionManager = ActionManager.getInstance(); + final ActionGroup actionGroup = (ActionGroup)actionManager.getAction(TOOL_WINDOW_TOOLBAR_ID); + ActionToolbar actionToolbar = actionManager.createActionToolbar(place, actionGroup, true); + setToolbar(actionToolbar.getComponent()); + initContent(); + setContent(myContent); + } + + private void initContent() { + final JComponent payloadControl = buildContent(); + myContent.add(ScrollPaneFactory.createScrollPane(payloadControl), CONTENT_CARD_NAME); + + myNonLinkedInfoPanel.setLayout(new GridBagLayout()); + final Color background = payloadControl.getBackground(); + myNonLinkedInfoPanel.setBackground(background); + List rowComponents = new ArrayList(); + final String sentence = GradleBundle.message("gradle.toolwindow.text.no.linked.project"); + for (String s : StringUtil.tokenize(new StringTokenizer(sentence, " \n", true))) { + if (s.isEmpty()) { + continue; + } + if (LINK_BUTTON_MARKER.equals(s)) { + final ActionManager actionManager = ActionManager.getInstance(); + final AnAction action = actionManager.getAction(LINK_PROJECT_ACTION_ID); + ActionButton button= new ActionButton( + action, action.getTemplatePresentation().clone(), GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE, new Dimension(0, 0)) + { + @Override + protected void paintButtonLook(Graphics g) { + // Don't draw border at the inline button. + ActionButtonLook look = getButtonLook(); + look.paintBackground(g, this); + look.paintIcon(g, this, getIcon()); + } + }; + rowComponents.add(button); + } + else if (s.contains("\n")) { + addRow(rowComponents, background); + rowComponents.clear(); + } + else { + final JLabel label = new JLabel(s); + label.setForeground(payloadControl.getForeground()); + label.setBackground(background); + label.setFont(payloadControl.getFont()); + rowComponents.add(label); + } + } + if (!rowComponents.isEmpty()) { + addRow(rowComponents, background); + } + + GridBagConstraints constraints = new GridBagConstraints(); + constraints.weighty = 1; + constraints.fill = GridBagConstraints.VERTICAL; + myNonLinkedInfoPanel.add(Box.createVerticalStrut(1), constraints); + myContent.add(myNonLinkedInfoPanel, NON_LINKED_CARD_NAME); + + myLayout.show(myContent, NON_LINKED_CARD_NAME); + } + + private void addRow(@NotNull Collection rowComponents, @NotNull Color backgroundColor) { + JPanel row = new MultiRowFlowPanel(FlowLayout.CENTER, 0, 3); + row.setBackground(backgroundColor); + if (rowComponents.isEmpty()) { + row.add(new JLabel(" ")); + } + else { + for (JComponent component : rowComponents) { + row.add(component); + } + } + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.weightx = 1; + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets.top = 3; + myNonLinkedInfoPanel.add(row, constraints); + } + + /** + * @return GUI control to be displayed at the current tab + */ + @NotNull + protected abstract JComponent buildContent(); +} diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/config/MultiRowFlowPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/MultiRowFlowPanel.java new file mode 100644 index 000000000000..d18b5b9aa8c9 --- /dev/null +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/config/MultiRowFlowPanel.java @@ -0,0 +1,83 @@ +package org.jetbrains.plugins.gradle.config; + +import javax.swing.*; +import java.awt.*; + +/** + * Flow layout calculates necessary size assuming that all components are layed out within a single row. + * That may cause troubles during initial space calculation, i.e. panel with flow layout is represented as a + * single wide row inside a scroll pane. This panel fixes that by calculating necessary size on its own. + * It fixes the width to use as a minimum between a width offered by default flow layout and customizable value + * and calculates the height. If the user has an ability to manually change panel size (e.g. indirectly via changing + * size of the dialog that serves as a container for the panel), that width is used as a maximum width. + * + * @author: Denis Zhdanov + * @since: Jul 28, 2008 + */ +public class MultiRowFlowPanel extends JPanel { + + private int maximumWidth = Toolkit.getDefaultToolkit().getScreenSize().width / 2; + + public MultiRowFlowPanel(int align, int hGap, int vGap) { + super(new FlowLayout(align, hGap, vGap)); + } + + /** + * Calculates a preferred size assuming that the maximum row width is a value returned from + * {@link #getMaxRowWidth()}. + */ + @Override + public Dimension getPreferredSize() { + return calculateSize(getMaxRowWidth()); + } + + /** + * Calculates a preferred size assuming that the maximum row width is a value returned from + * {@link #getMaxRowWidth()}. + */ + @Override + public Dimension getMinimumSize() { + return calculateSize(getMaxRowWidth()); + } + + /** + * @return current representation width if the component is already showed; minimum of default preferred + * width (when all components are layed in a single row) and half screen width + */ + private int getMaxRowWidth() { + int result = getSize().width; + if (result == 0) { + result = Math.min(super.getPreferredSize().width, maximumWidth); + } + return result; + } + + /** + * Iterates all child components and calculates the space enough to keep all of them assuming that the width is + * fixed. + */ + private Dimension calculateSize(int maxRowWidth) { + FlowLayout layout = (FlowLayout)getLayout(); + int height = 0; + int currentRowWidth = 0; + int currentRowHeight = 0; + for (int i = 0, count = getComponentCount(); i < count; ++i) { + Component comp = getComponent(i); + Dimension bounds = comp.getPreferredSize(); + if (!comp.isVisible()) { + continue; + } + currentRowHeight = Math.max(currentRowHeight, bounds.height); + if (currentRowWidth + layout.getHgap() + bounds.width <= maxRowWidth) { + if (bounds.width != 0) { + currentRowWidth += bounds.width + layout.getHgap(); + } + continue; + } + height += currentRowHeight + layout.getVgap(); + currentRowWidth = bounds.width; + currentRowHeight = bounds.height; + } + return new Dimension(maxRowWidth, height + currentRowHeight + 2 * layout.getVgap()); + } +} \ No newline at end of file diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java index 93967ebcee66..b03db8203783 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/sync/GradleProjectStructureChangesPanel.java @@ -1,6 +1,9 @@ package org.jetbrains.plugins.gradle.sync; +import com.intellij.ui.treeStructure.SimpleTree; import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.gradle.config.GradleToolWindowPanel; +import org.jetbrains.plugins.gradle.util.GradleConstants; import javax.swing.*; @@ -10,11 +13,20 @@ import javax.swing.*; * @author Denis Zhdanov * @since 11/3/11 3:58 PM */ -public class GradleProjectStructureChangesPanel extends JPanel { +public class GradleProjectStructureChangesPanel extends GradleToolWindowPanel { private final GradleProjectStructureChangesModel myModel; public GradleProjectStructureChangesPanel(@NotNull GradleProjectStructureChangesModel model) { + super(GradleConstants.TOOL_WINDOW_TOOLBAR_PLACE); myModel = model; } + + @NotNull + @Override + protected JComponent buildContent() { + // TODO den implement + return new SimpleTree(); + //return new JLabel("project-structure-change-content"); + } } diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleConstants.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleConstants.java index 6d721b1b5b3f..f6684687fccd 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleConstants.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/util/GradleConstants.java @@ -13,9 +13,10 @@ import javax.swing.*; */ public class GradleConstants { - public static final Icon GRADLE_ICON = IconLoader.getIcon("/icons/gradle.png"); - @NonNls public static final String EXTENSION = "gradle"; - @NonNls public static final String DEFAULT_SCRIPT_NAME = "build.gradle"; + public static final Icon GRADLE_ICON = IconLoader.getIcon("/icons/gradle.png"); + @NonNls public static final String EXTENSION = "gradle"; + @NonNls public static final String DEFAULT_SCRIPT_NAME = "build.gradle"; + @NonNls public static final String TOOL_WINDOW_TOOLBAR_PLACE = "GRADLE_SYNC_CHANGES_TOOLBAR"; private GradleConstants() { }