| /* |
| * Copyright 2007 Sascha Weinreuter |
| * |
| * 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 org.intellij.plugins.xpathView.ui; |
| |
| import com.intellij.ui.ListCellRendererWrapper; |
| import org.intellij.plugins.xpathView.Config; |
| import org.intellij.plugins.xpathView.HistoryElement; |
| import org.intellij.plugins.xpathView.eval.EvalExpressionDialog; |
| import org.intellij.plugins.xpathView.support.XPathSupport; |
| import org.intellij.plugins.xpathView.util.Namespace; |
| import org.intellij.plugins.xpathView.util.NamespaceCollector; |
| import org.intellij.plugins.xpathView.util.Variable; |
| |
| import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.javaee.ExternalResourceManager; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.event.DocumentAdapter; |
| import com.intellij.openapi.editor.event.DocumentEvent; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.ComboBox; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.psi.PsiDocumentManager; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiFileFactory; |
| import com.intellij.psi.PsiRecursiveElementVisitor; |
| import com.intellij.psi.PsiReference; |
| import com.intellij.psi.xml.XmlElement; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.ui.EditorTextField; |
| import com.intellij.uiDesigner.core.GridConstraints; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.LocalTimeCounter; |
| import com.intellij.util.containers.BidirectionalMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import org.intellij.lang.xpath.XPathFileType; |
| import org.intellij.lang.xpath.context.SimpleVariableContext; |
| import org.intellij.lang.xpath.context.ContextProvider; |
| import org.intellij.lang.xpath.context.ContextType; |
| import org.intellij.lang.xpath.context.NamespaceContext; |
| import org.intellij.lang.xpath.context.VariableContext; |
| import org.intellij.lang.xpath.psi.PrefixReference; |
| import org.intellij.lang.xpath.psi.QNameElement; |
| import org.intellij.lang.xpath.psi.XPathElement; |
| |
| import javax.swing.*; |
| import javax.swing.event.ListDataEvent; |
| import javax.swing.event.ListDataListener; |
| import javax.swing.plaf.basic.BasicComboBoxEditor; |
| import javax.xml.namespace.QName; |
| import java.awt.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.util.*; |
| import java.util.List; |
| |
| public abstract class InputExpressionDialog<FormType extends InputForm> extends ModeSwitchableDialog { |
| protected final Project myProject; |
| protected final FormType myForm; |
| protected final Config mySettings; |
| |
| private final HistoryModel myModel; |
| |
| private final Document myDocument; |
| private final MultilineEditor myEditor; |
| |
| private final EditorTextField myComboboxEditor; |
| private final ComboBox myComboBox = new ComboBox(300); |
| private JComponent myEditorComponent; |
| |
| @Nullable private Set<Namespace> myNamespaceCache; |
| private InteractiveContextProvider myContextProvider; |
| private final PsiFile myXPathFile; |
| |
| public InputExpressionDialog(final Project project, Config settings, final HistoryElement[] _history, FormType form) { |
| super(project, false); |
| |
| myProject = project; |
| myForm = form; |
| |
| setResizable(true); |
| setModal(true); |
| setHorizontalStretch(1.3f); |
| |
| mySettings = settings; |
| |
| myDocument = createXPathDocument(project, _history.length > 0 ? _history[_history.length - 1] : null); |
| myXPathFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); |
| myModel = new HistoryModel(_history, myDocument); |
| myEditor = new MultilineEditor(myDocument, project, XPathFileType.XPATH, myModel); |
| myModel.addListDataListener(new ListDataListener() { |
| final PsiDocumentManager docMgr = PsiDocumentManager.getInstance(project); |
| final DaemonCodeAnalyzer analyzer = DaemonCodeAnalyzer.getInstance(project); |
| |
| public void intervalAdded(ListDataEvent e) { |
| } |
| |
| public void intervalRemoved(ListDataEvent e) { |
| } |
| |
| public void contentsChanged(ListDataEvent e) { |
| final HistoryElement item = myModel.getSelectedItem(); |
| if (item != null) { |
| myContextProvider.getNamespaceContext().setMap(asMap(item.namespaces)); |
| if (myXPathFile != null) { |
| analyzer.restart(myXPathFile); |
| } |
| } |
| } |
| }); |
| |
| myComboboxEditor = new EditorTextField(myDocument, project, XPathFileType.XPATH); |
| myComboBox.setRenderer(new ListCellRendererWrapper<HistoryElement>() { |
| @Override |
| public void customize(JList list, HistoryElement value, int index, boolean selected, boolean hasFocus) { |
| setText(value != null ? value.expression : ""); |
| } |
| }); |
| myComboBox.setModel(myModel); |
| |
| myComboBox.setEditor(new EditorAdapter(myComboboxEditor)); |
| myComboBox.setEditable(true); |
| |
| myDocument.addDocumentListener(new DocumentAdapter() { |
| public void documentChanged(DocumentEvent e) { |
| updateOkAction(); |
| } |
| }); |
| |
| init(); |
| } |
| |
| protected void init() { |
| myForm.getIcon().setText(null); |
| myForm.getIcon().setIcon(Messages.getQuestionIcon()); |
| |
| myForm.getEditContextButton().addActionListener(new ActionListener() { |
| |
| @SuppressWarnings({"unchecked"}) |
| public void actionPerformed(ActionEvent e) { |
| final HistoryElement selectedItem = myModel.getSelectedItem(); |
| |
| final Collection<Namespace> n; |
| final Collection<Variable> v; |
| if (selectedItem != null) { |
| n = selectedItem.namespaces; |
| v = selectedItem.variables; |
| } else { |
| n = Collections.emptySet(); |
| v = Collections.emptySet(); |
| } |
| |
| // FIXME |
| final Collection<Namespace> namespaces = myNamespaceCache != null ? |
| merge(myNamespaceCache, n, false) : n; |
| |
| final Set<String> unresolvedPrefixes = findUnresolvedPrefixes(); |
| final EditContextDialog dialog = |
| new EditContextDialog(myProject, unresolvedPrefixes, namespaces, v, myContextProvider); |
| |
| dialog.show(); |
| |
| if (dialog.isOK()) { |
| final Pair<Collection<Namespace>, Collection<Variable>> context = dialog.getContext(); |
| final Collection<Namespace> newNamespaces = context.getFirst(); |
| final Collection<Variable> newVariables = context.getSecond(); |
| |
| updateContext(newNamespaces, newVariables); |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| final Editor editor = getEditor(); |
| if (editor != null) { |
| editor.getContentComponent().grabFocus(); |
| } |
| } |
| }); |
| } |
| } |
| }); |
| |
| updateOkAction(); |
| |
| super.init(); |
| } |
| |
| void updateContext(Collection<Namespace> namespaces, Collection<Variable> variables) { |
| final HistoryElement selectedItem = myModel.getSelectedItem(); |
| |
| final HistoryElement newElement; |
| if (selectedItem != null) { |
| newElement = selectedItem.changeContext(namespaces, variables); |
| } else { |
| newElement = new HistoryElement(myDocument.getText(), variables, namespaces); |
| } |
| myModel.setSelectedItem(newElement); |
| |
| // FIXME |
| if (myNamespaceCache == null) { |
| myContextProvider.getNamespaceContext().setMap(asMap(namespaces)); |
| } |
| |
| final DaemonCodeAnalyzer analyzer = DaemonCodeAnalyzer.getInstance(myProject); |
| analyzer.restart(myXPathFile); |
| } |
| |
| private Set<String> findUnresolvedPrefixes() { |
| final Set<String> prefixes = new HashSet<String>(); |
| |
| myXPathFile.accept(new PsiRecursiveElementVisitor(){ |
| public void visitElement(PsiElement element) { |
| if (element instanceof QNameElement) { |
| final PsiReference[] references = element.getReferences(); |
| for (PsiReference reference : references) { |
| if (reference instanceof PrefixReference) { |
| final PrefixReference prefixReference = (PrefixReference)reference; |
| if (prefixReference.isUnresolved()) { |
| prefixes.add(prefixReference.getPrefix()); |
| } |
| } |
| } |
| } |
| super.visitElement(element); |
| } |
| }); |
| return prefixes; |
| } |
| |
| protected FormType getForm() { |
| return myForm; |
| } |
| |
| protected JComponent createCenterPanel() { |
| return myForm.getComponent(); |
| } |
| |
| protected void updateOkAction() { |
| getOKAction().setEnabled(isOkEnabled()); |
| } |
| |
| protected boolean isOkEnabled() { |
| return myEditor.getField().getDocument().getTextLength() > 0; |
| } |
| |
| @Nullable |
| protected Editor getEditor() { |
| if (getMode() == Mode.ADVANCED) { |
| return myEditor.getField().getEditor(); |
| } else { |
| return myComboboxEditor.getEditor(); |
| } |
| } |
| |
| protected void setModeImpl(Mode mode) { |
| // mySettingsPanel.setVisible(mode == Mode.ADVANCED); |
| myForm.getEditContextButton().setVisible(mode == Mode.ADVANCED); |
| |
| if (mode == Mode.ADVANCED) { |
| setEditor(myEditor, GridConstraints.SIZEPOLICY_WANT_GROW); |
| myEditor.getField().selectAll(); |
| } else { |
| setEditor(myComboBox, GridConstraints.SIZEPOLICY_FIXED); |
| myComboBox.setModel(myModel); |
| myComboBox.getEditor().selectAll(); |
| } |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| final Editor editor = getEditor(); |
| if (editor != null) { |
| editor.getContentComponent().grabFocus(); |
| } |
| } |
| }); |
| } |
| |
| private void setEditor(JComponent editor, int vSizePolicy) { |
| if (myEditorComponent != null) { |
| myForm.getEditorPanel().remove(myEditorComponent); |
| } |
| |
| final GridConstraints gridConstraints = new GridConstraints(); |
| gridConstraints.setFill(vSizePolicy == GridConstraints.SIZEPOLICY_WANT_GROW ? GridConstraints.FILL_BOTH : GridConstraints.FILL_HORIZONTAL); |
| gridConstraints.setVSizePolicy(vSizePolicy); |
| myForm.getEditorPanel().add(myEditorComponent = editor, gridConstraints); |
| } |
| |
| protected static Document createXPathDocument(Project project, HistoryElement historyElement) { |
| |
| final String expression = historyElement != null ? historyElement.expression : ""; |
| final PsiFile file = PsiFileFactory.getInstance(project).createFileFromText("DummyFile.xpath", XPathFileType.XPATH, expression, LocalTimeCounter.currentTime(), true); |
| final Document document = PsiDocumentManager.getInstance(project).getDocument(file); |
| // not sure why this is required... |
| assert document != null; |
| document.setReadOnly(false); |
| |
| assert document.isWritable() : "WTF, document is not writable? Text = <" + expression + ">"; |
| return document; |
| } |
| |
| @SuppressWarnings({ "unchecked" }) |
| public boolean show(XmlElement contextElement) { |
| prepareShow(contextElement); |
| |
| show(); |
| |
| return isOK(); |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| private void prepareShow(XmlElement contextElement) { |
| |
| final NamespaceCollector.CollectedInfo collectedInfo; |
| if (contextElement != null) { |
| collectedInfo = NamespaceCollector.collectInfo((XmlFile)contextElement.getContainingFile()); |
| myNamespaceCache = collectedInfo.namespaces; |
| } else { |
| collectedInfo = NamespaceCollector.empty(); |
| myNamespaceCache = null; |
| } |
| |
| myContextProvider = new InteractiveContextProvider(contextElement, collectedInfo, myModel); |
| myContextProvider.attachTo(myXPathFile); |
| |
| final HistoryElement historyElement = myModel.getSelectedItem(); |
| if (historyElement != null) { |
| myContextProvider.getNamespaceContext().setMap(asMap(historyElement.namespaces)); |
| } else { |
| myContextProvider.getNamespaceContext().setMap(asMap(null)); |
| } |
| |
| updateOkAction(); |
| } |
| |
| protected static Collection<Namespace> merge(Collection<Namespace> namespaces, Collection<Namespace> cache, boolean merge) { |
| if (cache == null) return namespaces; |
| |
| final Set<Namespace> n; |
| |
| if (merge) { |
| n = new HashSet<Namespace>(cache); |
| n.removeAll(namespaces); |
| n.addAll(namespaces); |
| } else { |
| n = new HashSet<Namespace>(namespaces); |
| for (Namespace namespace : n) { |
| for (Namespace cached : cache) { |
| if (namespace.getUri().equals(cached.getUri())) { |
| namespace.setPrefix(cached.prefix); |
| } |
| } |
| } |
| } |
| return n; |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| protected Map<String, String> asMap(Collection<Namespace> namespaces) { |
| if (namespaces == null) { |
| if (myNamespaceCache != null) { |
| return Namespace.makeMap(myNamespaceCache); |
| } else { |
| return Collections.emptyMap(); |
| } |
| } |
| |
| if (this.myNamespaceCache != null) { |
| namespaces = merge(myNamespaceCache, namespaces, false); |
| } |
| |
| return Namespace.makeMap(namespaces); |
| } |
| |
| public JComponent getPreferredFocusedComponent() { |
| final Editor editor = getEditor(); |
| if (editor != null) { |
| return editor.getContentComponent(); |
| } else { |
| return null; |
| } |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| public Context getContext() { |
| final HistoryElement context = myModel.getSelectedItem(); |
| if (context == null || context.expression == null) { |
| final Set<Namespace> cache = myNamespaceCache != null ? myNamespaceCache : Collections.<Namespace>emptySet(); |
| return new Context(new HistoryElement(myDocument.getText(), Collections.<Variable>emptySet(), cache), getMode()); |
| } |
| |
| final Collection<Namespace> namespaces = myNamespaceCache != null ? |
| merge(myNamespaceCache, context.namespaces, false) : context.namespaces; |
| return new Context(new HistoryElement(context.expression, context.variables, namespaces), getMode()); |
| } |
| |
| public static class Context { |
| public final HistoryElement input; |
| public final Mode mode; |
| |
| Context(HistoryElement context, Mode mode) { |
| this.input = context; |
| this.mode = mode; |
| } |
| } |
| |
| protected class EditorAdapter extends BasicComboBoxEditor { |
| private final EditorTextField myTf; |
| |
| public EditorAdapter(EditorTextField tf) { |
| myTf = tf; |
| } |
| |
| public Component getEditorComponent() { |
| return myTf.getComponent(); |
| } |
| |
| @Nullable |
| public Object getItem() { |
| return myModel.getSelectedItem(); |
| } |
| |
| public void selectAll() { |
| myTf.selectAll(); |
| } |
| |
| public void setItem(Object object) { |
| if (object == null) { |
| myEditor.getField().setText(""); |
| } else { |
| myEditor.getField().setText(((HistoryElement)object).expression); |
| } |
| } |
| } |
| |
| private static class MyVariableResolver extends SimpleVariableContext { |
| private final HistoryModel myModel; |
| |
| public MyVariableResolver(HistoryModel model) { |
| myModel = model; |
| } |
| |
| @NotNull |
| public String[] getVariablesInScope(XPathElement element) { |
| final HistoryElement selectedItem = myModel.getSelectedItem(); |
| if (selectedItem != null) { |
| return Variable.asSet(selectedItem.variables).toArray(new String[selectedItem.variables.size()]); |
| } else { |
| return ArrayUtil.EMPTY_STRING_ARRAY; |
| } |
| } |
| } |
| |
| private class InteractiveContextProvider extends ContextProvider { |
| private final XmlElement myContextElement; |
| private final NamespaceCollector.CollectedInfo myCollectedInfo; |
| private final MyVariableResolver myVariableResolver; |
| private final EvalExpressionDialog.MyNamespaceContext myNamespaceContext; |
| |
| public InteractiveContextProvider(XmlElement contextElement, NamespaceCollector.CollectedInfo collectedInfo, HistoryModel model) { |
| myContextElement = contextElement; |
| myCollectedInfo = collectedInfo; |
| myVariableResolver = new MyVariableResolver(model); |
| myNamespaceContext = new EvalExpressionDialog.MyNamespaceContext(); |
| } |
| |
| @NotNull |
| public ContextType getContextType() { |
| return XPathSupport.TYPE; |
| } |
| |
| @Nullable |
| public XmlElement getContextElement() { |
| return myContextElement; |
| } |
| |
| @NotNull |
| public EvalExpressionDialog.MyNamespaceContext getNamespaceContext() { |
| return myNamespaceContext; |
| } |
| |
| public VariableContext getVariableContext() { |
| return myVariableResolver; |
| } |
| |
| public Set<QName> getAttributes(boolean forValidation) { |
| return myCollectedInfo.attributes; |
| } |
| |
| private Set<QName> filterDefaultNamespace(Set<QName> _set) { |
| final Set<QName> set = new HashSet<QName>(_set); |
| for (Iterator<QName> it = set.iterator(); it.hasNext();) { |
| final QName name = it.next(); |
| final String prefix = name.getPrefix(); |
| if (prefix == null || prefix.length() == 0) { |
| final String uri = name.getNamespaceURI(); |
| if (uri != null && uri.length() > 0) { |
| final String assignedPrefix = myNamespaceContext.getPrefixForURI(uri, null); |
| if (assignedPrefix == null || assignedPrefix.length() == 0) { |
| it.remove(); |
| } |
| } |
| } |
| } |
| return set; |
| } |
| |
| public Set<QName> getElements(boolean forValidation) { |
| return filterDefaultNamespace(myCollectedInfo.elements); |
| } |
| } |
| |
| protected class MyNamespaceContext implements NamespaceContext { |
| private BidirectionalMap<String, String> myMap; |
| |
| @Nullable |
| public String getNamespaceURI(String prefix, XmlElement context) { |
| final String s = myMap.get(prefix); |
| if (s == null && prefix.length() == 0) { |
| return ""; |
| } |
| return s; |
| } |
| |
| @Nullable |
| public String getPrefixForURI(String uri, XmlElement context) { |
| final List<String> list = myMap.getKeysByValue(uri); |
| return list != null && !list.isEmpty() ? list.get(0) : null; |
| } |
| |
| @NotNull |
| public Collection<String> getKnownPrefixes(XmlElement context) { |
| return myMap.keySet(); |
| } |
| |
| @Nullable |
| public PsiElement resolve(String prefix, XmlElement context) { |
| return null; |
| } |
| |
| public void setMap(Map<String, String> map) { |
| myMap = new BidirectionalMap<String, String>(); |
| myMap.putAll(map); |
| } |
| |
| public IntentionAction[] getUnresolvedNamespaceFixes(PsiReference reference, String localName) { |
| return new IntentionAction[]{ new MyRegisterPrefixAction(reference) }; |
| } |
| |
| @Override |
| public String getDefaultNamespace(XmlElement context) { |
| return null; |
| } |
| } |
| |
| private class MyRegisterPrefixAction implements IntentionAction { |
| private final PsiReference myReference; |
| |
| public MyRegisterPrefixAction(PsiReference reference) { |
| myReference = reference; |
| } |
| |
| @NotNull |
| public String getText() { |
| return "Register Namespace Prefix"; |
| } |
| |
| @NotNull |
| public String getFamilyName() { |
| return getText(); |
| } |
| |
| public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { |
| return myReference instanceof PrefixReference && myReference.getElement().isValid() && ((PrefixReference)myReference).isUnresolved(); |
| } |
| |
| public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { |
| final Set<String> prefix = Collections.singleton(myReference.getCanonicalText()); |
| |
| final Map<String, String> myMap = myContextProvider.getNamespaceContext().myMap; |
| final Collection<String> list; |
| if (myNamespaceCache == null) { |
| final ExternalResourceManager erm = ExternalResourceManager.getInstance(); |
| list = new ArrayList<String>(Arrays.asList(erm.getResourceUrls(null, true))); |
| for (String namespace : myMap.values()) { |
| list.remove(namespace); |
| } |
| Collections.sort((List<String>)list); |
| } else { |
| list = myMap.values(); |
| } |
| |
| final AddNamespaceDialog dlg = new AddNamespaceDialog(project, prefix, list, myNamespaceCache == null ? |
| AddNamespaceDialog.Mode.URI_EDITABLE : |
| AddNamespaceDialog.Mode.FIXED); |
| |
| dlg.show(); |
| |
| if (dlg.isOK()) { |
| final Namespace namespace = new Namespace(dlg.getPrefix(), dlg.getURI()); |
| |
| final HistoryElement selectedItem = myModel.getSelectedItem(); |
| final Collection<Namespace> n; |
| final Collection<Variable> v; |
| if (selectedItem != null) { |
| n = new HashSet<Namespace>(selectedItem.namespaces); |
| n.remove(namespace); |
| n.add(namespace); |
| v = selectedItem.variables; |
| } else { |
| n = Collections.singleton(namespace); |
| //noinspection unchecked |
| v = Collections.emptySet(); |
| } |
| |
| updateContext(n, v); |
| } |
| } |
| |
| public boolean startInWriteAction() { |
| return false; |
| } |
| } |
| } |