Source code: com/port80/eclipse/util/NavigateHistory.java
1 package com.port80.eclipse.util;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import org.eclipse.jface.action.Action;
7 import org.eclipse.jface.action.IMenuManager;
8 import org.eclipse.jface.action.IToolBarManager;
9
10 /**
11 * @author chrisl
12 *
13 * Implements a simple web style navigation metaphor over a list of objects for a viewer.
14 * Back, and forward functions are supported. Each time a new object is added
15 * to the list at an existing index, the rest of the list is truncated.
16 *
17 */
18 public class NavigateHistory {
19
20 ////////////////////////////////////////////////////////////////////////
21
22 public static final String NAME = "NavigateHistory";
23 public static final String ID = "com.port80.eclipse.util.NavigateHistory";
24 private static final int DEF_HISTORY_SIZE = 8;
25 private static final int DEF_VISITED_SIZE = 20;
26
27 ////////////////////////////////////////////////////////////////////////
28
29 /** History, in acess time order, oldest first.*/
30 private Object[] fHistory;
31 /** Unique objects visited, unordered.*/
32 private Object[] fVisited;
33 private List fListeners;
34 private int fPos;
35 private int fLast; // Last valid position in the history.
36 private int fVisitedSize;
37
38 private Action backAction;
39 private Action forwardAction;
40 private boolean fCanGoBack;
41 private boolean fCanGoForward;
42
43 ////////////////////////////////////////////////////////////////////////
44
45 public NavigateHistory() {
46 this(DEF_HISTORY_SIZE, DEF_VISITED_SIZE);
47 }
48
49 public NavigateHistory(int size) {
50 this(size, size);
51 }
52
53 public NavigateHistory(int historysize, int visitedsize) {
54 this.fHistory = new Object[historysize];
55 this.fVisited = new Object[visitedsize];
56 this.fListeners = new ArrayList();
57 this.fPos = -1;
58 this.fLast = -1;
59 this.fVisitedSize = 0;
60 fCanGoBack = canGoBack();
61 fCanGoForward = canGoForward();
62 }
63
64 ////////////////////////////////////////////////////////////////////////
65
66 public void addHistoryListener(IHistoryListener l) {
67 fListeners.add(l);
68 fireUpdateStatusEvent();
69 }
70 public void removeHistoryListener(IHistoryListener l) {
71 fListeners.remove(l);
72 }
73
74 ////////////////////////////////////////////////////////////////////////
75
76 /**
77 * Add an object to the history. Adding object now do not fire any GotoEvent.
78 *
79 * Typical event sequence is:
80 * Trigger (doubleclick,actions in views, eg. History forward/backward/goto action->GotoHistoryEvent)
81 * -> Editor open
82 * -> PartActivationEvent
83 * -> Add to history, select in views ... etc.
84 *
85 * When a new object is added when visiting an object in the middle of the history,
86 * the new object is inserted to the history after the current position in the history.
87 */
88 public void add(Object a) {
89 // Add to history.
90 if (a == null)
91 return;
92 if (fPos >= 0 && a.equals(fHistory[fPos])) {
93 return;
94 }
95 if (fPos < fLast && a.equals(fHistory[fPos + 1])) {
96 ++fPos;
97 updateButtonStatus();
98 updateVisited(fHistory[fPos]);
99 return;
100 }
101 if (fPos == fHistory.length - 1) {
102 System.arraycopy(fHistory, 1, fHistory, 0, fHistory.length - 1);
103 } else {
104 // Insert into history.
105 ++fPos;
106 for (int i = fHistory.length - 1; i > fPos; --i)
107 fHistory[i] = fHistory[i - 1];
108 if (fLast < fHistory.length - 1)
109 ++fLast;
110 }
111 fHistory[fPos] = a;
112 updateButtonStatus();
113 updateVisited(a);
114 }
115
116 ////////////////////////////////////////////////////////////////////////
117
118 public int historySize() {
119 return fLast + 1;
120 }
121
122 public int historyPos() {
123 return fPos;
124 }
125
126 /**
127 * @return History object at the given index.
128 */
129 public Object getHistory(int n) {
130 if (n < 0 || n > fLast)
131 return null;
132 return fHistory[n];
133 }
134
135 public Object[] getHistory() {
136 Object[] ret = new Object[historySize()];
137 System.arraycopy(fHistory, 0, ret, 0, historySize());
138 return ret;
139 }
140
141 /**
142 * Goto the specified position.
143 */
144 public void gotoHistory(int n) {
145 if (n != fPos && n >= 0 && n <= fLast) {
146 fPos = n;
147 updateButtonStatus();
148 updateVisited(fHistory[fPos]);
149 fireGotoEvent(fHistory[fPos]);
150 }
151 }
152
153 ////////////////////////////////////////////////////////////////////////
154
155 public int visitedSize() {
156 return fVisitedSize;
157 }
158
159 public Object getVisited(int n) {
160 return fVisited[n];
161 }
162
163 public Object[] getVisited() {
164 Object[] ret = new Object[fVisitedSize];
165 System.arraycopy(fVisited, 0, ret, 0, fVisitedSize);
166 return ret;
167 }
168
169 public void gotoVisited(int n) {
170 if (n >= 0 && n < fVisitedSize) {
171 Object ret = fVisited[n];
172 add(fVisited[n]);
173 fireGotoEvent(ret);
174 }
175 }
176
177 /** Added object to visited set. This is automatically invoked before firing a GotoEvent. */
178 public void updateVisited(Object a) {
179 if (a == null)
180 return;
181 int i = 0;
182 for (; i < fVisitedSize; ++i) {
183 if (a.equals(fVisited[i])) {
184 break;
185 }
186 }
187 if (i == fVisitedSize) {
188 // Not found.
189 if (fVisitedSize >= fVisited.length) {
190 System.arraycopy(fVisited, 1, fVisited, 0, fVisited.length - 1);
191 } else {
192 ++fVisitedSize;
193 }
194 } else if (i < fVisitedSize - 1) {
195 // Found before last entry, move the entry to the front (most recent used).
196 // Otherwise we can just replace it.
197 System.arraycopy(fVisited, i + 1, fVisited, i, fVisitedSize - 1 - i);
198 }
199 fVisited[fVisitedSize - 1] = a;
200 }
201
202 ////////////////////////////////////////////////////////////////////////
203
204 /**
205 * @return <code>true</code> if "go back" is possible; <code>false</code> otherwise
206 */
207 public boolean canGoBack() {
208 return fPos > 0;
209 }
210
211 /**
212 * @return <code>true</code> if "go into" is possible; <code>false</code> otherwise
213 */
214 public boolean canGoForward() {
215 return fPos < fLast;
216 }
217
218 /**
219 * Go back.
220 */
221 public boolean goBack() {
222 if (fPos >= 0) {
223 --fPos;
224 updateButtonStatus();
225 }
226 if (fPos < 0)
227 return false;
228 Object input = fHistory[fPos];
229 updateVisited(input);
230 fireGotoEvent(input);
231 return canGoBack();
232 }
233
234 /**
235 * Go forward.
236 */
237 public boolean goForward() {
238 if (fPos >= fLast)
239 return false;
240 Object input = fHistory[++fPos];
241 updateButtonStatus();
242 updateVisited(input);
243 fireGotoEvent(input);
244 return canGoForward();
245 }
246
247 /**
248 * Resets the history.
249 */
250 public void reset() {
251 for (int i = 0; i <= fLast; ++i)
252 fHistory[i] = null;
253 fPos = -1;
254 fLast = -1;
255 for (int i = 0; i < fVisitedSize; ++i)
256 fVisited[i] = null;
257 fVisitedSize = 0;
258 updateButtonStatus();
259 //fireGotoEvent(null);
260 }
261
262 /**
263 * Updates the enabled state for each navigation button.
264 */
265 protected void updateButtonStatus() {
266 boolean needupdate = ((fCanGoBack != canGoBack()) || (fCanGoForward != canGoForward()));
267 if (needupdate) {
268 fCanGoBack = canGoBack();
269 fCanGoForward = canGoForward();
270 if (backAction != null)
271 backAction.setEnabled(fCanGoBack);
272 if (forwardAction != null)
273 forwardAction.setEnabled(fCanGoForward);
274 fireUpdateStatusEvent();
275 }
276 }
277
278 ////////////////////////////////////////////////////////////////////////
279
280 private void fireUpdateStatusEvent() {
281 for (int i = 0; i < fListeners.size(); ++i)
282 ((IHistoryListener) fListeners.get(i)).updateHistoryStatus(
283 canGoBack(),
284 canGoForward(),
285 getHistory(fPos - 1),
286 getHistory(fPos + 1));
287 }
288
289 private void fireGotoEvent(Object a) {
290 for (int i = 0; i < fListeners.size(); ++i)
291 ((IHistoryListener) fListeners.get(i)).gotoObject(a);
292 }
293
294 ////////////////////////////////////////////////////////////////////////
295
296 /**
297 * Adds actions for "go back", "go home", and "go into" to a menu manager.
298 *
299 * @param manager is the target manager to update
300 */
301 public void addNavigationActions(IMenuManager manager) {
302 createActions();
303 manager.add(backAction);
304 manager.add(forwardAction);
305 updateButtonStatus();
306 }
307
308 /**
309 * Adds actions for "go back", "go home", and "go into" to a tool bar manager.
310 *
311 * @param manager is the target manager to update
312 */
313 public void addNavigationActions(IToolBarManager toolBar) {
314 createActions();
315 toolBar.add(backAction);
316 toolBar.add(forwardAction);
317 updateButtonStatus();
318 }
319
320 /**
321 * Create the actions for navigation.
322 */
323 private void createActions() {
324 // Only do this once
325 if (backAction != null)
326 return;
327 backAction = new Action(Messages.getString("GoBack.text")) {
328 public void run() {
329 goBack();
330 }
331 };
332 backAction.setToolTipText(Messages.getString("GoBack.toolTip")); //$NON-NLS-1$
333 UtilPluginImages.setLocalImageDescriptors(backAction, UtilPluginImages.IMG_BACKWARD);
334
335 // Forward.
336 forwardAction = new Action(Messages.getString("GoForward.text")) {
337 public void run() {
338 goForward();
339 }
340 };
341 forwardAction.setToolTipText(Messages.getString("GoFoward.toolTip")); //$NON-NLS-1$
342 UtilPluginImages.setLocalImageDescriptors(forwardAction, UtilPluginImages.IMG_FORWARD);
343
344 // Update the buttons when a selection change occurs.
345 updateButtonStatus();
346 }
347
348 ////////////////////////////////////////////////////////////////////////
349
350 }