alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 *
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun
 * Microsystems, Inc. All Rights Reserved.
 */


package org.netbeans.modules.tasklist.suggestions;

import org.openide.windows.TopComponent;
import org.openide.windows.Workspace;
import org.openide.windows.WindowManager;
import org.openide.windows.Mode;
import org.openide.loaders.DataObject;
import org.openide.text.*;
import org.openide.nodes.Node;
import org.openide.cookies.EditorCookie;
import org.openide.util.RequestProcessor;
import org.openide.filesystems.FileObject;
import org.openide.ErrorManager;
import org.netbeans.modules.tasklist.suggestions.settings.ManagerSettings;
import org.netbeans.modules.tasklist.core.TLUtils;
import org.netbeans.modules.tasklist.core.Task;
import org.netbeans.modules.tasklist.providers.DocumentSuggestionProvider;
import org.netbeans.modules.tasklist.providers.SuggestionProvider;

import javax.swing.*;
import javax.swing.Timer;
import javax.swing.event.*;
import javax.swing.text.Document;
import javax.swing.text.StyledDocument;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.awt.event.*;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.logging.Logger;
import java.lang.reflect.InvocationTargetException;

/**
 * Broker actively monitors environment and provides
 * suggestion lists (jobs) for:
 * 
    *
  • {@link #startBroker}, currently opened document (list managed by SuggestionsManager, see getListByRequest) *
  • {@link #startAllOpenedBroker}, all opened documents (list managed by this class) * XXX it does not catch changes/suggestions made by providers on their own *
* * @author Petr Kuzel * @author Tor Norbye (transitive from sacked SuggestionManagerImpl) */ public final class SuggestionsBroker { private static SuggestionsBroker instance; private SuggestionList list; private int clientCount = 0; private SuggestionManagerImpl manager = (SuggestionManagerImpl) SuggestionManagerImpl.getDefault(); // hook for unit tests Env env = new Env(); // FileObject, Set private Map openedFilesSuggestionsMap = new HashMap(); private int allOpenedClientsCount = 0; /** all opened mode is a client of currently opened job (this field). */ private Job allOpenedJob; private SuggestionList allOpenedList; private static final Logger LOGGER = TLUtils.getLogger(SuggestionsBroker.class); // It's list as it need to support duplicates, see overlayed add/remove in start/stop methods private List acceptors = new ArrayList(5); private final ProviderAcceptor compound = new ProviderAcceptor() { public boolean accept(SuggestionProvider provider) { Iterator it = acceptors.iterator(); while (it.hasNext()) { ProviderAcceptor acceptor = (ProviderAcceptor) it.next(); if (acceptor.accept(provider)) return true; } return false; } }; private SuggestionsBroker() { } public static SuggestionsBroker getDefault() { if (instance == null) { instance = new SuggestionsBroker(); } return instance; } public Job startBroker(ProviderAcceptor acceptor) { clientCount++; if (clientCount == 1) { manager.dispatchRun(); startActiveSuggestionFetching(); } return new Job(acceptor); } /** Handle for suggestions foe active document. */ public class Job { private boolean stopped = false; private final ProviderAcceptor acceptor; private Job(ProviderAcceptor acceptor) { this.acceptor = acceptor; acceptors.add(acceptor); } public void stopBroker() { if (stopped) return; stopped = true; acceptors.remove(acceptor); clientCount--; if (clientCount == 0) { stopActiveSuggestionFetching(); // Get rid of suggestion cache, we cannot invalidate its // entries properly without keeping a listener if (cache != null) { cache.flush(); } list = null; instance = null; } } /** * Returns live list containing current suggestions. * List is made live by invoking {@link SuggestionsBroker#startBroker} and * is abandoned ance last client calls {@link Job#stopBroker}. *

* It's global list so listeners must be carefully unregistered * unfortunatelly it's rather complex because list * is typically passed to other clasess (TaskChildren). * Hopefully you can WeakListener. */ public SuggestionList getSuggestionsList() { return getCurrentSuggestionsList(); } } /** Starts monitoring all opened files */ public AllOpenedJob startAllOpenedBroker(ProviderAcceptor acceptor) { allOpenedClientsCount++; if (allOpenedClientsCount == 1) { acceptors.add(acceptor); TopComponent[] documents = SuggestionsScanner.openedTopComponents(); SuggestionsScanner scanner = SuggestionsScanner.getDefault(); List allSuggestions = new LinkedList(); for (int i = 0; i * For lightweight document analysis, you can redo the scanning * whenever the editor is shown and hidden; for more expensive analysis, * you may only want to do it when the document is opened (after a timeout). *

* The API does not define which thread these methods are called on, * so don't make any assumptions. If you want to post something on * the AWT event dispatching thread for example use SwingUtilities. *

* Note that changes in document attributes only are "ignored" (in * the sense that they do not cause document edit notification.) * * @todo Document threading behavior * @todo Document timer behavior (some of the methods are called after * a delay, others are called immediately.) * */ /** Current request reference. Used to correlate register() * calls with requests sent to rescan()/clear() */ private volatile Long currRequest = new Long(0); /** Points to the last completed request. Set to currRequest * when rescan() is done. */ private volatile Comparable finishedRequest = null; final Object getCurrRequest() { return currRequest; } /** * Start scanning for source items. * Attaches top component registry and data object * registry listeners to monitor currently edited file. */ private void startActiveSuggestionFetching() { LOGGER.info("Starting active suggestions fetching...."); // NOI18N // must be removed in docStop WindowSystemMonitor monitor = getWindowSystemMonitor(); monitor.enableOpenCloseEvents(); env.addTCRegistryListener(monitor); env.addDORegistryListener(getDataSystemMonitor()); /* OLD: org.openide.windows.TopComponent.getRegistry(). addPropertyChangeListener(this); // Also scan the current node right away: pretend source listener was // notified of the change to the current node (which has already occurred) // ... unfortunately this is not as easy as just calling getActivatedNodes // on the registry -- because that node may not be the last EDITORvisible // node... So resort to some hacks. Node[] nodes = NewTaskAction.getEditorNodes(); if (nodes != null) { scanner.propertyChange(new PropertyChangeEvent( this, TopComponent.Registry.PROP_ACTIVATED_NODES, null, nodes)); } else { // Most likely you're not looking at a panel that has an // associated node, e.g. the welcome screen, or the editor isn't // open if (err.isLoggable(ErrorManager.INFORMATIONAL)) { err.log("Couldn't find current nodes..."); } } */ // NEW: /** HACK: We need to always know what the current source file in the editor is - and even when there isn't a source file there, we need to know: if you for example switch to the Welcome screen we should remove the tasks for the formerly shown source file. I've tried listening to the global node, since we should always get notified when the current node changes. However, this has a couple of problems. First, we may get notified of the node change BEFORE the source file is done editing; in that case we can't find the node in the editor (we need to check that a node is in the editor since we don't want the task window to for example show the tasks for the current selection in the explorer). Another problem is a scenario I just ran into where if you open A, B from explorer, then select A in the explorer, then select B in the editor: when you now double click A in the explorer there's no rescan. (I may have to debug this). So instead I will go to a more reliable scheme, which unfortunately smells more like a hack from a NetBeans perspective. The basic idea is this: I can find the source editor, and which top component is showing in the source editor. I can get notified of when this changes - by listening for componentHidden of the top most pane. Then I just have to go and see which component is now showing, and switch my component listener to this new component. (From the component I can discover which source file it's editing). This has the benefit that I'll know precisely when a new file has been loaded in, etc. It may have the disadvantage that if you open source files in other modes (by docking and undocking away from the standard configuration) things get broken. Perhaps I can keep my old activated-node-listener scheme in place as a backup solution when locating the source editor mode etc. fails. It gets more complicated. What if you open the task window when the editor is not visible? Then you can't attach a listener to the current window - so you don't get notified when a new file is opened. For that reason we also need to listen to the workspace's property change notification, which will tell us when the set of modes changes in the workspace. ...and of course the workspace itself can change. So we need to listen to the workspace change notification in the window manager as well... */ /* WindowManager manager = WindowManager.getDefault(); manager.addPropertyChangeListener(this); Workspace workspace = WindowManager.getDefault(). getCurrentWorkspace(); workspace.addPropertyChangeListener(this); */ doRescanInAWT(false); } /** Cache tracking suggestions in recently visited files */ private SuggestionCache cache = null; /** List of suggestions restored from the cache that we must delete when leaving this document */ private List docSuggestions = null; /** * Queries passive providers for suggestions. Monitors * actual document modification state using DocumentListener * and CaretListener. Actual topcomponent is guarded * by attached ComponentListener. *

* Those who do not want currect file monitoring * should call {@link #performRescanInRP} directly. * * @param delayed

  • true run {@link #performRescanInRP} later in TimerThread *
  • false run {@link #performRescanInRP} synchronously */ private void findCurrentFile(boolean delayed) { // Unregister previous listeners if (current != null) { current.removeComponentListener(getWindowSystemMonitor()); current = null; } if (document != null) { document.removeDocumentListener(getEditorMonitor()); handleDocHidden(document, dataobject); } removeCaretListeners(); // Find which component is showing in it // Add my own component listener to it // When componentHidden, unregister my own component listener // Redo above // Locate source editor TopComponent tc = env.findActiveEditor(); if (tc == null) { // The last editor-support window in the editor was probably // just closed - or was not on top // remove suggestions List previous = new ArrayList(getCurrentSuggestionsList().getTasks()); getCurrentSuggestionsList().addRemove(null, previous, false, null, null); // for opened files list it's done ahandeTopComponentCloased LOGGER.fine("Cannot find active source editor!"); // during startup return; } DataObject dao = extractDataObject(tc); if (dao == null) return; /* if (dao == lastDao) { // We've been asked to scan the same dataobject as last time; // don't do that. // Most likely you've temporarily switched to another (non-editor) // node, and switched back (for example, double clicking on a node // in the task window) and we're still on the same file so there's // no reason to rescan. We track changes to the currently scanned // object differently (through a document listener). err.log("Same dao as last time - not doing anything"); return; // Don't scan again } lastDao = dao; */ final EditorCookie edit = (EditorCookie) dao.getCookie(EditorCookie.class); if (edit == null) { //err.log("No editor cookie - not doing anything"); return; } final Document doc = edit.getDocument(); // Does not block /* This comment applies to the old implementation, where we're listening on activated node changes. Now that we're listening for tab changes, the document should already have been read in by the time the tab changes and we're notified of it: // We might have a race condition here... you open the // document, and our property change listener gets notified - // but the document hasn't completed loading yet despite our // 1 second timer. Thus we might not get a document... However // since we continue listening for changes, eventually we WILL // discover the document */ if (doc == null) { LOGGER.fine("No document is loaded in editor!"); // can happen during startup return; } document = doc; doc.addDocumentListener(getEditorMonitor()); // Listen for changes on this component so we know when // it's replaced by something else XXX looks like PROP_ACTIVATED duplication current = tc; current.addComponentListener(getWindowSystemMonitor()); dataobject = dao; notSaved = dao.isModified(); addCaretListeners(); // XXX Use scheduleRescan instead? (but then I have to call docShown instead of rescan; //haveShown = currRequest; //scheduleRescan(null, false, showScanDelay); if (cache != null) { // TODO check scanOnShow too! (when we have scanOnOpen // as default instead of scanOnShow as is the case now. // The semantics of the flag need to change before we // check it here; it's always true. Make it user selectable.) docSuggestions = cache.lookup(document); if (docSuggestions != null) { manager.register(null, docSuggestions, null, getCurrentSuggestionsList(), true); // TODO Consider putting the above on a runtimer - but // a much shorter runtimer (0.1 seconds or something like // that) such that the editor gets a chance to draw itself // etc. // Also wipe out the cache items since we will replace them // when docHidden is called, or when docEdited is called, // etc. //cache.remove(document); // Remember that we're done "scanning" finishedRequest = currRequest; return; } } if (ManagerSettings.getDefault().isScanOnShow()) { if (delayed) { performRescanInRP(current, dao, ManagerSettings.getDefault().getShowScanDelay()); } else { performRescanInRP(current, dao, 0); } } } private static DataObject extractDataObject(TopComponent topComponent) { DataObject dobj = (DataObject) topComponent.getLookup().lookup(DataObject.class); if (dobj != null && dobj.isValid()) { return dobj; } else { // System.err.println("[TODO] cannot get DO for " + topComponent); // Thread.dumpStack(); return null; } } /** * The given document has been edited or saved, and a time interval * (by default around 2 seconds I think) has passed without any * further edits or saves. *

    * Update your Suggestions as necessary. This may mean removing * previously registered Suggestions, or editing existing ones, * or adding new ones, depending on the current contents of the * document. *

    * Spawns Suggestions Broker thread that finishes actula work * asynchronously. * * @param tc * @param dataobject The Data Object for the file being opened * @param delay postpone the action by delay miliseconds * * @return parametrized task that rescans given dataobject in delay miliseconds */ private RequestProcessor.Task performRescanInRP(final TopComponent tc, final DataObject dataobject, int delay) { /* Scan requests are run in a separate "background" thread. However, what happens if the user switches to a different tab -while- a scan job is running? If the scan hasn't started, the timer is removed, but if the scan is in progress, we have to know to discard registered results. For that reason, we have a "current request" reference that we pass with scan requests, and that scanners will hand back with scan results. The reference is an integer. When we switch to a new tab, we increment the integer. So if we get a registration, with an "old" integer (not the current one), we know the results are obsolete. We also need to know if the current scan is done (to know whether or not we should flush these results into the cache, or if scanning must begin from the beginning when we return to this file.) For that reason, we also have a "finished request" integer which points to the most recent finished request; we only stuff the cache if finished == current. We can also use the request flag to bail in the middle of iterating over providers in case a new request has arrived. */ // Is MAX_VALUE even feasible here? There's no greater/lessthan // comparison, so wrapping around will work just fine, but I may // have to check manually and do it myself in case some kind // of overflow exception is thrown // Wait, I'm doing a comparison now - look for currRequest.longValue assert currRequest.longValue() != Long.MAX_VALUE : "Wrap around logic needed!"; // NOI18N currRequest = new Long(currRequest.longValue() + 1); final Object origRequest = currRequest; // free AWT && Timer threads return serializeOnBackground(new Runnable() { public void run() { scheduledRescan = null; // Stale request If so, just drop this one //if (origRequest != currRequest) return; // code is fixing (modifing) document if (wait) { waitingEvent = true; return; } // reassure that Tc was not meanwhile closed. Both current file job // and all opened files job monitors just files opened in editor pane final boolean[] isOpened = new boolean[1]; try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { isOpened[0] = tc.isOpened(); // must be called from AWT } }); } catch (InterruptedException e) { ErrorManager err = ErrorManager.getDefault(); err.annotate(e, "[TODO] ignoring rescan of " + tc.getDisplayName()); // NOI18N err.notify(e); } catch (InvocationTargetException e) { ErrorManager err = ErrorManager.getDefault(); err.annotate(e, "[TODO] ignoring rescan of " + tc.getDisplayName()); // NOI18N err.notify(e); } if (isOpened[0] == false) { // System.err.println("[TODO] ignoring rescan request for " + tc.getDisplayName()); return; } LOGGER.fine("Dispatching rescan() request to providers..."); setScanning(true); List scannedSuggestions = manager.dispatchScan(dataobject, compound); // update "allOpened" suggestion list if (allOpenedClientsCount > 0) { FileObject fo = dataobject.getPrimaryFile(); List previous = (List) openedFilesSuggestionsMap.remove(fo); openedFilesSuggestionsMap.put(fo, scannedSuggestions); // fast check if anything have changed, avoid firing events from tasklist if (previous == null || previous.size() != scannedSuggestions.size() || previous.containsAll(scannedSuggestions) == false) { getAllOpenedSuggestionList().addRemove(scannedSuggestions, previous, false, null, null); } } if (clientCount > 0) { List previous = new ArrayList(getCurrentSuggestionsList().getTasks()); // fast check if anything have changed, avoid firing events from tasklist if (previous == null || previous.size() != scannedSuggestions.size() || previous.containsAll(scannedSuggestions) == false) { getCurrentSuggestionsList().addRemove(scannedSuggestions, previous, false, null, null); } } // enforce comparable requests, works only for single request source if ((finishedRequest == null) || ((Comparable)origRequest).compareTo(finishedRequest) > 0) { finishedRequest = (Comparable) origRequest; } if (currRequest == finishedRequest) { setScanning(false); // XXX global state, works only for single request source LOGGER.fine("It was last pending request."); } } }, delay); } private RequestProcessor rp = new RequestProcessor("Suggestions Broker"); // NOI18N /** Enqueue request and perform it on background later on. */ private RequestProcessor.Task serializeOnBackground(Runnable request, int delay) { return rp.post(request, delay , Thread.MIN_PRIORITY); } /** * Grab all the suggestions associated with this document/dataobject * and push it into the suggestion cache. */ private void stuffCache(Document document, DataObject dataobject, boolean unregisterOnly) { boolean filteredTaskListFixed = false; //XXX register bellow if (filteredTaskListFixed == false) return; // XXX Performance: if docSuggestions != null, we should be able // to just reuse it, since the document must not have been edited! SuggestionList tasklist = getCurrentSuggestionsList(); if (tasklist.getTasks().size() == 0) { return; } Iterator it = tasklist.getTasks().iterator(); List sgs = new ArrayList(tasklist.getTasks().size()); while (it.hasNext()) { SuggestionImpl s = (SuggestionImpl) it.next(); Object seed = s.getSeed(); // Make sure we don't pick up category nodes here!!! if (seed != SuggestionList.CATEGORY_NODE_SEED) { sgs.add(s); } Iterator sit = s.subtasksIterator(); while (sit.hasNext()) { s = (SuggestionImpl) sit.next(); seed = s.getSeed(); if (seed != SuggestionList.CATEGORY_NODE_SEED) { sgs.add(s); } } } if (!unregisterOnly) { if (cache == null) { cache = new SuggestionCache(); } cache.add(document, dataobject, sgs); } // Get rid of tasks from list // XXX is not it already done by providers, it causes problems if (sgs.size() > 0) { manager.register(null, null, sgs, tasklist, true); } } /** The top-component we're currently tracking (active one) */ private TopComponent current = null; /** The document we're currently tracking (active one) */ private Document document = null; /** The data-object we're currently tracking (active one) */ private DataObject dataobject = null; /** The panes we're currently tracking (active one) */ //XXX first element should be enough private JEditorPane[] editorsWithCaretListener = null; /** The modification status sampled on tracing start and save operation */ private boolean notSaved = false; /** Add caret listener to dataobject's editor panes. */ private void addCaretListeners() { assert editorsWithCaretListener == null : "addCaretListeners() must not be called twice without removeCaretListeners() => memory leak"; // NOI18N EditorCookie edit = (EditorCookie) dataobject.getCookie(EditorCookie.class); if (edit != null) { JEditorPane panes[] = edit.getOpenedPanes(); if ((panes != null) && (panes.length > 0)) { // We want to know about cursor changes in ALL panes editorsWithCaretListener = panes; for (int i = 0; i < editorsWithCaretListener.length; i++) { editorsWithCaretListener[i].addCaretListener(getEditorMonitor()); } } } } /** Unregister prebiously added caret listeners. */ private void removeCaretListeners() { if (editorsWithCaretListener != null) { for (int i = 0; i < editorsWithCaretListener.length; i++) { editorsWithCaretListener[i].removeCaretListener(getEditorMonitor()); } } editorsWithCaretListener = null; } boolean pendingScan = false; /** Timed task which keeps track of outstanding scan requests; we don't scan briefly selected files */ private RequestProcessor.Task scheduledRescan; /** * Plan a rescan (meaning: put delayed task into RP). In whole * broker there is only one scheduled task (and at maximum one * running concurrenly if delay is smaller than execution time). * * @param delay If true, don't create a rescan if one isn't already * pending, but if one is, delay it. * @param scanDelay actual delay value in ms */ private void scheduleRescan(boolean delay, int scanDelay) { // This is just a delayer (e.g. for caret motion) - if there isn't // already a pending timeout, we're done. Caret motion shouldn't // -cause- a rescan, but if one is already planned, we want to delay // it. if (delay && (scheduledRescan == null)) { return; } // Stop our current timer; the previous node has not // yet been scanned; too brief an interval if (scheduledRescan != null) { scheduledRescan.cancel(); scheduledRescan = null; LOGGER.fine("Scheduled rescan task delayed by " + scanDelay + " ms."); // NOI18N } // trap, randomly triggered by multiview assert dataobject.equals(extractDataObject(current)) : "DO=" + dataobject + " TC=" + current; scheduledRescan = performRescanInRP(current, dataobject, scanDelay); } /** An event ocurred during quiet fix period. */ private boolean waitingEvent = false; private boolean wait = false; /** * Set fix mode (quiet period) in which self initialized modifications are expected. * @param wait

    • true postpone all listeners until ... *
    • false ressurect listeners activity */ final void setFixing(boolean wait) { boolean wasWaiting = this.wait; this.wait = wait; if (!wait && wasWaiting && (waitingEvent)) { scheduleRescan(false, ManagerSettings.getDefault().getEditScanDelay()); waitingEvent = false; } } /** The set of visible top components changed */ private void componentsChanged() { // We may receive "changed events" from different sources: // componentHidden (which is the only source which tells us // when you've switched between two open tabs) and // TopComponent.registry's propertyChange on PROP_OPENED // (which is the only source telling us about tabs closing). // However, there is some overlap - when you open a new // tab, we get notified by both. So coalesce these events by // enquing a change lookup on the next iteration through the // event loop; if a second notification comes in during the // same event processing iterationh it's simply discarded. doRescanInAWT(true); } /** * It sends asynchronously to AWT thread (selected editor TC must be grabbed in AWT). * @param delay if true schedule later acording to user settings otherwise do immediatelly */ private void doRescanInAWT(final boolean delay) { if (SwingUtilities.isEventDispatchThread()) { findCurrentFile(delay); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { // docStop() might have happened // in the mean time - make sure we don't do a // findCurrentFile(true) when we're not supposed to // be processing views if (clientCount > 0) { findCurrentFile(delay); } } }); } } /** * Stop scanning for source items, deregistering * environment listeners. */ private void stopActiveSuggestionFetching() { LOGGER.info("Stopping active suggestions fetching...."); // NOI18N if (scheduledRescan != null) { scheduledRescan.cancel(); scheduledRescan = null; } env.removeTCRegistryListener(getWindowSystemMonitor()); env.removeDORegistryListener(getDataSystemMonitor()); // Unregister previous listeners if (current != null) { current.removeComponentListener(getWindowSystemMonitor()); current = null; } if (document != null) { document.removeDocumentListener(getEditorMonitor()); // NOTE: we do NOT null it out since we still need to // see if the document is unchanged } removeCaretListeners(); handleDocHidden(document, dataobject); document = null; } private void setScanning(boolean scanning) { // XXX fishy direct access to view assuming 1:1 relation with list // SuggestionList tasklist = getList(); // TaskListView v = tasklist.getView(); // if (v instanceof SuggestionsView) { // SuggestionsView view = (SuggestionsView) v; // view.setScanning(scanning); // } } private void handleDocHidden(Document document, DataObject dataobject) { // This is not right - runTimer is telling us whether we have // a request pending - (and we should indeed kill the timer // if we do) - but we need to know if a RequestProcessor is // actually running. if (currRequest != finishedRequest) { if (cache != null) { cache.remove(document); } // Remove the items we've registered so far... (partial // registration) since we're in the middle of a request stuffCache(document, dataobject, true); } else { stuffCache(document, dataobject, false); } docSuggestions = null; } private void handleTopComponentClosed(TopComponent tc) { //System.err.println("[TODO] closing: " + tc.getDisplayName()); componentsChanged(); DataObject dobj = extractDataObject(tc); if (dobj == null) { //System.err.println("[TODO] has no DO: " + tc.getDisplayName()); return; } List previous = (List) openedFilesSuggestionsMap.remove(dobj.getPrimaryFile()); if (previous != null) { //System.err.println("[TODO] removing TODOs: " + tc.getDisplayName() + " :" + previous); getAllOpenedSuggestionList().addRemove(null, previous, false, null, null); } else { //System.err.println("[TODO] has no TODOs: " + tc.getDisplayName()); } } private void handleTopComponentOpened(TopComponent tc) { //System.err.println("[TODO] opened: " + tc.getDisplayName()); if (tc.isShowing()) { // currently selected one componentsChanged(); } else { // it is not selected anymore, it was opened in burst DataObject dao = extractDataObject(tc); if (dao == null) return; performRescanInRP(tc, dao, ManagerSettings.getDefault().getShowScanDelay()); } } private WindowSystemMonitor windowSystemMonitor; /** See note on {@link WindowSystemMonitor#enableOpenCloseEvents} */ private WindowSystemMonitor getWindowSystemMonitor() { if (windowSystemMonitor == null) { windowSystemMonitor = new WindowSystemMonitor(); } return windowSystemMonitor; } private class WindowSystemMonitor implements PropertyChangeListener, ComponentListener { /** Previous Set<TopComponent> */ private Set openedSoFar = Collections.EMPTY_SET; /** * Must be called before adding this listener to environment if in hope that * it will provide (initial) open/close events. */ private void enableOpenCloseEvents() { List list = Arrays.asList(SuggestionsScanner.openedTopComponents()); openedSoFar = new HashSet(list); Iterator it = list.iterator(); while (it.hasNext()) { TopComponent tc = (TopComponent) it.next(); tc.addComponentListener(new ComponentAdapter() { public void componentShown(ComponentEvent e) { TopComponent tcomp = (TopComponent) e.getComponent(); tcomp.removeComponentListener(this); handleTopComponentOpened(tcomp); } }); } } /** Reacts to changes */ public void propertyChange(PropertyChangeEvent ev) { String prop = ev.getPropertyName(); if (prop.equals(TopComponent.Registry.PROP_OPENED)) { LOGGER.fine("EVENT opened top-components changed"); // if (allOpenedClientsCount > 0) { // determine what components have been closed, window system does not // provide any other listener to do it in more smart way List list = Arrays.asList(SuggestionsScanner.openedTopComponents()); Set actual = new HashSet(list); if (openedSoFar != null) { Iterator it = openedSoFar.iterator(); while(it.hasNext()) { TopComponent tc = (TopComponent) it.next(); if (actual.contains(tc) ) continue; handleTopComponentClosed(tc); } Iterator ita = actual.iterator(); while(ita.hasNext()) { TopComponent tc = (TopComponent) ita.next(); if (openedSoFar.contains(tc)) continue; // defer actual action to componentShown, We need to assure opened TC is // selected one. At this moment previous one is still selected. tc.addComponentListener(new ComponentAdapter() { public void componentShown(ComponentEvent e) { TopComponent tcomp = (TopComponent) e.getComponent(); tcomp.removeComponentListener(this); handleTopComponentOpened(tcomp); } }); } } openedSoFar = actual; // } else { // componentsChanged(); // openedSoFar = null; // } } else if (TopComponent.Registry.PROP_ACTIVATED.equals(prop)) { LOGGER.fine("EVENT top-component activated"); if (clientCount >0 && current == null) { findCurrentFile(false); } } } public void componentShown(ComponentEvent e) { // Don't care } public void componentHidden(ComponentEvent e) { LOGGER.fine("EVENT " + e.getComponent() + " has been hidden"); //XXX it does not support both "current file" and "all opened" clients at same time if (allOpenedClientsCount == 0) { componentsChanged(); } } public void componentResized(ComponentEvent e) { // Don't care } public void componentMoved(ComponentEvent e) { // Don't care } } private DataSystemMonitor dataSystemMonitor; private DataSystemMonitor getDataSystemMonitor() { if (dataSystemMonitor == null) { dataSystemMonitor = new DataSystemMonitor(); } return dataSystemMonitor; } /** * Listener for DataObject.Registry changes. * * This class listens for modify-changes of dataobjects such that * it can notify files of Save operations. */ private class DataSystemMonitor implements ChangeListener { public void stateChanged(ChangeEvent e) { /* Not sure what the source is, but it isn't dataobject and the javadoc doesn't say anything specific, so I guess I can't rely on that as a filter if (e.getSource() != dataobject) { // If you reinstate this in some way, make sure it // works for Save ALL as well!!! return; } */ LOGGER.fine("EVENT " + e.getSource() + " changed."); Set mods = DataObject.getRegistry().getModifiedSet(); boolean wasModified = notSaved; notSaved = mods.contains(dataobject); if (notSaved != wasModified) { if (!notSaved) { if (ManagerSettings.getDefault().isScanOnSave()) { scheduleRescan(false, ManagerSettings.getDefault().getSaveScanDelay()); } } } } } private EditorMonitor editorMonitor; private EditorMonitor getEditorMonitor() { if (editorMonitor == null) { editorMonitor = new EditorMonitor(); } return editorMonitor; } private class EditorMonitor implements DocumentListener, CaretListener { //XXX missing reset logic private int prevLineNo = -1; public void changedUpdate(DocumentEvent e) { // Do nothing. // Changed update is only called for ATTRIBUTE changes in the // document, which I define as not relevant to the Document // Suggestion Providers. } public void insertUpdate(DocumentEvent e) { LOGGER.fine("EVENT document changed"); if (ManagerSettings.getDefault().isScanOnEdit()) { scheduleRescan(false, ManagerSettings.getDefault().getEditScanDelay()); } } public void removeUpdate(DocumentEvent e) { LOGGER.fine("EVENT document changed"); if (ManagerSettings.getDefault().isScanOnEdit()) { scheduleRescan(false, ManagerSettings.getDefault().getEditScanDelay()); } } /** Moving the cursor position should cause a delay in document scanning, * but not trigger a new update */ public void caretUpdate(CaretEvent caretEvent) { LOGGER.fine("EVENT caret moved"); scheduleRescan(true, ManagerSettings.getDefault().getEditScanDelay()); // Check to see if I have any existing errors on this line - and if so, // highlight them. if (document instanceof StyledDocument) { int offset = caretEvent.getDot(); int lineno = NbDocument.findLineNumber((StyledDocument) document, offset); if (lineno == prevLineNo) { // Just caret motion on the same line as the previous one -- ignore return; } prevLineNo = lineno; // Here we could add 1 to the line number, since findLineNumber // returns a 0-based line number, and most APIs return a 1-based // line number; however, Line.Set.getOriginal also expects // something zero based, so instead of doing the usual bit // of subtracting there, we drop the add and subtract altogether // Go to the given line Line line = TLUtils.getLineByNumber(dataobject, lineno + 1); /* try { LineCookie lc = (LineCookie)dataobject.getCookie(LineCookie.class); if (lc != null) { Line.Set ls = lc.getLineSet(); if (ls != null) { line = ls.getCurrent(lineno); } } } catch (Exception e) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e); } */ if (line != null) { // XXX badge editor suggestion in tasklist //[SuggestionsView]setCursorLine(line); } } } } /** * Binding to outer world that can be changed by unit tests */ static class Env { void addTCRegistryListener(PropertyChangeListener pcl) { TopComponent.getRegistry().addPropertyChangeListener(pcl); } void removeTCRegistryListener(PropertyChangeListener pcl) { TopComponent.getRegistry().removePropertyChangeListener(pcl); } void addDORegistryListener(ChangeListener cl) { DataObject.getRegistry().addChangeListener(cl); } void removeDORegistryListener(ChangeListener cl) { DataObject.getRegistry().removeChangeListener(cl); } public TopComponent findActiveEditor() { Mode mode = WindowManager.getDefault().findMode(CloneableEditorSupport.EDITOR_MODE); if (mode == null) { // The editor window was probablyjust closed return null; } TopComponent tc = mode.getSelectedTopComponent(); if (tc instanceof CloneableEditor) { // Found the source editor... // if (tc.isShowing()) { // FIXME it returns false for components I can positivelly see // hopefully mode does not return hidden TC as selected one. // It happens right after startup return tc; // } } return null; } } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.