View Javadoc

1   /**********************************************
2    * Copyright (C) 2009 Lukas Laag
3    * This file is part of Vectomatic.
4    * 
5    * Vectomatic is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    * 
10   * Vectomatic is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   * 
15   * You should have received a copy of the GNU General Public License
16   * along with Vectomatic.  If not, see http://www.gnu.org/licenses/
17   **********************************************/
18  package org.vectomatic.common.model;
19  
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Comparator;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.NoSuchElementException;
29  import java.util.Set;
30  
31  import org.vectomatic.common.events.IDrawingModelListener;
32  
33  /**
34   * Class to represent a drawing. Contains the collection
35   * of shapes in the drawing stored in a linked list.
36   */
37  public class DrawingModel {
38  	protected class Node {
39  		public Node prev;
40  		public Node next;
41  		public float z;
42  		public Shape shape;
43  	}
44  	
45  	protected static class AscendingNodeComparator implements Comparator<Node> {
46  		public int compare(Node o1, Node o2) {
47  			return Float.compare(o1.z, o2.z);
48  		}
49  	}
50  	
51  	protected static class DescendingNodeComparator implements Comparator<Node> {
52  		public int compare(Node o1, Node o2) {
53  			return Float.compare(o1.z, o2.z) * -1;
54  		}
55  	}
56  	
57  	protected class AscendingIterator implements Iterator<Shape> {
58  		Node _current;
59  		
60  		public boolean hasNext() {
61  			return (_current.next != _tail);
62  		}
63  		
64  		public Shape next() {
65  			if (_current.next == _tail) {
66  				throw new NoSuchElementException();
67  			}
68  			_current = _current.next;
69  			return _current.shape;
70  		}
71  		
72  		public void remove() {
73  			if (_current == _head) {
74  				throw new IllegalStateException();
75  			}
76  			Node prev = _current.prev;
77  			Node next = _current.next;
78  			prev.next = next;
79  			next.prev = prev;
80  			_current = prev;
81  		}		
82  	}
83  
84  	protected class DescendingIterator implements Iterator<Shape> {
85  		Node _current;
86  		
87  		public boolean hasNext() {
88  			return (_current.prev != _head);
89  		}
90  		
91  		public Shape next() {
92  			if (_current.prev == _head) {
93  				throw new NoSuchElementException();
94  			}
95  			_current = _current.prev;
96  			return _current.shape;
97  		}
98  		
99  		public void remove() {
100 			if (_current == _tail) {
101 				throw new IllegalStateException();
102 			}
103 			Node prev = _current.prev;
104 			Node next = _current.next;
105 			prev.next = next;
106 			next.prev = prev;
107 			_current = next;
108 		}		
109 	}
110 
111 	protected List<IDrawingModelListener> _drawingModelListeners;
112 	/**
113 	 * Shape linked list head node
114 	 */
115 	protected Node _head;
116 	/**
117 	 * Shape linked list tail node
118 	 */
119 	protected Node _tail;
120 	/**
121 	 * A Map<Shape,Node> to retrieve the linked list node which contains
122 	 * a shape.
123 	 */
124 	protected Map<Shape,Node> _shapeToNode;
125 	/**
126 	 * Linked list forward traversal (back to front)
127 	 */
128 	protected AscendingIterator _ascIterator;
129 	/**
130 	 * Linked list reverse traversal (front to back)
131 	 */
132 	protected DescendingIterator _descIterator;
133 	/**
134 	 * To sort Node based on their depth in ascending order
135 	 */
136 	protected static final Comparator<Node> ORDER_ASCENDING = new AscendingNodeComparator();
137 	/**
138 	 * To sort Node based on their depth in descending order
139 	 */
140 	protected static final Comparator<Node> ORDER_DESCENDING = new DescendingNodeComparator();
141 	/**
142 	 * Temporary set used for ordering computations
143 	 */
144 	protected Set<Shape> _shapeSet;
145 	
146 	public DrawingModel() {
147 		_shapeToNode = new HashMap<Shape,Node>();
148 		_head = new Node();
149 		_tail = new Node();
150 		connect(_head, _tail);
151 		_shapeSet = new HashSet<Shape>();
152 		_ascIterator = new AscendingIterator();
153 		_descIterator = new DescendingIterator();
154 	}
155 	
156 	public void addDrawingModelListener(IDrawingModelListener listener) {
157 		if (_drawingModelListeners == null) {
158 			_drawingModelListeners = new ArrayList<IDrawingModelListener>();
159 		}
160 		_drawingModelListeners.add(listener);
161 	}
162 	
163 	public void removeDrawingModelListener(IDrawingModelListener listener) {
164 		if (_drawingModelListeners != null) {
165 			_drawingModelListeners.remove(listener);
166 		}
167 	}
168 	
169 	public void fireModelHasChanged() {
170 		if (_drawingModelListeners != null) {
171 			for (int i = 0, size = _drawingModelListeners.size(); i < size; i++) {
172 				IDrawingModelListener listener = _drawingModelListeners.get(i);
173 				listener.modelHasChanged(this);
174 			}
175 		}
176 	}
177 	
178 	public void addShape(Shape shape) {
179 		if (shape == null) {
180 			throw new NullPointerException();
181 		}
182 		shape.setModel(this);
183 		Node prevNode = _tail.prev;
184 		Node node = new Node();
185 		node.shape = shape;
186 		node.z = 1f + prevNode.z;
187 		connect(prevNode, node);
188 		connect(node, _tail);
189 		_shapeToNode.put(shape, node);
190 	}
191 	
192 	public void removeShape(Shape shape) {
193 		if (shape == null) {
194 			throw new NullPointerException();
195 		}
196 		Node node = _shapeToNode.get(shape);
197 		if (node == null) {
198 			throw new IllegalArgumentException();
199 		}
200 		Node prevNode = node.prev;
201 		Node nextNode = node.next;
202 		connect(prevNode, nextNode);
203 		_shapeToNode.remove(shape);
204 		shape.setModel(null);
205 	}
206 	
207 	public void clear() {
208 		connect(_head, _tail);
209 		_shapeToNode.clear();
210 	}
211 	
212 	public void reorder(List<Shape> shapes, List<Float> orders) {
213 		Node[] nodes = toNodeArray(shapes);
214 		for (int i = 0; i < nodes.length; i++) {
215 			nodes[i].z = orders.get(i).floatValue();
216 			removeShape(nodes[i].shape);
217 		}
218 		Arrays.sort(nodes, ORDER_ASCENDING);
219 		Node node = _head;
220 		int index = 0, count = shapes.size();
221 		while (index < count) {
222 			if ((node == _tail) || (node.z > nodes[index].z)) {
223 				Node prevNode = node.prev;
224 				connect(prevNode, nodes[index]);
225 				connect(nodes[index], node);
226 				_shapeToNode.put(nodes[index].shape, nodes[index]);
227 				index++;
228 			} else {
229 				node = node.next;
230 			}
231 		}
232 	}
233 	
234 	public int count() {
235 		return _shapeToNode.size();
236 	}
237 	
238 	public Iterator<Shape> iterator() {
239 		_ascIterator._current = _head;
240 		return _ascIterator;
241 	}
242 
243 	public Iterator<Shape> reverseIterator() {
244 		_descIterator._current = _tail;
245 		return _descIterator;
246 	}
247 
248 	protected Node[] toNodeArray(List<Shape> shapes) {
249 		int size = shapes.size();
250 		Node[] nodes = new Node[size];
251 		for (int i = 0; i < size; i++) {
252 			nodes[i] = _shapeToNode.get(shapes.get(i));
253 		}
254 		return nodes;
255 	}
256 	
257 	public boolean canBringToFront(List<Shape> shapes) {
258 		boolean canBringToFront = false;
259 		_shapeSet.clear();
260 		_shapeSet.addAll(shapes);
261 		for (int i = 0, size = shapes.size(); i < size; i++) {
262 			Node nextNode = _shapeToNode.get(shapes.get(i)).next;
263 			if (nextNode != _tail && (!_shapeSet.contains(nextNode.shape))) {
264 				canBringToFront = true;
265 				break;
266 			}
267 		}
268 		return canBringToFront;
269 	}
270 	
271 	public boolean canSendToBack(List<Shape> shapes) {
272 		boolean canSendToBack = false;
273 		_shapeSet.clear();
274 		_shapeSet.addAll(shapes);
275 		for (int i = 0, size = shapes.size(); i < size; i++) {
276 			Node prevNode = _shapeToNode.get(shapes.get(i)).prev;
277 			if (prevNode != _head && (!_shapeSet.contains(prevNode.shape))) {
278 				canSendToBack = true;
279 				break;
280 			}
281 		}
282 		return canSendToBack;
283 	}
284 	
285 	public boolean canBringForward(List<Shape> shapes) {
286 		boolean canBringForward = false;
287 		_shapeSet.clear();
288 		_shapeSet.addAll(shapes);
289 		for (int i = 0, size = shapes.size(); i < size; i++) {
290 			Node nextNode = _shapeToNode.get(shapes.get(i)).next;
291 			if (nextNode != _tail && (!_shapeSet.contains(nextNode.shape))) {
292 				canBringForward = true;
293 				break;
294 			}
295 		}
296 		return canBringForward;
297 	}
298 	
299 	public boolean canSendBackward(List<Shape> shapes) {
300 		boolean canSendBackward = false;
301 		_shapeSet.clear();
302 		_shapeSet.addAll(shapes);
303 		for (int i = 0, size = shapes.size(); i < size; i++) {
304 			Node prevNode = _shapeToNode.get(shapes.get(i)).prev;
305 			if (prevNode != _head && (!_shapeSet.contains(prevNode.shape))) {
306 				canSendBackward = true;
307 				break;
308 			}
309 		}
310 		return canSendBackward;
311 	}
312 	
313 	public List<Float> bringToFront(List<Shape> shapes) {
314 		List<Float> orders = new ArrayList<Float>(shapes.size());
315 		_shapeSet.clear();
316 		_shapeSet.addAll(shapes);
317 		Node[] nodes = toNodeArray(shapes);
318 		for (int i = 0; i < nodes.length; i++) {
319 			orders.add(new Float(nodes[i].z));
320 		}
321 		Arrays.sort(nodes, ORDER_DESCENDING);
322 		Node tailNode = _tail;
323 		for (int i = 0; i < nodes.length; i++) {
324 			Node node = nodes[i];
325 			Node nextNode = node.next;
326 			if (nextNode != tailNode) {
327 				Node prevNode = node.prev;
328 				Node prevTailNode = tailNode.prev;
329 				connect(prevNode, nextNode);
330 				connect(prevTailNode, node);
331 				connect(node, tailNode);
332 				float nextz = (tailNode != _tail) ? tailNode.z : prevTailNode.z + 1f;
333 				node.z = (prevTailNode.z + nextz) * 0.5f;
334 			}
335 			tailNode = node;
336 		}
337 		return orders;
338 	}
339 	
340 	public List<Float> sendToBack(List<Shape> shapes) {
341 		List<Float> orders = new ArrayList<Float>(shapes.size());
342 		_shapeSet.clear();
343 		_shapeSet.addAll(shapes);
344 		Node[] nodes = toNodeArray(shapes);
345 		for (int i = 0; i < nodes.length; i++) {
346 			orders.add(new Float(nodes[i].z));
347 		}
348 		Arrays.sort(nodes, ORDER_ASCENDING);
349 		Node headNode = _head;
350 		for (int i = 0; i < nodes.length; i++) {
351 			Node node = nodes[i];
352 			Node prevNode = node.prev;
353 			if (prevNode != headNode) {
354 				Node nextNode = node.next;
355 				Node nextHeadNode = headNode.next;
356 				connect(prevNode, nextNode);
357 				connect(node, nextHeadNode);
358 				connect(headNode, node);
359 				float prevz = (headNode != _head) ? headNode.z : nextHeadNode.z - 1f;
360 				node.z = (nextHeadNode.z + prevz) * 0.5f;
361 			}
362 			headNode = node;
363 		}
364 		return orders;
365 	}
366 	
367 	public List<Float> bringForward(List<Shape> shapes) {
368 		List<Float> orders = new ArrayList<Float>(shapes.size());
369 		_shapeSet.clear();
370 		_shapeSet.addAll(shapes);
371 		Node[] nodes = toNodeArray(shapes);
372 		for (int i = 0; i < nodes.length; i++) {
373 			orders.add(new Float(nodes[i].z));
374 		}
375 		Arrays.sort(nodes, ORDER_DESCENDING);
376 		for (int i = 0; i < nodes.length; i++) {
377 			Node node = nodes[i];
378 			Node nextNode = node.next;
379 			if (nextNode != _tail && !_shapeSet.contains(nextNode)) {
380 				Node prevNode = node.prev;
381 				Node nextNextNode = nextNode.next;
382 				connect(prevNode, nextNode);
383 				connect(nextNode, node);
384 				connect(node, nextNextNode);
385 				float nextNextz = (nextNextNode != _tail) ? nextNextNode.z : nextNode.z + 1f;
386 				node.z = (nextNode.z + nextNextz) * 0.5f;
387 			}
388 		}
389 		return orders;
390 	}
391 	
392 	public List<Float> sendBackward(List<Shape> shapes) {
393 		List<Float> orders = new ArrayList<Float>(shapes.size());
394 		_shapeSet.clear();
395 		_shapeSet.addAll(shapes);
396 		Node[] nodes = toNodeArray(shapes);
397 		for (int i = 0; i < nodes.length; i++) {
398 			orders.add(new Float(nodes[i].z));
399 		}
400 		Arrays.sort(nodes, ORDER_ASCENDING);
401 		for (int i = 0; i < nodes.length; i++) {
402 			Node node = nodes[i];
403 			Node prevNode = node.prev;
404 			if (prevNode != _head && !_shapeSet.contains(prevNode)) {
405 				Node nextNode = node.next;
406 				Node prevPrevNode = prevNode.prev;
407 				connect(prevPrevNode, node);
408 				connect(node, prevNode);
409 				connect(prevNode, nextNode);
410 				float prevPrevz = (prevPrevNode != _head) ? prevPrevNode.z : prevNode.z - 1f;
411 				node.z = (prevNode.z + prevPrevz) * 0.5f;
412 			}
413 		}
414 		return orders;
415 	}
416 	
417 	private void connect(Node a, Node b) {
418 		a.next = b;
419 		b.prev = a;
420 	}
421 	
422 	public boolean contains(Shape shape) {
423 		return _shapeToNode.containsKey(shape);
424 	}
425 	
426 	public Shape[] toShapeArray() {
427 		Shape[] shapeArray = new Shape[count()];
428 		Iterator<Shape> iterator = iterator();
429 		int index = 0;
430 		while(iterator.hasNext()) {
431 			shapeArray[index++] = iterator.next();
432 		}
433 		return shapeArray;
434 	}
435 	
436 	public void fromShapeArray(Shape[] shapeArray) {
437 		clear();
438 		if (shapeArray != null) {
439 			for (int i = 0; i < shapeArray.length; i++) {
440 				addShape(shapeArray[i]);
441 			}
442 		}
443 	}
444 }