blob: c90bfde38176fc186632499529b611f9e30825df [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.editor.fold.ui;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.prefs.AbstractPreferences;
import java.util.prefs.BackingStoreException;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;
import org.netbeans.api.editor.fold.Fold;
import org.netbeans.api.editor.fold.FoldType;
import org.netbeans.api.editor.fold.FoldUtilities;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.editor.BaseKit;
import org.netbeans.editor.ext.ExtKit;
import org.netbeans.junit.NbTestCase;
import org.netbeans.spi.editor.fold.FoldHierarchyTransaction;
import org.netbeans.spi.editor.fold.FoldManager;
import org.netbeans.spi.editor.fold.FoldManagerFactory;
import org.netbeans.spi.editor.fold.FoldOperation;
import org.openide.filesystems.Repository;
import org.openide.filesystems.XMLFileSystem;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.xml.sax.SAXException;
import org.netbeans.modules.editor.fold.FoldHierarchyTestEnv;
import org.netbeans.spi.editor.fold.FoldHierarchyMonitor;
/**
* Checks that the folding succeeds in plugging into BaseCaret's operation.
* It checks that a doubleclick on a folded place does not select the surrounding text.
* Likewise it checks, that a doubleclick on an unfolded place WILL select the surrounding text
*
* @author sdedic
*/
public class BaseCaretTest extends NbTestCase {
private JEditorPane pane;
private FoldHierarchyTestEnv env;
public static Lkp DEFAULT_LOOKUP = null;
public BaseCaretTest(String name) {
super(name);
}
protected boolean runInEQ() {
return true;
}
public static void setLookup(Object[] instances, ClassLoader cl) {
DEFAULT_LOOKUP.doSetLookups(new Lookup[] {
Lookups.fixed(instances),
Lookups.metaInfServices(cl),
Lookups.singleton(cl),
});
}
public static class Lkp extends ProxyLookup {
{
DEFAULT_LOOKUP = this;
}
public void doSetLookups(Lookup... lkps) {
super.setLookups(lkps);
}
}
private static final Method PROCESS_EVENT_METHOD;
static {
System.setProperty("org.openide.util.Lookup", BaseCaretTest.Lkp.class.getName());
Method m = null;
try {
m = Component.class.getDeclaredMethod("processEvent", new Class[] { AWTEvent.class });
m.setAccessible(true);
} catch (Exception ex) {
Exceptions.printStackTrace(ex);
}
PROCESS_EVENT_METHOD = m;
}
public static void prepareTest(String[] additionalLayers, Object[] additionalLookupContent) throws IOException, SAXException, PropertyVetoException {
Collection<URL> allUrls = new ArrayList<URL>();
for (String u : additionalLayers) {
if (u.charAt(0) == '/') {
u = u.substring(1);
}
for (Enumeration<URL> en = Thread.currentThread().getContextClassLoader().getResources(u); en.hasMoreElements(); ) {
allUrls.add(en.nextElement());
}
}
XMLFileSystem system = new XMLFileSystem();
system.setXmlUrls(allUrls.toArray(new URL[allUrls.size()]));
Repository repository = new Repository(system);
Object[] lookupContent = new Object[additionalLookupContent.length + 1];
System.arraycopy(additionalLookupContent, 0, lookupContent, 1, additionalLookupContent.length);
lookupContent[0] = repository;
setLookup(lookupContent, BaseCaretTest.class.getClassLoader());
}
private FM fm;
private Fold fold;
private Callable<Boolean> delegate;
public void setUp() throws Exception {
super.setUp();
Lookup.getDefault();
prepareTest(new String[] {
"/org/netbeans/modules/editor/resources/layer.xml",
"/META-INF/generated-layer.xml",
"/org/netbeans/modules/defaults/mf-layer.xml",
},
new Object[0]);
// hack:
MimeLookup.getLookup("").lookup(FoldHierarchyMonitor.class);
env = new FoldHierarchyTestEnv(new FoldManagerFactory() {
@Override
public FoldManager createFoldManager() {
return fm = new FM();
}
});
pane = env.getPane();
env.getPane().setEditorKit(new Kit());
env.getDocument().insertString(0, "123456789-123456789-123 aa 89-123456789-1234567890", null);
// ensure initialized
env.getHierarchy();
// cannot be done in FoldManager, as setEditorKit() will replace document and reinitialize folds in 2nd thread, which may
// complete even faster than insertString(), so BLE could be thrown.
FoldHierarchyTransaction tran = fm.op.openTransaction();
fm.op.addToHierarchy(
FT,
"",
true,
10,
40,
1, 1,
null,
tran);
tran.commit();
fold = FoldUtilities.findOffsetFold(env.getHierarchy(), 25);
// get x/y of the folded part:
JFrame fr = new JFrame("test");
fr.setBounds(0, 0, 400, 100);
fr.add(pane);
fr.setVisible(true);
delegate = (Callable<Boolean>)pane.getClientProperty("org.netbeans.api.fold.expander");
pane.putClientProperty("org.netbeans.api.fold.expander", new Callable<Boolean>() {
public Boolean call() throws Exception {
return retValue = delegate.call();
}
public boolean equals(Object o) {
return retValue = delegate.equals(o);
}
});
pane.requestFocus();
selectWordCalled = false;
}
public void tearDown() throws Exception {
Window w = SwingUtilities.getWindowAncestor(pane);
w.setVisible(false);
super.tearDown();
}
private void processEvent(AWTEvent e) throws Exception{
PROCESS_EVENT_METHOD.invoke(pane, e);
}
private boolean retValue;
private static boolean selectWordCalled;
private static class Kit extends ExtKit {
@Override
protected Action[] getDeclaredActions() {
Action swa = new TextAction("") {
@Override
public void actionPerformed(ActionEvent evt) {
selectWordCalled = true;
}
};
swa.putValue(Action.NAME, BaseKit.selectWordAction);
Action[] actions = new Action[] {
swa
};
return TextAction.augmentList(super.createActions(), actions);
}
}
/**
* Folded region is double-clicked; it should be unfolded, rather than word-selected.
*/
public void testSelectFoldedRegion() throws Exception {
env.getHierarchy().collapse(fold);
Thread.sleep(300);
Rectangle r = pane.modelToView(10);
// 1st mouseclick on the pane, to position the caret, as if the 1st click in doubleclick was done:
MouseEvent firstPress = new MouseEvent(pane, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
InputEvent.BUTTON1_MASK, r.x, r.y, 1, false);
processEvent(firstPress);
// second press of the doubleclick
MouseEvent secondPress = new MouseEvent(pane, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
InputEvent.BUTTON1_MASK, r.x, r.y, 2, false);
processEvent(secondPress);
// check that no selection is present:
assertFalse(selectWordCalled);
assertNull(pane.getSelectedText());
// check that even the callable did the job:
assertFalse(fold.isCollapsed());
}
public void testSelectUnfoldedRegion() throws Exception {
env.getHierarchy().expand(fold);
Thread.sleep(300);
Rectangle r = pane.modelToView(25);
// 1st mouseclick on the pane, to position the caret, as if the 1st click in doubleclick was done:
MouseEvent firstPress = new MouseEvent(pane, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
InputEvent.BUTTON1_MASK, r.x, r.y, 1, false);
processEvent(firstPress);
// second press of the doubleclick
MouseEvent secondPress = new MouseEvent(pane, MouseEvent.MOUSE_PRESSED, System.currentTimeMillis(),
InputEvent.BUTTON1_MASK, r.x, r.y, 2, false);
env.getPane().requestFocus();
processEvent(secondPress);
assertFalse(retValue);
assertTrue(this.toString(), selectWordCalled);
}
private static final FoldType FT = new FoldType("test");
private static class FM implements FoldManager {
FoldOperation op;
@Override
public void init(FoldOperation operation) {
this.op = operation;
}
@Override
public void initFolds(FoldHierarchyTransaction transaction) {
}
@Override
public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
}
@Override
public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
}
@Override
public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) {
}
@Override
public void removeEmptyNotify(Fold epmtyFold) {
}
@Override
public void removeDamagedNotify(Fold damagedFold) {
}
@Override
public void expandNotify(Fold expandedFold) {
}
@Override
public void release() {
}
}
private static class P extends AbstractPreferences {
private Map<String, String> m = new HashMap<String, String>();
public P(AbstractPreferences parent, String name) {
super(parent, name);
}
@Override
protected void putSpi(String key, String value) {
m.put(key, value);
}
@Override
protected String getSpi(String key) {
return m.get(key);
}
@Override
protected void removeSpi(String key) {
m.remove(key);
}
@Override
protected void removeNodeSpi() throws BackingStoreException {
}
@Override
protected String[] keysSpi() throws BackingStoreException {
return new String[0];
}
@Override
protected String[] childrenNamesSpi() throws BackingStoreException {
return new String[0];
}
@Override
protected AbstractPreferences childSpi(String name) {
return null;
}
@Override
protected void syncSpi() throws BackingStoreException {
}
@Override
protected void flushSpi() throws BackingStoreException {
}
}
}