modifiable root model: stateless content entry editor
[fedora-idea.git] / platform / lang-impl / src / com / intellij / openapi / roots / ui / configuration / ContentRootPanel.java
blobf8d09d474f2436a27e425d0450c74bbd1bd06bf2
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.openapi.roots.ui.configuration;
19 import com.intellij.openapi.project.ProjectBundle;
20 import com.intellij.openapi.roots.ContentEntry;
21 import com.intellij.openapi.roots.ContentFolder;
22 import com.intellij.openapi.roots.ExcludeFolder;
23 import com.intellij.openapi.roots.SourceFolder;
24 import com.intellij.openapi.util.IconLoader;
25 import com.intellij.openapi.util.SystemInfo;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.openapi.vfs.VfsUtil;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.openapi.vfs.VirtualFileManager;
30 import com.intellij.ui.HoverHyperlinkLabel;
31 import com.intellij.ui.roots.FilePathClipper;
32 import com.intellij.ui.roots.IconActionComponent;
33 import com.intellij.ui.roots.ResizingWrapper;
34 import com.intellij.uiDesigner.core.GridConstraints;
35 import com.intellij.uiDesigner.core.GridLayoutManager;
36 import com.intellij.util.ui.UIUtil;
37 import org.jetbrains.annotations.Nullable;
39 import javax.swing.*;
40 import javax.swing.event.HyperlinkEvent;
41 import javax.swing.event.HyperlinkListener;
42 import java.awt.*;
43 import java.io.File;
44 import java.util.HashMap;
45 import java.util.Map;
47 /**
48 * @author Eugene Zhuravlev
49 * Date: Jan 19, 2004
51 public abstract class ContentRootPanel extends JPanel {
52 protected static final Color TESTS_COLOR = new Color(0x008C2E);
53 protected static final Color EXCLUDED_COLOR = new Color(0x992E00);
54 private static final Color SELECTED_HEADER_COLOR = new Color(0xDEF2FF);
55 private static final Color HEADER_COLOR = new Color(0xF5F5F5);
56 private static final Color SELECTED_CONTENT_COLOR = new Color(0xF0F9FF);
57 private static final Color CONTENT_COLOR = Color.WHITE;
58 private static final Color UNSELECTED_TEXT_COLOR = new Color(0x333333);
60 private static final Icon DELETE_ROOT_ICON = IconLoader.getIcon("/modules/deleteContentRoot.png");
61 private static final Icon DELETE_ROOT_ROLLOVER_ICON = IconLoader.getIcon("/modules/deleteContentRootRollover.png");
62 private static final Icon DELETE_FOLDER_ICON = IconLoader.getIcon("/modules/deleteContentFolder.png");
63 private static final Icon DELETE_FOLDER_ROLLOVER_ICON = IconLoader.getIcon("/modules/deleteContentFolderRollover.png");
65 protected final ActionCallback myCallback;
66 private JComponent myHeader;
67 private JComponent myBottom;
68 private final Map<JComponent, Color> myComponentToForegroundMap = new HashMap<JComponent, Color>();
70 public interface ActionCallback {
71 void deleteContentEntry();
72 void deleteContentFolder(ContentEntry contentEntry, ContentFolder contentFolder);
73 void navigateFolder(ContentEntry contentEntry, ContentFolder contentFolder);
74 void setPackagePrefix(SourceFolder folder, String prefix);
77 public ContentRootPanel(ActionCallback callback) {
78 super(new GridBagLayout());
79 myCallback = callback;
82 @Nullable
83 protected abstract ContentEntry getContentEntry();
85 public void initUI() {
86 myHeader = createHeader();
87 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));
89 addFolderGroupComponents();
91 myBottom = new JPanel(new BorderLayout());
92 myBottom.add(Box.createVerticalStrut(3), BorderLayout.NORTH);
93 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));
95 setSelected(false);
98 protected abstract void addFolderGroupComponents();
100 private JComponent createHeader() {
101 final JPanel panel = new JPanel(new GridBagLayout());
102 final JLabel headerLabel = new JLabel(toDisplayPath(getContentEntry().getUrl()));
103 headerLabel.setFont(headerLabel.getFont().deriveFont(Font.BOLD));
104 headerLabel.setOpaque(false);
105 if (getContentEntry().getFile() == null) {
106 headerLabel.setForeground(Color.RED);
108 final IconActionComponent deleteIconComponent = new IconActionComponent(DELETE_ROOT_ICON, DELETE_ROOT_ROLLOVER_ICON,
109 ProjectBundle.message("module.paths.remove.content.tooltip"), new Runnable() {
110 public void run() {
111 myCallback.deleteContentEntry();
114 final ResizingWrapper wrapper = new ResizingWrapper(headerLabel);
115 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));
116 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));
117 FilePathClipper.install(headerLabel, wrapper);
118 return panel;
121 protected JComponent createFolderGroupComponent(String title, ContentFolder[] folders, Color foregroundColor) {
122 final JPanel panel = new JPanel(new GridLayoutManager(folders.length, 3, new Insets(1, 17, 0, 2), 0, 1));
123 panel.setOpaque(false);
125 for (int idx = 0; idx < folders.length; idx++) {
126 final ContentFolder folder = folders[idx];
127 final int verticalPolicy = idx == folders.length - 1? GridConstraints.SIZEPOLICY_CAN_GROW : GridConstraints.SIZEPOLICY_FIXED;
128 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));
129 int column = 1;
130 int colspan = 2;
132 JComponent additionalComponent = createAdditionalComponent(folder);
133 if (additionalComponent != null) {
134 panel.add(additionalComponent, new GridConstraints(idx, column++, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, verticalPolicy, null, null, null));
135 colspan = 1;
137 panel.add(createFolderDeleteComponent(folder), new GridConstraints(idx, column, 1, colspan, GridConstraints.ANCHOR_EAST, GridConstraints.FILL_NONE, GridConstraints.SIZEPOLICY_FIXED, verticalPolicy, null, null, null));
140 final JLabel titleLabel = new JLabel(title);
141 final Font labelFont = UIUtil.getLabelFont();
142 titleLabel.setFont(labelFont.deriveFont(Font.BOLD).deriveFont((float)labelFont.getSize() - 0.5f ));
143 titleLabel.setOpaque(false);
144 titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 0));
145 registerTextComponent(titleLabel, foregroundColor);
147 final JPanel groupPanel = new JPanel(new BorderLayout());
148 groupPanel.setOpaque(false);
149 groupPanel.add(titleLabel, BorderLayout.NORTH);
150 groupPanel.add(panel, BorderLayout.CENTER);
152 return groupPanel;
155 @Nullable
156 protected JComponent createAdditionalComponent(ContentFolder folder) {
157 return null;
160 private void registerTextComponent(final JComponent component, final Color foreground) {
161 component.setForeground(foreground);
162 myComponentToForegroundMap.put(component, foreground);
165 private JComponent createFolderComponent(final ContentFolder folder, Color foreground) {
166 final VirtualFile folderFile = folder.getFile();
167 final VirtualFile contentEntryFile = getContentEntry().getFile();
168 final String packagePrefix = folder instanceof SourceFolder? ((SourceFolder)folder).getPackagePrefix() : "";
169 if (folderFile != null && contentEntryFile != null) {
170 String path = folderFile.equals(contentEntryFile)? "." :VfsUtil.getRelativePath(folderFile, contentEntryFile, File.separatorChar);
171 if (packagePrefix.length() > 0) {
172 path = path + " (" + packagePrefix + ")";
174 HoverHyperlinkLabel hyperlinkLabel = new HoverHyperlinkLabel(path, foreground);
175 hyperlinkLabel.setMinimumSize(new Dimension(0, 0));
176 hyperlinkLabel.addHyperlinkListener(new HyperlinkListener() {
177 public void hyperlinkUpdate(HyperlinkEvent e) {
178 myCallback.navigateFolder(getContentEntry(), folder);
181 registerTextComponent(hyperlinkLabel, foreground);
182 return new UnderlinedPathLabel(hyperlinkLabel);
184 else {
185 String path = toRelativeDisplayPath(folder.getUrl(), getContentEntry().getUrl());
186 if (packagePrefix.length() > 0) {
187 path = path + " (" + packagePrefix + ")";
189 final JLabel pathLabel = new JLabel(path);
190 pathLabel.setOpaque(false);
191 pathLabel.setForeground(Color.RED);
193 return new UnderlinedPathLabel(pathLabel);
197 private JComponent createFolderDeleteComponent(final ContentFolder folder) {
198 final String tooltipText;
199 if (folder.getFile() != null && getContentEntry().getFile() != null) {
200 if (folder instanceof SourceFolder) {
201 tooltipText = ((SourceFolder)folder).isTestSource()
202 ? ProjectBundle.message("module.paths.unmark.tests.tooltip")
203 : ProjectBundle.message("module.paths.unmark.source.tooltip");
205 else if (folder instanceof ExcludeFolder) {
206 tooltipText = ProjectBundle.message("module.paths.include.excluded.tooltip");
208 else {
209 tooltipText = null;
212 else {
213 tooltipText = ProjectBundle.message("module.paths.remove.tooltip");
215 return new IconActionComponent(DELETE_FOLDER_ICON, DELETE_FOLDER_ROLLOVER_ICON, tooltipText, new Runnable() {
216 public void run() {
217 myCallback.deleteContentFolder(getContentEntry(), folder);
222 public boolean isExcluded(VirtualFile file) {
223 return getExcludeFolder(file) != null;
226 public boolean isUnderExcludedDirectory(final VirtualFile file) {
227 final ContentEntry contentEntry = getContentEntry();
228 if (contentEntry == null) {
229 return false;
231 final ExcludeFolder[] excludeFolders = contentEntry.getExcludeFolders();
232 for (ExcludeFolder excludeFolder : excludeFolders) {
233 final VirtualFile excludedDir = excludeFolder.getFile();
234 if (excludedDir == null) {
235 continue;
237 if (VfsUtil.isAncestor(excludedDir, file, true)) {
238 return true;
241 return false;
244 @Nullable
245 public ExcludeFolder getExcludeFolder(VirtualFile file) {
246 final ContentEntry contentEntry = getContentEntry();
247 if (contentEntry == null) {
248 return null;
250 final ExcludeFolder[] excludeFolders = contentEntry.getExcludeFolders();
251 for (final ExcludeFolder excludeFolder : excludeFolders) {
252 final VirtualFile f = excludeFolder.getFile();
253 if (f == null) {
254 continue;
256 if (f.equals(file)) {
257 return excludeFolder;
260 return null;
263 protected static String toRelativeDisplayPath(String url, String ancestorUrl) {
264 if (!StringUtil.endsWithChar(ancestorUrl, '/')) {
265 ancestorUrl = ancestorUrl + "/";
267 if (url.startsWith(ancestorUrl)) {
268 return url.substring(ancestorUrl.length()).replace('/', File.separatorChar);
270 return toDisplayPath(url);
273 private static String toDisplayPath(final String url) {
274 return VirtualFileManager.extractPath(url).replace('/', File.separatorChar);
278 public void setSelected(boolean selected) {
279 if (selected) {
280 myHeader.setBackground(SELECTED_HEADER_COLOR);
281 setBackground(SELECTED_CONTENT_COLOR);
282 myBottom.setBackground(SELECTED_HEADER_COLOR);
283 for (final JComponent component : myComponentToForegroundMap.keySet()) {
284 component.setForeground(myComponentToForegroundMap.get(component));
287 else {
288 myHeader.setBackground(HEADER_COLOR);
289 setBackground(CONTENT_COLOR);
290 myBottom.setBackground(HEADER_COLOR);
291 for (final JComponent component : myComponentToForegroundMap.keySet()) {
292 component.setForeground(UNSELECTED_TEXT_COLOR);
297 private static class UnderlinedPathLabel extends ResizingWrapper {
298 private static final float[] DASH = new float[]{0, 2, 0, 2};
299 private static final Color DASH_LINE_COLOR = new Color(0xC9C9C9);
301 public UnderlinedPathLabel(JLabel wrappedComponent) {
302 super(wrappedComponent);
303 FilePathClipper.install(wrappedComponent, this);
306 protected void paintComponent(Graphics g) {
307 super.paintComponent(g);
308 final int startX = myWrappedComponent.getWidth();
309 final int endX = getWidth();
310 if (endX > startX) {
311 final FontMetrics fontMetrics = myWrappedComponent.getFontMetrics(myWrappedComponent.getFont());
312 final int y = fontMetrics.getMaxAscent();
313 final Color savedColor = g.getColor();
314 g.setColor(DASH_LINE_COLOR);
315 drawDottedLine((Graphics2D)g, startX, y, endX, y);
316 g.setColor(savedColor);
320 private void drawDottedLine(Graphics2D g, int x1, int y1, int x2, int y2) {
322 // TODO!!!
323 final Color color = g.getColor();
324 g.setColor(getBackground());
325 g.setColor(color);
326 for (int i = x1 / 2 * 2; i < x2; i += 2) {
327 g.drawRect(i, y1, 0, 0);
330 if (SystemInfo.isMac) {
331 UIUtil.drawLine(g, x1, y1, x2, y2);
333 else {
334 final Stroke saved = g.getStroke();
335 g.setStroke(new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, DASH, y1 % 2));
337 UIUtil.drawLine(g, x1, y1, x2, y2);
339 g.setStroke(saved);