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    }