001    /**
002     * planningtool - A Planning Tool with Critiquing Support.
003     * 
004     * Copyright (C) 2006 olale
005    
006     * This program is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU General Public License
008     * as published by the Free Software Foundation; either version 2
009     * of the License, or (at your option) any later version.
010    
011     * This program is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014     * GNU General Public License for more details.
015    
016     * You should have received a copy of the GNU General Public License
017     * along with this program; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019    
020     * Contact information: 
021     * E-mail: olale@ida.liu.se
022     *         olale@lysator.liu.se
023     */
024    package se.liu.ida.critiquer.gui;
025    
026    import java.awt.BorderLayout;
027    import java.awt.Dimension;
028    import java.awt.Graphics2D;
029    import java.awt.Paint;
030    import java.awt.geom.Rectangle2D;
031    import java.util.ArrayList;
032    import java.util.Calendar;
033    import java.util.Collection;
034    import java.util.HashMap;
035    import java.util.List;
036    
037    import javax.swing.BorderFactory;
038    import javax.swing.DefaultListModel;
039    import javax.swing.JList;
040    import javax.swing.JPanel;
041    import javax.swing.JScrollPane;
042    import javax.swing.JSlider;
043    import javax.swing.event.ChangeEvent;
044    import javax.swing.event.ChangeListener;
045    import javax.swing.event.ListSelectionEvent;
046    import javax.swing.event.ListSelectionListener;
047    
048    import org.jfree.chart.ChartPanel;
049    import org.jfree.chart.JFreeChart;
050    import org.jfree.chart.axis.CategoryAxis;
051    import org.jfree.chart.axis.DateAxis;
052    import org.jfree.chart.plot.PlotOrientation;
053    import org.jfree.data.gantt.Task;
054    import org.jfree.data.gantt.TaskSeries;
055    import org.jfree.data.gantt.TaskSeriesCollection;
056    import org.jfree.data.general.DatasetChangeEvent;
057    import org.jfree.data.general.DatasetChangeListener;
058    
059    import se.liu.ida.critiquer.activities.Activity;
060    import se.liu.ida.critiquer.activities.ActivityUpdateListener;
061    import se.liu.ida.critiquer.activities.ActivityUtils;
062    import se.liu.ida.critiquer.activities.parameters.Parameter;
063    import se.liu.ida.critiquer.constraints.VisualConstraint;
064    import se.liu.ida.critiquer.constraints.VisualConstraints;
065    import se.liu.ida.critiquer.mics.ReferenceHolder;
066    import se.liu.ida.critiquer.resources.Agent;
067    import se.liu.ida.critiquer.simulation.SimulationEngine;
068    
069    /**
070     * <p>
071     * This class presents the time view. It is a bit special since the constraints
072     * are notified by the TimeLinePlot and not the view itself. That's because we
073     * want to interact with the timeline and therefore it's easier if the input
074     * coordinate system is the same as the one used by the activity areas. The
075     * constraints are therefore only given the graphics context of the chart area
076     * when they display their information.
077     * </p>
078     * 
079     * <p>Interaction in this view goes as follows:
080     * </p> 
081     * <ul>
082     * 
083     * <li>The user drags activities back and forth to change them. </li>
084     * 
085     * <li>If the user begins a drag operation close to either end of the activity,
086     * it is extended in time, otherwise moved. </li>
087     * 
088     * </ul>
089     */
090    
091    public class TimeView extends JPanel implements
092                                                                            View,
093                                                                            DatasetChangeListener,
094                                                                            TimeLineRenderingListener,
095                                                                            ActivityUpdateListener {
096    
097            /**
098         * 
099         */
100            private static final long                         serialVersionUID = 1L;
101    
102            private ChartPanel                                       chartPane;
103    
104            private TaskSeriesCollection               tasks;
105    
106            private JList                                             activityList;
107    
108            private DefaultListModel                           listModel;
109    
110            private TimeLinePlot                               plot;
111    
112            private HashMap<Comparable, Paint>         colorMap                = new HashMap<Comparable, Paint>();
113    
114            private JSlider                                         slider=new JSlider();
115    
116            private JPanel                                           southPanel;
117    
118            private JPanel                                           timePanel;
119    
120            private JScrollPane                                     listPane;
121    
122            private SimulationEngine                           simulationEngine;
123    
124            protected ConstraintComponent  critiqueComponent = new ConstraintComponent();
125    
126            private HashMap<Activity, Rectangle2D> activityAreas      = new HashMap<Activity, Rectangle2D>();
127    
128            public JPanel getConstraintPanel() {
129                    return critiqueComponent.getPanel();
130            }
131    
132            public TimeView() {
133                    ReferenceHolder.timeView = this;
134                    setLayout(new BorderLayout());
135                    critiqueComponent.getPanel().setBorder(BorderFactory.createEtchedBorder());
136    
137                    initActivityList();
138                    southPanel = new JPanel(new BorderLayout());
139                    southPanel.add(listPane, BorderLayout.CENTER);
140                    southPanel.add(critiqueComponent.getPanel(), BorderLayout.SOUTH);
141                    add(southPanel, BorderLayout.SOUTH);
142                    initTimePanel();
143                    add(timePanel, BorderLayout.CENTER);
144                    simulationEngine = new SimulationEngine();
145            }
146    
147            private void initTimePanel() {
148    
149                    slider = new JSlider();
150                    /**
151             * Disable the slider until there are activities to show
152             */
153                    slider.setEnabled(false);
154                    slider.addChangeListener(new ChangeListener() {
155                            
156                            /**
157                 * When the slider is updated, run the simulation engine based on
158                 * the time difference and update the graphical information
159                 */
160                            public void stateChanged(ChangeEvent e) {
161                                    if (slider.isEnabled()) {
162                                            
163                                            Double value = new Double(slider.getValue());
164                                            setCurrentTime(value);
165                                            simulationEngine.stepTo(getCurrentTime());
166                                            repaint();
167                                            
168                                    }
169                            }
170    
171                    });
172                    initColorMap();
173                    final JFreeChart timeChart = createTimeChart();
174                    chartPane = new ChartPanel(timeChart);
175                    chartPane.setMouseZoomable(false);
176                    chartPane.addMouseMotionListener(new ActivityDraggedAdapter(this, timeChart));
177    
178                    timePanel = new JPanel(new BorderLayout());
179                    timePanel.add(chartPane, BorderLayout.CENTER);
180                    timePanel.add(slider, BorderLayout.NORTH);
181    
182            }
183    
184            public void updateView(Graphics2D g2) {
185                    for (VisualConstraint constraint : VisualConstraints.getActiveConstraints()) {
186                            if (constraint.isApplicableFor(this)) {
187                                    constraint.viewUpdated(this, g2);
188                            }
189                    }
190            }
191    
192            private void initActivityList() {
193                    listModel = new DefaultListModel();
194    
195                    activityList = new JList(listModel);
196                    Activity.addActivityUpdateListener(this);
197                    activityList.addListSelectionListener(new ListSelectionListener() {
198    
199                            public void valueChanged(ListSelectionEvent e) {
200                                    initSimulation();
201                                    updateTimeChart();
202                            }
203    
204                    });
205                    listPane = new JScrollPane(activityList);
206                    listPane.setPreferredSize(new Dimension(200, 80));
207    
208            }
209    
210            private JFreeChart createTimeChart() {
211                    tasks = new TaskSeriesCollection();
212                    tasks.addChangeListener(this);
213                    CategoryAxis categoryAxis = new CategoryAxis("Tasks");
214                    DateAxis dateAxis = new DateAxis("Time");
215                    TimeLineRenderer renderer = new TimeLineRenderer(tasks, colorMap);
216                    renderer.addListener(this);
217                    plot = new TimeLinePlot(tasks, categoryAxis, dateAxis, renderer);
218    
219                    /**
220             * This was a failed attempt at making the range axis have a fixed range
221             * 
222             * GregorianCalendar now = new GregorianCalendar(); GregorianCalendar
223             * then = new GregorianCalendar(); then.add(Calendar.DAY_OF_YEAR,1);
224             * dateAxis.setRangeWithMargins(new
225             * Range(now.getTimeInMillis(),then.getTimeInMillis()), false, false);
226             * dateAxis.setAutoRange(false);
227             */
228                    plot.setView(this);
229                    plot.setOrientation(PlotOrientation.HORIZONTAL);
230                    plot.getDataset();
231                    return new JFreeChart("Mission tasks", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
232            }
233    
234            /**
235         * Create a new tasks series from the activities in the list model. Make
236         * sure there is only one task series for each resource.
237         */
238    
239            @SuppressWarnings("unchecked")
240            private void updateTimeChart() {                
241                    
242                    tasks.removeAll();
243                    HashMap<String, TaskSeries> resources = new HashMap<String, TaskSeries>();
244                    Object[] selectedActivities = activityList.getSelectedValues();
245                    for (int i = 0; i < selectedActivities.length; i++) {
246                            Activity activity = (Activity) selectedActivities[i];
247                            ArrayList<TaskSeries> c = activity.createTaskSeries();
248                            for (TaskSeries s : c) {
249                                    if (resources.keySet().contains(s.getKey())) {
250                                            TaskSeries seriesInCollection = resources.get(s.getKey());
251                                            List<Task> taskList = s.getTasks();
252                                            for (Task task : taskList) {
253                                                    seriesInCollection.add(task);
254                                            }
255                                    } else {
256                                            resources.put((String) s.getKey(), s);
257                                    }
258                            }
259                    }
260                    Collection<TaskSeries> taskCollection = resources.values();
261                    for (TaskSeries series : taskCollection) {
262                            tasks.add(series);
263                    }
264            }
265    
266            public void initSimulation() {
267                    updateSlider();
268                    simulationEngine.init(getCurrentTime());
269            }
270    
271            private void initColorMap() {
272                    colorMap.put(Agent.dummyAgent.toString(), Agent.dummyAgent.getTimeLineColor());
273                    for (Agent agent : ReferenceHolder.organizationModel.getAllAgents()) {
274                            colorMap.put(agent.toString(), agent.getTimeLineColor());
275                    }
276            }
277    
278            /**
279         * <p>
280         * Update the slider whenever the chart should be updated. This should only
281         * have to be done once, but since we don't have the data area available
282         * until the graph has been drawn at least once, we do it every time the
283         * chart is updated.
284         * </p>
285         * <p>
286         * In order for the slider to work correctly, it should have the bounding
287         * rectangle of the data area in the time chart as its range since the plot
288         * is able to retrieve date values from <code>double</code> values within
289         * the range of the data area.
290         * </p>
291         */
292            private void updateSlider() {
293                    slider.setMinimum((int) getTimeChartArea().getX());
294                    slider.setMaximum((int) getTimeChartArea().getMaxX());
295                    slider.setValue((int) getTimeChartArea().getX());
296                    setCurrentTime(getTimeChartArea().getX());
297                    slider.setEnabled(true);
298            }
299    
300            /**
301         * @param a
302         *            The activity to set.
303         */
304            public void activityUpdated(Activity a) {
305                    if (a != null) {
306                            listModel.removeAllElements();
307                            for (Activity activity : ReferenceHolder.allActivities) {
308                                    listModel.addElement(activity);
309                            }
310                            // Is this really necessary?
311                            updateTimeChart();
312                    }
313            }
314    
315            /**
316         * One may select only a set of activities in the Time View for which time
317         * charts should be drawn.
318         */
319            public ArrayList<Activity> getEvaluationActivities() {
320                    Object[] selectedObjects = activityList.getSelectedValues();
321                    ArrayList<Activity> selectedActivities = new ArrayList<Activity>();
322                    for (Object object : selectedObjects) {
323                            if (object instanceof Activity) {
324                                    Activity a = (Activity) object;
325                                    selectedActivities.add(a);
326                            }
327                    }
328                    return selectedActivities;
329            }
330    
331            public void datasetChanged(DatasetChangeEvent event) {
332                    activityAreas.clear();
333            }
334    
335            public void activityCreated(Activity activity) {
336                    if (!listModel.contains(activity)) {
337                            listModel.addElement(activity);
338                    }
339            }
340    
341            public <T> void paramAdded(Activity activity, Parameter<T> p) {
342                    /**
343             * Nothing to do here..
344             */
345            }
346    
347            /**
348         * Recalculate the time chart based on when parameters change. This need
349         * only be done when parameters affecting the time of the mission are
350         * changed, but we are not so fussy about that.
351         */
352    
353            public <T> void paramChanged(Activity activity, Parameter<T> p) {
354                            
355                    if (activity.getPendingUpdates() == 0 && getEvaluationActivities().contains(activity)) {
356                            initSimulation();
357                            updateTimeChart();
358                    }
359    
360            }
361    
362            public void activityRemoved(Activity a) {
363                    listModel.removeElement(a);
364            }
365    
366            /**
367         * 
368         * Retrieves the current time as set by the slider
369         * 
370         * @see se.liu.ida.critiquer.gui.TimeLinePlot#getCurrentTime()
371         */
372            public Calendar getCurrentTime() {
373                    return plot.getCurrentTime();
374            }
375    
376            /**
377         * Adjust the time according to the parameter, which is converted into a
378         * date by the plot
379         * 
380         * @see se.liu.ida.critiquer.gui.TimeLinePlot#setCurrentTime(java.lang.Double)
381         */
382            public void setCurrentTime(Double currentTime) {
383                    plot.setCurrentTime(currentTime);
384            }
385    
386            /*
387         * (non-Javadoc)
388         * 
389         * @see se.liu.ida.critiquer.gui.TimeLinePlot#getDataArea()
390         */
391            public Rectangle2D getTimeChartArea() {
392                    return plot.getDataArea();
393            }
394    
395            /*
396         * (non-Javadoc)
397         * 
398         * @see se.liu.ida.critiquer.gui.TimeLinePlot#locationToDate(double)
399         */
400            public Calendar locationToDate(double x) {
401                    return plot.locationToDate(x);
402            }
403    
404            /**
405         * @return Returns the chartPane.
406         */
407            public ChartPanel getChartPane() {
408                    return chartPane;
409            }
410    
411            /**
412         * @return Returns the timePanel.
413         */
414            public JPanel getTimePanel() {
415                    return timePanel;
416            }
417    
418            /*
419         * (non-Javadoc)
420         * 
421         * @see se.liu.ida.critiquer.gui.TimeLineRenderer#getActivityAreas()
422         */
423            public HashMap<Activity, Rectangle2D> getActivityAreas() {
424                    return activityAreas;
425            }
426    
427            public void updateActivityArea(final Comparable key, Rectangle2D bar) {
428                    Activity activity = ActivityUtils.getActivityByName(key);
429                    if (!activityAreas.containsKey(activity)) {
430                            activityAreas.put(activity, bar);
431                    } else {
432                            activityAreas.get(activity).add(bar);
433                    }
434    
435            }
436    
437            public Rectangle2D getActivityArea(Activity activity) {
438                    return activityAreas.get(activity);
439            }
440    
441    }