View Javadoc

1   /*
2    * Copyright 2006 - 2012 Christina Bohk and Roland Ewald
3    *  
4    * Licensed under the Apache License, Version 2.0 (the "License"); 
5    * you may not use this file except in compliance with the License. 
6    * You may obtain a copy of the License at 
7    *  
8    *  http://www.apache.org/licenses/LICENSE-2.0
9    *  
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License. 
15   */
16  package p3j.gui.panels.matrices;
17  
18  import java.awt.Toolkit;
19  import java.awt.datatransfer.Clipboard;
20  import java.awt.datatransfer.DataFlavor;
21  import java.awt.datatransfer.StringSelection;
22  import java.awt.event.ActionEvent;
23  import java.awt.event.ActionListener;
24  import java.awt.event.KeyEvent;
25  import java.util.StringTokenizer;
26  import java.util.logging.Level;
27  
28  import javax.swing.JComponent;
29  import javax.swing.KeyStroke;
30  
31  import net.sf.jeppers.grid.JGrid;
32  import net.sf.jeppers.grid.SelectionModel;
33  
34  import org.jamesii.SimSystem;
35  
36  /**
37   * An adapter to integrate Copy/Paste-Behaviour to {@link JGrid}. Implements
38   * 'fast' navigation and selection by pressing CTRL + Arrow or CTRL + SHIFT +
39   * Arrow. Implements clipboard operations to deliver a user-friendly UI.
40   * 
41   * Inspired by code from Nils O. Sel(ao)sdal (see
42   * http://groups.google.com/group/comp
43   * .lang.java.gui/browse_frm/thread/2289d2f55aaed5ad
44   * /3665b8ac63e4656a?tvc=1&q=copy+paste+excel+nach+jtable#3665b8ac63e4656a)
45   * 
46   * Created on January 10, 2007
47   * 
48   * @author Christina Bohk
49   * @author Roland Ewald
50   */
51  public class GridBehaviourAdapter implements ActionListener {
52  
53  	/** The action command for 'right'. */
54  	public static final String ACTION_COMMAND_RIGHT = "Right";
55  
56  	/** The action command for 'left' */
57  	public static final String ACTION_COMMAND_LEFT = "Left";
58  
59  	/** The action command for 'up'. */
60  	public static final String ACTION_COMMAND_UP = "Up";
61  
62  	/** The action command for 'down'. */
63  	public static final String ACTION_COMMAND_DOWN = "Down";
64  
65  	/** The prefix for 'fast' action commands. */
66  	public static final String FAST_PREFIX = "fast";
67  
68  	/** The prefix for 'fast' action commands on the selection. */
69  	public static final String FAST_SELECTION_CMD_PREFIX = "fastSel";
70  
71  	/** Reference to grid. */
72  	private JGrid grid;
73  
74  	/** Selection model of JGrid. */
75  	private SelectionModel selModel;
76  
77  	/** Reference to system clipboard. */
78  	private Clipboard clipBoard;
79  
80  	// Constants to indicate the directions
81  
82  	/** Upwards. */
83  	public static final int DIR_UP = 0;
84  
85  	/** Right direction. */
86  	public static final int DIR_RIGHT = 1;
87  
88  	/** Downwards. */
89  	public static final int DIR_DOWN = 2;
90  
91  	/** Left direction. */
92  	public static final int DIR_LEFT = 3;
93  
94  	/**
95  	 * Returns {@link KeyStroke} for copying to clipboard (CTRL + C).
96  	 * 
97  	 * @return key stroke for copy
98  	 */
99  	public static KeyStroke getCopyKeyStroke() {
100 		return KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK, false);
101 	}
102 
103 	/**
104 	 * Returns {@link KeyStroke} for cutting to clipboard (CTRL + X).
105 	 * 
106 	 * @return key stroke for cut
107 	 */
108 	public static KeyStroke getCutKeyStroke() {
109 		return KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK, false);
110 	}
111 
112 	/**
113 	 * Returns {@link KeyStroke} for pasting from clipboard (CTRL + V).
114 	 * 
115 	 * @return key stroke for paste
116 	 */
117 	public static KeyStroke getPasteKeyStroke() {
118 		return KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK, false);
119 	}
120 
121 	/**
122 	 * Default constructor.
123 	 * 
124 	 * @param target
125 	 *          the grid of which the behaviour shall be controlled
126 	 */
127 	public GridBehaviourAdapter(JGrid target) {
128 
129 		this.grid = target;
130 		this.selModel = target.getSelectionModel();
131 		clipBoard = Toolkit.getDefaultToolkit().getSystemClipboard();
132 
133 		KeyStroke cut = getCutKeyStroke();
134 		KeyStroke copy = getCopyKeyStroke();
135 		KeyStroke paste = getPasteKeyStroke();
136 
137 		KeyStroke delete = KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0, false);
138 
139 		KeyStroke fastUp = KeyStroke.getKeyStroke(KeyEvent.VK_UP,
140 		    ActionEvent.CTRL_MASK, false);
141 		KeyStroke fastDown = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,
142 		    ActionEvent.CTRL_MASK, false);
143 		KeyStroke fastLeft = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,
144 		    ActionEvent.CTRL_MASK, false);
145 		KeyStroke fastRight = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,
146 		    ActionEvent.CTRL_MASK, false);
147 
148 		KeyStroke fastSelUp = KeyStroke.getKeyStroke(KeyEvent.VK_UP,
149 		    ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK, false);
150 		KeyStroke fastSelDown = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN,
151 		    ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK, false);
152 		KeyStroke fastSelLeft = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT,
153 		    ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK, false);
154 		KeyStroke fastSelRight = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT,
155 		    ActionEvent.CTRL_MASK | ActionEvent.SHIFT_MASK, false);
156 
157 		target.registerKeyboardAction(this, "Cut", cut, JComponent.WHEN_FOCUSED);
158 		target.registerKeyboardAction(this, "Copy", copy, JComponent.WHEN_FOCUSED);
159 		target
160 		    .registerKeyboardAction(this, "Paste", paste, JComponent.WHEN_FOCUSED);
161 		target.registerKeyboardAction(this, "Delete", delete,
162 		    JComponent.WHEN_FOCUSED);
163 		target.registerKeyboardAction(this, FAST_PREFIX + ACTION_COMMAND_UP,
164 		    fastUp, JComponent.WHEN_FOCUSED);
165 		target.registerKeyboardAction(this, FAST_PREFIX + ACTION_COMMAND_DOWN,
166 		    fastDown, JComponent.WHEN_FOCUSED);
167 		target.registerKeyboardAction(this, FAST_PREFIX + ACTION_COMMAND_LEFT,
168 		    fastLeft, JComponent.WHEN_FOCUSED);
169 		target.registerKeyboardAction(this, FAST_PREFIX + ACTION_COMMAND_RIGHT,
170 		    fastRight, JComponent.WHEN_FOCUSED);
171 		target.registerKeyboardAction(this, FAST_SELECTION_CMD_PREFIX
172 		    + ACTION_COMMAND_UP, fastSelUp, JComponent.WHEN_FOCUSED);
173 		target.registerKeyboardAction(this, FAST_SELECTION_CMD_PREFIX
174 		    + ACTION_COMMAND_DOWN, fastSelDown, JComponent.WHEN_FOCUSED);
175 		target.registerKeyboardAction(this, FAST_SELECTION_CMD_PREFIX
176 		    + ACTION_COMMAND_LEFT, fastSelLeft, JComponent.WHEN_FOCUSED);
177 		target.registerKeyboardAction(this, FAST_SELECTION_CMD_PREFIX
178 		    + ACTION_COMMAND_RIGHT, fastSelRight, JComponent.WHEN_FOCUSED);
179 
180 	}
181 
182 	@Override
183 	public void actionPerformed(ActionEvent e) {
184 		String actionCommand = e.getActionCommand();
185 		if (actionCommand.equals("Cut")) {
186 			copyToClipBoard(true);
187 		} else if (actionCommand.equals("Copy")) {
188 			copyToClipBoard(false);
189 		} else if (actionCommand.equals("Paste")) {
190 			pasteFromClipboard();
191 		} else if (actionCommand.equals("Delete")) {
192 			deleteSelection();
193 		} else if (actionCommand.indexOf(FAST_SELECTION_CMD_PREFIX) == 0) {
194 			moveFast(Direction.getDirection(actionCommand
195 			    .substring(FAST_SELECTION_CMD_PREFIX.length())), true);
196 		} else if (actionCommand.indexOf(FAST_PREFIX) == 0) {
197 			moveFast(
198 			    Direction.getDirection(actionCommand.substring(FAST_PREFIX.length())),
199 			    false);
200 		}
201 	}
202 
203 	/**
204 	 * Copies or cuts selected values to clipboard.
205 	 * 
206 	 * @param cut
207 	 *          if true, values will be cut out
208 	 */
209 	protected void copyToClipBoard(boolean cut) {
210 		StringBuffer buffer = new StringBuffer();
211 		int fromX = selModel.getFirstSelectedRow();
212 		int fromY = selModel.getFirstSelectedColumn();
213 		int toX = selModel.getLastSelectedRow();
214 		int toY = selModel.getLastSelectedColumn();
215 
216 		for (int i = fromX; i <= toX; i++) {
217 			for (int j = fromY; j < toY; j++) {
218 				buffer.append(grid.getValueAt(i, j));
219 				buffer.append('\t');
220 				if (cut) {
221 					grid.setValueAt("", i, j);
222 				}
223 			}
224 			buffer.append(grid.getValueAt(i, toY));
225 			buffer.append('\n');
226 			if (cut) {
227 				grid.setValueAt("", i, toY);
228 			}
229 		}
230 
231 		StringSelection strSelection = new StringSelection(buffer.toString());
232 		clipBoard.setContents(strSelection, strSelection);
233 
234 		if (cut) {
235 			grid.repaint();
236 		}
237 	}
238 
239 	/**
240 	 * Pastes from clipboard.
241 	 */
242 	protected void pasteFromClipboard() {
243 
244 		String insertString = "";
245 
246 		try {
247 			insertString = (String) (clipBoard.getContents(this)
248 			    .getTransferData(DataFlavor.stringFlavor));
249 		} catch (Exception ex) {
250 			SimSystem
251 			    .report(Level.WARNING, "Could not load data from clipboard.", ex);
252 		}
253 
254 		if (insertString.length() == 0) {
255 			return;
256 		}
257 
258 		String rowString;
259 		StringTokenizer tokenizer = new StringTokenizer(insertString, "\n");
260 
261 		int startRow = selModel.getFirstSelectedRow();
262 		int startCol = selModel.getFirstSelectedColumn();
263 		int rowCount = grid.getRowCount();
264 		int colCount = grid.getColumnCount();
265 
266 		for (int i = 0; tokenizer.hasMoreTokens(); i++) {
267 
268 			rowString = tokenizer.nextToken();
269 			String[] values = rowString.split("\t");
270 
271 			if (startRow + i >= rowCount) {
272 				break;
273 			}
274 
275 			for (int j = 0; j < values.length; j++) {
276 				if (startCol + j >= colCount) {
277 					continue;
278 				}
279 				grid.setValueAt(values[j], startRow + i, startCol + j);
280 			}
281 		}
282 
283 		grid.repaint();
284 	}
285 
286 	/**
287 	 * Deletes selected values.
288 	 */
289 	private void deleteSelection() {
290 		SelectionModel sModel = grid.getSelectionModel();
291 		for (int i = sModel.getFirstSelectedRow(); i <= sModel.getLastSelectedRow(); i++) {
292 			for (int j = sModel.getFirstSelectedColumn(); j <= sModel
293 			    .getLastSelectedColumn(); j++) {
294 				grid.setValueAt("", i, j);
295 			}
296 		}
297 	}
298 
299 	/**
300 	 * Emulates positioning when CTRIL is pressed. Used for fast selection and
301 	 * navigation in the {@link JGrid}. This methods walks in the given
302 	 * {@link Direction} until it encounters an empty field or the end of the
303 	 * matrix.
304 	 * 
305 	 * @param direction
306 	 *          the direction in which to go
307 	 * @param select
308 	 *          flag to determine if passed elements shall be selected (i.e., is
309 	 *          SHIFT also pressed?)
310 	 */
311 	protected void moveFast(Direction direction, boolean select) {
312 
313 		if (direction == Direction.UNKNOWN) {
314 			return;
315 		}
316 
317 		int fromX = selModel.getFirstSelectedRow();
318 		int toX = selModel.getLastSelectedRow();
319 		int fromY = selModel.getFirstSelectedColumn();
320 		int toY = selModel.getLastSelectedColumn();
321 
322 		int rowCount = grid.getRowCount();
323 		int colCount = grid.getColumnCount();
324 
325 		int currentX = fromX;
326 		int currentY = fromY;
327 
328 		String value = getValue(direction.modifiesRows(), direction.getOffset(),
329 		    currentX, currentY, rowCount, colCount).toString();
330 
331 		// Step into direction until empty grid element is reached
332 		while (!value.equals("")) {
333 			if (direction.modifiesRows()) {
334 				currentX += direction.getOffset();
335 			} else {
336 				currentY += direction.getOffset();
337 			}
338 			value = getValue(direction.modifiesRows(), direction.getOffset(),
339 			    currentX, currentY, rowCount, colCount).toString();
340 		}
341 
342 		if (select) {
343 			selModel.setSelectionRange(Math.min(fromX, currentX),
344 			    Math.min(fromY, currentY), Math.max(toX, currentX),
345 			    Math.max(toY, currentY));
346 		} else {
347 			selModel.setSelectionRange(currentX, currentY, currentX, currentY);
348 		}
349 
350 		grid.repaint();
351 	}
352 
353 	/**
354 	 * Gets a value from the {@link JGrid} component. Checks bounds etc.
355 	 * 
356 	 * @param modifyX
357 	 *          flag to determine whether X is modified (true) or Y is modified
358 	 *          (false)
359 	 * @param offSet
360 	 *          either -1 or 1, depending on the direction and whether the
361 	 *          column/row index has to be incremented or decremented to go in
362 	 *          this direction, see {@link Direction#getOffset()}
363 	 * @param currentRow
364 	 *          current row number
365 	 * @param currentCol
366 	 *          current column number
367 	 * @param rowCount
368 	 *          number of rows
369 	 * @param colCount
370 	 *          number of columns
371 	 * 
372 	 * @return String representation of next value in path
373 	 */
374 	protected String getValue(boolean modifyX, int offSet, int currentRow,
375 	    int currentCol, int rowCount, int colCount) {
376 		boolean rowOutOfBounds = (currentRow + offSet < 0 || currentRow + offSet >= rowCount);
377 		boolean colOutOfBounds = (currentCol + offSet < 0 || currentCol + offSet >= colCount);
378 		if ((modifyX && rowOutOfBounds) || colOutOfBounds) {
379 			return "";
380 		}
381 
382 		Object o = modifyX ? grid.getValueAt(currentRow + offSet, currentCol)
383 		    : grid.getValueAt(currentRow, currentCol + offSet);
384 
385 		if (o == null) {
386 			return "";
387 		}
388 		return o.toString();
389 	}
390 }
391 
392 /**
393  * Auxiliary enumeration to define the direction of changing the element focus.
394  * Also provides additional information, namely if row or column index is
395  * modified by this direction, and if the modification is positive or negative.
396  * 
397  * Created: August 26, 2008
398  * 
399  * @author Christina Bohk
400  * @author Roland Ewald
401  * 
402  */
403 enum Direction {
404 
405 	/**
406 	 * Upwards.
407 	 */
408 	UP,
409 
410 	/**
411 	 * Downwards.
412 	 */
413 	DOWN,
414 
415 	/**
416 	 * Left.
417 	 */
418 	LEFT,
419 
420 	/**
421 	 * Right.
422 	 */
423 	RIGHT,
424 
425 	/**
426 	 * Default value, set when direction is unknown. See
427 	 * {@link Direction#getDirection(String)}.
428 	 */
429 	UNKNOWN;
430 
431 	/**
432 	 * Gets direction from action command string.
433 	 * 
434 	 * @param str
435 	 *          the action command
436 	 * @return the associated direction
437 	 */
438 	static Direction getDirection(String str) {
439 		if (str.equals(GridBehaviourAdapter.ACTION_COMMAND_UP)) {
440 			return UP;
441 		}
442 		if (str.equals(GridBehaviourAdapter.ACTION_COMMAND_DOWN)) {
443 			return DOWN;
444 		}
445 		if (str.equals(GridBehaviourAdapter.ACTION_COMMAND_LEFT)) {
446 			return LEFT;
447 		}
448 		if (str.equals(GridBehaviourAdapter.ACTION_COMMAND_RIGHT)) {
449 			return RIGHT;
450 		}
451 		return UNKNOWN;
452 	}
453 
454 	/**
455 	 * Tests whether following this direction modifies the row index.
456 	 * 
457 	 * @return true iff following this direction modifies the row index
458 	 */
459 	boolean modifiesRows() {
460 		return this == UP || this == DOWN;
461 	}
462 
463 	/**
464 	 * Tests whether following this direction modifies the column index.
465 	 * 
466 	 * @return true iff following this direction modifies the column index
467 	 */
468 	boolean modifiesColumns() {
469 		return this == LEFT || this == RIGHT;
470 	}
471 
472 	/**
473 	 * Get increment. {@link Direction#DOWN} and {@link Direction#RIGHT} increase
474 	 * the row/column index, so that 1 is returned. {@link Direction#UP} and
475 	 * {@link Direction#LEFT} decrease row/column index, so -1 is returned in that
476 	 * case.
477 	 * 
478 	 * @return the increment (1 or -1)
479 	 */
480 	int getOffset() {
481 		return (this == DOWN || this == RIGHT) ? 1 : -1;
482 	}
483 }