/*
* Copyright 2012 Andrew C. Dvorak.
*
* 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 net.andydvorak.intellij.lessc.ui.configurable;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonShortcuts;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.*;
import com.intellij.openapi.util.Conditions;
import com.intellij.util.PlatformIcons;
import net.andydvorak.intellij.lessc.LessManager;
import net.andydvorak.intellij.lessc.state.CssDirectory;
import net.andydvorak.intellij.lessc.ui.messages.UIBundle;
import net.andydvorak.intellij.lessc.state.LessProfile;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.io.File;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
class LessProfilesPanel extends MasterDetailsComponent implements SearchableConfigurable {
@NotNull private final Project project;
@NotNull private final LessManager lessManager;
@NotNull private final AtomicBoolean isInitialized = new AtomicBoolean(false);
private final List<LessProfileConfigurableForm> profileForms = new ArrayList<LessProfileConfigurableForm>();
public LessProfilesPanel(@NotNull final Project project) {
this.project = project;
this.lessManager = LessManager.getInstance(project);
initTree();
}
protected boolean wasObjectStored(final Object o) {
if (o == null || LessProfile.class != o.getClass()) return false;
final LessProfile profile = (LessProfile) o;
return lessManager.getProfileMap().containsKey(profile.getId());
}
public void apply() throws ConfigurationException {
final Set<String> names = new HashSet<String>();
// Check for duplicate profile names
for (int i = 0; i < myRoot.getChildCount(); i++) {
final MyNode node = (MyNode) myRoot.getChildAt(i);
final LessProfileConfigurableForm form = (LessProfileConfigurableForm) node.getConfigurable();
final LessProfile profile = form.getCurrentState();
final String name = profile.getName();
final String dirPath = profile.getLessDirPath();
if (StringUtils.isBlank(name)) {
selectNodeInTree(name);
throw new ConfigurationException(UIBundle.message("blank.less.profile.name"));
}
if (names.contains(name)) {
selectNodeInTree(name);
throw new ConfigurationException(UIBundle.message("duplicate.less.profile.name", name));
}
if (StringUtils.isBlank(dirPath)) {
selectNodeInTree(name);
throw new ConfigurationException(UIBundle.message("blank.less.profile.source.dir"));
}
if (!profile.getLessDir().exists()) {
if (!confirmWarning(UIBundle.message("nonexistent.less.profile.source.dir.title"),
UIBundle.message("nonexistent.less.profile.source.dir.prompt", dirPath))) {
selectNodeInTree(name);
throw new ConfigurationException(UIBundle.message("nonexistent.less.profile.source.dir.error", dirPath));
}
}
if (profile.getCssDirectories().isEmpty()) {
selectNodeInTree(name);
throw new ConfigurationException(UIBundle.message("no.less.profile.css.dirs"));
}
for (final CssDirectory cssDirectory : profile.getCssDirectories()) {
if (!new File(cssDirectory.getPath()).exists()) {
if (!confirmWarning(UIBundle.message("nonexistent.less.profile.css.dir.title"),
UIBundle.message("nonexistent.less.profile.css.dir.prompt", dirPath))) {
selectNodeInTree(name);
throw new ConfigurationException(UIBundle.message("nonexistent.less.profile.css.dir.error", dirPath));
}
}
}
names.add(name);
}
super.apply();
}
protected void processRemovedItems() {
final Map<Integer, LessProfile> profiles = getProfileMap();
final List<Integer> deletedIds = new ArrayList<Integer>();
// Compile a list of all profiles that are no longer present in the UI
for (final LessProfile profile : lessManager.getProfiles()) {
if (!profiles.containsKey(profile.getId())) {
deletedIds.add(profile.getId());
}
}
// Remove the deleted profiles from the manager
for (final int id : deletedIds) {
lessManager.removeProfile(id);
}
}
public void reset() {
reloadTree();
super.reset();
}
@Override
public void disposeUIResources() {
super.disposeUIResources();
isInitialized.set(false);
}
@Nullable
protected ArrayList<AnAction> createActions(final boolean fromPopup) {
final ArrayList<AnAction> result = new ArrayList<AnAction>();
final String addText = UIBundle.message("action.add.less.profile.text");
final String addDescription = UIBundle.message("action.add.less.profile.description");
final String addPromptTitle = UIBundle.message("action.add.less.profile.prompt.title");
result.add(new AnAction(addText, addDescription, PlatformIcons.ADD_ICON) {
{
registerCustomShortcutSet(CommonShortcuts.INSERT, myTree);
}
public void actionPerformed(final AnActionEvent event) {
final String name = askForProfileName(addPromptTitle, "");
if (name == null) return;
final LessProfile lessProfile = new LessProfile(getNextId(), name);
addProfileNode(lessProfile);
}
});
result.add(new MyDeleteAction(forAll(Conditions.alwaysTrue())));
final String copyText = UIBundle.message("action.copy.less.profile.text");
final String copyDescription = UIBundle.message("action.copy.less.profile.description");
final String copyPromptTitle = UIBundle.message("action.copy.less.profile.prompt.title");
result.add(new AnAction(copyText, copyDescription, PlatformIcons.COPY_ICON) {
{
registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_MASK)), myTree);
}
public void actionPerformed(final AnActionEvent event) {
final String profileName = askForProfileName(copyPromptTitle, "");
if (profileName == null) return;
final LessProfile clone = new LessProfile(getNextId(), (LessProfile) getSelectedObject());
clone.setName(profileName);
addProfileNode(clone);
}
public void update(final AnActionEvent event) {
super.update(event);
event.getPresentation().setEnabled(getSelectedObject() != null);
}
});
return result;
}
private void addProfileNode(final LessProfile lessProfile) {
final LessProfileConfigurableForm profileForm = new LessProfileConfigurableForm(project, lessProfile, this, TREE_UPDATER);
profileForm.setModified(true);
profileForms.add(profileForm);
final MyNode node = new MyNode(profileForm);
addNode(node, myRoot);
selectNodeInTree(node);
}
private void reloadTree() {
myRoot.removeAllChildren();
profileForms.clear();
final Collection<LessProfile> profiles = lessManager.getProfiles();
for (final LessProfile profile : profiles) {
final LessProfile clone = new LessProfile(profile.getId(), profile);
final LessProfileConfigurableForm profileForm = new LessProfileConfigurableForm(project, clone, this, TREE_UPDATER);
profileForms.add(profileForm);
addNode(new MyNode(profileForm), myRoot);
}
isInitialized.set(true);
}
private Map<Integer, LessProfile> getProfileMap() {
if (!isInitialized.get()) {
return getManagerProfileMap();
} else {
return getUIProfileMap();
}
}
private Map<Integer, LessProfile> getManagerProfileMap() {
return lessManager.getProfileMap();
}
private Map<Integer, LessProfile> getUIProfileMap() {
final Map<Integer, LessProfile> profiles = new com.intellij.util.containers.HashMap<Integer, LessProfile>();
for (int i = 0; i < myRoot.getChildCount(); i++) {
final MyNode node = (MyNode) myRoot.getChildAt(i);
final LessProfile lessProfile = ((LessProfileConfigurableForm) node.getConfigurable()).getEditableObject();
profiles.put(lessProfile.getId(), lessProfile);
}
return profiles;
}
private int getNextId() {
int id = -1;
for (final LessProfile profile : getManagerProfileMap().values()) {
if (profile.getId() > id) {
id = profile.getId();
}
}
for (final LessProfile profile : getUIProfileMap().values()) {
if (profile.getId() > id) {
id = profile.getId();
}
}
return id + 1;
}
@Nullable
private String askForProfileName(final String title, final String initialName) {
final String message = UIBundle.message("action.new.less.profile.prompt.message");
return Messages.showInputDialog(message, title, Messages.getQuestionIcon(), initialName, new InputValidator() {
public boolean checkInput(final String newName) {
if (newName == null || newName.length() == 0) {
return false;
}
// Don't allow duplicate profile names
for (final LessProfile profile : getProfileMap().values()) {
if (StringUtils.equals(newName, profile.getName())) {
return false;
}
}
return true;
}
public boolean canClose(final String s) {
return checkInput(s);
}
});
}
private boolean confirmWarning(final String title, final String message) {
return Messages.YES == Messages.showYesNoDialog(message, title, Messages.getWarningIcon());
}
public void setPromptButtonsEnabled(final boolean enabled) {
for (final LessProfileConfigurableForm profileForm : profileForms) {
profileForm.setPromptButtonEnabled(enabled);
}
}
/*
* Method overrides and interface implementations
*/
@NotNull
public String getId() {
return "lessc.profiles";
}
@Nls
public String getDisplayName() {
return UIBundle.message("pref.display.name");
}
@Nullable
@NonNls
public String getHelpTopic() {
return null;
}
public Runnable enableSearch(final String option) {
return null;
}
@Override
protected String getEmptySelectionString() {
return UIBundle.message("profile.empty.selection");
}
@Override
protected String getComponentStateKey() {
return "LessCompiler.UI";
}
@Override
protected MasterDetailsStateService getStateService() {
return MasterDetailsStateService.getInstance(project);
}
}