001 /*
002 * Copyright 2006 Mat Gessel <mat.gessel@gmail.com>
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package asquare.gwt.tk.client.ui.behavior;
017
018 import java.util.Vector;
019
020 import asquare.gwt.tk.client.util.GwtUtil;
021
022 import com.google.gwt.user.client.ui.FocusWidget;
023 import com.google.gwt.user.client.ui.HasFocus;
024
025 /**
026 * A primitive focus model. Tracks the widgets in a focus cycle and which widget
027 * <em>should</em> be focused.
028 */
029 public class FocusModel
030 {
031 private final Vector m_listeners = new Vector();
032 private final Vector m_widgets = new Vector();
033
034 private int m_focusIndex = -1;
035
036 public void addListener(FocusModelListener listener)
037 {
038 m_listeners.add(listener);
039 }
040
041 public void removeListener(FocusModelListener listener)
042 {
043 m_listeners.remove(listener);
044 }
045
046 /**
047 * Get the number of widgets in the model.
048 */
049 public int getSize()
050 {
051 return m_widgets.size();
052 }
053
054 /**
055 * Remove all widgets from the model.
056 */
057 public void clear()
058 {
059 HasFocus[] removed = new HasFocus[m_widgets.size()];
060 for (int i = removed.length - 1; i >= 0; i--)
061 {
062 removed[i] = (HasFocus) m_widgets.get(i);
063 }
064 m_widgets.clear();
065 m_focusIndex = -1;
066 fireRemoved(removed);
067 }
068
069 /**
070 * Add widgets to the model in bulk.
071 *
072 * @param widgets an array of 0 or more widgets
073 * @throws IllegalArgumentException if <code>widgets</code> is <code>null</code>
074 * @throws IllegalArgumentException if an element is <code>null</code>
075 * @throws IllegalArgumentException if an element is already present in the model
076 * @see #shouldAdd(HasFocus)
077 */
078 public void add(HasFocus[] widgets)
079 {
080 if (widgets == null)
081 throw new IllegalArgumentException();
082
083 if (widgets.length == 0)
084 return;
085
086 Vector added = new Vector();
087 for (int i = 0; i < widgets.length; i++)
088 {
089 if (insertImpl(widgets[i], m_widgets.size()))
090 {
091 added.add(widgets[i]);
092 }
093 }
094 if (added.size() > 0)
095 {
096 HasFocus[] result = new HasFocus[added.size()];
097 GwtUtil.toArray(added, result);
098 fireAdded(result);
099 }
100 }
101
102 /**
103 * Add a widget to the model.
104 *
105 * @param widget a widget
106 * @throws IllegalArgumentException if <code>widget</code> is <code>null</code>
107 * @throws IllegalArgumentException if <code>widget</code> is already present in the model
108 * @see #shouldAdd(HasFocus)
109 */
110 public void add(HasFocus widget)
111 {
112 insert(widget, m_widgets.size());
113 }
114
115 /**
116 * Inserts a widget into to the model at the specified index.
117 *
118 * @param widget a widget
119 * @param index an integer greater than 0 and less than or equal to number of
120 * widgets in the model
121 * @throws IllegalArgumentException if <code>widget</code> is <code>null</code>
122 * @throws IllegalArgumentException if <code>widget</code> is already present in the model
123 * @throws IndexOutOfBoundsException if <code>index < 0 || index > {@link #getSize()}</code>
124 * @see #shouldAdd(HasFocus)
125 */
126 public void insert(HasFocus widget, int index)
127 {
128 if (insertImpl(widget, index))
129 {
130 fireAdded(new HasFocus[] {widget});
131 }
132 }
133
134 /**
135 * @return true if <code>widget</code> is added to the model
136 */
137 private boolean insertImpl(HasFocus widget, int index)
138 {
139 if (widget == null)
140 throw new IllegalArgumentException("widget cannot be null");
141
142 GwtUtil.rangeCheck(0, m_widgets.size(), index, true);
143
144 if (m_widgets.indexOf(widget) != -1)
145 throw new IllegalArgumentException("cannot add widget twice");
146
147 boolean result = false;
148
149 if(shouldAdd(widget))
150 {
151 m_widgets.insertElementAt(widget, index);
152 if (index <= m_focusIndex)
153 {
154 m_focusIndex++;
155 }
156 result = true;
157 }
158 return result;
159 }
160
161 /**
162 * Determines whether the specified Widget can be added to the model.
163 *
164 * @param widget a widget which is candidate to be added to the model
165 * @return <code>true</code> if <code>widget.getTabIndex() >= 0</code>
166 */
167 protected boolean shouldAdd(HasFocus widget)
168 {
169 return widget.getTabIndex() >= 0;
170 }
171
172 /**
173 * Get the index corresponding to the currently focused widget.
174 *
175 * @return a value between <code>0</code> and
176 * <code>{@link #getSize()}</code>, or <code>-1</code> if no
177 * widget is focused
178 */
179 public int getCurrentIndex()
180 {
181 return m_focusIndex;
182 }
183
184 /**
185 * Get the index of the specified widget in the model.
186 *
187 * @param widget a widget
188 * @return a valid index, or <code>-1</code> if <code>widget</code> is
189 * not present in the model
190 * @throws IllegalArgumentException if <code>widget</code> is
191 * <code>null</code>
192 */
193 public int getIndexOf(HasFocus widget)
194 {
195 if (widget == null)
196 throw new IllegalArgumentException();
197
198 return m_widgets.indexOf(widget);
199 }
200
201 /**
202 * Get the widget at the specified index.
203 *
204 * @param index an integer greater than 0 and less than or equal to number of
205 * widgets in the model
206 * @return the widget
207 * @throws IndexOutOfBoundsException if <code>index</code> is out of range
208 */
209 public HasFocus getWidgetAt(int index)
210 {
211 GwtUtil.rangeCheck(0, m_widgets.size(), index, false);
212
213 return (HasFocus) m_widgets.get(index);
214 }
215
216 /**
217 * Removes a widget from the model.
218 *
219 * @param widget
220 */
221 public void remove(HasFocus widget)
222 {
223 int index = m_widgets.indexOf(widget);
224 if (index >= 0)
225 {
226 remove(index);
227 }
228 }
229
230 private void remove(int index)
231 {
232 HasFocus widget = (HasFocus) m_widgets.remove(index);
233 if (m_focusIndex > index)
234 {
235 m_focusIndex--;
236 }
237 else if (m_focusIndex == index)
238 {
239 m_focusIndex = -1;
240 }
241 fireRemoved(new HasFocus[] {widget});
242 }
243
244 /**
245 * Get the widget which is focused.
246 *
247 * @return a widget, or <code>null</code> if no widget is focused
248 */
249 public HasFocus getFocusWidget()
250 {
251 return (m_focusIndex != -1) ? (HasFocus) m_widgets.get(m_focusIndex) : null;
252 }
253
254 /**
255 * Set the widget which is focused.
256 *
257 * @param widget a widget in the model, or <code>null</code>
258 * @throws IllegalArgumentException if <code>widget</code> is not
259 * present in the model and not <code>null</code>
260 */
261 public void setFocusWidget(HasFocus widget)
262 {
263 int index;
264 if (widget == null)
265 {
266 index = -1;
267 }
268 else
269 {
270 index = m_widgets.indexOf(widget);
271 if (index == -1)
272 throw new IllegalArgumentException();
273 }
274 m_focusIndex = index;
275 // m_focusIndex = (widget == null) ? -1 : m_widgets.indexOf(widget);
276 }
277
278 /**
279 * Get the next widget in the cycle.
280 * The widget is skipped if <code>(widget.getTabIndex() < 0 || ! widget.isEnabled())</code>.
281 *
282 * @param forward <code>true</code> to cycle forward, <code>false</code> to cycle backward
283 * @return the next focusable widget, or <br/>
284 * the currently focused widget if no other focusable widget is available, or<br/>
285 * <code>null</code> no if focusable widget is available
286 * @throws IllegalStateException if the model is empty
287 */
288 public HasFocus getNextWidget(boolean forward)
289 {
290 int nextIndex = getNextIndex(m_focusIndex, forward);
291 if (nextIndex != -1)
292 {
293 return (HasFocus) getWidgetAt(nextIndex);
294 }
295 return null;
296 }
297
298 /**
299 * Get the widget after the currently focused widget.
300 *
301 * @return the next widget
302 * @throws IllegalStateException if the model is empty
303 */
304 public HasFocus getNextWidget()
305 {
306 return getNextWidget(true);
307 }
308
309 /**
310 * Get the widget previous to the currently focused widget.
311 *
312 * @return the previous widget
313 * @throws IllegalStateException if the model is empty
314 */
315 public HasFocus getPreviousWidget()
316 {
317 return getNextWidget(false);
318 }
319
320 private int getNextIndex(int index, boolean forward)
321 {
322 int size = m_widgets.size();
323 GwtUtil.rangeCheck(-1, size + 1, index, false);
324 if (size == 0)
325 throw new IllegalStateException();
326
327 return getNextIndex(index, index, m_widgets.size(), forward);
328 }
329
330 private int getNextIndex(int initialIndex, int current, int size, boolean forward)
331 {
332 // avoid infinite loop if initialIndex = -1
333 if (initialIndex == -1)
334 {
335 if (forward && current == size - 1 || ! forward && current == 0)
336 return initialIndex;
337 }
338
339 if (forward)
340 {
341 current = (current + 1) % size;
342 }
343 else
344 {
345 current = (current > 0) ? current - 1 : size - 1;
346 }
347
348 // give up after looping once
349 if (current == initialIndex)
350 return initialIndex;
351
352 // return if widget is not a disabled FocusWidget
353 if (shouldFocus((HasFocus) m_widgets.get(current)))
354 return current;
355
356 return getNextIndex(initialIndex, current, size, forward);
357 }
358
359 /**
360 * Determines whether the specified Widget can receive focus.
361 *
362 * @param w a widget in this model which is candidate for focus
363 * @return <code>true</code> unless <code>w</code> is a disabled
364 * {@link FocusWidget}
365 */
366 protected boolean shouldFocus(HasFocus w)
367 {
368 if (w instanceof FocusWidget)
369 {
370 return ((FocusWidget) w).isEnabled();
371 }
372 return true;
373 }
374
375 private void fireAdded(HasFocus[] added)
376 {
377 Object[] listeners = m_listeners.toArray();
378 for (int i = 0; i < listeners.length; i++)
379 {
380 ((FocusModelListener) listeners[i]).widgetsAdded(this, added);
381 }
382 }
383
384 private void fireRemoved(HasFocus[] removed)
385 {
386 Object[] listeners = m_listeners.toArray();
387 for (int i = 0; i < listeners.length; i++)
388 {
389 ((FocusModelListener) listeners[i]).widgetsRemoved(this, removed);
390 }
391 }
392 }