/*
* Copyright 2015 Marc Prengemann
*
* 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 de.mprengemann.intellij.plugin.androidicons.dialogs;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileChooser.FileChooser;
import com.intellij.openapi.fileChooser.FileChooserDescriptor;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbAwareRunnable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Consumer;
import com.intellij.util.io.ZipUtil;
import com.intellij.util.ui.UIUtil;
import de.mprengemann.intellij.plugin.androidicons.IconApplication;
import de.mprengemann.intellij.plugin.androidicons.controllers.multi.IMultiImporterController;
import de.mprengemann.intellij.plugin.androidicons.controllers.multi.MultiImporterController;
import de.mprengemann.intellij.plugin.androidicons.controllers.multi.MultiImporterObserver;
import de.mprengemann.intellij.plugin.androidicons.controllers.settings.ISettingsController;
import de.mprengemann.intellij.plugin.androidicons.listeners.SimpleMouseListener;
import de.mprengemann.intellij.plugin.androidicons.model.Format;
import de.mprengemann.intellij.plugin.androidicons.model.ImageInformation;
import de.mprengemann.intellij.plugin.androidicons.model.Resolution;
import de.mprengemann.intellij.plugin.androidicons.util.ImageUtils;
import de.mprengemann.intellij.plugin.androidicons.widgets.ExportNameField;
import de.mprengemann.intellij.plugin.androidicons.widgets.FileBrowserField;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.activation.MimetypesFileTypeMap;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class AndroidMultiDrawableImporter extends DialogWrapper implements MultiImporterObserver {
private final FileChooserDescriptor archiveDescriptor = new FileChooserDescriptor(true,
false,
true,
true,
false,
false) {
@Override
public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
return file.isDirectory() || isZip(file.getName());
}
@Override
public boolean isFileSelectable(VirtualFile file) {
return isZip(file.getName());
}
private boolean isZip(String name) {
return name.contains(".zip");
}
};
private static final Resolution[] RESOLUTIONS = new Resolution[]{
Resolution.XXXHDPI,
Resolution.XXHDPI,
Resolution.XHDPI,
Resolution.HDPI,
Resolution.MDPI,
Resolution.LDPI,
Resolution.TVDPI
};
private static final String TAG = AndroidMultiDrawableImporter.class.getSimpleName();
private static final Logger LOGGER = Logger.getInstance(TAG);
private final Project project;
private final Module module;
private final IMultiImporterController controller;
private Action zipImportAction;
private FileBrowserField resRoot;
private FileBrowserField ldpiFile;
private FileBrowserField mdpiFile;
private FileBrowserField hdpiFile;
private FileBrowserField xhdpiFile;
private FileBrowserField xxhdpiFile;
private FileBrowserField xxxhdpiFile;
private FileBrowserField tvdpiFile;
private JLabel imageContainer;
private ExportNameField resExportName;
private JPanel uiContainer;
private JComboBox formatSpinner;
private final ActionListener formatListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final JComboBox source = (JComboBox) e.getSource();
final Format selectedItem = (Format) source.getSelectedItem();
controller.setFormat(selectedItem);
}
};
private final ISettingsController settingsController;
private final IconApplication container;
public AndroidMultiDrawableImporter(final Project project, final Module module) {
super(project, true);
this.project = project;
this.module = module;
container = ApplicationManager.getApplication().getComponent(IconApplication.class);
settingsController = container.getControllerFactory().getSettingsController();
this.controller = new MultiImporterController(container.getControllerFactory().getDefaultsController());
this.controller.addObserver(this);
initResourceRoot();
initBrowser(Resolution.LDPI, ldpiFile);
initBrowser(Resolution.MDPI, mdpiFile);
initBrowser(Resolution.HDPI, hdpiFile);
initBrowser(Resolution.XHDPI, xhdpiFile);
initBrowser(Resolution.XXHDPI, xxhdpiFile);
initBrowser(Resolution.XXXHDPI, xxxhdpiFile);
initBrowser(Resolution.TVDPI, tvdpiFile);
setTitle("Android Multi Drawable Importer");
init();
pack();
}
@Nullable
@Override
protected ValidationInfo doValidate() {
if (StringUtils.isEmpty(controller.getTargetRoot())) {
return new ValidationInfo("Please select the resources root.", resRoot);
}
if (StringUtils.isEmpty(controller.getExportName())) {
return new ValidationInfo("Please select a name for the drawable.", resExportName);
}
return super.doValidate();
}
@Override
protected void doOKAction() {
controller.getTask(project).queue();
if (!controller.containsNinePatch()) {
container.getControllerFactory().getDefaultsController().setFormat(controller.getFormat());
}
super.doOKAction();
}
@Override
public void updated() {
updateImage();
updateTargetRoot();
updateName();
updateFormat();
}
private void updateFormat() {
formatSpinner.removeActionListener(formatListener);
formatSpinner.removeAllItems();
for (Format format : Format.nonVectorValues()) {
formatSpinner.addItem(format);
}
formatSpinner.setSelectedItem(controller.getFormat());
formatSpinner.setEnabled(!controller.containsNinePatch());
formatSpinner.addActionListener(formatListener);
}
private void updateTargetRoot() {
final String targetRoot = controller.getTargetRoot();
if (targetRoot == null) {
return;
}
resRoot.setText(targetRoot);
}
private void updateName() {
final String exportName = controller.getExportName();
if (exportName == null) {
return;
}
resExportName.setText(exportName);
resExportName.addPropertyChangeListener("value", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
controller.setExportName(((String) resExportName.getValue()));
}
});
}
private void updateImage() {
final File file = controller.getMostRecentImage();
if (file == null) {
return;
}
ImageUtils.updateImage(imageContainer, file, controller.getFormat());
}
@Nullable
@Override
protected JComponent createCenterPanel() {
return uiContainer;
}
private void initResourceRoot() {
resRoot.setSelectionListener(new Consumer<File>() {
@Override
public void consume(File file) {
controller.setTargetRoot(file);
}
});
resRoot.initWithResourceRoot(project, module, settingsController);
}
private void initBrowser(final Resolution resolution, final FileBrowserField fileBrowser) {
fileBrowser.init(project, settingsController);
fileBrowser.setSelectionListener(new Consumer<File>() {
@Override
public void consume(File file) {
controller.addImage(file, resolution);
}
});
fileBrowser.getTextField().addMouseListener(new SimpleMouseListener() {
@Override
public void mouseClicked(MouseEvent mouseEvent) {
controller.setMostRecentImage(resolution);
}
});
}
private void createUIComponents() {
resRoot = new FileBrowserField(FileBrowserField.RESOURCE_DIR_CHOOSER);
ldpiFile = new FileBrowserField(FileBrowserField.IMAGE_FILE_CHOOSER);
mdpiFile = new FileBrowserField(FileBrowserField.IMAGE_FILE_CHOOSER);
hdpiFile = new FileBrowserField(FileBrowserField.IMAGE_FILE_CHOOSER);
xhdpiFile = new FileBrowserField(FileBrowserField.IMAGE_FILE_CHOOSER);
xxhdpiFile = new FileBrowserField(FileBrowserField.IMAGE_FILE_CHOOSER);
xxxhdpiFile = new FileBrowserField(FileBrowserField.IMAGE_FILE_CHOOSER);
tvdpiFile = new FileBrowserField(FileBrowserField.IMAGE_FILE_CHOOSER);
}
@Override
protected void createDefaultActions() {
super.createDefaultActions();
this.zipImportAction = new AndroidMultiDrawableImporter.ZipImportAction();
}
protected final Action getZipImportAction() {
return this.zipImportAction;
}
private void openZipArchive() {
FileChooser.chooseFile(archiveDescriptor, project, getInitialFile(), new Consumer<VirtualFile>() {
@Override
public void consume(VirtualFile virtualFile) {
if (virtualFile == null) {
return;
}
final String filePath = virtualFile.getCanonicalPath();
if (filePath == null) {
return;
}
settingsController.saveLastImageFolder(filePath);
controller.resetZipInformation();
importZipArchive(virtualFile);
}
});
}
private void importZipArchive(VirtualFile virtualFile) {
final String filePath = virtualFile.getCanonicalPath();
if (filePath == null) {
return;
}
final File tempDir = new File(ImageInformation.getTempDir(), virtualFile.getNameWithoutExtension());
final String archiveName = virtualFile.getName();
new Task.Modal(project, "Importing Archive...", true) {
@Override
public void run(@NotNull final ProgressIndicator progressIndicator) {
progressIndicator.setIndeterminate(true);
try {
FileUtils.forceMkdir(tempDir);
ZipUtil.extract(new File(filePath), tempDir, new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
final String mimeType = new MimetypesFileTypeMap().getContentType(name);
final String type = mimeType.split("/")[0];
return type.equals("image");
}
}, true);
progressIndicator.checkCanceled();
final Iterator<File> fileIterator = FileUtils.iterateFiles(tempDir, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
while (fileIterator.hasNext()) {
File file = fileIterator.next();
if (file.isDirectory() || file.isHidden()) {
continue;
}
final String fileRoot = file.getParent().toUpperCase();
final String name = FilenameUtils.getBaseName(file.toString());
if (name.startsWith(".") ||
fileRoot.contains("__MACOSX")) {
continue;
}
for (Resolution resolution : RESOLUTIONS) {
if (name.toUpperCase().contains("-" + resolution) ||
name.toUpperCase().contains("_" + resolution) ||
fileRoot.contains(resolution.toString())) {
controller.addZipImage(file, resolution);
break;
}
}
}
progressIndicator.checkCanceled();
final Map<Resolution, List<ImageInformation>> zipImages = controller.getZipImages();
final List<Resolution> foundResolutions = new ArrayList<Resolution>();
int foundAssets = 0;
for (Resolution resolution : zipImages.keySet()) {
final List<ImageInformation> assetInformation = zipImages.get(resolution);
if (assetInformation != null && !assetInformation.isEmpty()) {
foundAssets += assetInformation.size();
foundResolutions.add(resolution);
}
}
progressIndicator.checkCanceled();
final int finalFoundAssets = foundAssets;
UIUtil.invokeLaterIfNeeded(new DumbAwareRunnable() {
public void run() {
final String title = String.format("Import '%s'", archiveName);
if (foundResolutions.isEmpty() || finalFoundAssets == 0) {
Messages.showErrorDialog("No assets found.", title);
FileUtils.deleteQuietly(tempDir);
return;
}
final String[] options = new String[] {"Import", "Cancel"};
final String description = String.format("Import %d assets for %s to %s.",
finalFoundAssets,
StringUtils.join(foundResolutions, ", "),
controller.getTargetRoot());
final int selection = Messages.showDialog(description,
title,
options,
0,
Messages.getQuestionIcon());
if (selection == 0) {
controller.getZipTask(project, tempDir).queue();
close(0);
} else {
FileUtils.deleteQuietly(tempDir);
}
}
});
} catch (ProcessCanceledException e) {
FileUtils.deleteQuietly(tempDir);
} catch (IOException e) {
LOGGER.error(e);
}
}
}.queue();
}
private VirtualFile getInitialFile() {
String directoryName = settingsController.getLastImageFolder();
VirtualFile path;
String expandPath = expandPath(directoryName);
if (expandPath == null) {
return null;
}
for (path = LocalFileSystem.getInstance().findFileByPath(expandPath);
path == null && directoryName.length() > 0;
path = LocalFileSystem.getInstance().findFileByPath(directoryName)) {
int pos = directoryName.lastIndexOf(47);
if (pos <= 0) {
break;
}
directoryName = directoryName.substring(0, pos);
}
return path;
}
private String expandPath(String dirName) {
if (project != null) {
dirName = PathMacroManager.getInstance(project).expandPath(dirName);
}
if (module != null) {
dirName = PathMacroManager.getInstance(module).expandPath(dirName);
}
return dirName;
}
@Override
@NotNull
protected Action[] createActions() {
final ArrayList<Action> actions = new ArrayList<Action>();
actions.add(this.getZipImportAction());
actions.add(this.getCancelAction());
actions.add(this.getOKAction());
if (SystemInfo.isMac) {
Collections.reverse(actions);
}
return actions.toArray(new Action[actions.size()]);
}
private class ZipImportAction extends AbstractAction {
public ZipImportAction() {
this.putValue("Name", "Import Zip Archive");
}
public void actionPerformed(ActionEvent e) {
AndroidMultiDrawableImporter.this.openZipArchive();
}
}
}