001 /*
002 * Created on Jun 10, 2008
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 *
014 * Copyright @2008-2010 the original author or authors.
015 */
016 package org.fest.swing.driver;
017
018 import static java.lang.String.valueOf;
019 import static org.fest.swing.driver.ComponentStateValidator.validateIsEnabledAndShowing;
020 import static org.fest.swing.driver.JTableCancelCellEditingTask.cancelEditing;
021 import static org.fest.swing.driver.JTableCellEditorQuery.cellEditorIn;
022 import static org.fest.swing.driver.JTableCellValidator.validateCellIsEditable;
023 import static org.fest.swing.driver.JTableCellValidator.validateIndices;
024 import static org.fest.swing.driver.JTableStopCellEditingTask.stopEditing;
025 import static org.fest.swing.driver.JTableStopCellEditingTask.validateAndStopEditing;
026 import static org.fest.swing.edt.GuiActionRunner.execute;
027 import static org.fest.swing.exception.ActionFailedException.actionFailure;
028 import static org.fest.swing.timing.Pause.pause;
029 import static org.fest.util.Strings.concat;
030
031 import java.awt.Component;
032 import java.awt.Point;
033
034 import javax.swing.JTable;
035 import javax.swing.table.TableCellEditor;
036
037 import org.fest.swing.annotation.RunsInCurrentThread;
038 import org.fest.swing.annotation.RunsInEDT;
039 import org.fest.swing.cell.JTableCellWriter;
040 import org.fest.swing.core.*;
041 import org.fest.swing.edt.GuiQuery;
042 import org.fest.swing.exception.ActionFailedException;
043 import org.fest.swing.exception.WaitTimedOutError;
044
045 /**
046 * Understands the base class for implementations of <code>{@link JTableCellWriter}</code>.
047 *
048 * @author Alex Ruiz
049 * @author Yvonne Wang
050 */
051 public abstract class AbstractJTableCellWriter implements JTableCellWriter {
052
053 protected final Robot robot;
054 protected final JTableLocation location = new JTableLocation();
055 private TableCellEditor cellEditor;
056
057 private static final long EDITOR_LOOKUP_TIMEOUT = 5000;
058
059 public AbstractJTableCellWriter(Robot robot) {
060 this.robot = robot;
061 }
062
063 /** {@inheritDoc} */
064 @RunsInEDT
065 public void cancelCellEditing(JTable table, int row, int column) {
066 if (cellEditor == null) {
067 doCancelCellEditing(table, row, column);
068 return;
069 }
070 doCancelCellEditing();
071 }
072
073 @RunsInEDT
074 private void doCancelCellEditing(JTable table, int row, int column) {
075 cancelEditing(table, row, column);
076 robot.waitForIdle();
077 }
078
079 @RunsInEDT
080 private void doCancelCellEditing() {
081 cancelEditing(cellEditor);
082 robot.waitForIdle();
083 }
084
085 /** {@inheritDoc} */
086 @RunsInEDT
087 public void stopCellEditing(JTable table, int row, int column) {
088 if (cellEditor == null) {
089 doStopCellEditing(table, row, column);
090 return;
091 }
092 doStopCellEditing();
093 }
094
095 @RunsInEDT
096 private void doStopCellEditing(JTable table, int row, int column) {
097 validateAndStopEditing(table, row, column);
098 robot.waitForIdle();
099 }
100
101 @RunsInEDT
102 private void doStopCellEditing() {
103 stopEditing(cellEditor);
104 robot.waitForIdle();
105 }
106
107 /**
108 * Returns the editor for the given table cell. This method is executed in the EDT.
109 * @param table the target <code>JTable</code>.
110 * @param row the row index of the cell.
111 * @param column the column index of the cell.
112 * @return the editor for the given table cell.
113 */
114 @RunsInEDT
115 protected static TableCellEditor cellEditor(final JTable table, final int row, final int column) {
116 return execute(new GuiQuery<TableCellEditor>() {
117 protected TableCellEditor executeInEDT() throws Throwable {
118 return table.getCellEditor(row, column);
119 }
120 });
121 }
122
123 /**
124 * Scrolls the given <code>{@link JTable}</code> to the given cell.
125 * <p>
126 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
127 * responsible for calling this method from the EDT.
128 * </p>
129 * @param table the target <code>JTable</code>.
130 * @param row the row index of the cell.
131 * @param column the column index of the cell.
132 * @param location understands how to get the bounds of the given cell.
133 */
134 @RunsInCurrentThread
135 protected static void scrollToCell(JTable table, int row, int column, JTableLocation location) {
136 table.scrollRectToVisible(location.cellBounds(table, row, column));
137 }
138
139 /** {@inheritDoc} */
140 @RunsInEDT
141 public Component editorForCell(JTable table, int row, int column) {
142 return cellEditorComponent(table, row, column);
143 }
144
145 @RunsInEDT
146 private static Component cellEditorComponent(final JTable table, final int row, final int column) {
147 return execute(new GuiQuery<Component>() {
148 protected Component executeInEDT() {
149 validateIndices(table, row, column);
150 return cellEditorIn(table, row, column);
151 }
152 });
153 }
154
155 /**
156 * Finds the component used as editor for the given <code>{@link JTable}</code>.
157 * <p>
158 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
159 * responsible for calling this method from the EDT.
160 * </p>
161 * @param <T> the generic type of the supported editor type.
162 * @param table the target <code>JTable</code>.
163 * @param row the row index of the cell.
164 * @param column the column index of the cell.
165 * @param supportedType the type of component we expect as editor.
166 * @return the component used as editor for the given table cell.
167 * @throws IndexOutOfBoundsException if any of the indices is out of bounds or if the <code>JTable</code> does not
168 * have any rows.
169 * @throws IllegalStateException if the <code>JTable</code> is disabled.
170 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
171 * @throws IllegalStateException if the table cell in the given coordinates is not editable.
172 * @throws IndexOutOfBoundsException if any of the indices is out of bounds or if the <code>JTable</code> does not
173 * have any rows.
174 * @throws ActionFailedException if an editor for the given cell cannot be found or cannot be activated.
175 */
176 @RunsInCurrentThread
177 protected static <T extends Component> T editor(JTable table, int row, int column, Class<T> supportedType) {
178 validate(table, row, column);
179 Component editor = cellEditorIn(table, row, column);
180 if (supportedType.isInstance(editor)) return supportedType.cast(editor);
181 throw cannotFindOrActivateEditor(row, column);
182 }
183
184 /**
185 * Returns the location of the given table cell.
186 * @param table the target <code>JTable</code>.
187 * @param row the row index of the cell.
188 * @param column the column index of the cell.
189 * @param location knows how to get the location of a table cell.
190 * @return the location of the given table cell.
191 * @throws IllegalStateException if the <code>JTable</code> is disabled.
192 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
193 * @throws IndexOutOfBoundsException if any of the indices is out of bounds or if the <code>JTable</code> does not
194 * have any rows.
195 * @throws IllegalStateException if the table cell in the given coordinates is not editable.
196 */
197 @RunsInEDT
198 protected static Point cellLocation(final JTable table, final int row, final int column, final JTableLocation location) {
199 return execute(new GuiQuery<Point>() {
200 protected Point executeInEDT() {
201 validate(table, row, column);
202 scrollToCell(table, row, column, location);
203 return location.pointAt(table, row, column);
204 }
205 });
206 }
207
208 /**
209 * Validates that:
210 * <ol>
211 * <li>the given <code>JTable</code> is enabled and showing on the screen</li>
212 * <li>the row and column indices are correct (not out of bounds)</li>
213 * <li>the table cell at the given indices is editable</li>
214 * </ol>
215 * <p>
216 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
217 * responsible for calling this method from the EDT.
218 * </p>
219 * @param table the target <code>JTable</code>.
220 * @param row the row index of the cell.
221 * @param column the column index of the cell.
222 * @throws IllegalStateException if the <code>JTable</code> is disabled.
223 * @throws IllegalStateException if the <code>JTable</code> is not showing on the screen.
224 * @throws IndexOutOfBoundsException if any of the indices is out of bounds or if the <code>JTable</code> does not
225 * have any rows.
226 * @throws IllegalStateException if the table cell in the given coordinates is not editable.
227 */
228 @RunsInCurrentThread
229 protected static void validate(final JTable table, final int row, final int column) {
230 validateIndices(table, row, column);
231 validateIsEnabledAndShowing(table);
232 validateCellIsEditable(table, row, column);
233 }
234
235 /**
236 * Waits until the editor of the given table cell is showing on the screen. Component lookup is performed by type.
237 * @param <T> the generic type of the cell editor.
238 * @param table the target <code>JTable</code>.
239 * @param row the row index of the cell.
240 * @param column the column index of the cell.
241 * @param supportedType the type of component we expect as editor.
242 * @return the editor of the given table cell once it is showing on the screen.
243 * @throws ActionFailedException if an editor for the given cell cannot be found or cannot be activated.
244 */
245 @RunsInEDT
246 protected final <T extends Component> T waitForEditorActivation(JTable table, int row,
247 int column, Class<T> supportedType) {
248 return waitForEditorActivation(new TypeMatcher(supportedType, true), table, row, column, supportedType);
249 }
250
251 /**
252 * Waits until the editor of the given table cell is showing on the screen.
253 * @param <T> the generic type of the cell editor.
254 * @param matcher the condition that the cell editor to look for needs to satisfy.
255 * @param table the target <code>JTable</code>.
256 * @param row the row index of the cell.
257 * @param column the column index of the cell.
258 * @param supportedType the type of component we expect as editor.
259 * @return the editor of the given table cell once it is showing on the screen.
260 * @throws ActionFailedException if an editor for the given cell cannot be found or cannot be activated.
261 */
262 @RunsInEDT
263 protected final <T extends Component> T waitForEditorActivation(ComponentMatcher matcher, JTable table, int row,
264 int column, Class<T> supportedType) {
265 ComponentFoundCondition condition = new ComponentFoundCondition("", robot.finder(), matcher, table);
266 try {
267 pause(condition, EDITOR_LOOKUP_TIMEOUT);
268 } catch (WaitTimedOutError e) {
269 throw cannotFindOrActivateEditor(row, column);
270 }
271 return supportedType.cast(condition.found());
272 }
273
274 /**
275 * Throws a <code>{@link ActionFailedException}</code> if this <code>{@link JTableCellWriter}</code> could not find or
276 * activate the cell editor of the supported type.
277 * @param row the row index of the cell.
278 * @param column the column index of the cell.
279 * @return the thrown exception.
280 */
281 protected static ActionFailedException cannotFindOrActivateEditor(int row, int column) {
282 String msg = concat("Unable to find or activate editor for cell [", valueOf(row), ",", valueOf(column), "]");
283 throw actionFailure(msg);
284 }
285
286
287 /**
288 * Returns the cell editor being currently used. This method will return <code>null</code> if no table cell is being
289 * currently edited.
290 * @return the cell editor being currently used, or <code>null</code> if no table cell is being currently edited.
291 */
292 protected final TableCellEditor cellEditor() { return cellEditor; }
293
294 /**
295 * Sets the cell editor being currently used.
296 * @param newCellEditor the cell editor being currently used.
297 */
298 protected final void cellEditor(TableCellEditor newCellEditor) {
299 cellEditor = newCellEditor;
300 }
301 }