Java tutorial
/* * Copyright 2008 Jeff Dwyer * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.apress.progwt.client.gui.timeline; import java.util.ArrayList; import java.util.Date; import java.util.List; import com.allen_sauer.gwt.log.client.Log; import com.apress.progwt.client.college.gui.RemembersPosition; import com.apress.progwt.client.college.gui.ViewPanel; import com.apress.progwt.client.college.gui.ext.DblClickListener; import com.apress.progwt.client.college.gui.timeline.TimelineController; import com.apress.progwt.client.consts.ConstHolder; import com.apress.progwt.client.ext.collections.GWTSortedMap; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.user.client.ui.ClickListener; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; /** * A generic timeline. It is able to be reused for many different sorts of * parameterized TimeLineObj types. * * NOTE: make sure you understand the moveOccurred() and objectHasMoved() * callbacks * * @author Jeff Dwyer * */ public class ZoomableTimeline<T> extends ViewPanel implements ClickListener, DblClickListener { /** * Wrap the two backdrop click listeners together and send events to * registered BackdropListener. * * @author Jeff Dwyer * */ private class BackdropClickConverter implements ClickListener, DblClickListener { public void onClick(Widget sender) { if (getFocusBackdrop().getLastClickEventCtrl()) { fireUserEvent(); } else { setSelected(null, false); } } public void onDblClick(Widget sender) { fireUserEvent(); } private void fireUserEvent() { if (backdropListener != null) { int x = getFocusBackdrop().getLastClickClientX(); int y = getFocusBackdrop().getLastClickClientY(); backdropListener.onBackdropUserEvent(x, y); } } } private BackdropListener backdropListener; private static final int NUM_LABELS = 5; private static final int WINDOW_GUTTER = 7; /** * This needs to correspond to the width of the background images. * It's basically an extra zoom */ private static final int X_SPREAD = 600; private static final int Y_SPREAD = 30; private ZoomLevel currentZoom; private int height; private List<ProteanLabel> labelList = new ArrayList<ProteanLabel>(); private Image magBig; private Image magSmall; private ZoomLevel oldZoom; private TimelineRemembersPosition selectedRP; private GWTSortedMap<TimeLineObj<T>, Object> sorted = new GWTSortedMap<TimeLineObj<T>, Object>(); private Label whenlabel; private int width; private int yEnd; private int[] ySlots; private boolean ySlotsDirty = false; private int ySpread; private int yStart; private SimplePanel editWidget; private TimelineController timelineController; public ZoomableTimeline(int width, int height, TimelineController timelineController) { super(); this.height = height; this.width = width; this.timelineController = timelineController; init(); setStylePrimaryName("ZoomableTL"); setPixelSize(width, height); setDoYTranslate(false); setDoZoom(true); currentZoom = ZoomLevel.Year; currentScale = currentZoom.getScale(); createDecorations(); drawHUD(); setBackground(currentScale); BackdropClickConverter bdClickListener = new BackdropClickConverter(); getFocusBackdrop().addDblClickListener(bdClickListener); getFocusBackdrop().addClickListener(bdClickListener); } public void setBackdropListener(BackdropListener backdropListener) { this.backdropListener = backdropListener; } /** * Append more TLOs to the current display. * * @param timeObjects */ public void add(List<TimeLineObj<T>> timeObjects) { Log.debug("!!!!!Zoom add " + timeObjects.size() + " sorted size " + sorted.size()); for (TimeLineObj<T> timeLineObj : timeObjects) { sorted.put(timeLineObj, null); } super.clear(); initYSlots(false); Log.debug("addObj " + sorted.size()); for (TimeLineObj<T> tlo : sorted.keySet()) { // int top = (int) (Math.random()*(double)height); TimelineRemembersPosition rp = timelineController.getTimeLineObjFactory().getWidget(this, timelineController, tlo); int slot = getBestSlotFor(rp); int top = yStart + (slot * ySpread); rp.setTop(top); if (slot < 0) { rp.getWidget().setVisible(false); } addObject(rp); } for (int i = -NUM_LABELS; i < NUM_LABELS; i++) { ProteanLabel ll = new ProteanLabel(i, yStart - 15); labelList.add(ll); addObject(ll); } setCenterOfView(); updateLabels(); redraw(); } /** * Try to center the display. Look at our TLO's and pick the last one, * then center on that. If none exist, just center on today. */ private void setCenterOfView() { if (!sorted.isEmpty()) { TimeLineObj<T> last = sorted.getKeyList().get(sorted.size() - 1); Log.debug("last " + last); if (last != null) { // Log.debug("move to "+last.getLeft()+" // "+TimeLineObj.getDateForLeft(last.getLeft())); centerOn(last.getLeft(), 0); return; } } // if no objects, center on today centerOn(TimeLineObj.getLeftForDate(new Date()), 0); } @Override public void clear() { super.clear(); sorted.clear(); } private void createDecorations() { whenlabel = new Label(); magBig = ConstHolder.images.magnifyingBig().createImage(); magBig.addClickListener(new ClickListener() { public void onClick(Widget arg0) { zoomIn(); } }); magSmall = ConstHolder.images.magnifyingSmall().createImage(); magSmall.addClickListener(new ClickListener() { public void onClick(Widget arg0) { zoomOut(); } }); editWidget = new SimplePanel(); add(editWidget); add(magSmall); add(whenlabel); add(magBig); } private void drawHUD() { int center = width / 2 - 50; center -= 50;// offset left int y = yEnd + 30; setWidgetPosition(magSmall, center - 40, y - 15); setWidgetPosition(whenlabel, center, y); setWidgetPosition(magBig, center + 70, y - 15); // setWidgetPosition(showCreated, center + 115, y); setWidgetPosition(editWidget, center + 115, y - 20); } /** * Determine how far each element extends in the x-axis * * return -1 if we can't fit * * @param left * @param string * @return */ private int getBestSlotFor(TimelineRemembersPosition rp) { int i = 0; int mywidth = (int) (rp.getWidth() / (double) getXSpread() / currentScale); for (; i < ySlots.length; i++) { int lastLeftForThisSlot = ySlots[i]; // Log.debug("gb "+i+" "+lastLeftForThisSlot+" // "+tlo.getLeft()+" mywid // "+mywidth); if (lastLeftForThisSlot < rp.getLeft()) { ySlots[i] = (int) (rp.getLeft() + mywidth); // Log.debug("Ether.choose "+i); return i; } } // Log.debug("Ether.fail!!!!!!!!"); return -1; } @Override protected int getHeight() { return height; } public Widget getWidget() { return this; } @Override protected int getWidth() { return width; } @Override protected int getXSpread() { return X_SPREAD; } private void init() { yStart = 25; yEnd = height - 60; ySpread = Y_SPREAD; ySlots = new int[(yEnd - yStart) / ySpread]; initYSlots(); } // private String getZoomStr(double scale) { // int index = zoomList.indexOf(new Double(scale)); // return backGroundList.get(index); // } /** * force a re-jiggering of the yslots on the next redraw() * * @param dirty */ private void initYSlots() { initYSlots(true); } private void initYSlots(boolean dirty) { Log.debug("ZoomableTimeline.initYSlots(" + dirty + ")"); ySlotsDirty = dirty; for (int i = 0; i < ySlots.length; i++) { ySlots[i] = Integer.MIN_VALUE; } } @Override protected void moveOccurredCallback() { // Log.debug("ZoomableTimeline.moveOccurredCallback // settingYSlots !dirty"); // 600, otherwise 1 pixel per SCALE length updateLabels(); ySlotsDirty = false; // setWidgetPosition(ll.getWidget(), ll.getLeft(), ll.getTop()); } protected void objectHasMoved(RemembersPosition o, int halfWidth, int halfHeight, int centerX, int centerY) { // ProteanLabels come in here too if (o instanceof TimelineRemembersPosition) { // Log.debug("ZoomableTimelin.objHasMoved " + // ySlotsDirty + " " + o.getLeft() // + " " + o.getTop() + " " + o); // PEND MED necesary if they've been editting a range, but // besides that this is not // necessary o.zoomToScale(currentScale); TimelineRemembersPosition tlw = (TimelineRemembersPosition) o; if (ySlotsDirty) { int slot = getBestSlotFor(tlw); // Log.debug("best top "+slot+" // "+tlw.getTlo().getTopic().getTopicTitle()+" // "+(yStart + (slot * ySpread))); if (slot < 0) { o.getWidget().setVisible(false); } else { tlw.setTop(yStart + (slot * ySpread)); o.getWidget().setVisible(true); } } } } public void onClick(Widget sender) { TimelineRemembersPosition rp = (TimelineRemembersPosition) sender; setSelected(rp, true); } public void onDblClick(Widget sender) { } @Override protected void postZoomCallback(double currentScale) { updateLabels(); } public void resize(int newWidth, int newHeight) { width = newWidth; height = newHeight; init(); setPixelSize(width, height); drawHUD(); redraw(); } @Override protected void setBackground(double scale) { ZoomLevel newZoom = ZoomLevel.getZoomForScale(scale); setBackground(newZoom); } /** * Use the StyleInjection css classes to get the inline css data * images set as our background. * * @param zoomLevel */ protected void setBackground(ZoomLevel zoomLevel) { if (oldZoom != null) { removeStyleDependentName(oldZoom.getCssClass()); } addStyleDependentName(currentZoom.getCssClass()); Log.debug("SetBackground: " + currentZoom.getCssClass() + "::" + getStyleName()); } public Date setDateFromDrag(TimeLineObj tlo, TimelineRemembersPosition rp, int clientX, boolean leftSide, boolean doSave) { // subtract the window left. // PEND low, this is missing the gutter ~10px clientX -= getAbsoluteLeft() + WINDOW_GUTTER; Date rtn = null; if (leftSide) { // Log.debug("LEFT left " + left + " size " + // sizeThisZoom); // left += dx; // Log.debug("ZoomableTimeline start " + // tlo.getHasDate().getStartDate()); rtn = tlo.setStartDateToX(getPositionXFromGUIX(clientX)); // Log.debug("ZoomableTimeline start " + // tlo.getHasDate().getStartDate()); } else { // Log.debug("RIGHT left " + left + " size " + // sizeThisZoom); // Log.debug("ZoomableTimeline end " + // tlo.getHasDate().getEndDate()); rtn = tlo.setEndDateToX(getPositionXFromGUIX(clientX)); // Log.debug("ZoomableTimeline end " + // tlo.getHasDate().getEndDate()); } if (doSave) { timelineController.onTLOChange(tlo); } redraw(rp); return rtn; } private void setSelected(TimelineRemembersPosition rp, boolean selected) { if (selected) { unselect(); selectedRP = rp; selectedRP.addStyleDependentName("Selected"); timelineController.setSelected(rp.getTLO()); } else { unselect(); } } public void showOnly(List<TimeLineObj<T>> timeObjects) { clear(); add(timeObjects); } @Override protected void unselect() { editWidget.setVisible(false); if (selectedRP != null) { selectedRP.removeStyleDependentName("Selected"); } selectedRP = null; } private void updateLabels() { // int index = zoomList.indexOf(new Double(currentScale)); int ii = getCenterX(); Date d2 = TimeLineObj.getDateFromViewPanelX(ii); whenlabel.setText(ZoomLevel.Month.getDfFormat().format(d2)); // Log.debug("ZoomableTimeline.updateLabels curback // "+-getCurbackX()+" "+" "+d2+" // ii "+ii+" "+backGroundList.get(index)); DateTimeFormat format = currentZoom.getDfFormat(); for (ProteanLabel label : labelList) { label.setCenter(d2, currentZoom); } } public void zoom(int upDown) { double oldScale = currentScale; oldZoom = currentZoom; // int index = zoomList.indexOf(new Double(oldScale)); // // // Log.debug("ZoomableTL zoom: index " + index + " next " // // + (index + upDown)); // index += upDown; // // index = index < 0 ? 0 : index; // // // TODO !!!!!!! // // NOTE the 2 this makes us unable to go up to Millenium, which // is // // only there to give us a higherScale // index = index >= zoomList.size() - 1 ? zoomList.size() - 2 // : index; Log.debug("previous zoom: " + currentZoom + " css: " + currentZoom.getCssClass()); ZoomLevel newZoom = null; if (upDown > 0) { newZoom = ZoomLevel.zoomOutOneFrom(currentZoom); } else { newZoom = ZoomLevel.zoomInOneFrom(currentZoom); } if (newZoom != null) { currentZoom = newZoom; } currentScale = currentZoom.getScale(); Log.debug("new zoom: " + currentZoom + " css: " + currentZoom.getCssClass()); initYSlots(); finishZoom(oldScale); } @Override public void zoomIn() { zoom(-1); } @Override public void zoomOut() { zoom(1); } /** * Helper method to get the date from an x-coordinate. * * @param x * @return */ public Date getDateFromGUIX(int x) { return TimeLineObj.getDateFromViewPanelX(getPositionXFromGUIX(x)); } /** * set a new status window widget * * @param widget */ public void showStatus(Widget widget) { editWidget.clear(); editWidget.add(widget); editWidget.setVisible(true); } }