This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 194147
Collapse All | Expand All

(-)a/masterfs/src/org/netbeans/modules/masterfs/watcher/Watcher.java (-4 / +21 lines)
Lines 59-64 Link Here
59
import org.openide.util.Utilities;
59
import org.openide.util.Utilities;
60
import org.openide.util.lookup.ServiceProvider;
60
import org.openide.util.lookup.ServiceProvider;
61
import org.openide.util.lookup.ServiceProviders;
61
import org.openide.util.lookup.ServiceProviders;
62
import org.openide.util.ApplicationActivationManager;
62
63
63
/**
64
/**
64
 *
65
 *
Lines 213-226 Link Here
213
    private Set<FileObject> pending; // guarded by lock
214
    private Set<FileObject> pending; // guarded by lock
214
    private static RequestProcessor RP = new RequestProcessor("Pending refresh", 1);
215
    private static RequestProcessor RP = new RequestProcessor("Pending refresh", 1);
215
216
217
    private RequestProcessor.Task refreshTask = RP.create(new Refresher());
218
    class Refresher implements Runnable {
219
        private final Runnable actualRefresher = new ActualRefresher();
220
        public @Override void run() {
221
            //will run synchronously immediately if the application is active.
222
            //Otherwise, will be enqueued on a background thread.  The contract
223
            //of ApplicationActivationManager.runWhenNextActive() guarantees that
224
            //it will not be run more than once, since we always pass the same
225
            //object
226
            ApplicationActivationManager.getDefault().runWhenNextActive(
227
                    actualRefresher, false);
228
        }
216
229
217
    private RequestProcessor.Task refreshTask = RP.create(new Runnable() {
230
        private final class ActualRefresher implements Runnable {
218
        public @Override void run() {
231
            @Override
232
            public void run() {
219
            Set<FileObject> toRefresh;
233
            Set<FileObject> toRefresh;
220
            synchronized(lock) {
234
            synchronized(lock) {
221
                toRefresh = pending;
235
                toRefresh = pending;
222
                pending = null;
236
                pending = null;
223
            }
237
            }
238
                if (pending == null) { //we may be on our own thread, or one from ApplicationActivationManager
239
                    return;
240
                }
224
            LOG.log(Level.FINE, "Refreshing {0} directories", toRefresh.size());
241
            LOG.log(Level.FINE, "Refreshing {0} directories", toRefresh.size());
225
242
226
            for (FileObject fileObject : toRefresh) {
243
            for (FileObject fileObject : toRefresh) {
Lines 230-237 Link Here
230
            
247
            
231
            LOG.fine("Refresh finished");
248
            LOG.fine("Refresh finished");
232
        }
249
        }
233
    });
250
        }
234
251
    }
235
252
236
    private void enqueue(FileObject fo) {
253
    private void enqueue(FileObject fo) {
237
        assert fo != null;
254
        assert fo != null;
(-)a/openide.util/apichanges.xml (+24 lines)
Lines 51-56 Link Here
51
    <apidef name="actions">Actions API</apidef>
51
    <apidef name="actions">Actions API</apidef>
52
</apidefs>
52
</apidefs>
53
<changes>
53
<changes>
54
    <change id="ApplicationActivationManager">
55
        <api name="util"/>
56
        <summary><code>ApplicationActivationManager</code> added</summary>
57
        <version major="8" minor="15"/>
58
        <date day="16" month="2" year="2011"/>
59
        <author login="tboudreau"/>
60
        <compatibility addition="yes"/>
61
        <description>
62
            <p>
63
                Added ApplicationActivationManager, which allows for global
64
                listening to the application's active (focused) and idle
65
                (focused but not used for some time) state, without spurious
66
                activation events caused by the application being briefly
67
                refocused.  Useful for scheduling background work which
68
                should be done when the IDE is idle, and to avoid performing
69
                work when the application is inactive (i.e. the user is
70
                doing something else and the application should not be
71
                doing unnecessary work which could interfere with the performance
72
                of other applications).
73
            </p>
74
        </description>
75
        <class package="org.openide.util" name="ApplicationActivationManager"/>
76
        <issue number="194147"/>
77
    </change>    
54
    <change id="NetworkSettings">
78
    <change id="NetworkSettings">
55
        <api name="util"/>
79
        <api name="util"/>
56
        <summary><code>NetworkSettings</code> added</summary>
80
        <summary><code>NetworkSettings</code> added</summary>
(-)a/openide.util/arch.xml (+19 lines)
Lines 274-279 Link Here
274
        </api>
274
        </api>
275
    </li>
275
    </li>
276
    
276
    
277
    <li><api name="application.activation.delay" category="devel" group="property" type="export" >
278
        <a href="@TOP@/org/openide/util/ApplicationActivationManager.html">ApplicationActivationManager</a> 
279
        checks the system property <code>application.activation.delay</code>
280
        to set the delay in milliseconds between an application window gaining
281
        or losing focus, and the delivery of activation state changes - the
282
        length of time the application must remain active or inactive before
283
        listeners should be notified and the return value of getState() should
284
        include ACTIVE.
285
        </api>
286
    </li>
287
288
    <li><api name="application.idle.delay" category="devel" group="property" type="export" >
289
        <a href="@TOP@/org/openide/util/ApplicationActivationManager.html">ApplicationActivationManager</a> 
290
        checks the system property <code>application.idle.delay</code>
291
        to set the delay in milliseconds since the last user activity
292
        before the application is considered to be IDLE, and notifications
293
        should be delivered to any listeners interested in the IDLE state.
294
        </api>
295
    </li>
277
    
296
    
278
  </ul>
297
  </ul>
279
 </answer>
298
 </answer>
(-)a/openide.util/manifest.mf (-1 / +1 lines)
Lines 1-5 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.openide.util
2
OpenIDE-Module: org.openide.util
3
OpenIDE-Module-Localizing-Bundle: org/openide/util/Bundle.properties
3
OpenIDE-Module-Localizing-Bundle: org/openide/util/Bundle.properties
4
OpenIDE-Module-Specification-Version: 8.14
4
OpenIDE-Module-Specification-Version: 8.15
5
5
(-)a/openide.util/src/org/netbeans/modules/openide/util/ApplicationActivationManagerImpl.java (+516 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s): Tim Boudreau
39
 *
40
 * Portions Copyrighted 2011 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.openide.util;
43
44
import java.awt.AWTEvent;
45
import java.awt.EventQueue;
46
import java.awt.KeyboardFocusManager;
47
import java.awt.Toolkit;
48
import java.awt.Window;
49
import java.awt.event.AWTEventListener;
50
import java.awt.event.MouseEvent;
51
import java.beans.PropertyChangeEvent;
52
import java.beans.PropertyChangeListener;
53
import java.lang.ref.Reference;
54
import java.lang.ref.WeakReference;
55
import java.util.Arrays;
56
import java.util.Collections;
57
import java.util.EnumSet;
58
import java.util.HashSet;
59
import java.util.Iterator;
60
import java.util.LinkedHashSet;
61
import java.util.LinkedList;
62
import java.util.List;
63
import java.util.Set;
64
import java.util.concurrent.atomic.AtomicBoolean;
65
import java.util.logging.Level;
66
import java.util.logging.Logger;
67
import org.openide.util.Parameters;
68
import org.openide.util.RequestProcessor;
69
import org.openide.util.RequestProcessor.Task;
70
import org.openide.util.lookup.ServiceProvider;
71
import org.openide.util.ApplicationActivationManager;
72
73
/**
74
 * Default implementation of ApplicationActivationManager.
75
 *
76
 * @author Tim Boudreau
77
 */
