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 }