View Javadoc

1   /*--------------------------------------------------------------------------
2    *  Copyright 2007 utgenome.org
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  //--------------------------------------
17  // GenomeBrowser Project
18  //
19  // TrackGroup.java
20  // Since: Jun 18, 2007
21  //
22  // $URL$ 
23  // $Author$
24  //--------------------------------------
25  package org.utgenome.gwt.utgb.client.track;
26  
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import org.utgenome.gwt.utgb.client.BrowserServiceAsync;
32  import org.utgenome.gwt.utgb.client.RPCServiceManager;
33  import org.utgenome.gwt.utgb.client.UTGBClientErrorCode;
34  import org.utgenome.gwt.utgb.client.UTGBClientException;
35  import org.utgenome.gwt.utgb.client.UTGBEntryPointBase;
36  import org.utgenome.gwt.utgb.client.track.Track.TrackFactory;
37  import org.utgenome.gwt.utgb.client.track.impl.TrackGroupPropertyImpl;
38  import org.utgenome.gwt.utgb.client.track.lib.NavigatorTrack;
39  import org.utgenome.gwt.utgb.client.util.Properties;
40  import org.utgenome.gwt.utgb.client.util.xml.XMLAttribute;
41  import org.utgenome.gwt.utgb.client.util.xml.XMLUtil;
42  import org.utgenome.gwt.utgb.client.util.xml.XMLWriter;
43  import org.utgenome.gwt.utgb.client.view.TrackView;
44  import org.utgenome.gwt.utgb.client.view.TrackView.Coordinate;
45  
46  /**
47   * Any TrackGroup must implement this interface to enable instantiation of track groups using a given properties.
48   * 
49   * @author leo
50   * 
51   */
52  interface HasFactory {
53  	public static abstract class TrackGroupFactory {
54  		/**
55  		 * obtain a new factory instance.
56  		 * 
57  		 * @return new Factory instance.
58  		 */
59  		public abstract TrackGroup newInstance();
60  
61  		/**
62  		 * set a property.
63  		 * 
64  		 * @param key
65  		 *            property name to be set.
66  		 * @param value
67  		 *            property value to be set.
68  		 */
69  		public void setProperty(final String key, final String value) {
70  		}
71  
72  		/**
73  		 * get a property value.
74  		 * 
75  		 * @param key
76  		 *            property name you want to know.
77  		 * @return property value.
78  		 */
79  		public String getProperty(final String key) {
80  			return null;
81  		}
82  
83  		public void clear() {
84  		}
85  	}
86  }
87  
88  /**
89   * <p>
90   * A {@link TrackGroup} manages a set of tracks. You can create a track group with several tracks by using
91   * {@link #addTrack(Track)} method. Every {@link TrackGroup} can have several child {@link TrackGroup}s. For example,
92   * when a {@link TrackGroup} A has a set of tracks [T1, T2, T3], and another TrackGroup B consists of [T4, T5],
93   * {@link #addTrackGroup(TrackGroup)} to the {@link TrackGroup} A yields a nested track group: [T1, T2, T3, [T4, T5]].
94   * </p>
95   * 
96   * <p>
97   * Broadcasts to this track group are send to all tracks in this group, including T1, T2, T3 and the track group B([T4,
98   * T5]). However, T4 and T5 do not recieve this broadcast message directly, since it is subsumed in
99   * {@link TrackGroup#onParentTrackGroupPropertyChange(TrackGroupPropertyChange)} and
100  * {@link TrackGroup#onParentTrackWindowChange(TrackWindow)} method in the track group B. So, whether bypassing
101  * broadcast messages into child tracks or not depends on your {@link TrackGroup} implementation. In the default
102  * implementation of {@link TrackGroup} (i.e. {@link TrackGroupBase}),
103  * {@link TrackGroup#onParentTrackGroupPropertyChange(TrackGroupPropertyChange)} and
104  * {@link TrackGroup#onParentTrackWindowChange(TrackWindow)} methods just ignore such broadcast messages from the parent
105  * group.
106  * 
107  * In order to recieve broadcast messages from the parent group, override
108  * {@link TrackGroup#onParentTrackGroupPropertyChange(TrackGroupPropertyChange)} or
109  * {@link TrackGroup#onParentTrackWindowChange(TrackWindow)} methods as follows: <code>
110  * 
111  * public void onParentTrackGroupPropertyChange(TrackGroupPropertyChange change)
112  * {
113  *     change.apply(getPropertyWriter());
114  * }
115  * 
116  * public void onParentTrackWindowChange(TrackWindow newWindow)
117  * {
118  *     getPropertyWriter().setTrackWindow(newWindow);
119  * }
120  * 
121  * </code>
122  * 
123  * Changes to the {@link TrackGroupPropertyWriter} are automatically broadcasted to tracks in the group.
124  * </p>
125  * 
126  * <p>
127  * {@link TrackGroup} has another important role to localize broadcast message within a {@link TrackGroup}. For example,
128  * a broadcast message within the {@link TrackGroup} B is send to the track T4 and T5 only; tracks T1, T2, and T3 do not
129  * recieve this localized broadcast message.
130  * </p>
131  * 
132  * <p>
133  * When you want to use common variables shared within a track group, extend {@link TrackGroup} class and put these
134  * variables as field parameters of this extention.
135  * </p>
136  * 
137  * @author leo
138  * 
139  */
140 public class TrackGroup implements TrackEntry, Comparable<TrackGroup>, HasFactory {
141 	public static TrackGroupFactory factory() {
142 		return new TrackGroupFactory() {
143 			String name = "";
144 
145 			@Override
146 			public TrackGroup newInstance() {
147 				return new TrackGroup(name);
148 			}
149 
150 			@Override
151 			public void setProperty(String key, String value) {
152 				if (key.equals("name"))
153 					name = value;
154 			}
155 		};
156 	}
157 
158 	protected ArrayList<Track> _trackList = new ArrayList<Track>();
159 	protected ArrayList<TrackGroup> _trackGroupList = new ArrayList<TrackGroup>();
160 	protected TrackGroupPropertyImpl _trackGroupProperty = new TrackGroupPropertyImpl(this);
161 	protected ArrayList<TrackUpdateListener> _trackEventListenerList = new ArrayList<TrackUpdateListener>();
162 	protected TrackLayoutManager _layoutManager;
163 	protected TrackGroup _parentTrackGroup = null;
164 	protected String _trackGroupName = "";
165 	protected boolean _notifyResize = true;
166 
167 	public void clear() {
168 		// take a coy of the track list
169 		ArrayList<Track> trackList = new ArrayList<Track>();
170 		trackList.addAll(_trackList);
171 		for (Iterator<Track> it = trackList.iterator(); it.hasNext();)
172 			removeTrack(it.next());
173 		// take a copy of the track group list
174 		ArrayList<TrackGroup> trackGroupList = new ArrayList<TrackGroup>();
175 		trackGroupList.addAll(_trackGroupList);
176 		for (Iterator<TrackGroup> it = trackGroupList.iterator(); it.hasNext();)
177 			removeTrackGroup(it.next());
178 	}
179 
180 	public TrackGroup(String trackGroupName) {
181 		_trackGroupName = trackGroupName;
182 	}
183 
184 	/**
185 	 * Add a new track to this group.
186 	 * 
187 	 * @param track
188 	 * @return a frame for the inserted track
189 	 */
190 	public void addTrack(Track track) {
191 		addTrackInternal(track);
192 		for (Iterator<TrackUpdateListener> it = _trackEventListenerList.iterator(); it.hasNext();) {
193 			TrackUpdateListener listener = it.next();
194 			listener.onInsertTrack(track);
195 		}
196 		// notifyResize();
197 	}
198 
199 	private void addTrackInternal(Track track) {
200 		_trackList.add(track);
201 		track.setTrackGroup(this);
202 	}
203 
204 	/**
205 	 * Insert a new track before the specified index
206 	 * 
207 	 * @param track
208 	 * @param beforeIndex
209 	 */
210 	public void insertTrack(Track track, int beforeIndex) {
211 		addTrackInternal(track);
212 		for (Iterator<TrackUpdateListener> it = _trackEventListenerList.iterator(); it.hasNext();) {
213 			TrackUpdateListener listener = it.next();
214 			listener.onInsertTrack(track, beforeIndex);
215 		}
216 		// notifyResize();
217 	}
218 
219 	/**
220 	 * Add a track group as a child of this track group. Broadcasts to the parent track group will be notified to its
221 	 * child track groups.
222 	 * 
223 	 * Note: {@link TrackUpdateListener}s added to the parent track group are propagated to the inserted trackGroup.
224 	 * 
225 	 * @param trackGroup
226 	 */
227 	public void addTrackGroup(TrackGroup trackGroup) {
228 		_trackGroupList.add(trackGroup);
229 		trackGroup.setParentTrackGroup(this);
230 		this.setResizeNotification(false);
231 		// notify to the listners
232 		for (Iterator<TrackUpdateListener> it = _trackEventListenerList.iterator(); it.hasNext();) {
233 			TrackUpdateListener listener = it.next();
234 			listener.onAddTrackGroup(trackGroup);
235 			// propagates the listners of this group
236 			// trackGroup.addTrackUpdateListener(listener);
237 		}
238 		this.setResizeNotification(true);
239 		notifyResize();
240 	}
241 
242 	/**
243 	 * Broadcast the property change to all of the tracks in this track group.
244 	 * 
245 	 * @param change
246 	 *            changes of track properties
247 	 */
248 	public void broadcastChange(TrackGroupPropertyChange change, TrackWindow newWindow) {
249 
250 		// broadcast for all tracks in this group
251 		for (Iterator<Track> it = _trackList.iterator(); it.hasNext();) {
252 			Track track = it.next();
253 			if (track.isInitialized()) {
254 				track.beforeChangeTrackWindow(newWindow);
255 			}
256 		}
257 
258 		// broadcast for all tracks in this group
259 		for (Iterator<Track> it = _trackList.iterator(); it.hasNext();) {
260 			Track track = it.next();
261 			if (track.isInitialized()) {
262 				track.onChange(change, newWindow);
263 			}
264 		}
265 		// bypass the broadcast message to the other track groups
266 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
267 			TrackGroup group = it.next();
268 			group.onParentChange(change, newWindow);
269 		}
270 
271 	}
272 
273 	public void broadCastScrollTrackWindow(double scrollPercentage) {
274 		// bypass the broadcast message to the other track groups
275 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
276 			TrackGroup group = it.next();
277 			NavigatorTrack.scroll(group, scrollPercentage);
278 		}
279 	}
280 
281 	public void broadCastWindowSizeChange(int scaleDiff) {
282 		// bypass the broadcast message to the other track groups
283 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
284 			TrackGroup group = it.next();
285 			NavigatorTrack.zoom(group, scaleDiff);
286 		}
287 	}
288 
289 	/**
290 	 * Get the {@link TrackGroupProperty} of this track group
291 	 * 
292 	 * @return
293 	 */
294 	public TrackGroupProperty getPropertyReader() {
295 		return _trackGroupProperty;
296 	}
297 
298 	public String getProperty(String key, String defaultValue) {
299 		return getPropertyReader().getProperty(key, defaultValue);
300 	}
301 
302 	public String getProperty(String key) {
303 		return getPropertyReader().getProperty(key);
304 	}
305 
306 	/**
307 	 * Get the {@link TrackGroupPropertyWriter} of this track group. Any modification via the setter methods in the
308 	 * {@link TrackGroupPropertyWriter} will be broadcasted to this group via
309 	 * {@link #onParentTrackGroupPropertyChange(TrackGroupProperty)}.
310 	 * 
311 	 * @return
312 	 */
313 	public TrackGroupPropertyWriter getPropertyWriter() {
314 		return _trackGroupProperty;
315 	}
316 
317 	/**
318 	 * Get the list of track groups containd in this group, <strong>excluding</strong> track groups within the sub
319 	 * groups.
320 	 * 
321 	 * @return
322 	 */
323 	public List<TrackGroup> getTrackGroupList() {
324 		return _trackGroupList;
325 	}
326 
327 	/**
328 	 * Get the list of tracks containd in this group, <strong>excluding</strong> tracks within the sub groups.
329 	 * 
330 	 * @return
331 	 */
332 	public List<Track> getTrackList() {
333 		return _trackList;
334 	}
335 
336 	/**
337 	 * Get the list of tracks containd in this group, <strong>including </strong> tracks within the sub groups.
338 	 * 
339 	 * @return
340 	 */
341 	public List<Track> getAllTrackList() {
342 		ArrayList<Track> trackList = new ArrayList<Track>();
343 		trackList.addAll(_trackList);
344 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
345 			TrackGroup group = it.next();
346 			trackList.addAll(group.getTrackList());
347 		}
348 		return trackList;
349 	}
350 
351 	/**
352 	 * Get the {@link TrackWindow} of this track group
353 	 * 
354 	 * @return
355 	 */
356 	public TrackWindow getTrackWindow() {
357 		return _trackGroupProperty.getTrackWindow();
358 	}
359 
360 	public void onParentChange(TrackGroupPropertyChange change, TrackWindow newWindow) {
361 		//getPropertyWriter().setProperty(change, newWindow);
362 	}
363 
364 	/**
365 	 * An event handler when the {@link TrackWindow} of this group has changed. Override this method to implement your
366 	 * own event handler for this track group.
367 	 * 
368 	 * @param newWindow
369 	 */
370 	public void onParentTrackWindowChange(TrackWindow newWindow) {
371 		// do nothing
372 	}
373 
374 	/**
375 	 * An event handler when the {@link TrackGroupProperty} of the parent group has changed. Override this method to
376 	 * implement your own event handler for this track group.
377 	 * 
378 	 * @param change
379 	 */
380 	public void onParentTrackGroupPropertyChange(TrackGroupPropertyChange change) {
381 		// do nothing
382 	}
383 
384 	/**
385 	 * Redraw all child tracks, including tracks within the sub groups.
386 	 */
387 	public void redrawAll() {
388 		// draw all tracks in this group
389 		for (Iterator<Track> it = _trackList.iterator(); it.hasNext();) {
390 			Track track = it.next();
391 			track.refresh();
392 		}
393 		// draw other track groups
394 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
395 			TrackGroup group = it.next();
396 			group.redrawAll();
397 		}
398 	}
399 
400 	/**
401 	 * Remove the specified track from this group.
402 	 * 
403 	 * @param track
404 	 */
405 	public void removeTrack(Track track) {
406 		_trackList.remove(track);
407 		for (Iterator<TrackUpdateListener> it = _trackEventListenerList.iterator(); it.hasNext();) {
408 			TrackUpdateListener listener = it.next();
409 			listener.onRemoveTrack(track);
410 		}
411 		// notifyResize();
412 	}
413 
414 	/**
415 	 * Removed the specified track group and its belonging tracks from this group.
416 	 * 
417 	 * Note: {@link TrackUpdateListener}s propagated to the removed track group will be removed.
418 	 * 
419 	 * @param trackGroup
420 	 */
421 	public void removeTrackGroup(TrackGroup trackGroup) {
422 		_trackGroupList.remove(trackGroup);
423 		trackGroup.removeParentTrackGroup();
424 		this.setResizeNotification(false);
425 		for (Iterator<TrackUpdateListener> it = _trackEventListenerList.iterator(); it.hasNext();) {
426 			TrackUpdateListener listener = it.next();
427 			listener.onRemoveTrackGroup(trackGroup);
428 			// propagates the listners of this group
429 			// trackGroup.removeTrackUpdateListener(listener);
430 		}
431 		this.setResizeNotification(true);
432 		notifyResize();
433 	}
434 
435 	/**
436 	 * Set the window location on the genome. The change will be reported to all of the tracks in this group via the
437 	 * {@link #onParentTrackWindowChange(TrackWindow)} method.
438 	 * 
439 	 * @param startOnGenome
440 	 * @param endOnGenome
441 	 */
442 	public void setTrackWindowLocation(int startOnGenome, int endOnGenome) {
443 		_trackGroupProperty.setTrackWindow(startOnGenome, endOnGenome);
444 	}
445 
446 	/**
447 	 * Set the width of the track window of this group. The change will be notified to all of the tracks in this group
448 	 * via the {@link #onParentTrackWindowChange(TrackWindow)}.
449 	 * 
450 	 * @param windowWidth
451 	 */
452 	public void setTrackWindowWidth(int windowWidth) {
453 		_trackGroupProperty.setTrackWindowSize(windowWidth);
454 	}
455 
456 	/**
457 	 * Add a {@link TrackUpdateListener}
458 	 * 
459 	 * @param listener
460 	 */
461 	public void addTrackUpdateListener(TrackUpdateListener listener) {
462 		_trackEventListenerList.add(listener);
463 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
464 			TrackGroup group = it.next();
465 			group.addTrackUpdateListener(listener);
466 		}
467 	}
468 
469 	/**
470 	 * Remove the specified {@link TrackUpdateListener}
471 	 * 
472 	 * @param listener
473 	 */
474 	public void removeTrackUpdateListener(TrackUpdateListener listener) {
475 		_trackEventListenerList.remove(listener);
476 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
477 			TrackGroup group = it.next();
478 			group.removeTrackUpdateListener(listener);
479 		}
480 	}
481 
482 	/**
483 	 * Return the index of the given track in the group.
484 	 * 
485 	 * @param track
486 	 * @return track index in the group
487 	 */
488 	public int getTrackIndex(Track track) {
489 		return _layoutManager.getTrackIndex(track);
490 	}
491 
492 	/**
493 	 * Set the track window (window size, start on genome, end on genome) at once
494 	 * 
495 	 * @param newWindow
496 	 */
497 	public void setTrackWindow(TrackWindow newWindow) {
498 		_trackGroupProperty.setTrackWindow(newWindow);
499 	}
500 
501 	/**
502 	 * Return the current parent trackgroup.
503 	 * 
504 	 * @return currentParentTrackGroup
505 	 */
506 	public TrackGroup getParentTrackGroup() {
507 		return _parentTrackGroup;
508 	}
509 
510 	protected void setParentTrackGroup(TrackGroup parentTrackGroup) {
511 		this._parentTrackGroup = parentTrackGroup;
512 	}
513 
514 	/**
515 	 * Notify the change of the inner track widget size to this group
516 	 */
517 	public void notifyResize() {
518 		if (!_notifyResize)
519 			return;
520 		for (Iterator<TrackUpdateListener> it = _trackEventListenerList.iterator(); it.hasNext();) {
521 			TrackUpdateListener listener = it.next();
522 			listener.onResizeTrack();
523 			listener.onResizeTrackWindow(_trackGroupProperty.getTrackWindow().getPixelWidth());
524 		}
525 	}
526 
527 	/**
528 	 * enable/disable notification of track frame resizes
529 	 * 
530 	 * @param notify
531 	 *            true to enable, false to disable
532 	 */
533 	public void setResizeNotification(boolean enable) {
534 		_notifyResize = enable;
535 		// apply the same satting for the child track groups
536 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
537 			TrackGroup g = it.next();
538 			g.setResizeNotification(enable);
539 		}
540 	}
541 
542 	/**
543 	 * @return get the total height of the track group widget
544 	 */
545 	public int getHeight() {
546 		int height = 0;
547 		for (Iterator<Track> it = _trackList.iterator(); it.hasNext();) {
548 			Track track = it.next();
549 			if (track.isInitialized()) {
550 				TrackFrame frame = track.getFrame();
551 				int frameHeight = frame.getOffsetHeight();
552 				if (frameHeight < TrackFrameState.DEFAULT_MIN_TRACKFRAME_HEIGHT)
553 					frameHeight = TrackFrameState.DEFAULT_MIN_TRACKFRAME_HEIGHT;
554 				height += frameHeight;
555 			}
556 		}
557 		for (Iterator<TrackGroup> it = _trackGroupList.iterator(); it.hasNext();) {
558 			TrackGroup group = it.next();
559 			height += group.getHeight();
560 		}
561 		return height;
562 	}
563 
564 	public void setTrackLayoutManager(TrackLayoutManager layout) {
565 		_layoutManager = layout;
566 	}
567 
568 	public String getTrackGroupName() {
569 		return _trackGroupName;
570 	}
571 
572 	public void setTrackGroupName(String newTrackGroupName) {
573 		this._trackGroupName = newTrackGroupName;
574 	}
575 
576 	protected void setTrackGroupProperty(final TrackGroupPropertyImpl trackGroupProperty) {
577 		if (trackGroupProperty != null)
578 			_trackGroupProperty = trackGroupProperty;
579 	}
580 
581 	public String getName() {
582 		return _trackGroupName;
583 	}
584 
585 	public boolean isTrack() {
586 		return false;
587 	}
588 
589 	public boolean isTrackGroup() {
590 		return true;
591 	}
592 
593 	/**
594 	 * Get the list of {@link TrackEntry}s, including both of {@link Track} and {@link TrackGroup}.
595 	 * 
596 	 * @return
597 	 */
598 	public List<TrackEntry> getTrackEntryList() {
599 		ArrayList<TrackEntry> list = new ArrayList<TrackEntry>();
600 		list.addAll(_trackList);
601 		list.addAll(_trackGroupList);
602 		return list;
603 	}
604 
605 	/**
606 	 * Add a {@link TrackGroupPropertyChangeListener}
607 	 * 
608 	 * @param listener
609 	 */
610 	public void addTrackGroupPropertyChangeListener(TrackGroupPropertyChangeListener listener) {
611 		_trackGroupProperty.addTrackGroupPropertyChangeListener(listener);
612 	}
613 
614 	/**
615 	 * Remove the specified {@link TrackGroupPropertyChangeListener}
616 	 * 
617 	 * @param listener
618 	 */
619 	public void removeTrackGroupPropertyChangeListener(TrackGroupPropertyChangeListener listener) {
620 		_trackGroupProperty.removeTrackGroupPropertyChangeListener(listener);
621 	}
622 
623 	/**
624 	 * Get the root {@link TrackGroup}
625 	 * 
626 	 * @return
627 	 */
628 	public TrackGroup getRootTrackGroup() {
629 		TrackGroup rootGroup = null;
630 		TrackGroup groupCursor = this;
631 		while (groupCursor != null) {
632 			rootGroup = groupCursor;
633 			groupCursor = groupCursor.getParentTrackGroup();
634 		}
635 		return rootGroup;
636 	}
637 
638 	protected String getClassName() {
639 		return this.getClass().getName();
640 	}
641 
642 	protected void removeParentTrackGroup() {
643 		setParentTrackGroup(null);
644 	}
645 
646 	/**
647 	 * 
648 	 * @see Comparable#compareTo(Object)
649 	 * @return
650 	 */
651 	public int compareTo(TrackGroup o) {
652 		if (o instanceof TrackGroup) {
653 			final TrackGroup _o = o;
654 			return getTrackGroupName().compareTo(_o.getTrackGroupName());
655 		}
656 		else if (o instanceof Track) {
657 			return 1;
658 		}
659 		else {
660 			throw new ClassCastException("The specified object is neither TrackGroup nor Track");
661 		}
662 	}
663 
664 	protected void setTrackGroupProperty(final TrackGroupProperty trackGroupProperty) {
665 	}
666 
667 	/**
668 	 * Output the state of this track group into {@link XMLWriter}
669 	 * 
670 	 * @param xmlWriter
671 	 */
672 	public void toXML(XMLWriter xmlWriter) {
673 		xmlWriter.start("trackGroup", new XMLAttribute("className", getClassName()).add("name", _trackGroupName));
674 		Properties trackGroupInternalProperties = new Properties();
675 		storeInternalProperties(trackGroupInternalProperties);
676 		XMLUtil.toXML(trackGroupInternalProperties, xmlWriter);
677 		getPropertyReader().toXML(xmlWriter);
678 		final List<TrackGroup> trackGroupList = getTrackGroupList();
679 		for (int i = 0; i < trackGroupList.size(); i++) {
680 			final TrackGroup trackGroup = (trackGroupList.get(i));
681 			trackGroup.toXML(xmlWriter);
682 		}
683 		final List<Track> trackList = getTrackList();
684 		for (int i = 0; i < trackList.size(); i++) {
685 			final Track track = (trackList.get(i));
686 			track.toXML(xmlWriter);
687 		}
688 		xmlWriter.end(); // track-group
689 	}
690 
691 	/**
692 	 * Save (store) the internal properties of this track group into the given {@link Properties}
693 	 * 
694 	 * @param saveData
695 	 */
696 	protected void storeInternalProperties(Properties saveData) {
697 		saveData.add("name", _trackGroupName);
698 	}
699 
700 	/**
701 	 * Create a track group from a given {@link TrackView}
702 	 * 
703 	 * @param view
704 	 * @return
705 	 * @throws UTGBClientException
706 	 */
707 	public static TrackGroup createTrackGroup(TrackView view) throws UTGBClientException {
708 
709 		TrackView.TrackGroup g = view.trackGroup;
710 		if (g == null)
711 			g = new TrackView.TrackGroup();
712 		String groupClass = view.trackGroup.class_;
713 		if (groupClass == null)
714 			groupClass = "org.utgenome.gwt.utgb.client.track.TrackGroup";
715 
716 		// instantiate a track group
717 		TrackGroupFactory trackGroupFactory = TrackFactoryHolder.getTrackGroupFactory(groupClass);
718 		final TrackGroup group = trackGroupFactory.newInstance();
719 
720 		// set track group properties
721 
722 		Properties p = new Properties();
723 		p.putAll(g.property);
724 		p.put(UTGBProperty.SPECIES, g.coordinate.species);
725 		p.put(UTGBProperty.REVISION, g.coordinate.ref);
726 		p.put(UTGBProperty.TARGET, g.coordinate.chr);
727 
728 		group.getPropertyWriter().setProperty(p);
729 
730 		// set track window (coordinate)
731 		Coordinate c = g.coordinate;
732 		if (c.pixelWidth < 0)
733 			c.pixelWidth = UTGBEntryPointBase.computeTrackWidth();
734 		group.setTrackWindow(new TrackWindow(c.pixelWidth, c.start, c.end));
735 
736 		// instantiate tracks defined in the track group 
737 		for (TrackView.Track t : view.track) {
738 			String className = t.class_;
739 			TrackFactory trackFactory = TrackFactoryHolder.getTrackFactory(className);
740 			if (trackFactory == null) {
741 				// search for default package
742 				String defaultClass = "org.utgenome.gwt.utgb.client.track.lib." + className;
743 				trackFactory = TrackFactoryHolder.getTrackFactory(defaultClass);
744 				if (trackFactory == null)
745 					throw new UTGBClientException(UTGBClientErrorCode.UNKNOWN_TRACK, "unknown track class: " + defaultClass);
746 			}
747 
748 			Track track = trackFactory.newInstance();
749 			track.loadView(t);
750 			group.addTrack(track);
751 		}
752 
753 		return group;
754 
755 	}
756 
757 	public BrowserServiceAsync getBrowserService() {
758 		return RPCServiceManager.getRPCService();
759 	}
760 
761 }