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
;
40 import javax
.swing
.event
.HyperlinkEvent
;
41 import javax
.swing
.event
.HyperlinkListener
;
44 import java
.util
.HashMap
;
48 * @author Eugene Zhuravlev
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
;
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));
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() {
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
);
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));
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));
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
);
156 protected JComponent
createAdditionalComponent(ContentFolder folder
) {
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
);
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");
213 tooltipText
= ProjectBundle
.message("module.paths.remove.tooltip");
215 return new IconActionComponent(DELETE_FOLDER_ICON
, DELETE_FOLDER_ROLLOVER_ICON
, tooltipText
, new Runnable() {
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) {
231 final ExcludeFolder
[] excludeFolders
= contentEntry
.getExcludeFolders();
232 for (ExcludeFolder excludeFolder
: excludeFolders
) {
233 final VirtualFile excludedDir
= excludeFolder
.getFile();
234 if (excludedDir
== null) {
237 if (VfsUtil
.isAncestor(excludedDir
, file
, true)) {
245 public ExcludeFolder
getExcludeFolder(VirtualFile file
) {
246 final ContentEntry contentEntry
= getContentEntry();
247 if (contentEntry
== null) {
250 final ExcludeFolder
[] excludeFolders
= contentEntry
.getExcludeFolders();
251 for (final ExcludeFolder excludeFolder
: excludeFolders
) {
252 final VirtualFile f
= excludeFolder
.getFile();
256 if (f
.equals(file
)) {
257 return excludeFolder
;
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
) {
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
));
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();
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
) {
323 final Color color = g.getColor();
324 g.setColor(getBackground());
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
);
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
);