diff -r 766e8327016d spi.debugger.ui/apichanges.xml --- a/spi.debugger.ui/apichanges.xml Fri Jun 06 19:28:24 2008 +0200 +++ b/spi.debugger.ui/apichanges.xml Wed Jun 11 16:38:59 2008 +0200 @@ -91,6 +91,27 @@ made subject to such option by the copyr + + + EditorContextDispatcher class added. + + + + + +

+ EditorContextDispatcher class added, in order to simplify the access to current + active elements in the IDE (like current FileObject and editor pane) and + to make the events dispatching more efficient - reduce the number of listeners on context switching. + EditorContextDispatcher allows registration of listeners based on files MIME type, therefore + listeners obtain change events only when context change concerns a file of the given MIME type. +

+
+ + +
+ + diff -r 766e8327016d spi.debugger.ui/manifest.mf --- a/spi.debugger.ui/manifest.mf Fri Jun 06 19:28:24 2008 +0200 +++ b/spi.debugger.ui/manifest.mf Wed Jun 11 16:38:59 2008 +0200 @@ -2,6 +2,6 @@ OpenIDE-Module: org.netbeans.spi.debugge OpenIDE-Module: org.netbeans.spi.debugger.ui/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/debugger/ui/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/debugger/resources/mf-layer.xml -OpenIDE-Module-Specification-Version: 2.12 +OpenIDE-Module-Specification-Version: 2.13 OpenIDE-Module-Provides: org.netbeans.spi.debugger.ui OpenIDE-Module-Install: org/netbeans/modules/debugger/ui/DebuggerModule.class diff -r 766e8327016d spi.debugger.ui/nbproject/project.xml --- a/spi.debugger.ui/nbproject/project.xml Fri Jun 06 19:28:24 2008 +0200 +++ b/spi.debugger.ui/nbproject/project.xml Wed Jun 11 16:38:59 2008 +0200 @@ -98,6 +98,14 @@ made subject to such option by the copyr + org.openide.filesystems + + + + 7.9 + + + org.openide.loaders diff -r 766e8327016d spi.debugger.ui/src/org/netbeans/spi/debugger/ui/EditorContextDispatcher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/spi.debugger.ui/src/org/netbeans/spi/debugger/ui/EditorContextDispatcher.java Wed Jun 11 16:38:59 2008 +0200 @@ -0,0 +1,555 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + */ + +package org.netbeans.spi.debugger.ui; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.swing.JEditorPane; +import javax.swing.text.Caret; +import javax.swing.text.StyledDocument; + +import org.openide.cookies.EditorCookie; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileStateInvalidException; +import org.openide.loaders.DataObject; +import org.openide.loaders.DataObjectNotFoundException; +import org.openide.text.NbDocument; +import org.openide.util.Lookup; +import org.openide.util.LookupEvent; +import org.openide.util.LookupListener; +import org.openide.util.RequestProcessor; +import org.openide.util.Utilities; +import org.openide.util.WeakListeners; +import org.openide.windows.TopComponent; + +/** + * Dispatcher of context-related events and provider of active elements in the IDE. + * + * @author Martin Entlicher + * @since 2.13 + */ +public final class EditorContextDispatcher { + + /** + * Name of property fired when the current file changes. + */ + public static final String PROP_FILE = "file"; // NOI18N + /** + * Name of property fired when the current editor changes. + */ + public static final String PROP_EDITOR = "editor"; // NOI18N + + private static EditorContextDispatcher context; + + /** + * Get the default instance of EditorContextDispatcher. + * @return The EditorContextDispatcher + */ + public static synchronized EditorContextDispatcher getDefault() { + if (context == null) { + context = new EditorContextDispatcher(); + } + return context; + } + + + private final RequestProcessor refreshProcessor; + private final Lookup.Result resFileObject; + private final Lookup.Result resEditorCookie; + private final PropertyChangeListener tcListener; + + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private final Map pcsByMIMEType = new HashMap(); + + private String lastFiredMIMEType = null; + private Map lastMIMETypeEvents = new HashMap(); + + private String currentURL; + private FileObject currentFile = null; + private EditorCookie currentEditorCookie = null; + private JEditorPane currentOpenedPane = null; + + // Most recent in editor: + private Reference mostRecentFileRef = new WeakReference(null); + private Reference mostRecentEditorCookieRef = new WeakReference(null); + private Reference mostRecentOpenedPaneRef = new WeakReference(null); + + private EditorContextDispatcher() { + refreshProcessor = new RequestProcessor("Refresh Editor Context", 1); // NOI18N + + resFileObject = Utilities.actionsGlobalContext().lookupResult(FileObject.class); + resFileObject.addLookupListener(new EditorLookupListener(FileObject.class)); + + resEditorCookie = Utilities.actionsGlobalContext().lookupResult(EditorCookie.class); + resEditorCookie.addLookupListener(new EditorLookupListener(EditorCookie.class)); + + tcListener = new EditorLookupListener(TopComponent.class); + TopComponent.getRegistry ().addPropertyChangeListener (WeakListeners.propertyChange( + tcListener, TopComponent.getRegistry())); + } + + /** + * Get the current active file. + * @return The current file or null when there is no active file. + */ + public synchronized FileObject getCurrentFile() { + return currentFile; + } + + /** + * Get the String representation of URL of the current active file. + * @return The String representation of URL of the current active file or + * an empty String when there is no active file. + */ + public synchronized String getCurrentURLAsString() { + if (currentURL == null) { + FileObject fo = getCurrentFile(); + if (fo != null) { + try { + currentURL = fo.getURL().toString (); + } catch (FileStateInvalidException ex) {} + } + if (currentURL == null) { + currentURL = ""; // NOI18N + } + } + return currentURL; + } + + /** + * Get the {@link org.openide.cookies.EditorCookie} of currently edited file. + * @return The current {@link org.openide.cookies.EditorCookie} or + * null when there is no currently edited file. + */ + public synchronized EditorCookie getCurrentEditorCookie() { + if (currentOpenedPane != null) { + return currentEditorCookie; + } else { + return null; + } + } + + /** + * Get the {@link javax.swing.JEditorPane} of currently edited file. + * @return The current {@link javax.swing.JEditorPane} or + * null when there is no currently edited file. + */ + public synchronized JEditorPane getCurrentEditor() { + return currentOpenedPane; + } + + /** + * Get the line number of the caret in the current editor. + * @return the line number or -1 when there is no current editor. + */ + public int getCurrentLineNumber() { + EditorCookie e = getCurrentEditorCookie (); + if (e == null) return -1; + JEditorPane ep = getCurrentEditor (); + if (ep == null) return -1; + StyledDocument d = e.getDocument (); + if (d == null) return -1; + Caret caret = ep.getCaret (); + if (caret == null) return -1; + int ln = NbDocument.findLineNumber ( + d, + caret.getDot () + ); + return ln + 1; + } + + /** + * Get the most recent active file. This returns the active file if there's + * one, or a file, that was most recently active. + * @return The most recent file or null when there was no recent + * active file. + */ + public synchronized FileObject getMostRecentFile() { + return mostRecentFileRef.get(); + } + + /** + * Get the String representation of URL of the most recent active file. + * @return The String representation of URL of the most recent file or + * an empty String when there was no recent active file. + */ + public synchronized String getMostRecentURLAsString() { + FileObject fo = getMostRecentFile(); + if (fo != null) { + try { + return fo.getURL().toString (); + } catch (FileStateInvalidException ex) {} + } + return ""; // NOI18N + } + + private synchronized EditorCookie getMostRecentEditorCookie() { + if (getMostRecentEditor() != null) { + return mostRecentEditorCookieRef.get(); + } else { + return null; + } + } + + private synchronized JEditorPane getMostRecentEditor() { + return mostRecentOpenedPaneRef.get(); + } + + /** + * Get the line number of the caret in the most recent editor. + * This returns the current line number in the current editor if there's one, + * or a line number of the caret in the editor, that was most recently active. + * @return the line number or -1 when there was no recent active editor. + */ + public int getMostRecentLineNumber() { + EditorCookie e = getMostRecentEditorCookie (); + if (e == null) return -1; + JEditorPane ep = getMostRecentEditor (); + if (ep == null) return -1; + StyledDocument d = e.getDocument (); + if (d == null) return -1; + Caret caret = ep.getCaret (); + if (caret == null) return -1; + int ln = NbDocument.findLineNumber ( + d, + caret.getDot () + ); + return ln + 1; + } + + /** + * Add a PropertyChangeListener to this context dispatcher. + * @param l The PropertyChangeListener + */ + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + /** + * Remove a PropertyChangeListener from this context dispatcher. + * @param l The PropertyChangeListener + */ + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + // Also remove the listener from all MIME types + synchronized (pcsByMIMEType) { + Set MIMETypes = new HashSet(pcsByMIMEType.keySet()); + for (String MIMEType : MIMETypes) { + PropertyChangeSupport pcs = pcsByMIMEType.get(MIMEType); + pcs.removePropertyChangeListener(l); + if (pcs.getPropertyChangeListeners().length == 0) { + pcsByMIMEType.remove(MIMEType); + } + } + } + } + + /** + * Add a PropertyChangeListener to this context dispatcher to be notified + * about changes of files with a specified MIME type. + * @param MIMEType The MIME type to report changes for + * @param l The PropertyChangeListener + */ + public void addPropertyChangeListener(String MIMEType, PropertyChangeListener l) { + PropertyChangeSupport pcs; + synchronized (pcsByMIMEType) { + pcs = pcsByMIMEType.get(MIMEType); + if (pcs == null) { + pcs = new PropertyChangeSupport(this); + pcsByMIMEType.put(MIMEType, pcs); + } + } + pcs.addPropertyChangeListener(l); + } + + /*public void removePropertyChangeListener(String MIMEType, PropertyChangeListener l) { + PropertyChangeSupport pcs; + synchronized (pcsByMIMEType) { + pcs = pcsByMIMEType.get(MIMEType); + if (pcs == null) { + return ; + } + } + pcs.removePropertyChangeListener(l); + }*/ + + private void firePropertyChange(String propertyName, Object oldValue, Object newValue) { + //System.err.println("EditorContextDispatcher.firePropertyChange("+propertyName+", "+oldValue+", "+newValue+")"); + pcs.firePropertyChange(propertyName, oldValue, newValue); + } + + private void firePropertyChange(PropertyChangeEvent evt, String preferredMIMEType) { + //System.err.println("EditorContextDispatcher.firePropertyChange("+evt.getPropertyName()+", "+evt.getOldValue()+", "+evt.getNewValue()+")"); + pcs.firePropertyChange(evt); + if (PROP_FILE.equals(evt.getPropertyName())) { + // Retrieve the files MIME types and fire to appropriate MIME type listeners: + FileObject oldFile = (FileObject) evt.getOldValue(); + FileObject newFile = (FileObject) evt.getNewValue(); + String oldMIMEType = (oldFile != null) ? oldFile.getMIMEType() : null; + String newMIMEType = (newFile != null) ? newFile.getMIMEType() : null; + PropertyChangeSupport pcsMIMEOld = null, pcsMIMENew = null; + PropertyChangeEvent evtOld = null, evtNew = null; + synchronized (pcsByMIMEType) { + if (oldMIMEType != null && oldMIMEType.equals(newMIMEType)) { + pcsMIMEOld = pcsByMIMEType.get(oldMIMEType); + evtOld = evt; + //System.err.println("EditorContextDispatcher.fireMIMETypeChange("+oldMIMEType+", "+evt.getPropertyName()+", "+evt.getOldValue()+", "+evt.getNewValue()+")"); + } else { + if (oldMIMEType != null) { + pcsMIMEOld = pcsByMIMEType.get(oldMIMEType); + if (pcsMIMEOld != null) { + evtOld = new PropertyChangeEvent(evt.getSource(), + evt.getPropertyName(), + evt.getOldValue(), + null); + } + //System.err.println("EditorContextDispatcher.fireMIMETypeChange("+oldMIMEType+", "+evt.getPropertyName()+", "+evt.getOldValue()+", null)"); + } + if (newMIMEType != null) { + pcsMIMENew = pcsByMIMEType.get(newMIMEType); + if (pcsMIMENew != null) { + evtNew = new PropertyChangeEvent(evt.getSource(), + evt.getPropertyName(), + null, + evt.getNewValue()); + } + //System.err.println("EditorContextDispatcher.fireMIMETypeChange("+newMIMEType+", "+evt.getPropertyName()+", null, "+evt.getNewValue()+")"); + } + } + } + if (pcsMIMEOld != null) { + pcsMIMEOld.firePropertyChange(evtOld); + } + if (pcsMIMENew != null) { + pcsMIMENew.firePropertyChange(evtNew); + } + // Now check, whether the MIME type has changed and whether we should + // fire non-file change events with 'null' new values to listeners + // registered for a particular MIME type: + if (oldMIMEType != null && !oldMIMEType.equals(newMIMEType) && pcsMIMEOld != null) { + String lastMIMEType; + Map lastEvents; + synchronized (this) { + lastMIMEType = lastFiredMIMEType; + lastEvents = new HashMap(lastMIMETypeEvents); + if (lastMIMEType != null && lastMIMEType.equals(oldMIMEType)) { + lastFiredMIMEType = null; + lastMIMETypeEvents.clear(); + } else { + lastEvents = null; + } + } + if (lastEvents != null) { + for (String property : lastEvents.keySet()) { + pcsMIMEOld.firePropertyChange(property, lastEvents.get(property), null); + } + } + } + } else { + PropertyChangeSupport pcsMIME = null; + if (preferredMIMEType != null) { + synchronized (pcsByMIMEType) { + pcsMIME = pcsByMIMEType.get(preferredMIMEType); + } + if (pcsMIME != null) { + pcsMIME.firePropertyChange(evt); + } + } + //System.err.println("EditorContextDispatcher.fireMIMETypeChange("+preferredMIMEType+", "+evt.getPropertyName()+", "+evt.getOldValue()+", "+evt.getNewValue()+")"); + synchronized (this) { + if (pcsMIME != null) { + lastFiredMIMEType = preferredMIMEType; + lastMIMETypeEvents.put(evt.getPropertyName(), evt.getNewValue()); + } else { + lastFiredMIMEType = null; + lastMIMETypeEvents.clear(); + } + } + } + } + + private class EditorLookupListener extends Object implements LookupListener, PropertyChangeListener { + + private Class type; + + public EditorLookupListener(Class type) { + this.type = type; + } + + public void resultChanged(LookupEvent ev) { + //System.err.println("EditorContextDispatcher.resultChanged(), type = "+type); + if (type == FileObject.class) { + Collection fos = resFileObject.allInstances(); + FileObject oldFile; + FileObject newFile; + synchronized (EditorContextDispatcher.this) { + oldFile = currentFile; + if (fos.size() == 0) { + currentFile = null; + } else if (fos.size() == 1) { + currentFile = fos.iterator().next(); + } else { + currentFile = findPrimary(fos); + } + //System.err.println("\nCURRENT FILES = "+fos+"\n"); + newFile = currentFile; + currentURL = null; + /*if (newFile != null) { - NO, we need the last file in editor. + mostRecentFileRef = new WeakReference(newFile); + }*/ + } + if (oldFile != newFile) { + refreshProcessor.post(new EventFirer(PROP_FILE, oldFile, newFile)); + } + } else if (type == EditorCookie.class) { + Collection ecs = resEditorCookie.allInstances(); + EditorCookie newEditor; + synchronized (EditorContextDispatcher.this) { + if (ecs.size() == 0) { + currentEditorCookie = null; + } else { + currentEditorCookie = ecs.iterator().next(); + } + newEditor = currentEditorCookie; + if (currentFile != null) { + if (newEditor != null) { + mostRecentEditorCookieRef = new WeakReference(newEditor); + } + } + } + /* Fire the editor event only when JEditorPane is set/unset + if (oldEditor != newEditor) { + // refreshProcessor.post(new EventFirer(PROP_EDITOR, oldEditor, newEditor, MIMEType)); + } + */ + } + } + + public void propertyChange(PropertyChangeEvent evt) { + String propertyName = evt.getPropertyName(); + //System.err.println("EditorContextDispatcher.propertyChanged("+evt.getPropertyName()+": "+evt.getOldValue()+", "+evt.getNewValue()+"), type = "+type); + if (type == TopComponent.class) { + if (propertyName.equals(TopComponent.Registry.PROP_ACTIVATED)) { + TopComponent newComponnet = (TopComponent) evt.getNewValue(); + JEditorPane oldEditor; + JEditorPane newEditor; + String MIMEType = null; + synchronized (EditorContextDispatcher.this) { + boolean isSetPane = false; + oldEditor = currentOpenedPane; + if (currentEditorCookie != null && newComponnet != null) { + JEditorPane[] openedPanes = currentEditorCookie.getOpenedPanes(); + if (openedPanes != null && openedPanes.length >= 1) { + for (JEditorPane openedPane : openedPanes) { + if (newComponnet.isAncestorOf(openedPane)) { + //System.err.println("\n"+newComponnet+".isAncestorOf("+openedPane+")\n"); + currentOpenedPane = openedPane; + isSetPane = true; + break; + } + } + } + } + if (!isSetPane) { + currentOpenedPane = null; + } + newEditor = currentOpenedPane; + if (currentFile != null) { + MIMEType = currentFile.getMIMEType(); + if (newEditor != null) { + mostRecentOpenedPaneRef = new WeakReference(newEditor); + mostRecentFileRef = new WeakReference(currentFile); + } + } else { + MIMEType = null; + } + //System.err.println("\nCurrent Opened Pane = "+currentOpenedPane+", currentFile = "+currentFile+"\n"); + } + if (oldEditor != newEditor) { + refreshProcessor.post(new EventFirer(PROP_EDITOR, oldEditor, newEditor, MIMEType)); + } + } + } + } + + private FileObject findPrimary(Collection fos) { + for (FileObject fo : fos) { + try { + DataObject dobj = DataObject.find(fo); + if (fo.equals(dobj.getPrimaryFile())) { + return fo; + } + } catch (DataObjectNotFoundException ex) {} + } + // No primary file, return just the first one: + return fos.iterator().next(); + } + + } + + private final class EventFirer implements Runnable { + + private final PropertyChangeEvent evt; + private final String MIMEType; + + public EventFirer(String propertyName, Object oldValue, Object newValue) { + this(propertyName, oldValue, newValue, null); + } + + public EventFirer(String propertyName, Object oldValue, Object newValue, String MIMEType) { + this.evt = new PropertyChangeEvent(EditorContextDispatcher.this, propertyName, oldValue, newValue); + this.MIMEType = MIMEType; + } + + public void run() { + firePropertyChange(evt, MIMEType); + } + + } + +}