Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User can now add custom icons, path ignore regex now works on relative path to project base dir #21

Merged
merged 15 commits into from
Feb 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,12 @@
id="genericIconProvider"
order="first"
implementation="lermitage.intellij.extra.icons.ExtraIconProvider"/>
<applicationService serviceImplementation="lermitage.intellij.extra.icons.cfg.settings.SettingsIDEService" />
<projectService serviceImplementation="lermitage.intellij.extra.icons.cfg.settings.SettingsProjectService"/>
<applicationService serviceImplementation="lermitage.intellij.extra.icons.cfg.services.impl.SettingsIDEService" />
<projectService serviceImplementation="lermitage.intellij.extra.icons.cfg.services.impl.SettingsProjectService"/>

<applicationConfigurable provider="lermitage.intellij.extra.icons.cfg.provider.IDEConfigurableProvider" id="LermitageExtraIcons"
<applicationConfigurable provider="lermitage.intellij.extra.icons.cfg.providers.IDEConfigurableProvider" id="LermitageExtraIcons"
displayName="Extra Icons" parentId="appearance"/>
<projectConfigurable provider="lermitage.intellij.extra.icons.cfg.provider.ProjectConfigurableProvider" id="LermitageExtraIcons-Project"
<projectConfigurable provider="lermitage.intellij.extra.icons.cfg.providers.ProjectConfigurableProvider" id="LermitageExtraIcons-Project"
displayName="Project" parentId="LermitageExtraIcons"/>
</extensions>

Expand Down
87 changes: 65 additions & 22 deletions src/lermitage/intellij/extra/icons/BaseIconProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,24 @@

import com.intellij.ide.IconProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import lermitage.intellij.extra.icons.cfg.SettingsService;
import lermitage.intellij.extra.icons.cfg.settings.SettingsIDEService;
import lermitage.intellij.extra.icons.cfg.settings.SettingsProjectService;
import lermitage.intellij.extra.icons.cfg.services.impl.SettingsIDEService;
import lermitage.intellij.extra.icons.cfg.services.impl.SettingsProjectService;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* @author Edoardo Luppi
Expand Down Expand Up @@ -44,24 +48,25 @@ protected boolean isSupported(@NotNull final PsiFile psiFile) {
return true;
}

