/* * Copyright 2003-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 jetbrains.mps.ide.ui.dialogs.properties.roots.editors; import com.intellij.icons.AllIcons.Modules; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.Gray; import com.intellij.ui.HoverHyperlinkLabel; import com.intellij.ui.JBColor; import com.intellij.ui.components.JBPanel; 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.EventDispatcher; import com.intellij.util.ui.JBInsets; import com.intellij.util.ui.UIUtil; import jetbrains.mps.extapi.persistence.FileBasedModelRoot; import jetbrains.mps.extapi.persistence.SourceRootKind; import jetbrains.mps.extapi.persistence.SourceRoot; import jetbrains.mps.ide.vfs.VirtualFileUtils; import jetbrains.mps.vfs.IFile; import jetbrains.mps.vfs.path.Path; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.ui.persistence.ModelRootEntry; import org.jetbrains.mps.openapi.ui.persistence.ModelRootEntryExt; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JLabel; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Stroke; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import static com.intellij.uiDesigner.core.GridConstraints.ANCHOR_EAST; import static com.intellij.uiDesigner.core.GridConstraints.ANCHOR_NORTHWEST; import static com.intellij.uiDesigner.core.GridConstraints.FILL_HORIZONTAL; import static com.intellij.uiDesigner.core.GridConstraints.FILL_NONE; import static com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_GROW; import static com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_CAN_SHRINK; import static com.intellij.uiDesigner.core.GridConstraints.SIZEPOLICY_FIXED; import static jetbrains.mps.ide.ui.dialogs.properties.roots.editors.FileBasedModelRootEditor.getKindText; public final class FileBasedModelRootEntry implements ModelRootEntry<FileBasedModelRoot>, ModelRootEntryExt { private final EventDispatcher<ModelRootEntryListener> myEventDispatcher = EventDispatcher.create(ModelRootEntryListener.class); private final FileBasedModelRoot myFileBasedModelRoot; private final Map<JComponent, Color> myComponentToForegroundMap = new HashMap<>(); private FileBasedModelRootEditor myFileBasedModelRootEditor; public FileBasedModelRootEntry(@NotNull FileBasedModelRoot modelRoot) { myFileBasedModelRoot = modelRoot; } @NotNull @Override public FileBasedModelRoot getModelRoot() { return myFileBasedModelRoot; } @NotNull @Override public String getDetailsText() { return ""; } @Nullable @Override public JComponent getDetailsComponent() { JBPanel panel = new JBPanel(new GridBagLayout()); for (SourceRootKind kind : myFileBasedModelRoot.getSupportedFileKinds1()) { Collection<SourceRoot> sourceRoots = myFileBasedModelRoot.getSourceRoots(kind); if (!sourceRoots.isEmpty()) { final JComponent kindComponent = createKindGroupComponent(getKindText(kind), sourceRoots, getKindColor(kind)); panel.add(kindComponent, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, new JBInsets(0, 0, 10, 0), 0, 0)); } } panel.setBackground(UIUtil.isUnderDarcula() ? UIUtil.getPanelBackground() : ModelRootEntryContainer.SELECTED_CONTENT_COLOR); return panel; } @Override public void setForegroundColor(Color foregroundColor) { for (final JComponent component : myComponentToForegroundMap.keySet()) { component.setForeground(foregroundColor); } } @Override public void resetForegroundColor() { for (final JComponent component : myComponentToForegroundMap.keySet()) { component.setForeground(myComponentToForegroundMap.get(component)); } } protected Color getKindColor(@NotNull SourceRootKind kind) { return kind.isExcluded() ? ModelRootEntryContainer.EXCLUDED_COLOR : ModelRootEntryContainer.SOURCES_COLOR; } protected Icon getKindIcon(SourceRootKind kind) { return kind.isExcluded() ? Modules.ExcludeRoot : Modules.SourceRoot; } protected JComponent createKindGroupComponent(String title, Collection<SourceRoot> sourceRoots, Color foregroundColor) { if (sourceRoots.isEmpty()) { return new JBPanel<>(); } final JBPanel<JBPanel> panel = new JBPanel<>(new GridLayoutManager(sourceRoots.size(), 3, new JBInsets(1, 17, 0, 2), 0, 1)); panel.setOpaque(false); int idx = 0; for (SourceRoot sourceRoot : sourceRoots) { int verticalPolicy = (idx == (sourceRoots.size() - 1)) ? SIZEPOLICY_CAN_GROW : SIZEPOLICY_FIXED; panel.add(createKindFileComponent(sourceRoot, foregroundColor), new GridConstraints(idx, 0, 1, 1, ANCHOR_NORTHWEST, FILL_HORIZONTAL, SIZEPOLICY_CAN_GROW | SIZEPOLICY_CAN_SHRINK, verticalPolicy, null, null, null)); int column = 1; int colspan = 2; panel.add(createKindFileDeleteComponent(sourceRoot), new GridConstraints(idx, column, 1, colspan, ANCHOR_EAST, FILL_NONE, SIZEPOLICY_FIXED, verticalPolicy, null, null, null)); idx++; } 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 JBPanel groupPanel = new JBPanel(new BorderLayout()); groupPanel.setOpaque(false); groupPanel.add(titleLabel, BorderLayout.NORTH); groupPanel.add(panel, BorderLayout.CENTER); return groupPanel; } private JComponent createKindFileComponent(@NotNull SourceRoot sourceRoot, @NotNull Color foreground) { String pathPresentation = sourceRoot.getPath(); if (pathPresentation.equals("")) { pathPresentation = "."; } JLabel label2Return = new JLabel(pathPresentation); VirtualFile virtualFile = VirtualFileUtils.getProjectVirtualFile(sourceRoot.getAbsolutePath()); if (virtualFile != null && virtualFile.exists()) { HoverHyperlinkLabel hyperlinkLabel = new HoverHyperlinkLabel(pathPresentation, foreground); hyperlinkLabel.setMinimumSize(new Dimension(0, 0)); hyperlinkLabel.addHyperlinkListener(e -> myFileBasedModelRootEditor.selectFile(sourceRoot.getAbsolutePath())); registerTextComponent(hyperlinkLabel, foreground); label2Return = hyperlinkLabel; } else { label2Return.setOpaque(false); label2Return.setForeground(Color.RED); } return new UnderlinedPathLabel(label2Return); } private JComponent createKindFileDeleteComponent(final SourceRoot sourceRoot) { final String tooltipText = "Remove"; return new IconActionComponent(Modules.DeleteContentFolder, Modules.DeleteContentFolderRollover, tooltipText, () -> { myFileBasedModelRoot.removeSourceRoot(sourceRoot); updateUI(); }); } private void registerTextComponent(final JComponent component, final Color foreground) { component.setForeground(foreground); myComponentToForegroundMap.put(component, foreground); } @Override public boolean isValid() { try { IFile contentDirectory = myFileBasedModelRoot.getContentDirectory(); return contentDirectory != null && contentDirectory.exists(); } catch (Exception e) { return false; } } @NotNull @Override public FileBasedModelRootEditor getEditor() { if (myFileBasedModelRootEditor == null) { myFileBasedModelRootEditor = new FileBasedModelRootEditor(); myFileBasedModelRootEditor.setFileBasedModelRootEntry(this); } return myFileBasedModelRootEditor; } @Override public void addModelRootEntryListener(@NotNull ModelRootEntryListener listener) { myEventDispatcher.addListener(listener); } @Override public void removeModelRootEntryListener(@NotNull ModelRootEntryListener listener) { myEventDispatcher.removeListener(listener); } public void updateUI() { myEventDispatcher.getMulticaster().fireDataChanged(); } @Override public void dispose() { } 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); } } }