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  // utgb-core Project
18  //
19  // NavigatorTrack.java
20  // Since: Oct 2, 2007
21  //
22  // $URL$ 
23  // $Author$
24  //--------------------------------------
25  package org.utgenome.gwt.utgb.client.track.lib;
26  
27  import java.util.ArrayList;
28  import java.util.Date;
29  import java.util.Iterator;
30  
31  import org.utgenome.gwt.utgb.client.track.Track;
32  import org.utgenome.gwt.utgb.client.track.TrackBase;
33  import org.utgenome.gwt.utgb.client.track.TrackFrame;
34  import org.utgenome.gwt.utgb.client.track.TrackGroup;
35  import org.utgenome.gwt.utgb.client.track.TrackGroupPropertyChange;
36  import org.utgenome.gwt.utgb.client.track.TrackWindow;
37  import org.utgenome.gwt.utgb.client.track.UTGBProperty;
38  import org.utgenome.gwt.utgb.client.track.bean.SequenceInfo;
39  import org.utgenome.gwt.utgb.client.ui.FormLabel;
40  import org.utgenome.gwt.utgb.client.util.CanonicalProperties;
41  import org.utgenome.gwt.utgb.client.util.JSONUtil;
42  import org.utgenome.gwt.utgb.client.util.StringUtil;
43  import org.utgenome.gwt.utgb.client.util.xml.XMLWriter;
44  import org.utgenome.gwt.widget.client.Style;
45  
46  import com.google.gwt.core.client.GWT;
47  import com.google.gwt.event.dom.client.ChangeEvent;
48  import com.google.gwt.event.dom.client.ChangeHandler;
49  import com.google.gwt.event.dom.client.ClickEvent;
50  import com.google.gwt.event.dom.client.ClickHandler;
51  import com.google.gwt.event.dom.client.KeyCodes;
52  import com.google.gwt.event.dom.client.KeyUpEvent;
53  import com.google.gwt.event.dom.client.KeyUpHandler;
54  import com.google.gwt.json.client.JSONArray;
55  import com.google.gwt.json.client.JSONException;
56  import com.google.gwt.json.client.JSONObject;
57  import com.google.gwt.json.client.JSONParser;
58  import com.google.gwt.json.client.JSONValue;
59  import com.google.gwt.user.client.DOM;
60  import com.google.gwt.user.client.ui.Button;
61  import com.google.gwt.user.client.ui.Composite;
62  import com.google.gwt.user.client.ui.FormPanel;
63  import com.google.gwt.user.client.ui.Hidden;
64  import com.google.gwt.user.client.ui.HorizontalPanel;
65  import com.google.gwt.user.client.ui.ListBox;
66  import com.google.gwt.user.client.ui.TextBox;
67  import com.google.gwt.user.client.ui.VerticalPanel;
68  import com.google.gwt.user.client.ui.Widget;
69  
70  /**
71   * Navigator of the UTGB
72   * 
73   * @author leo
74   * 
75   */
76  public class NavigatorTrack extends TrackBase {
77  	public static TrackFactory factory() {
78  		return new TrackFactory() {
79  			@Override
80  			public Track newInstance() {
81  				return new NavigatorTrack();
82  			}
83  		};
84  	}
85  
86  	public VerticalPanel panel = new VerticalPanel();
87  	private ListBox speciesBox = new ListBox();
88  	private ListBox revisionBox = new ListBox();
89  	private TextBox targetBox = new TextBox();
90  	private TextBox regionBox = new TextBox();
91  	private ArrayList<SequenceInfo> sequenceInfoList = new ArrayList<SequenceInfo>();
92  
93  	private class PropertyChangeHandler implements ChangeHandler {
94  		private String proeprty;
95  		private ListBox listBox;
96  
97  		public PropertyChangeHandler(String property, ListBox listBox) {
98  			this.proeprty = property;
99  			this.listBox = listBox;
100 		}
101 
102 		public void onChange(ChangeEvent e) {
103 			getTrackGroup().getPropertyWriter().setProperty(proeprty, listBox.getItemText(listBox.getSelectedIndex()));
104 		}
105 	}
106 
107 	private class SequenceRangeChangeListner implements KeyUpHandler {
108 		public void onKeyUp(KeyUpEvent e) {
109 			int keyCode = e.getNativeKeyCode();
110 			if (keyCode == KeyCodes.KEY_ENTER || keyCode == KeyCodes.KEY_TAB) {
111 				try {
112 
113 					int prevStart = getTrackWindow().getStartOnGenome();
114 					int prevEnd = getTrackWindow().getEndOnGenome();
115 					int width = getTrackWindow().getSequenceLength();
116 					if (width < 1) {
117 						width = 1;
118 					}
119 
120 					String[] region = regionBox.getText().split("-");
121 					if (region.length != 2)
122 						return;
123 
124 					int start = StringUtil.toInt(region[0]);
125 					int end = StringUtil.toInt(region[1]);
126 
127 					if (start <= 0) {
128 						start = 1;
129 					}
130 
131 					if (end <= start) {
132 						end = start + width;
133 					}
134 
135 					getTrackGroup().setTrackWindowLocation(start, end);
136 				}
137 				catch (NumberFormatException ex) {
138 					GWT.log("(" + regionBox.getText() + ") is invalid range", ex);
139 				}
140 			}
141 		}
142 	}
143 
144 	public static void scroll(TrackGroup group, double movePercentageOnWindow) {
145 		TrackWindow window = group.getTrackWindow();
146 		int genomeRange = window.getEndOnGenome() - window.getStartOnGenome() + 1;
147 		boolean isPlusStrand = true;
148 		if (genomeRange < 0) {
149 			genomeRange = -genomeRange;
150 			isPlusStrand = false;
151 		}
152 		int offset = (int) (genomeRange * (movePercentageOnWindow / 100.0));
153 		if (!isPlusStrand)
154 			offset = -offset;
155 
156 		if (window.getStartOnGenome() + offset < 0) {
157 			offset = -window.getStartOnGenome() + 1;
158 		}
159 		if (window.getEndOnGenome() + offset < 0) {
160 			offset = -window.getEndOnGenome() + 1;
161 		}
162 
163 		group.getPropertyWriter().setTrackWindow(window.getStartOnGenome() + offset, window.getEndOnGenome() + offset);
164 	}
165 
166 	public static void zoom(TrackGroup group, int scaleDiff) {
167 		if (scaleDiff == 0)
168 			return; // do nothing
169 
170 		TrackWindow currentWindow = group.getTrackWindow();
171 		int start = currentWindow.getStartOnGenome();
172 		int end = currentWindow.getEndOnGenome();
173 		int windowSize = (start < end) ? end - start + 1 : start - end + 1;
174 
175 		// compute a close window size that is a power of 10, e.g., 10, 100, 1000, 10000, ...
176 		int windowUpperBound = (int) Math.pow(10L, Math.ceil(Math.log(windowSize) / Math.log(10)));
177 		int windowLowerBound = (int) Math.pow(10L, Math.floor(Math.log(windowSize) / Math.log(10)));
178 
179 		if (windowUpperBound == windowLowerBound) {
180 			if (scaleDiff > 0)
181 				windowUpperBound *= 10;
182 			else
183 				windowLowerBound /= 10;
184 		}
185 
186 		final double magnificationRatio = 0.25;
187 
188 		int tickWidth = (int) (windowUpperBound * magnificationRatio);
189 		int currentPos = windowSize / tickWidth;
190 		int nextPos = currentPos + scaleDiff;
191 		int nextWindowSize;
192 		if (nextPos <= 0) {
193 			nextWindowSize = windowLowerBound;
194 		}
195 		else
196 			nextWindowSize = tickWidth * nextPos;
197 
198 		zoomWindow(group, nextWindowSize);
199 	}
200 
201 	public final static int MINIMUM_WINDOW_SIZE = 10;
202 
203 	public static void zoomWindow(TrackGroup group, int windowSize) {
204 		TrackWindow currentWindow = group.getTrackWindow();
205 		int start = currentWindow.getStartOnGenome();
206 		int end = currentWindow.getEndOnGenome();
207 
208 		int middle = (start + end) / 2;
209 
210 		if (windowSize <= MINIMUM_WINDOW_SIZE)
211 			windowSize = MINIMUM_WINDOW_SIZE;
212 		if (windowSize >= 1000000000) // 1G
213 			windowSize = 1000000000;
214 
215 		int half = windowSize / 2;
216 		int remaining = windowSize % 2;
217 
218 		if (middle - half < 0)
219 			middle = half;
220 
221 		if (start <= end)
222 			group.setTrackWindowLocation(middle - half + 1, middle + half + remaining);
223 		else
224 			group.setTrackWindowLocation(middle + half + remaining, middle - half + 1);
225 
226 	}
227 
228 	class ScrollButtonSet extends Composite {
229 		HorizontalPanel _panel = new HorizontalPanel();
230 
231 		class WindowScrollButton extends Button implements ClickHandler {
232 			int movePercentageOnWindow;
233 
234 			public WindowScrollButton(String label, int movePercentageOnWindow) {
235 				super(label);
236 				setStyleName("scrollbutton");
237 				addClickHandler(this);
238 				this.movePercentageOnWindow = movePercentageOnWindow;
239 			}
240 
241 			public void onClick(ClickEvent e) {
242 				scroll(getTrackGroup(), movePercentageOnWindow);
243 			}
244 		}
245 
246 		class ZoomButton extends Button implements ClickHandler {
247 			int windowSize;
248 
249 			public ZoomButton(String label, int windowSize) {
250 				super(label);
251 				this.windowSize = windowSize;
252 				setStyleName("scrollbutton");
253 				addClickHandler(this);
254 			}
255 
256 			public void onClick(ClickEvent e) {
257 				zoomWindow(getTrackGroup(), windowSize);
258 			}
259 		}
260 
261 		public ScrollButtonSet() {
262 			_panel.setVerticalAlignment(HorizontalPanel.ALIGN_MIDDLE);
263 			_panel.add(new WindowScrollButton("<<< ", -95));
264 			_panel.add(new WindowScrollButton("<< ", -50));
265 			_panel.add(new WindowScrollButton("< ", -25));
266 			_panel.add(new WindowScrollButton("> ", 25));
267 			_panel.add(new WindowScrollButton(">> ", 50));
268 			_panel.add(new WindowScrollButton(">>> ", 95));
269 			_panel.add(new FormLabel("Window Size:"));
270 			_panel.add(new ZoomButton("100B", 100));
271 			_panel.add(new ZoomButton("1K", 1000));
272 			_panel.add(new ZoomButton("10K", 10000));
273 			_panel.add(new ZoomButton("100K", 100000));
274 			_panel.add(new ZoomButton("1M", 1000000));
275 			_panel.add(new ZoomButton("10M", 10000000));
276 			_panel.add(new ZoomButton("100M", 100000000));
277 			_panel.add(new ZoomButton("1G", 1000000000));
278 			initWidget(_panel);
279 		}
280 	}
281 
282 	private final HorizontalPanel hp = new HorizontalPanel();
283 	private final Track _self = this;
284 	private boolean isPlusStrand = true;
285 
286 	public NavigatorTrack() {
287 		super("NavigatorTrack");
288 		panel.setStyleName("toolbox");
289 		panel.setWidth("100%");
290 
291 		speciesBox.addChangeHandler(new PropertyChangeHandler(UTGBProperty.SPECIES, speciesBox));
292 		revisionBox.addChangeHandler(new PropertyChangeHandler(UTGBProperty.REVISION, revisionBox));
293 		regionBox.addKeyUpHandler(new SequenceRangeChangeListner());
294 		targetBox.addKeyUpHandler(new KeyUpHandler() {
295 			public void onKeyUp(KeyUpEvent e) {
296 				int keyCode = e.getNativeKeyCode();
297 				if (keyCode == KeyCodes.KEY_ENTER || keyCode == KeyCodes.KEY_TAB) {
298 					getTrackGroup().getPropertyWriter().setProperty(UTGBProperty.TARGET, targetBox.getText());
299 				}
300 			}
301 		});
302 		targetBox.setWidth("100px");
303 		// value selectors
304 		hp.setVerticalAlignment(HorizontalPanel.ALIGN_MIDDLE);
305 		hp.add(new FormLabel("Species"));
306 		hp.add(speciesBox);
307 		hp.add(new FormLabel("Ref."));
308 		hp.add(revisionBox);
309 		hp.add(new FormLabel("Chr."));
310 		hp.add(targetBox);
311 		// window locator
312 		regionBox.setWidth("160px");
313 		HorizontalPanel hp2 = new HorizontalPanel();
314 		hp2.setVerticalAlignment(HorizontalPanel.ALIGN_MIDDLE);
315 		hp2.add(new FormLabel("Region"));
316 		hp2.add(regionBox);
317 
318 		Button strandSwitch = new Button("reverse");
319 		Style.margin(strandSwitch, Style.LEFT, 2);
320 		Style.border(strandSwitch, 2, Style.BORDER_OUTSET, "white");
321 		strandSwitch.addClickHandler(new ClickHandler() {
322 			public void onClick(ClickEvent e) {
323 				isPlusStrand = !isPlusStrand;
324 				TrackWindow window = getTrackGroup().getTrackWindow();
325 				if (isPlusStrand) {
326 					getTrackGroup().setTrackWindowLocation(window.getEndOnGenome(), window.getStartOnGenome());
327 				}
328 				else {
329 					getTrackGroup().setTrackWindowLocation(window.getEndOnGenome(), window.getStartOnGenome());
330 				}
331 			}
332 		});
333 		// TODO reverse button
334 		//hp2.add(strandSwitch);
335 		hp2.add(new ScrollButtonSet());
336 		// save view
337 		final FormPanel saveViewForm = new FormPanel();
338 		saveViewForm.setAction(GWT.getModuleBaseURL() + "utgb-core/EchoBackView");
339 		saveViewForm.setEncoding(FormPanel.ENCODING_URLENCODED);
340 		saveViewForm.setMethod(FormPanel.METHOD_POST);
341 		final Hidden viewData = new Hidden("view");
342 		final Hidden time = new Hidden("time");
343 		final Button saveButton = new Button("save view");
344 		HorizontalPanel formLayout = new HorizontalPanel();
345 		formLayout.setVerticalAlignment(HorizontalPanel.ALIGN_MIDDLE);
346 		viewData.setVisible(false);
347 		formLayout.add(viewData);
348 		formLayout.add(time);
349 		formLayout.add(saveButton);
350 		saveButton.addClickHandler(new ClickHandler() {
351 			public void onClick(ClickEvent e) {
352 				XMLWriter xmlWriter = new XMLWriter();
353 				getTrackGroup().toXML(xmlWriter);
354 				String view = xmlWriter.toString();
355 				viewData.setValue(view);
356 				// send the time stamp
357 				Date today = new Date();
358 				time.setValue(Long.toString(today.getTime()));
359 				saveViewForm.submit();
360 			}
361 		});
362 		saveViewForm.add(formLayout);
363 		DOM.setStyleAttribute(saveViewForm.getElement(), "margin", "0");
364 		hp.add(saveViewForm);
365 		Button loadButton = new Button("load view");
366 		loadButton.addClickHandler(new ClickHandler() {
367 			public void onClick(ClickEvent e) {
368 				getTrackGroup().insertTrack(new ViewLoaderTrack(), getTrackGroup().getTrackIndex(_self) + 1);
369 			}
370 		});
371 		hp.add(loadButton);
372 		// layout widgets
373 		panel.add(hp);
374 		panel.add(hp2);
375 
376 	}
377 
378 	public Widget getWidget() {
379 		return panel;
380 	}
381 
382 	private void retrieveSpeciesList() {
383 		speciesBox.clear();
384 		for (Iterator<SequenceInfo> it = sequenceInfoList.iterator(); it.hasNext();) {
385 			SequenceInfo sequenceInfo = it.next();
386 			speciesBox.addItem(sequenceInfo.getSpecies());
387 		}
388 		if (!sequenceInfoList.isEmpty())
389 			updateListBox();
390 	}
391 
392 	private void updateListBox() {
393 		String species = getSelectedSpecies();
394 
395 		ArrayList<String> revisionList = new ArrayList<String>();
396 		for (Iterator<SequenceInfo> it = sequenceInfoList.iterator(); it.hasNext();) {
397 			SequenceInfo sequenceInfo = it.next();
398 			if (sequenceInfo.getSpecies().equals(species)) {
399 				revisionBox.clear();
400 				for (Iterator<String> rit = sequenceInfo.getRevisionList().iterator(); rit.hasNext();) {
401 					String revision = rit.next();
402 					revisionBox.addItem(revision);
403 				}
404 			}
405 		}
406 
407 		boolean canSelectRevision = selectItem(revisionBox, getTrackGroup().getPropertyReader().getProperty(UTGBProperty.REVISION));
408 		if (!canSelectRevision) {
409 			getTrackGroup().getPropertyWriter().setProperty(UTGBProperty.REVISION, revisionBox.getItemText(0));
410 		}
411 
412 	}
413 
414 	private String getSelectedSpecies() {
415 		return speciesBox.getItemText(speciesBox.getSelectedIndex());
416 	}
417 
418 	private boolean selectItem(ListBox listBox, String value) {
419 		for (int i = 0; i < listBox.getItemCount(); i++) {
420 			String itemText = listBox.getItemText(i);
421 			if (itemText.equals(value)) {
422 				listBox.setSelectedIndex(i);
423 				return true;
424 			}
425 		}
426 		return false;
427 	}
428 
429 	public void updateRangeBox() {
430 		regionBox.setText(StringUtil.formatNumber(getTrackWindow().getStartOnGenome()) + "-" + StringUtil.formatNumber(getTrackWindow().getEndOnGenome()));
431 	}
432 
433 	@Override
434 	public void onChangeTrackGroupProperty(TrackGroupPropertyChange change) {
435 		final String[] relatedProperties = new String[] { UTGBProperty.SPECIES, UTGBProperty.REVISION, UTGBProperty.TARGET };
436 		if (change.containsOneOf(relatedProperties)) {
437 			String newSpecies = change.getProperty(UTGBProperty.SPECIES);
438 			if (newSpecies != null && !newSpecies.equals(getSelectedSpecies())) {
439 				selectItem(speciesBox, newSpecies);
440 			}
441 			updateListBox();
442 
443 			if (change.contains(UTGBProperty.TARGET))
444 				targetBox.setText(change.getProperty(UTGBProperty.TARGET));
445 
446 			updateRangeBox();
447 		}
448 	}
449 
450 	@Override
451 	public void onChangeTrackWindow(TrackWindow newWindow) {
452 		updateRangeBox();
453 	}
454 
455 	@Override
456 	public void setUp(TrackFrame trackFrame, TrackGroup group) {
457 		trackFrame.disableClose();
458 		TrackWindow w = group.getTrackWindow();
459 		regionBox.setText(StringUtil.formatNumber(w.getStartOnGenome()) + "-" + StringUtil.formatNumber(w.getEndOnGenome()));
460 		targetBox.setText(group.getPropertyReader().getProperty("target", "chr1"));
461 
462 		retrieveSpeciesList();
463 	}
464 
465 	@Override
466 	public void saveProperties(CanonicalProperties saveData) {
467 
468 		StringBuffer buf = new StringBuffer();
469 		buf.append("[");
470 		int count = 0;
471 		for (Iterator<SequenceInfo> it = sequenceInfoList.iterator(); it.hasNext(); count++) {
472 			if (count > 0)
473 				buf.append(",");
474 			SequenceInfo sequenceInfo = it.next();
475 			buf.append(sequenceInfo.toJSON());
476 		}
477 		buf.append("]");
478 		saveData.add("sequenceList", buf.toString());
479 	}
480 
481 	@Override
482 	public void restoreProperties(CanonicalProperties properties) {
483 
484 		try {
485 			JSONValue v = JSONParser.parse(properties.get("sequenceList", "[]"));
486 			sequenceInfoList.clear();
487 			JSONArray list = v.isArray();
488 			if (list != null) {
489 				for (int i = 0; i < list.size(); i++) {
490 					JSONObject sequenceInfo = list.get(i).isObject();
491 					if (sequenceInfo != null) {
492 						JSONValue speciesValue = sequenceInfo.get("species");
493 						if (speciesValue == null)
494 							continue;
495 
496 						String species = JSONUtil.toStringValue(speciesValue);
497 						SequenceInfo si = new SequenceInfo(species);
498 						JSONValue arrayValue = sequenceInfo.get("revision");
499 						JSONArray revisionArray = arrayValue.isArray();
500 						for (int j = 0; j < revisionArray.size(); j++) {
501 							String revision = JSONUtil.toStringValue(revisionArray.get(j));
502 							si.addRevision(revision);
503 						}
504 						sequenceInfoList.add(si);
505 					}
506 				}
507 			}
508 		}
509 		catch (JSONException e) {
510 			GWT.log("failed to parse json : " + properties.get("sequenceList"), e);
511 		}
512 
513 	}
514 }