blob: 4309621aa3dd74732740b29bebc71cc16f3c0eae [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.css.visual;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.FeatureDescriptor;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyEditorSupport;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.Icon;
import javax.swing.JColorChooser;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import javax.swing.SpinnerModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.css.indexing.api.CssIndex;
import org.netbeans.modules.css.lib.api.CssColor;
import org.netbeans.modules.css.lib.api.properties.FixedTextGrammarElement;
import org.netbeans.modules.css.lib.api.properties.Properties;
import org.netbeans.modules.css.lib.api.properties.PropertyDefinition;
import org.netbeans.modules.css.lib.api.properties.ResolvedProperty;
import org.netbeans.modules.css.lib.api.properties.Token;
import org.netbeans.modules.css.lib.api.properties.TokenAcceptor;
import org.netbeans.modules.css.lib.api.properties.UnitGrammarElement;
import org.netbeans.modules.css.model.api.Model;
import org.netbeans.modules.css.model.api.PropertyDeclaration;
import org.netbeans.modules.css.refactoring.api.RefactoringElementType;
import org.netbeans.modules.css.visual.actions.GoToSourceAction;
import org.netbeans.modules.web.common.ui.api.WebUIUtils;
import org.openide.explorer.propertysheet.ExPropertyEditor;
import org.openide.explorer.propertysheet.PropertyEnv;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
/**
*
* @author marekfukala
*/
@NbBundle.Messages({
"choose.color.item=Choose Color"
})
public class PropertyValuesEditor extends PropertyEditorSupport implements ExPropertyEditor {
private Collection<UnitGrammarElement> unitElements;
private Collection<FixedTextGrammarElement> fixedElements;
private boolean addNoneProperty;
private List<String> tags;
private Map<String, FixedTextGrammarElement> tags2fixedElement = new HashMap<>();
private boolean containsColor;
private FileObject file;
private PropertyDefinition pmodel;
private RuleEditorPanel panel;
private final boolean isAggregatedProperty;
private static final String CHOOSE_COLOR_ITEM = new StringBuilder().append("<html><b>").append(Bundle.choose_color_item()).append("</b></html>").toString(); //NOI18N
private static JColorChooser COLOR_CHOOSER;
private PropertyDeclaration declaration;
public PropertyValuesEditor(RuleEditorPanel panel,
PropertyDefinition pmodel,
Model model,
Collection<FixedTextGrammarElement> fixedElements,
Collection<UnitGrammarElement> unitElements,
PropertyDeclaration declaration,
boolean addNoneProperty) {
this.panel = panel;
this.fixedElements = fixedElements;
this.unitElements = unitElements;
this.addNoneProperty = addNoneProperty;
this.file = model.getLookup().lookup(FileObject.class);
this.pmodel = pmodel; //may be null
this.isAggregatedProperty = pmodel != null ? Properties.isAggregatedProperty(file, pmodel) : false;
this.declaration = declaration;
}
private static JColorChooser getJColorChooser() {
if(COLOR_CHOOSER == null) {
COLOR_CHOOSER = new JColorChooser();
}
return COLOR_CHOOSER;
}
private boolean canIncrementDecrement() {
if (declaration == null) {
return false;
}
ResolvedProperty resolvedProperty = declaration.getResolvedProperty();
if (resolvedProperty == null) {
return false;
}
List<Token> tokens = resolvedProperty.getTokens();
if (tokens.size() != 1) {
return false; //zero or multiple tokens, cannot increment/decrement
}
Token value = tokens.get(0);
for (TokenAcceptor genericAcceptor : TokenAcceptor.ACCEPTORS) {
if (genericAcceptor instanceof TokenAcceptor.NumberPostfixAcceptor) {
TokenAcceptor.NumberPostfixAcceptor acceptor = (TokenAcceptor.NumberPostfixAcceptor) genericAcceptor;
if (acceptor.accepts(value)) {
return true;
}
} else if (genericAcceptor instanceof TokenAcceptor.Number) {
TokenAcceptor.Number acceptor = (TokenAcceptor.Number) genericAcceptor;
if (acceptor.accepts(value)) {
return true;
}
}
}
return false; //none of the number acceptors accepts the value
}
@Override
public Component getCustomEditor() {
return null;
}
@Override
public synchronized String[] getTags() {
// if (isAggregatedProperty) {
// //no drop down for aggregated properties
// return null;
// }
if (tags == null) {
tags = new ArrayList<>();
//sort the items alphabetically first
Collection<String> fixedElementNames = new TreeSet<>();
for (FixedTextGrammarElement element : fixedElements) {
String value = element.getValue();
if (value.length() > 0 && Character.isLetter(value.charAt(0))) { //filter operators & similar
fixedElementNames.add(value);
tags2fixedElement.put(value, element);
//TBD possibly refactor out so it is not so hardcoded
if ("@colors-list".equals(element.origin())) { //NOI18N
containsColor = true;
}
}
}
//the rest will handle the order by itself
tags.addAll(fixedElementNames);
if (containsColor) {
if (file != null) {
Project project = FileOwnerQuery.getOwner(file);
if (project != null) {
try {
Collection<String> hashColorCodes = new TreeSet<>();
CssIndex index = CssIndex.create(project);
Map<FileObject, Collection<String>> result = index.findAll(RefactoringElementType.COLOR);
for (Collection<String> colors : result.values()) {
// boolean usedInCurrentFile = f.equals(file);
for (String color : colors) {
hashColorCodes.add(color);
}
}
tags.addAll(0, hashColorCodes);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
tags.add(0, CHOOSE_COLOR_ITEM);
}
// if (addNoneProperty) {
// //put as first item
// tagsList.add(0, RuleEditorNode.NONE_PROPERTY_NAME);
// }
}
return tags.isEmpty() ? null : tags.toArray(new String[]{});
}
@Override
public void setAsText(String str) {
if (str == null) {
return;
}
if (str.isEmpty()) {
return;
}
//same value, ignore
if (str.equals(getValue())) {
return;
}
if (CHOOSE_COLOR_ITEM.equals(str)) {
//color chooser
final AtomicReference<Color> color_ref = new AtomicReference<>();
JDialog dialog = JColorChooser.createDialog(EditorRegistry.lastFocusedComponent(), Bundle.choose_color_item(), true, getJColorChooser(),
new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//disalog confirmed
color_ref.set(getJColorChooser().getColor());
}
}, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//dialog cancelled
}
});
dialog.setVisible(true);
dialog.dispose();
Color color = color_ref.get();
if (color != null) {
str = WebUIUtils.toHexCode(color);
} else {
//dialog cancelled, no value - do not allow the CHOOSE_COLOR_ITEM marker to be set the to property
return;
}
}
editingFinished();
setValue(str);
}
@Override
public String getAsText() {
return getValue().toString();
}
private void editingFinished() {
panel.editingFinished();
}
private void editingCancelled() {
panel.disposeEditedDeclaration();
}
@Override
public String toString() {
return getClass().getSimpleName() + "; property: " + pmodel != null ? pmodel.getName() : "?"; //NOI18N
}
@Override
public void attachEnv(PropertyEnv env) {
//if there's at least one unit element, then the text field needs to be editable
env.getFeatureDescriptor().setValue("canEditAsText", Boolean.TRUE); //NOI18N
env.getFeatureDescriptor().setValue("nb.property.editor.callback", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if("editingCancelled".equals(evt.getPropertyName())) {
editingCancelled();
}
}
}); //NOI18N
env.getFeatureDescriptor().setValue("nb.propertysheet.mouse.doubleclick.listener", new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent me) {
if (me.getID() == MouseEvent.MOUSE_PRESSED && SwingUtilities.isLeftMouseButton(me)) {
if (me.getClickCount() > 1) {
FeatureDescriptor selected = panel.getSelected();
if (selected != null) {
if (selected instanceof RuleEditorNode.DeclarationProperty) {
RuleEditorNode.DeclarationProperty declarationProperty = (RuleEditorNode.DeclarationProperty) selected;
GoToSourceAction action = new GoToSourceAction(panel, declarationProperty);
action.actionPerformed(null);
}
}
}
}
}
});
if (containsColor) {
env.getFeatureDescriptor().setValue("customListCellRendererSupport", new ColorListCellRendererSupport()); //NOI18N
}
if(canIncrementDecrement()) {
env.getFeatureDescriptor().setValue("valueIncrement", new SpinnerModel() { //NOI18N
private String getNextValue(boolean forward) {
String value = getAsText();
for (TokenAcceptor genericAcceptor : TokenAcceptor.ACCEPTORS) {
if (genericAcceptor instanceof TokenAcceptor.NumberPostfixAcceptor) {
TokenAcceptor.NumberPostfixAcceptor acceptor = (TokenAcceptor.NumberPostfixAcceptor) genericAcceptor;
if (acceptor.accepts(value)) {
int i = acceptor.getNumberValue(value).intValue();
CharSequence postfix = acceptor.getPostfix(value);
StringBuilder sb = new StringBuilder();
sb.append(i + (forward ? 1 : -1));
if (postfix != null) {
sb.append(postfix);
}
return sb.toString();
}
} else if (genericAcceptor instanceof TokenAcceptor.Number) {
TokenAcceptor.Number acceptor = (TokenAcceptor.Number) genericAcceptor;
if (acceptor.accepts(value)) {
int i = acceptor.getNumberValue(value).intValue();
StringBuilder sb = new StringBuilder();
sb.append(i + (forward ? 1 : -1));
return sb.toString();
}
}
}
//not acceptable token
return null;
}
@Override
public Object getValue() {
//no-op
return null;
}
@Override
public void setValue(Object value) {
//no-op
}
@Override
public Object getNextValue() {
return getNextValue(true);
}
@Override
public Object getPreviousValue() {
return getNextValue(false);
}
@Override
public void addChangeListener(ChangeListener l) {
//no-op
}
@Override
public void removeChangeListener(ChangeListener l) {
//no-op
}
});
}
}
private class ColorListCellRendererSupport extends AtomicReference<ListCellRenderer> implements ListCellRenderer {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
ListCellRenderer peer = get();
assert peer != null; //the ComboInplaceEditor must set the original renreder!
if (peer instanceof ColorListCellRendererSupport) {
System.out.println("warning: nesting of ColorListCellRendererSupport!");
}
Component res = peer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (res instanceof JLabel) {
JLabel label = (JLabel) res;
String strval = (value == null) ? "null" : value.toString(); // NOI18N
Icon icon = null;
if (strval.startsWith("#")) { //NOI18N
String colorCode = strval.substring(1);
icon = WebUIUtils.createColorIcon(colorCode); //null CssColor will create default icon
}
if (strval.equals(CHOOSE_COLOR_ITEM)) {
Color chooserColor = getJColorChooser().getColor();
String hexCode = chooserColor != null ? WebUIUtils.toHexCode(chooserColor) : null;
icon = WebUIUtils.createColorIcon(hexCode);
}
FixedTextGrammarElement element = tags2fixedElement.get(strval);
if (!"inherit".equals(strval)) { //filter out colors for inherit
if (element != null) {
if ("@colors-list".equals(element.origin())) { //NOI18N
//try to find color code
CssColor color = CssColor.getColor(strval);
icon = WebUIUtils.createColorIcon(color == null ? null : color.colorCode()); //null CssColor will create default icon
}
}
}
label.setIcon(icon);
} else {
System.out.println("res instance " + res);
}
return res;
}
}
}