001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.napi.gsfret.source.support;
042:
043: import java.util.ArrayList;
044: import java.util.HashMap;
045: import java.util.List;
046: import java.util.Map;
047: import java.util.WeakHashMap;
048: import javax.swing.event.CaretEvent;
049: import javax.swing.event.CaretListener;
050: import javax.swing.event.ChangeEvent;
051: import javax.swing.event.ChangeListener;
052: import javax.swing.text.JTextComponent;
053: import org.netbeans.napi.gsfret.source.Phase;
054: import org.netbeans.napi.gsfret.source.Source.Priority;
055: import org.netbeans.napi.gsfret.source.SourceTaskFactory;
056: import org.openide.filesystems.FileObject;
057: import org.openide.util.RequestProcessor;
058:
059: /**
060: * This file is originally from Retouche, the Java Support
061: * infrastructure in NetBeans. I have modified the file as little
062: * as possible to make merging Retouche fixes back as simple as
063: * possible.
064: *
065: * A {@link SourceTaskFactorySupport} that registers tasks to all files that are
066: * opened in the editor and are visible. This factory also listens on the selection in
067: * opened and visible JTextComponents and reschedules the tasks as necessary.
068: *
069: * The tasks may access current selection span using {@link #getLastSelection} method.
070: *
071: * @since 0.15
072: *
073: * @author Jan Lahoda
074: */
075: public abstract class SelectionAwareSourceTaskFactory extends
076: SourceTaskFactory {
077:
078: private static final int DEFAULT_RESCHEDULE_TIMEOUT = 300;
079: private static final RequestProcessor WORKER = new RequestProcessor(
080: "SelectionAwareSourceTaskFactory worker");
081:
082: private int timeout;
083: private String[] supportedMimeTypes;
084:
085: /**Construct the SelectionAwareSourceTaskFactory with given {@link Phase} and {@link Priority}.
086: *
087: * @param phase phase to use for tasks created by {@link #createTask}
088: * @param priority priority to use for tasks created by {@link #createTask}
089: */
090: public SelectionAwareSourceTaskFactory(Phase phase,
091: Priority priority) {
092: this (phase, priority, (String[]) null);
093: }
094:
095: /**Construct the SelectionAwareSourceTaskFactory with given {@link Phase} and {@link Priority}.
096: *
097: * @param phase phase to use for tasks created by {@link #createTask}
098: * @param priority priority to use for tasks created by {@link #createTask}
099: * @param supportedMimeTypes a list of mime types on which the tasks created by this factory should be run
100: * @since 0.22
101: */
102: public SelectionAwareSourceTaskFactory(Phase phase,
103: Priority priority, String... supportedMimeTypes) {
104: super (phase, priority);
105: //XXX: weak, or something like this:
106: OpenedEditors.getDefault().addChangeListener(
107: new ChangeListenerImpl());
108: this .timeout = DEFAULT_RESCHEDULE_TIMEOUT;
109: this .supportedMimeTypes = supportedMimeTypes != null ? supportedMimeTypes
110: .clone()
111: : null;
112: }
113:
114: /**@inheritDoc*/
115: public List<FileObject> getFileObjects() {
116: List<FileObject> files = OpenedEditors
117: .filterSupportedMIMETypes(OpenedEditors.getDefault()
118: .getVisibleEditorsFiles(), supportedMimeTypes);
119:
120: return files;
121: }
122:
123: private Map<JTextComponent, ComponentListener> component2Listener = new HashMap<JTextComponent, SelectionAwareSourceTaskFactory.ComponentListener>();
124: private static Map<FileObject, Integer> file2SelectionStartPosition = new WeakHashMap<FileObject, Integer>();
125: private static Map<FileObject, Integer> file2SelectionEndPosition = new WeakHashMap<FileObject, Integer>();
126:
127: /**Returns current selection span in current {@link JTextComponent} for a given file.
128: *
129: * @param file file from which the position should be found
130: * @return selection span in the current {@link JTextComponent} for a given file.
131: * <code>null</code> if no selection available so far.
132: */
133: public synchronized static int[] getLastSelection(FileObject file) {
134: if (file == null) {
135: throw new NullPointerException("Cannot pass null file!");
136: }
137:
138: Integer startPosition = file2SelectionStartPosition.get(file);
139: Integer endPosition = file2SelectionEndPosition.get(file);
140:
141: if (startPosition == null || endPosition == null) {
142: //no position set yet:
143: return null;
144: }
145:
146: return new int[] { startPosition, endPosition };
147: }
148:
149: private synchronized static void setLastSelection(FileObject file,
150: int startPosition, int endPosition) {
151: file2SelectionStartPosition.put(file, startPosition);
152: file2SelectionEndPosition.put(file, endPosition);
153: }
154:
155: private class ChangeListenerImpl implements ChangeListener {
156:
157: public void stateChanged(ChangeEvent e) {
158: List<JTextComponent> added = new ArrayList<JTextComponent>(
159: OpenedEditors.getDefault().getVisibleEditors());
160: List<JTextComponent> removed = new ArrayList<JTextComponent>(
161: component2Listener.keySet());
162:
163: added.removeAll(component2Listener.keySet());
164: removed.removeAll(OpenedEditors.getDefault()
165: .getVisibleEditors());
166:
167: for (JTextComponent c : removed) {
168: c.removeCaretListener(component2Listener.remove(c));
169: }
170:
171: for (JTextComponent c : added) {
172: ComponentListener l = new ComponentListener(c);
173:
174: c.addCaretListener(l);
175: component2Listener.put(c, l);
176:
177: //TODO: are we in AWT Thread?:
178: setLastSelection(OpenedEditors.getFileObject(c), c
179: .getSelectionStart(), c.getSelectionEnd());
180: }
181:
182: fileObjectsChanged();
183: }
184:
185: }
186:
187: private class ComponentListener implements CaretListener {
188:
189: private JTextComponent component;
190: private final RequestProcessor.Task rescheduleTask;
191:
192: public ComponentListener(final JTextComponent component) {
193: this .component = component;
194: rescheduleTask = WORKER.create(new Runnable() {
195: public void run() {
196: FileObject file = OpenedEditors
197: .getFileObject(ComponentListener.this .component);
198:
199: if (file != null) {
200: reschedule(file);
201: }
202: }
203: });
204: }
205:
206: public void caretUpdate(CaretEvent e) {
207: FileObject file = OpenedEditors.getFileObject(component);
208:
209: if (file != null) {
210: setLastSelection(
211: OpenedEditors.getFileObject(component),
212: component.getSelectionStart(), component
213: .getSelectionEnd());
214: rescheduleTask.schedule(timeout);
215: }
216: }
217:
218: }
219: }
|