private String parent(PsiFileSystemItem psiDirectory) {
return psiDirectory.getParent() == null ? null : psiDirectory.getParent().getName().toLowerCase();
private String parent(@NotNull PsiFileSystemItem fileSystemItem) {
return fileSystemItem.getParent() == null ? null : fileSystemItem.getParent().getName().toLowerCase();
}

@Nullable
@Override
public final Icon getIcon(@NotNull final PsiElement psiElement, final int flags) {
Project project = psiElement.getProject();
if (psiElement instanceof PsiDirectory) {
PsiDirectory psiDirectory = (PsiDirectory) psiElement;
final String parentName = parent(psiDirectory);
final String folderName = psiDirectory.getName().toLowerCase();
if (isPatternIgnored(psiElement.getProject(), parentName, folderName)) {
if (isPatternIgnored(project, psiDirectory.getVirtualFile())) {
return null;
}
final Optional<String> fullPath = getFullPath(psiDirectory);
for (final Model model : models) {
if (model.getModelType() == ModelType.DIR && isModelEnabled(psiElement.getProject(), model) && model.check(parentName, folderName, fullPath)) {
return IconLoader.getIcon(model.getIcon());
for (final Model model : getModelsIncludingUserModels(project)) {
if (model.getModelType() == ModelType.DIR && isModelEnabled(project, model) && model.check(parentName, folderName, fullPath)) {
return CustomIconLoader.getIcon(model);
}
}
} else {
Expand All @@ -70,13 +75,13 @@ public final Icon getIcon(@NotNull final PsiElement psiElement, final int flags)
if (optFile.isPresent() && isSupported(file = optFile.get())) {
final String parentName = parent(file);
final String fileName = file.getName().toLowerCase();
if (isPatternIgnored(psiElement.getProject(), parentName, fileName)) {
if (isPatternIgnored(project, file.getVirtualFile())) {
return null;
}
final Optional<String> fullPath = getFullPath(file);
for (final Model model : models) {
if (model.getModelType() == ModelType.FILE && isModelEnabled(psiElement.getProject(), model) && model.check(parentName, fileName, fullPath)) {
return IconLoader.getIcon(model.getIcon());
for (final Model model : getModelsIncludingUserModels(project)) {
if (model.getModelType() == ModelType.FILE && isModelEnabled(project, model) && model.check(parentName, fileName, fullPath)) {
return CustomIconLoader.getIcon(model);
}
}
}
Expand All @@ -85,35 +90,73 @@ public final Icon getIcon(@NotNull final PsiElement psiElement, final int flags)
return null;
}

/**
* Depending on whether the checkbox in the settings is checked, this method appends
* the user added models to the model list.
*/
private List<Model> getModelsIncludingUserModels(@Nullable Project project) {
SettingsService service = SettingsService.getInstance(project);
SettingsProjectService projectService = (SettingsProjectService) service;
Stream<Model> customModelsStream;

if (projectService.isOverrideIDESettings()) {
if (projectService.isAddToIDEUserIcons()) {
customModelsStream = Stream.concat(SettingsIDEService.getInstance().getCustomModels().stream(),
projectService.getCustomModels().stream());
}
else {
customModelsStream = projectService.getCustomModels().stream();
}
}
else {
customModelsStream = SettingsIDEService.getInstance().getCustomModels().stream();
}

return Stream.concat(customModelsStream, models.stream()).collect(Collectors.toList());
}

private Optional<String> getFullPath(@NotNull PsiFileSystemItem file) {
if (file.getVirtualFile() != null) {
return Optional.of(file.getVirtualFile().getPath().toLowerCase());
}
return Optional.empty();
}

private boolean isModelEnabled(Project project, Model model) {
private boolean isModelEnabled(@Nullable Project project, @NotNull Model model) {
SettingsService service = getSettingsService(project);
return !service.getDisabledModelIds().contains(model.getId()) && model.isEnabled();
}

/**
* Returns the project service if the checkbox in the project settings was checked,
* otherwise returns the IDE settings service.
*/
private SettingsService getSettingsService(Project project) {
SettingsService service = SettingsService.getInstance(project);
if (!((SettingsProjectService) service).isOverrideIDESettings()) {
service = SettingsIDEService.getInstance();
}
return !service.getDisabledModelIds().contains(model.getId());
return service;
}

/**
* Indicates if given file/folder should be ignored.
* @param project project.
* @param parent optional parent.
* @param file current file or folder.
*/
private boolean isPatternIgnored(Project project, String parent, String file) {
SettingsService service = SettingsService.getInstance(project);
if (!((SettingsProjectService) service).isOverrideIDESettings()) {
service = SettingsIDEService.getInstance();
}
private boolean isPatternIgnored(Project project, VirtualFile file) {
SettingsService service = getSettingsService(project);
if (service.getIgnoredPatternObj() == null || service.getIgnoredPattern() == null || service.getIgnoredPattern().isEmpty()) {
return false;
}
return service.getIgnoredPatternObj().matcher(parent == null ? "" : parent + "/" + file).matches();
VirtualFile contentRoot = ProjectFileIndex.SERVICE.getInstance(project).getContentRootForFile(file);
if (contentRoot == null) {
return false;
}
String relativePath = VfsUtilCore.getRelativePath(file, contentRoot);
if (relativePath == null) {
return false;
}
return service.getIgnoredPatternObj().matcher(relativePath).matches();
}
}
165 changes: 165 additions & 0 deletions src/lermitage/intellij/extra/icons/CustomIconLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package lermitage.intellij.extra.icons;

import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.*;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class CustomIconLoader {

public static Icon getIcon(Model model) {
if (model.getIconType() == IconType.PATH) {
return IconLoader.getIcon(model.getIcon());
}
if (model.getIconType() == IconType.ICON) {
return model.getIntelliJIcon();
}
ImageWrapper fromBase64 = fromBase64(model.getIcon(), model.getIconType());
if (fromBase64 == null) {
return null;
}
return IconUtil.createImageIcon(fromBase64.getImage());
}

public static ImageWrapper loadFromVirtualFile(VirtualFile virtualFile) throws IllegalArgumentException {
if (virtualFile.getExtension() != null) {
Image image;
IconType iconType;
byte[] fileContents;
try {
fileContents = virtualFile.contentsToByteArray();
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(fileContents);
if (virtualFile.getExtension().equals("svg") && new String(fileContents).startsWith("<")) {
iconType = IconType.SVG;
image = SVGLoader.load(byteArrayInputStream, 1.0f);
} else {
iconType = IconType.IMG;
image = ImageLoader.loadFromStream(byteArrayInputStream);
}
} catch (IOException ex) {
throw new IllegalArgumentException("IOException while trying to load image.");
}
if (image == null) {
throw new IllegalArgumentException("Could not load image properly.");
}
return new ImageWrapper(iconType, scaleImage(image), fileContents);
}
return null;
}

public static ImageWrapper fromBase64(String base64, IconType iconType) {
byte[] decodedBase64 = Base64.decode(base64);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decodedBase64);
Image image = null;
try {
switch (iconType) {
case SVG:
image = SVGLoader.load(byteArrayInputStream, 1.0f);
break;
case IMG:
image = ImageLoader.loadFromStream(byteArrayInputStream);
break;
}
}
catch (IOException ex) {
return null;
}
if (image == null) {
return null;
}
return new ImageWrapper(iconType, scaleImage(image), decodedBase64);
}

public static String toBase64(ImageWrapper imageWrapper) {
String base64 = null;
switch (imageWrapper.getIconType()) {
case SVG:
base64 = Base64.encode(imageWrapper.getImageAsByteArray());
break;
case IMG:
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
Image image = imageWrapper.getImage();
if (image instanceof JBHiDPIScaledImage) {
image = ((JBHiDPIScaledImage) image).getDelegate();
}
ImageIO.write((BufferedImage) image, "png", outputStream);
}
catch (IOException ignored) {

}
base64 = Base64.encode(outputStream.toByteArray());
break;
}
return base64;
}

private static Image scaleImage(Image image) {
int width = image.getWidth(null);
int height = image.getHeight(null);

if (width != height) {
throw new IllegalArgumentException("Image should be square.");
}

if (width <= 0) {
throw new IllegalArgumentException("Width and height are unknown.");
}

if (width == 16) {
return image;
}

if (width == 32) {
return RetinaImage.createFrom(image);
}

float widthToScaleTo = 16f;
boolean retina = false;

if (width >= 32) {
widthToScaleTo = 32f;
retina = true;
}

Image scaledImage = ImageLoader.scaleImage(image, widthToScaleTo / width);

if (retina) {
return RetinaImage.createFrom(scaledImage);
}

return scaledImage;
}

public static class ImageWrapper {

private final IconType iconType;
private final Image image;
private final byte[] imageAsByteArray;

public ImageWrapper(IconType iconType, Image image, byte[] imageAsByteArray) {
this.iconType = iconType;
this.image = image;
this.imageAsByteArray = imageAsByteArray;
}

public IconType getIconType() {
return iconType;
}

public Image getImage() {
return image;
}

public byte[] getImageAsByteArray() {
return imageAsByteArray;
}
}
}
8 changes: 8 additions & 0 deletions src/lermitage/intellij/extra/icons/IconType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package lermitage.intellij.extra.icons;

public enum IconType {
PATH,
ICON,
SVG,
IMG
}
Loading