78
@ServiceProvider(service = ApplicationActivationManager.class)
79
public class ApplicationActivationManagerImpl extends ApplicationActivationManager {
80
81
    private static final RequestProcessor rp = new RequestProcessor(ApplicationActivationManagerImpl.class);
82
    private static final int DEFAULT_ACTIVATION_DELAY = 800;
83
    private static final int DEFAULT_IDLE_DELAY = 120000; //two minutes - *really* idle
84
    private final Set<ListenerHolder> listeners = Collections.synchronizedSet(new LinkedHashSet<ListenerHolder>());
85
    private final Object stateLock = new Object();
86
    private final Object eventLock = new Object();
87
    private final Object idleLock = new Object();
88
    private final Set<State> state = EnumSet.noneOf(State.class);
89
    private final List<StateEvent> events = new LinkedList<StateEvent>();
90
    private final AtomicBoolean inNotificationLoop = new AtomicBoolean();
91
    private final Set<RunOnceListener> runOnceReferences = Collections.synchronizedSet(new HashSet<RunOnceListener>());
92
    //avoid rapid transiently switching to the application, for example
93
    //moving between virtual desktops, to trigger work
94
    private final IdleListener idleListener = new IdleListener();
95
    private final Task addIdleStateTask = rp.create(idleListener);
96
    private final KeyboardFocusListener keyboardFocusManagerListener = new KeyboardFocusListener();
97
    private final Task updateActiveStateTask = rp.create(keyboardFocusManagerListener);
98
    private volatile boolean stateIsIdle;
99
    private volatile boolean listeningForIdle;
100
    private int idleDelay;
101
    private int activationDelay;
102
    private static final String IDLE_SYSTEM_PROPERTY = "application.idle.delay";
103
    private static final String ACTIVATION_SYSTEM_PROPERTY = "application.activation.delay";
104
105
    public ApplicationActivationManagerImpl() {
106
        //check system properties
107
        try {
108
            String prop = System.getProperty(IDLE_SYSTEM_PROPERTY);
109
            if (prop != null) {
110
                idleDelay = Integer.parseInt(prop);
111
                if (idleDelay <= 0) {
112
                    throw new NumberFormatException("Negative " + IDLE_SYSTEM_PROPERTY);
113
                }
114
            } else {
115
                idleDelay = DEFAULT_IDLE_DELAY;
116
            }
117
        } catch (NumberFormatException nfe) {
118
            Logger.getLogger(ApplicationActivationManagerImpl.class.getName()).log(Level.WARNING, IDLE_SYSTEM_PROPERTY, nfe);
119
            idleDelay = DEFAULT_IDLE_DELAY;
120
        }
121
        try {
122
            String prop = System.getProperty(ACTIVATION_SYSTEM_PROPERTY);
123
            if (prop != null) {
124
                activationDelay = Integer.parseInt(prop);
125
            } else {
126
                activationDelay = DEFAULT_ACTIVATION_DELAY;
127
            }
128
            if (activationDelay <= 0) {
129
                throw new NumberFormatException("Negative " + ACTIVATION_SYSTEM_PROPERTY);
130
            }
131
        } catch (NumberFormatException nfe) {
132
            Logger.getLogger(ApplicationActivationManagerImpl.class.getName()).log(Level.WARNING, ACTIVATION_SYSTEM_PROPERTY, nfe);
133
            activationDelay = DEFAULT_ACTIVATION_DELAY;
134
        }
135
        //attach our listener
136
        EventQueue.invokeLater(new Runnable() {
137
138
            @Override
139
            public void run() {
140
                KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusedWindow", keyboardFocusManagerListener);
141
                boolean active = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow() != null;
142
                if (active) {
143
                    setState(EnumSet.of(State.ACTIVE));
144
                }
145
            }
146
        });
147
    }
148
149
    @Override
150
    public Set<State> getState() {
151
        synchronized (stateLock) {
152
            return EnumSet.copyOf(state);
153
        }
154
    }
155
156
    void setState(Set<State> newState) {
157
        Set<State> oldState;
158
        synchronized (stateLock) {
159
            oldState = getState();
160
            if (newState.equals(oldState)) {
161
                return;
162
            }
163
            this.state.clear();
164
            this.state.addAll(newState);
165
            onChange(this.state);
166
        }
167
        notify(oldState, newState);
168
    }
169
170
    private void notify(Set<State> old, Set<State> nue) {
171
        if (old != null) {
172
            synchronized (eventLock) {
173
                events.add(new StateEvent(old, nue));
174
            }
175
        }
176
        boolean notify = inNotificationLoop.compareAndSet(false, true);
177
        //another caller can already be performing notifications, depending on
178
        //which thread last triggered a state change.  If so, they will be
179
        //delivered
180
        if (notify) {
181
            //list of things to run on whatever thread this is not
182
            final List<Notification> notifyOnOtherThread = new LinkedList<Notification>();
183
            boolean inEQ = EventQueue.isDispatchThread();
184
            try {
185
                final List<StateEvent> notifyEvents = new LinkedList<StateEvent>();
186
                synchronized (eventLock) {
187
                    notifyEvents.addAll(this.events);
188
                    this.events.clear();
189
                }
190
                while (!notifyEvents.isEmpty()) {
191
                    for (StateEvent evt : notifyEvents) {
192
                        for (Iterator<ListenerHolder> it = listeners.iterator(); it.hasNext();) {
193
                            ListenerHolder holder = it.next();
194
                            Listener l = holder.getListener();
195
                            if (l == null) {
196
                                it.remove();
197
                            } else {
198
                                //Filter out states this listener does not care about
199
                                Set<State> oldState = holder.filter(evt.old);
200
                                Set<State> newState = holder.filter(evt.nue);
201
                                //If the result is no change, do not notify
202
                                if (!oldState.equals(newState)) {
203
                                    //Synchronously notify whichever listeners want
204
                                    //to be notified on this thread
205
                                    if (holder.notifyInBackground != inEQ) {
206
                                        try {
207
                                            l.onChange(oldState, newState);
208
                                        } catch (RuntimeException e) {
209
                                            Logger.getLogger(ApplicationActivationManagerImpl.class.getName()).log(Level.SEVERE, null, e);
210
                                        }
211
                                    } else {
212
                                        //add the rest to the queued list, for
213
                                        //notification on a background thread
214
                                        notifyOnOtherThread.add(new Notification(l, new StateEvent(oldState, newState)));
215
                                    }
216
                                }
217
                            }
218
                        }
219
                    }
220
                    //Collect any events that have arrived while
221
                    //we have been looping
222
                    synchronized (eventLock) {
223
                        notifyEvents.clear();
224
                        notifyEvents.addAll(this.events);
225
                        this.events.clear();
226
                    }
227
                }
228
            } finally {
229
                inNotificationLoop.set(false);
230
            }
231
            boolean renotify;
232
            synchronized (eventLock) {
233
                //it is possible for another thread to have entered while the
234
                //loop variable was set to false - so do a quick check to make
235
                //sure we don't have events that should be delivered but won't be
236
                //unless we call ourselves again
237
                renotify = !events.isEmpty();
238
            }
239
            if (!notifyOnOtherThread.isEmpty()) {
240
                //Asynchronously notify any listeners we are on the wrong
241
                //thread for
242
                Runnable r = new Runnable() {
243
244
                    @Override
245
                    public void run() {
246
                        for (Notification l : notifyOnOtherThread) {
247
                            try {
248
                                l.notifyListener();
249
                            } catch (RuntimeException e) {
250
                                Logger.getLogger(ApplicationActivationManagerImpl.class.getName()).log(Level.SEVERE, null, e);
251
                            }
252
                        }
253
                    }
254
                };
255
                if (inEQ) {
256
                    rp.post(r);
257
                } else {
258
                    EventQueue.invokeLater(r);
259
                }
260
            }
261
            if (renotify) {
262
                notify(null, null);
263
            }
264
        }
265
    }
266
267
    private void startListeningForIdleEvents() {
268
        if (!listeningForIdle) {
269
            //ensure we don't have a diff between the value of listeningForIdle
270
            //and whether or not we are actually listening
271
            synchronized (idleLock) {
272
                if (!listeningForIdle) {
273
                    listeningForIdle = true;
274
                    Toolkit.getDefaultToolkit().addAWTEventListener(idleListener, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
275
                    addIdleStateTask.schedule(idleDelay);
276
                }
277
            }
278
        }
279
    }
280
281
    private void stopListeningForIdleEvents() {
282
        if (listeningForIdle) {
283
            synchronized (idleLock) {
284
                if (listeningForIdle) {
285
                    addIdleStateTask.cancel();
286
                    Toolkit.getDefaultToolkit().removeAWTEventListener(idleListener);
287
                    listeningForIdle = false;
288
                }
289
            }
290
        }
291
    }
292
293
    private final class IdleListener implements AWTEventListener, Runnable {
294
295
        private volatile long lastEventTime = System.currentTimeMillis();
296
297
        @Override
298
        public void eventDispatched(AWTEvent event) {
299
            int id = event.getID();
300
            //ignore mouse motion
301
            if (id == MouseEvent.MOUSE_MOVED || id == MouseEvent.MOUSE_ENTERED || id == MouseEvent.MOUSE_EXITED || id == MouseEvent.MOUSE_DRAGGED) {
302
                return;
303
            }
304
            lastEventTime = System.currentTimeMillis();
305
            //postpone the idle task
306
            addIdleStateTask.schedule(idleDelay);
307
            if (stateIsIdle) { //avoids the lock in the common case that the state is not idle
308
                synchronized (stateLock) {
309
                    Set<State> states = getState();
310
                    states.remove(State.IDLE);
311
                    //will run notifications under lock
312
                    setState(states);
313
                }
314
            }
315
        }
316
317
        @Override
318
        public void run() {
319
            //run after the idle delay
320
            long timeSinceLastEvent = System.currentTimeMillis() - lastEventTime;
321
            if (timeSinceLastEvent > idleDelay && getState().equals(EnumSet.of(State.ACTIVE))) {
322
                //add the idle state
323
                changeState(State.IDLE);
324
            }
325
        }
326
    }
327
328
    private void onChange(Set<State> states) {
329
        //update the stateIsIdle value so we avoid taking a lock on every
330
        //keystroke / mouse click
331
        stateIsIdle = states.contains(State.IDLE);
332
        if (states.contains(State.ACTIVE)) {
333
            startListeningForIdleEvents();
334
        } else {
335
            stopListeningForIdleEvents();
336
        }
337
    }
338
339
    private static final class StateEvent {
340
341
        private final Set<State> old;
342
        private final Set<State> nue;
343
344
        public StateEvent(Set<State> old, Set<State> nue) {
345
            assert !old.equals(nue);
346
            this.old = old;
347
            this.nue = nue;
348
        }
349
350
        public String toString() {
351
            return old + " to " + nue;
352
        }
353
    }
354
355
    private static final class Notification {
356
357
        private final Listener listener;
358
        private final StateEvent evt;
359
360
        public Notification(Listener listener, StateEvent evt) {
361
            this.listener = listener;
362
            this.evt = evt;
363
        }
364
365
        void notifyListener() {
366
            listener.onChange(evt.old, evt.nue);
367
        }
368
    }
369
370
    public void runWhenNextActive(Runnable toRun, boolean notifyInForeground) {
371
        Parameters.notNull("toRun", toRun);
372
        if (isActive()) {
373
            toRun.run();
374
        } else {
375
            RunOnceListener l = new RunOnceListener(toRun);
376
            if (!runOnceReferences.contains(l)) {
377
                runOnceReferences.add(l);
378
                addListener(l, notifyInForeground, State.ACTIVE);
379
            }
380
        }
381
    }
382
383
    private final class RunOnceListener implements Listener {
384
385
        private final Runnable run;
386
387
        RunOnceListener(Runnable run) {
388
            this.run = run;
389
        }
390
391
        @Override
392
        public void onChange(Set<State> from, Set<State> to) {
393
            if (to.contains(State.ACTIVE) && runOnceReferences.remove(this)) {
394
                run.run();
395
            }
396
        }
397
        
398
        public boolean equals(Object o) {
399
            return o instanceof RunOnceListener && ((RunOnceListener) o).run.equals(run);
400
        }
401
        
402
        public int hashCode() {
403
            return run.hashCode();
404
        }
405
    }
406
407
    @Override
408
    public void addListener(Listener listener, boolean notifyInForeground, State... interestedIn) {
409
        Parameters.notNull("listener", listener);
410
        if (interestedIn.length == 0) {
411
            throw new IllegalArgumentException("No states");
412
        }
413
        listeners.add(new ListenerHolder(listener, notifyInForeground,
414
                EnumSet.copyOf(Arrays.asList(interestedIn))));
415
    }
416
417
    private static final class ListenerHolder {
418
419
        private final Reference<Listener> listenerRef;
420
        private final boolean notifyInBackground;
421
        private final Set<State> interestedIn;
422
423
        public ListenerHolder(Listener listener, boolean notifyInBackground, Set<State> interestedIn) {
424
            this.notifyInBackground = notifyInBackground;
425
            this.interestedIn = interestedIn;
426
            this.listenerRef = new WeakReference<Listener>(listener);
427
        }
428
429
        Listener getListener() {
430
            return listenerRef.get();
431
        }
432
433
        Set<State> filter(Set<State> states) {
434
            Set<State> nue = EnumSet.copyOf(states);
435
            nue.retainAll(interestedIn);
436
            return nue;
437
        }
438
    }
439
440
    private boolean changeState(State toAdd, State... toRemove) {
441
        //add one state and optionally remove others, then update the state
442
        Set<State> oldState;
443
        Set<State> newState;
444
        synchronized (stateLock) {
445
            oldState = getState();
446
            newState = EnumSet.copyOf(oldState);
447
            if (toAdd != null) {
448
                newState.add(toAdd);
449
            }
450
            newState.removeAll(Arrays.asList(toRemove));
451
            if (newState.equals(oldState)) {
452
                return false;
453
            }
454
            this.state.clear();
455
            this.state.addAll(newState);
456
            onChange(this.state);
457
        }
458
        notify(oldState, newState);
459
        return true;
460
    }
461
462
    private final class KeyboardFocusListener implements PropertyChangeListener, Runnable {
463
464
        volatile boolean active;
465
        State destState;
466
467
        @Override
468
        public void propertyChange(PropertyChangeEvent evt) {
469
            //Note one weirdness I've never been able to decipher:  In a regular
470
            //swing application, when you show a dialog, focus goes from the
471
            //parent window to the dialog.  In NetBeans, it goes from the 
472
            //parent window to null to the dialog, and the same when closing.
473
            //This causes a plethora of extra BECOMING_ACTIVE / BECOMING_INACTIVE
474
            //events which are unavoidable
475
            Window old = (Window) evt.getOldValue();
476
            Window nue = (Window) evt.getNewValue();
477
            if ((old == null) != (nue == null)) {
478
                active = nue != null;
479
                if (!active) {
480
                    //add the becoming inactive state, remove becoming active if present
481
                    changeState(State.BECOMING_INACTIVE, State.BECOMING_ACTIVE);
482
                    //set our single destination state
483
                    synchronized (this) {
484
                        destState = null;
485
                    }
486
                    //reschedule updating the final state
487
                    updateActiveStateTask.schedule(activationDelay);
488
                } else {
489
                    //add the becoming active state, remove the becoming inactive state
490
                    changeState(State.BECOMING_ACTIVE, State.BECOMING_INACTIVE);
491
                    //set our single destination state
492
                    synchronized (this) {
493
                        destState = State.ACTIVE;
494
                    }
495
                    //reschedule the task
496
                    updateActiveStateTask.schedule(activationDelay);
497
                }
498
            }
499
        }
500
501
        @Override
502
        public void run() {
503
            //Called when the state has been BECOMING_* for a sufficient
504
            //amount of time
505
            State dest;
506
            synchronized (this) {
507
                dest = this.destState;
508
            }
509
            if (dest == null) {
510
                changeState(null, State.BECOMING_INACTIVE, State.ACTIVE);
511
            } else {
512
                changeState(destState, State.BECOMING_ACTIVE);
513
            }
514
        }
515
    }
516
}
(-)a/openide.util/src/org/openide/util/ApplicationActivationManager.java (+210 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s): Tim Boudreau
39
 *
40
 * Portions Copyrighted 2011 Sun Microsystems, Inc.
41
 */
42
package org.openide.util;
43
44
import java.awt.Window;
45
import java.util.EnumSet;
46
import java.util.Set;
47
48
/**
49
 * Lifecycle hook for determining whether the application is "active" (the user
50
 * is using it) or not, and whether or not it is "idle" (active but not in use
51
 * for a given period of time), and listening for changes in that state.
52
 * <p/>
53
 * Usage:  If you need to perform some work (such as checking for external 
54
 * changes) when the application becomes active, this class can help.  Another
55
 * case is if you have a long-running operation to perform (such as scanning
56
 * remote resources for changes) which must be done periodically, and you
57
 * do not want to interfere with normal operation - the idle state is useful
58
 * for this.
59
 * <p/>
60
 * The application achieves the "ACTIVE" state when it has been given focus
61
 * and not lost focus again for a sufficient amount of time;  it achieves the
62
 * "IDLE" state when it has focus but no user input has been received for
63
 * a sufficient amount of time.  By listening for changes in the ACTIVE state
64
 * you can perform work only when the application has really been reactivated,
65
 * avoiding notification because of very brief reactivation (for example, a 
66
 * user navigating between virtual desktops or applications).
67
 *
68
 * @author Tim Boudreau
69
 */
70
public abstract class ApplicationActivationManager {
71
72
    public static ApplicationActivationManager getDefault() {
73
        ApplicationActivationManager result = Lookup.getDefault().lookup(ApplicationActivationManager.class);
74
        if (result == null) {
75
            return new DummyImpl();
76
        }
77
        return result;
78
    }
79
80
    /**
81
     * Get the current set of states associated with the application.
82
     * 
83
     * @return A set of states, which may be empty if the application is
84
     * inactive
85
     */
86
    public abstract Set<State> getState();
87
88
    /**
89
     * Add a listener to be notified on changes in state.  The listener will
90
     * be notified on a background thread or the AWT event thread, depending
91
     * on the <code>notifyInForeground</code> parameter, when the application
92
     * changes state and the old or new set of states include a state in the
93
     * array of states passed to this method.
94
     * <p/>
95
     * The caller must hold a <i>strong reference</i> to the listener or it
96
     * may be immediately garbage collected.
97
     * 
98
     * @param listener The listener
99
     * @param notifyInForeground If true, the listener will be notified in the
100
     * AWT event thread;  if false, the listener will be notified on a background
101
     * thread.
102
     * @param interestedIn The set of states this listener is interested in.
103
     * May not be empty.
104
     */
105
    public abstract void addListener(Listener listener, boolean notifyInForeground, State... interestedIn);
106
    
107
    /**
108
     * Pass a runnable which should be run the next time the application becomes
109
     * active.  If the application is currently active, it will be run
110
     * immediately.  Otherwise, it will be run exactly once, the next time
111
     * the application becomes active.
112
     * <p/>
113
     * If the same runnable (in terms of equals()) 
114
     * is passed to this method multiple times, it should
115
     * still only be run once upon the next activation.
116
     * 
117
     * @param toRun A runnable
118
     * @param notifyInForeground If true, the notification should run in the
119
     * event thread
120
     */
121
    public abstract void runWhenNextActive(Runnable toRun, boolean notifyInForeground);
122
123
    /**
124
     * Determine if the application is active.
125
     * 
126
     * @return Whether or not the application is active
127
     */
128
    public static boolean isActive() {
129
        return getDefault().getState().contains(State.ACTIVE);
130
    }
131
132
    /**
133
     * Determine if the application is currently idle (active but no keyboard
134
     * input or non-motion-related mouse input for a period of time).
135
     * @return Whether or not the application is idle
136
     */
137
    public static boolean isIdle() {
138
        return getDefault().getState().contains(State.IDLE);
139
    }
140
141
    /**
142
     * Callback which is notified when the application state changes
143
     */
144
    public interface Listener {
145
146
        /**
147
         * Called when the application state changes
148
         * @param from The old set of states
149
         * @param to The new set of states
150
         */
151
        public void onChange(Set<State> from, Set<State> to);
152
    }
153
154
    /**
155
     * States the application can have, relating to whether it is active
156
     * or idle.
157
     */
158
    public enum State {
159
160
        /**
161
         * The application is definitely active
162
         */
163
        ACTIVE,
164
        /**
165
         * The application has received focus, and if it still has focus after
166
         * a suitable delay, will be determined to be active.
167
         * <p/>
168
         * Note that this state can change very frequently, for example,
169
         * because a dialog was shown.
170
         */
171
        BECOMING_ACTIVE,
172
        /**
173
         * The application has lost focus, and if it still does not have focus
174
         * after a suitable delay, will be determined to be inactive.
175
         * <p/>
176
         * Note that this state can change very frequently, for example,
177
         * because a dialog was shown.
178
         */
179
        BECOMING_INACTIVE,
180
        /*
181
         * The application <i>is active</i> but no input events have been
182
         * received for longer than a suitable delay, so the application is
183
         * presumed to be idle (the user is not using the machine).
184
         */
185
        IDLE
186
    }
187
188
    private static final class DummyImpl extends ApplicationActivationManager {
189
190
        @Override
191
        public Set<State> getState() {
192
            for (Window w : Window.getWindows()) {
193
                if (w.isActive() && w.isFocusOwner()) {
194
                    return EnumSet.of(State.ACTIVE);
195
                }
196
            }
197
            return EnumSet.noneOf(State.class);
198
        }
199
200
        @Override
201
        public void addListener(Listener listener, boolean notifyInForeground, State... interestedIn) {
202
            //do nothing
203
        }
204
205
        @Override
206
        public void runWhenNextActive(Runnable toRun, boolean notifyInForeground) {
207
            toRun.run();
208
        }
209
    }
210
}

Return to bug 194147