/* * Copyright 2000-2012 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.roots.ui.configuration; import com.intellij.icons.AllIcons; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.roots.ContentEntry; import com.intellij.openapi.roots.ContentFolder; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.ui.Gray; import com.intellij.ui.HoverHyperlinkLabel; import com.intellij.ui.JBColor; import com.intellij.ui.roots.FilePathClipper; import com.intellij.ui.roots.IconActionComponent; import com.intellij.ui.roots.ResizingWrapper; import com.intellij.uiDesigner.core.GridConstraints; import com.intellij.uiDesigner.core.GridLayoutManager; import com.intellij.util.containers.MultiMap; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import consulo.roots.ContentFolderScopes; import consulo.roots.ContentFolderTypeProvider; import consulo.roots.impl.ExcludedContentFolderTypeProvider; import javax.swing.*; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import java.awt.*; import java.io.File; import java.util.Collection; import java.util.HashMap; import java.util.Map; /** * @author Eugene Zhuravlev * Date: Jan 19, 2004 */ public abstract class ContentRootPanel extends JPanel { private static final Color SELECTED_HEADER_COLOR = new JBColor(new Color(0xDEF2FF), UIUtil.getPanelBackground().darker()); private static final Color HEADER_COLOR = new JBColor(new Color(0xF5F5F5), Gray._82); private static final Color SELECTED_CONTENT_COLOR = new Color(0xF0F9FF); private static final Color CONTENT_COLOR = new JBColor(Color.WHITE, UIUtil.getPanelBackground()); private static final Color UNSELECTED_TEXT_COLOR = Gray._51; protected final ActionCallback myCallback; private JComponent myHeader; private JComponent myBottom; private final Map<JComponent, Color> myComponentToForegroundMap = new HashMap<JComponent, Color>(); public interface ActionCallback { void deleteContentEntry(); void deleteContentFolder(ContentEntry contentEntry, ContentFolder contentFolder); void showChangeOptionsDialog(ContentEntry contentEntry, ContentFolder contentFolder); void navigateFolder(ContentEntry contentEntry, ContentFolder contentFolder); } public ContentRootPanel(ActionCallback callback) { super(new GridBagLayout()); myCallback = callback; } @NotNull protected abstract ContentEntry getContentEntry(); public void initUI() { myHeader = createHeader(); this.add(myHeader, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 8, 0), 0, 0)); addFolderGroupComponents(); myBottom = new JPanel(new BorderLayout()); myBottom.add(Box.createVerticalStrut(3), BorderLayout.NORTH); this.add(myBottom, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); setSelected(false); } protected void addFolderGroupComponents() { final ContentFolder[] contentFolders = getContentEntry().getFolders(ContentFolderScopes.all()); MultiMap<ContentFolderTypeProvider, ContentFolder> folderByType = new MultiMap<ContentFolderTypeProvider, ContentFolder>(); for (ContentFolder folder : contentFolders) { if (folder.isSynthetic()) { continue; } final VirtualFile folderFile = folder.getFile(); if (folderFile != null && isExcludedOrUnderExcludedDirectory(folderFile)) { continue; } folderByType.putValue(folder.getType(), folder); } Insets insets = new Insets(0, 0, 10, 0); GridBagConstraints constraints = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, insets, 0, 0); for (Map.Entry<ContentFolderTypeProvider, Collection<ContentFolder>> entry : folderByType.entrySet()) { Collection<ContentFolder> folders = entry.getValue(); if (folders.isEmpty()) continue; ContentFolderTypeProvider contentFolderTypeProvider = entry.getKey(); ContentFolder[] foldersArray = folders.toArray(new ContentFolder[folders.size()]); final JComponent sourcesComponent = createFolderGroupComponent(contentFolderTypeProvider.getName(), foldersArray, contentFolderTypeProvider.getGroupColor(), contentFolderTypeProvider); add(sourcesComponent, constraints); } } private JComponent createHeader() { final JPanel panel = new JPanel(new GridBagLayout()); final JLabel headerLabel = new JLabel(toDisplayPath(getContentEntry().getUrl())); headerLabel.setFont(headerLabel.getFont().deriveFont(Font.BOLD)); headerLabel.setOpaque(false); if (getContentEntry().getFile() == null) { headerLabel.setForeground(Color.RED); } final IconActionComponent deleteIconComponent = new IconActionComponent(AllIcons.Modules.DeleteContentRoot, AllIcons.Modules.DeleteContentRootRollover, ProjectBundle.message("module.paths.remove.content.tooltip"), new Runnable() { @Override public void run() { myCallback.deleteContentEntry(); } }); final ResizingWrapper wrapper = new ResizingWrapper(headerLabel); panel.add(wrapper, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 0, 0), 0, 0)); panel.add(deleteIconComponent, new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 1, 0.0, 1.0, GridBagConstraints.EAST, GridBagConstraints.NONE, new Insets(0, 0, 0, 2), 0, 0)); FilePathClipper.install(headerLabel, wrapper); return panel; } protected JComponent createFolderGroupComponent(String title, ContentFolder[] folders, Color foregroundColor, @NotNull ContentFolderTypeProvider editor) { final JPanel panel = new JPanel(new GridLayoutManager(folders.length, 3, new Insets(1, 17, 0, 2), 0, 1)); panel.setOpaque(false); for (int idx = 0; idx < folders.length; idx++) { final ContentFolder folder = folders[idx]; final int verticalPolicy = idx == folders.length - 1 ? GridConstraints.SIZEPOLICY_CAN_GROW : GridConstraints.SIZEPOLICY_FIXED; panel.add(createFolderComponent(folder, foregroundColor), new GridConstraints(idx, 0, 1, 1, GridConstraints.ANCHOR_NORTHWEST, GridConstraints.FILL_HORIZONTAL, GridConstraints.SIZEPOLICY_CAN_GROW | GridConstraints.SIZEPOLICY_CAN_SHRINK, verticalPolicy, null, null, null)); panel.add(createFolderChangeOptionsComponent(folder, editor), new GridConstraints(idx, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, verticalPolicy, null, null, null)); panel.add(createFolderDeleteComponent(folder, editor), new GridConstraints(idx, 2, 1, 1, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, verticalPolicy, null, null, null)); } final JLabel titleLabel = new JLabel(title); final Font labelFont = UIUtil.getLabelFont(); titleLabel.setFont(labelFont.deriveFont(Font.BOLD)); titleLabel.setOpaque(false); titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0)); registerTextComponent(titleLabel, foregroundColor); final JPanel groupPanel = new JPanel(new BorderLayout()); groupPanel.setOpaque(false); groupPanel.add(titleLabel, BorderLayout.NORTH); groupPanel.add(panel, BorderLayout.CENTER); return groupPanel; } private void registerTextComponent(final JComponent component, final Color foreground) { component.setForeground(foreground); myComponentToForegroundMap.put(component, foreground); } private JComponent createFolderComponent(final ContentFolder folder, Color foreground) { final VirtualFile folderFile = folder.getFile(); final VirtualFile contentEntryFile = getContentEntry().getFile(); final String properties =""; if (folderFile != null && contentEntryFile != null) { String path = folderFile.equals(contentEntryFile) ? "." : VfsUtilCore.getRelativePath(folderFile, contentEntryFile, File.separatorChar); HoverHyperlinkLabel hyperlinkLabel = new HoverHyperlinkLabel(path + properties, foreground); hyperlinkLabel.setMinimumSize(new Dimension(0, 0)); hyperlinkLabel.addHyperlinkListener(new HyperlinkListener() { @Override public void hyperlinkUpdate(HyperlinkEvent e) { myCallback.navigateFolder(getContentEntry(), folder); } }); registerTextComponent(hyperlinkLabel, foreground); return new UnderlinedPathLabel(hyperlinkLabel); } else { String path = toRelativeDisplayPath(folder.getUrl(), getContentEntry().getUrl()); final JLabel pathLabel = new JLabel(path + properties); pathLabel.setOpaque(false); pathLabel.setForeground(Color.RED); return new UnderlinedPathLabel(pathLabel); } } private JComponent createFolderChangeOptionsComponent(final ContentFolder folder, @NotNull ContentFolderTypeProvider editor) { return new IconActionComponent(AllIcons.Modules.ContentFolderOptions, AllIcons.Modules.ContentFolderOptions, ProjectBundle.message("module.paths.properties.tooltip"), new Runnable() { @Override public void run() { myCallback.showChangeOptionsDialog(getContentEntry(), folder); } }); } private JComponent createFolderDeleteComponent(final ContentFolder folder, @NotNull ContentFolderTypeProvider editor) { final String tooltipText; if (folder.getFile() != null && getContentEntry().getFile() != null) { tooltipText = ProjectBundle.message("module.paths.unmark.0.tooltip", editor.getName()); } else { tooltipText = ProjectBundle.message("module.paths.remove.tooltip"); } return new IconActionComponent(AllIcons.Modules.DeleteContentFolder, AllIcons.Modules.DeleteContentFolderRollover, tooltipText, new Runnable() { @Override public void run() { myCallback.deleteContentFolder(getContentEntry(), folder); } }); } public boolean isExcludedOrUnderExcludedDirectory(final VirtualFile file) { final ContentEntry contentEntry = getContentEntry(); for (VirtualFile excludedDir : contentEntry.getFolderFiles(ContentFolderScopes.of(ExcludedContentFolderTypeProvider.getInstance()))) { if (VfsUtilCore.isAncestor(excludedDir, file, false)) { return true; } } return false; } protected static String toRelativeDisplayPath(String url, String ancestorUrl) { if (!StringUtil.endsWithChar(ancestorUrl, '/')) { ancestorUrl += "/"; } if (url.startsWith(ancestorUrl)) { return url.substring(ancestorUrl.length()).replace('/', File.separatorChar); } return toDisplayPath(url); } private static String toDisplayPath(final String url) { return VirtualFileManager.extractPath(url).replace('/', File.separatorChar); } public void setSelected(boolean selected) { if (selected) { myHeader.setBackground(SELECTED_HEADER_COLOR); setBackground(UIUtil.isUnderDarcula() ? UIUtil.getPanelBackground() : SELECTED_CONTENT_COLOR); myBottom.setBackground(UIUtil.isUnderDarcula() ? UIUtil.getPanelBackground() : SELECTED_HEADER_COLOR); for (final JComponent component : myComponentToForegroundMap.keySet()) { component.setForeground(myComponentToForegroundMap.get(component)); } } else { myHeader.setBackground(HEADER_COLOR); setBackground(CONTENT_COLOR); myBottom.setBackground(UIUtil.isUnderDarcula() ? UIUtil.getPanelBackground() : HEADER_COLOR); for (final JComponent component : myComponentToForegroundMap.keySet()) { component.setForeground(UNSELECTED_TEXT_COLOR); } } } private static class UnderlinedPathLabel extends ResizingWrapper { private static final float[] DASH = {0, 2, 0, 2}; private static final Color DASH_LINE_COLOR = new JBColor(Gray._201, Gray._100); public UnderlinedPathLabel(JLabel wrappedComponent) { super(wrappedComponent); FilePathClipper.install(wrappedComponent, this); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); final int startX = myWrappedComponent.getWidth(); final int endX = getWidth(); if (endX > startX) { final FontMetrics fontMetrics = myWrappedComponent.getFontMetrics(myWrappedComponent.getFont()); final int y = fontMetrics.getMaxAscent(); final Color savedColor = g.getColor(); g.setColor(DASH_LINE_COLOR); drawDottedLine((Graphics2D)g, startX, y, endX, y); g.setColor(savedColor); } } private void drawDottedLine(Graphics2D g, int x1, int y1, int x2, int y2) { /* // TODO!!! final Color color = g.getColor(); g.setColor(getBackground()); g.setColor(color); for (int i = x1 / 2 * 2; i < x2; i += 2) { g.drawRect(i, y1, 0, 0); } */ final Stroke saved = g.getStroke(); if (!SystemInfo.isMac && !UIUtil.isUnderDarcula()) { g.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, DASH, y1 % 2)); } if (UIUtil.isUnderDarcula()) { UIUtil.drawDottedLine(g, x1, y1, x2, y2, null, g.getColor()); } else { UIUtil.drawLine(g, x1, y1, x2, y2); } g.setStroke(saved); } } }