001 package se.liu.ida.critiquer.gui; 002 003 import java.awt.Cursor; 004 import java.awt.Point; 005 import java.awt.event.MouseEvent; 006 import java.awt.event.MouseMotionAdapter; 007 import java.awt.geom.Point2D; 008 import java.awt.geom.Rectangle2D; 009 import java.util.Calendar; 010 011 import org.jfree.chart.JFreeChart; 012 import org.jfree.chart.entity.CategoryItemEntity; 013 import org.jfree.chart.entity.ChartEntity; 014 015 import se.liu.ida.critiquer.activities.Activity; 016 import se.liu.ida.critiquer.activities.ActivityUtils; 017 import se.liu.ida.critiquer.activities.parameters.TimeParameter; 018 019 /** 020 * 021 * Move the activities in the time line by dragging them. If you start the drag 022 * close to either end of the activity (where <i>close</i> is defined as the 023 * 15% of the activity rectangle closest to the ends), the duration is changed 024 * instead. If you start your drag operation close to the beginning of the 025 * activity, it will change the start time. If you begin the drag operation 026 * close to the end, it will change the end time. Drag anywhere else and both 027 * start and end are affected equally. There is currently some jerkiness, 028 * probably due to constraints that fire when activities are moved. 029 * 030 * @author olale 031 * 032 */ 033 final class ActivityDraggedAdapter extends MouseMotionAdapter { 034 /** 035 * 036 */ 037 private final TimeView view; 038 039 private final JFreeChart chart; 040 041 /** 042 * t0 is the last time selected in milliseconds by the current drag 043 * operation. 044 */ 045 046 private long t0 = 0; 047 048 /** 049 * t1 is the current time in milliseconds selected by the current drag 050 * operation. 051 */ 052 private long t1 = 0; 053 054 private Activity draggedActivity = null; 055 056 /** 057 * How close to the ends of the activity should we begin extending the 058 * activity instead of móving it? 059 */ 060 private double sensitivity = .15; 061 062 private Rectangle2D activityRectangle; 063 064 private boolean moveStart; 065 066 private boolean moveEnd; 067 068 ActivityDraggedAdapter(TimeView view, JFreeChart chart) { 069 super(); 070 this.view = view; 071 this.chart = chart; 072 } 073 074 /** 075 * 076 * Change the cursor so the user knows what a drag operation will do in the 077 * current context 078 * 079 * @see java.awt.event.MouseMotionAdapter#mouseMoved(java.awt.event.MouseEvent) 080 */ 081 @Override 082 public void mouseMoved(MouseEvent e) { 083 if (shouldDrag(e.getPoint())) { 084 if (moveStart && !moveEnd) { 085 view.getChartPane().setCursor(new Cursor(Cursor.W_RESIZE_CURSOR)); 086 } else if (!moveStart && moveEnd) { 087 view.getChartPane().setCursor(new Cursor(Cursor.E_RESIZE_CURSOR)); 088 } else if (moveStart && moveEnd) { 089 view.getChartPane().setCursor(new Cursor(Cursor.HAND_CURSOR)); 090 } 091 } else { 092 view.getChartPane().setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); 093 } 094 if (t0 != 0 || t1 != 0) { 095 t0 = t1 = 0; 096 draggedActivity = null; 097 } 098 099 } 100 101 private boolean closeToStart(Rectangle2D rect, Point point) { 102 return Math.abs(point.x - rect.getX()) / rect.getWidth() < sensitivity; 103 } 104 105 private boolean closeToEnd(Rectangle2D rect, Point point) { 106 return Math.abs(point.x - rect.getMaxX()) / rect.getWidth() < sensitivity; 107 } 108 109 /** 110 * Change start and/or the end time of an activity as part of the drag 111 * operation 112 * 113 * @see java.awt.event.MouseMotionAdapter#mouseDragged(java.awt.event.MouseEvent) 114 */ 115 @Override 116 public void mouseDragged(MouseEvent e) { 117 /** 118 * We must be listening to events from the ChartPanel.. 119 */ 120 assert (e.getSource() == view.getChartPane()); 121 TimeLinePlot plot = (TimeLinePlot) chart.getPlot(); 122 Point mousePosition = e.getPoint(); 123 /** 124 * Translate the mouse position so that it has the same coordinate 125 * system as the activity areas 126 * 127 */ 128 129 boolean shouldDrag = false; 130 if (draggedActivity == null) { 131 shouldDrag = shouldDrag(mousePosition); 132 } else { 133 shouldDrag = true; 134 } 135 if (shouldDrag) { 136 TimeParameter startParam = ActivityUtils.getStartTimeParameter(draggedActivity); 137 TimeParameter endParam = ActivityUtils.getEndTimeParameter(draggedActivity); 138 long diff = 0; 139 /** 140 * First time called with this activity, so initialize both t0 and 141 * t1. 142 */ 143 if (t1 == t0 && t0 == 0) { 144 t0 = t1 = plot.locationToDate(e.getX()).getTimeInMillis(); 145 } else { 146 t1 = plot.locationToDate(e.getX()).getTimeInMillis(); 147 } 148 diff = t1 - t0; 149 if (moveStart && startParam.isEditable()) { 150 moveTimeParam(startParam, diff); 151 } 152 if (moveEnd && endParam.isEditable()) { 153 moveTimeParam(endParam, diff); 154 } 155 /** 156 * Compensate for moving the activity 157 */ 158 t0 = t1 + diff; 159 160 } 161 162 } 163 164 /** 165 * Initialize the drag operation. This entails two things: 166 * 167 * <ul> 168 * 169 * <li>selecting the activity which will be dragged, if any </li> 170 * 171 * <li>determining if the activity should be moved or extended in time. 172 * </li> 173 * 174 * </ul> 175 * 176 * @param mousePosition 177 * 178 * @return true if the mousePosition is within an activity area, false 179 * otherwise. 180 */ 181 private boolean shouldDrag(Point mousePosition) { 182 ChartEntity entity = view.getChartPane().getEntityForPoint(mousePosition.x, mousePosition.y); 183 boolean withinActivityArea = false; 184 if (entity != null && entity instanceof CategoryItemEntity) { 185 withinActivityArea = true; 186 CategoryItemEntity timeBar = (CategoryItemEntity) entity; 187 draggedActivity = ActivityUtils.getActivityByName((Comparable) timeBar.getCategory()); 188 activityRectangle = ((Rectangle2D) timeBar.getArea()); 189 Point2D truePos = view.getChartPane().translateScreenToJava2D(mousePosition); 190 mousePosition.x = (int) truePos.getX(); 191 mousePosition.y = (int) truePos.getY(); 192 moveStart = !closeToEnd(activityRectangle, mousePosition); 193 moveEnd = !closeToStart(activityRectangle, mousePosition); 194 } 195 return withinActivityArea; 196 } 197 198 private void moveTimeParam(TimeParameter timeParam, long diff) { 199 Calendar startTime = null; 200 startTime = timeParam.getValue(); 201 startTime.add(Calendar.MILLISECOND, (int) diff); 202 timeParam.setValue(startTime); 203 } 204 }