package com.aurimasniekis.phpclasstemplates.dialog;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.actionSystem.KeyboardShortcut;
import com.intellij.openapi.actionSystem.Shortcut;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiManager;
import com.intellij.ui.EditorTextField;
import com.intellij.util.Alarm;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PathUtil;
import com.intellij.util.PlatformIcons;
import com.intellij.util.ui.FormBuilder;
import com.jetbrains.php.PhpBundle;
import com.jetbrains.php.actions.PhpNewFileDialog;
import com.jetbrains.php.lang.PhpLangUtil;
import com.jetbrains.php.refactoring.PhpNameUtil;
import com.jetbrains.php.roots.PhpDirectoryByPsrProvider;
import com.jetbrains.php.roots.PhpNamespaceCompositeProvider;
import com.jetbrains.php.roots.ui.PhpNamespaceComboBox;
import com.jetbrains.php.roots.ui.PhpPsrDirectoryComboBox;
import com.jetbrains.php.templates.PhpCreateFileFromTemplateDataProvider;
import com.jetbrains.php.templates.PhpFileTemplateUtil;
import com.jetbrains.php.templates.PhpTemplatesSettings;
import com.jetbrains.php.ui.PhpUiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.io.File;
import java.util.List;
import java.util.Properties;
abstract public class PhpNewClassDialog extends DialogWrapper implements PhpCreateFileFromTemplateDataProvider {
protected FormBuilder myBuilder;
protected Project myProject;
protected PsiDirectory myDirectory;
protected DocumentListener myNameFieldListener;
protected final Alarm myAlarm;
protected boolean myDisposed;
protected EditorTextField myNameTextField;
protected PhpNamespaceComboBox myNamespaceCombobox;
protected PhpPsrDirectoryComboBox myDirectoryCombobox;
protected EditorTextField myFileNameTextField;
protected ComboBox myExtensionComboBox;
protected Properties myProperties;
protected JLabel myExtensionUpDownHint;
public PhpNewClassDialog(@NotNull Project project, @Nullable PsiDirectory directory) {
super(project);
this.myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
Disposer.register(this.getDisposable(), new Disposable() {
public void dispose() {
PhpNewClassDialog.this.myAlarm.cancelAllRequests();
PhpNewClassDialog.this.myDisposed = true;
}
});
this.myProperties = new Properties();
this.myProject = project;
this.myDirectory = directory;
init();
}
protected void initMainFields() {
// Setup Name Field
this.myNameTextField = new EditorTextField();
this.myNameTextField.setCaretPosition(0);
// Setup Namespace Combobox
this.myNamespaceCombobox = new PhpNamespaceComboBox(
this.myProject,
"",
this.getDisposable()
);
this.myNamespaceCombobox.setEditable(true);
this.myNamespaceCombobox.setMinimumAndPreferredWidth(400);
this.myFileNameTextField = new EditorTextField();
this.myDirectoryCombobox = new PhpPsrDirectoryComboBox(this.myProject) {
public List<String> suggestDirectories(@NotNull String namespace) {
return PhpDirectoryByPsrProvider.getSourceRootProvide().suggestDirectories(
PhpNewClassDialog.this.myProject,
namespace,
this.myBaseDir,
this.myBaseNamespace
);
}
};
if(this.myDirectory != null) {
List<String> namespaces = PhpNamespaceCompositeProvider.INSTANCE.suggestNamespaces(this.myDirectory);
String mainSuggestion = "";
if (namespaces != null && !namespaces.isEmpty()) {
mainSuggestion = (String)namespaces.get(0);
}
List<String> suggestions = null;
if (namespaces != null && namespaces.size() > 1) {
suggestions = namespaces.subList(1, namespaces.size());
}
this.myNamespaceCombobox.updateItems(mainSuggestion, suggestions);
this.myDirectoryCombobox.init(
this.myDirectory.getVirtualFile(),
this.getNamespace()
);
} else {
this.myDirectoryCombobox.init(
this.myProject.getBaseDir(),
""
);
}
this.myDirectoryCombobox.getComboBox().addFocusListener(
new FocusAdapter() {
public void focusGained(FocusEvent e) {
PhpNewClassDialog.this.myDirectoryCombobox.getComboBox().setEditable(true);
}
}
);
// File Extension Combobox
this.myExtensionComboBox = new ComboBox();
this.myExtensionComboBox.setMinimumAndPreferredWidth(400);
String[] registeredExtensions = PhpFileTemplateUtil.getRegisteredPhpFileExtensions();
this.myExtensionComboBox.setModel(new DefaultComboBoxModel<String>(registeredExtensions));
int indexOfLast = ArrayUtil.indexOf(
registeredExtensions,
PhpTemplatesSettings.getInstance(this.myProject).NEW_PHP_CLASS_LAST_EXTENSION
);
if(indexOfLast > -1) {
this.myExtensionComboBox.setSelectedIndex(indexOfLast);
} else {
this.myExtensionComboBox.setSelectedIndex(0);
}
this.myExtensionUpDownHint = new JLabel();
this.myExtensionUpDownHint.setIcon(PlatformIcons.UP_DOWN_ARROWS);
this.myExtensionUpDownHint.setToolTipText(PhpBundle.message("actions.new.php.base.arrows.extension.tooltip"));
if(this.isFileNameAutoUpdate()) {
this.myNameFieldListener = new DocumentAdapter() {
public void documentChanged(DocumentEvent e) {
PhpNewClassDialog.this.addUpdateRequest(() -> {
PhpNewClassDialog.this.myFileNameTextField.setText(PhpNewClassDialog.this.getName());
}, 10);
}
};
this.myNameTextField.getDocument().addDocumentListener(this.myNameFieldListener);
this.myNamespaceCombobox.getEditorTextField().addDocumentListener(new DocumentAdapter() {
public void documentChanged(DocumentEvent event) {
PhpNewClassDialog.this.addUpdateRequest(() -> {
PhpNewClassDialog.this.myDirectoryCombobox.updateDirectories(PhpNewClassDialog.this.getNamespace());
}, 10);
}
});
}
AnAction extensionArrow = PhpNewFileDialog.getCbArrowAction(this.myExtensionComboBox);
KeyboardShortcut up = new KeyboardShortcut(KeyStroke.getKeyStroke(38, 0), (KeyStroke)null);
KeyboardShortcut down = new KeyboardShortcut(KeyStroke.getKeyStroke(40, 0), (KeyStroke)null);
extensionArrow.registerCustomShortcutSet(new CustomShortcutSet(new Shortcut[]{up, down}), this.myFileNameTextField);
}
protected void subInit() {
this.myBuilder = new FormBuilder();
this.myBuilder.setVerticalGap(5);
this.initMainFields();
}
@Override
protected void init() {
this.subInit();
this.buildForm();
super.init();
}
protected void buildForm() {
BorderLayout layout = new BorderLayout();
layout.setHgap(5);
JPanel filenamePanel = new JPanel(layout);
filenamePanel.add(this.myFileNameTextField, BorderLayout.CENTER);
filenamePanel.add(this.myExtensionUpDownHint, BorderLayout.EAST);
this.myBuilder.addLabeledComponent(new JLabel("Namespace:"), this.myNamespaceCombobox);
this.myBuilder.addTooltip(
PhpBundle.message(
"0.completion.shortcut",
"namespace", PhpUiUtil.getShortcutTextByActionName("CodeCompletion"))
);
this.myBuilder.addLabeledComponent(new JLabel("Filename:"), filenamePanel);
this.myBuilder.addLabeledComponent(new JLabel("Directory:"), this.myDirectoryCombobox);
this.myBuilder.addTooltip(
PhpBundle.message(
"0.completion.shortcut",
"path", PhpUiUtil.getShortcutTextByActionName("CodeCompletion"))
);
this.myBuilder.addLabeledComponent(new JLabel("File Extension:"), this.myExtensionComboBox);
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return this.myNameTextField;
}
@Override
protected JComponent createCenterPanel() {
return this.myBuilder.getPanel();
}
@NotNull
public String getFileName() {
return this.getFileNameText().trim();
}
public String getFileNameText() {
return this.myFileNameTextField.getText();
}
@NotNull
protected String getName() {
return this.myNameTextField.getText().trim();
}
protected void doOKAction() {
PhpTemplatesSettings.getInstance(this.myProject).NEW_PHP_CLASS_LAST_EXTENSION = this.getExtension();
super.doOKAction();
}
@NotNull
public String getTemplateName() {
return "PHP Class";
}
@NotNull
protected String getExtension() {
return (String)this.myExtensionComboBox.getSelectedItem();
}
@NotNull
@Override
public PsiDirectory getBaseDirectory() {
return this.myDirectory;
}
public String getClassName() {
return this.myNameTextField.getText();
}
public String getNamespaceName() {
return this.getNamespace();
}
@NotNull
protected final String getNamespace() {
return PhpLangUtil.toName(this.myNamespaceCombobox.getSelectedNamespace().trim());
}
protected boolean isFileNameAutoUpdate() {
return true;
}
protected void addUpdateRequest(@NotNull Runnable runnable) {
this.addUpdateRequest(runnable, 100);
}
protected void addUpdateRequest(@NotNull Runnable runnable, int delay) {
SwingUtilities.invokeLater(() -> {
if(!this.myDisposed) {
this.myAlarm.cancelAllRequests();
this.myAlarm.addRequest(runnable, delay);
}
});
}
protected void dispose() {
if(this.isFileNameAutoUpdate()) {
this.myNameTextField.getDocument().removeDocumentListener(this.myNameFieldListener);
}
super.dispose();
}
@NotNull
public final String getFilePath() {
String chosenExtension = this.getExtension();
String filename = PathUtil.toSystemIndependentName(this.getFileName());
String extension = PhpNameUtil.getExtension(filename);
String fullFileName = chosenExtension.equals(extension)?filename:PhpNameUtil.getFullFileName(filename, chosenExtension);
String relativePath = this.myDirectoryCombobox.getRelativePath();
return StringUtil.isEmpty(relativePath)?fullFileName:relativePath + "/" + StringUtil.trimEnd(fullFileName, "/");
}
@Nullable
protected PsiDirectory getDirectory() {
VirtualFile directory = this.myDirectoryCombobox.getExistingParent();
if(directory != null) {
PsiDirectory psiDirectory = PsiManager.getInstance(this.myProject).findDirectory(directory);
if(psiDirectory != null) {
return psiDirectory;
}
}
return null;
}
@NotNull
public Properties getProperties(@NotNull PsiDirectory directory) {
this.myProperties.setProperty("NAME", this.getName());
String namespace = this.getNamespace();
if(StringUtil.isNotEmpty(namespace)) {
this.myProperties.setProperty("NAMESPACE", namespace);
}
return this.myProperties;
}
protected boolean postponeValidation() {
return true;
}
protected ValidationInfo doValidate() {
String name = this.getName();
if(!PhpNameUtil.isValidClassName(name)) {
return new ValidationInfo(
PhpBundle.message(
"validation.class.not.valid.name",
name),
this.myNameTextField
);
} else {
String namespace = this.getNamespace();
if(StringUtil.isNotEmpty(namespace) && !PhpNameUtil.isValidNamespaceFullName(namespace)) {
return new ValidationInfo(
PhpBundle.message(
"validation.namespace.not.valid.name",
namespace),
this.myNamespaceCombobox
);
} else {
PsiDirectory directory = this.getDirectory();
if(directory == null) {
return new ValidationInfo(
PhpBundle.message(
"validation.value.is.not.specified.or.invalid",
"directory")
);
} else {
String errorMessage = this.isValidFilePath(this.getFilePath(), directory);
return errorMessage != null?new ValidationInfo(errorMessage, this.myFileNameTextField):null;
}
}
}
}
protected String isValidFilePath(@NotNull String fullFilePath, @NotNull PsiDirectory baseDirectory) {
String filePath = StringUtil.replace(
fullFilePath,
File.separator,
"/"
);
if(filePath.length() == 0) {
return PhpBundle.message(
"validation.file.not.valid.name",
fullFilePath);
} else {
List<String> split = StringUtil.split(filePath, "/");
if(split.size() == 0) {
return PhpBundle.message(
"validation.file.not.valid.name",
fullFilePath);
} else {
for (String aSplit : split) {
if (!PhpNameUtil.isValidFileName(aSplit)) {
return PhpBundle.message(
"validation.file.not.valid.name",
fullFilePath);
}
}
VirtualFile baseDirectoryFile = baseDirectory.getVirtualFile();
VirtualFile fileByRelativePath = baseDirectoryFile.findFileByRelativePath(
PhpNameUtil.trimStart(
filePath,
'/')
);
if (fileByRelativePath != null) {
return PhpBundle.message(
"validation.file.already.exists",
fullFilePath);
} else {
return null;
}
}
}
}
}