Skip to main content
summaryrefslogtreecommitdiffstats
blob: fc2fca7e3c55b93737b5ffc3278305386766dadb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*******************************************************************************
 * Copyright (c) 2006 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.internal.databinding.provisional.swt;

import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.ObservableTracker;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.widgets.Menu;

/**
 * NON-API - A MenuUpdater updates an SWT menu in response to changes in the model. By
 * wrapping a block of code in a MenuUpdater, clients can rely on the fact that
 * the block of code will be re-executed whenever anything changes in the model
 * that might affect its behavior.
 * 
 * <p>
 * MenuUpdaters only execute once their menus are shown. If something changes in
 * the model, the updater is flagged as dirty and it stops listening to the
 * model until the next time the menu is shown. If the menu is visible while the
 * model changes, it will be updated right away.
 * </p>
 * 
 * <p>
 * Clients should subclass this when copying information from the model to a
 * menu. Typical usage:
 * </p>
 * 
 * <ul>
 * <li>Override updateMenu. It should do whatever is necessary to display the
 * contents of the model in the menu.</li>
 * <li>In the constructor, attach listeners to the model. The listeners should
 * call markDirty whenever anything changes in the model that affects
 * updateMenu. Note: this step can be omitted when calling any method tagged
 * with "@TrackedGetter" since MenuUpdater will automatically attach a listener
 * to any object if a "@TrackedGetter" method is called in updateMenu.</li>
 * <li>(optional)Extend dispose() to remove any listeners attached in the
 * constructor</li>
 * </ul>
 * 
 * @since 1.1
 */
public abstract class MenuUpdater {
	
	private class PrivateInterface implements MenuListener, 
		DisposeListener, Runnable, IChangeListener {

		// DisposeListener implementation
		public void widgetDisposed(DisposeEvent e) {
			MenuUpdater.this.dispose();
		}
		
		// Runnable implementation. This method runs at most once per repaint whenever the
		// value gets marked as dirty.
		public void run() {
			if (theMenu != null && !theMenu.isDisposed() && theMenu.isVisible()) {
				updateIfNecessary();
			}
		}
		
		// IChangeListener implementation (listening to the ComputedValue)
		public void handleChange(ChangeEvent event) {
			// Whenever this updator becomes dirty, schedule the run() method 
			makeDirty();
		}

		public void menuHidden(MenuEvent e) {
			// do nothing
		}

		public void menuShown(MenuEvent e) {
			updateIfNecessary();
		}
		
	}
	
	private Runnable updateRunnable = new Runnable() {
		public void run() {
			updateMenu();
		}
	};
	
	private PrivateInterface privateInterface = new PrivateInterface();
	private Menu theMenu;
	private IObservable[] dependencies = new IObservable[0];
	private boolean dirty = false;
	
	/**
	 * Creates an updator for the given menu.  
	 * 
	 * @param toUpdate menu to update
	 */
	public MenuUpdater(Menu toUpdate) {
		theMenu = toUpdate;
		
		theMenu.addDisposeListener(privateInterface);
		theMenu.addMenuListener(privateInterface);
		makeDirty();
	}
	
	private void updateIfNecessary() {
		if (dirty) {
			dependencies = ObservableTracker.runAndMonitor(updateRunnable, privateInterface, null);
			dirty = false;
		}
	}

	/**
	 * This is called automatically when the menu is disposed. It may also
	 * be called explicitly to remove this updator from the menu. Subclasses
	 * will normally extend this method to detach any listeners they attached
	 * in their constructor.
	 */
	public void dispose() {
		theMenu.removeDisposeListener(privateInterface);
		theMenu.removeMenuListener(privateInterface);

		stopListening();
	}

	private void stopListening() {
		// Stop listening for dependency changes
		for (int i = 0; i < dependencies.length; i++) {
			IObservable observable = dependencies[i];
				
			observable.removeChangeListener(privateInterface);
		}
	}

	/**
	 * Updates the menu. This method will be invoked once after the
	 * updater is created, and once for any SWT.Show event if this 
	 * updater is marked as dirty at that time.
	 *  
	 * <p>
	 * Subclasses should overload this method to provide any code that 
	 * udates the menu.
	 * </p>
	 */
	protected abstract void updateMenu();
	
	/**
	 * Marks this updator as dirty. Causes the updateControl method to
	 * be invoked before the next time the control is repainted.
	 */
	protected final void makeDirty() {
		if (!dirty) {
			dirty = true;
			stopListening();
			SWTUtil.runOnce(theMenu.getDisplay(), privateInterface);
		}
	}
	
}

Back to the top