1 package de.jaret.examples.timebars.scheduling.swing;
2
3 import java.awt.BorderLayout;
4 import java.awt.Component;
5 import java.awt.Dimension;
6 import java.awt.FlowLayout;
7 import java.awt.datatransfer.StringSelection;
8 import java.awt.dnd.DnDConstants;
9 import java.awt.dnd.DragGestureEvent;
10 import java.awt.dnd.DragGestureListener;
11 import java.awt.dnd.DragGestureRecognizer;
12 import java.awt.dnd.DragSource;
13 import java.awt.dnd.DragSourceDragEvent;
14 import java.awt.dnd.DragSourceDropEvent;
15 import java.awt.dnd.DragSourceEvent;
16 import java.awt.dnd.DragSourceListener;
17 import java.awt.dnd.DropTarget;
18 import java.awt.dnd.DropTargetDragEvent;
19 import java.awt.dnd.DropTargetDropEvent;
20 import java.awt.dnd.DropTargetEvent;
21 import java.awt.dnd.DropTargetListener;
22 import java.awt.event.ActionEvent;
23 import java.awt.event.ActionListener;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.TooManyListenersException;
27
28 import javax.swing.AbstractAction;
29 import javax.swing.Action;
30 import javax.swing.JButton;
31 import javax.swing.JPanel;
32 import javax.swing.JPopupMenu;
33 import javax.swing.JScrollPane;
34 import javax.swing.JSlider;
35 import javax.swing.JSplitPane;
36 import javax.swing.JTable;
37 import javax.swing.event.ChangeEvent;
38 import javax.swing.event.ChangeListener;
39
40 import de.jaret.examples.timebars.scheduling.model.Job;
41 import de.jaret.examples.timebars.scheduling.model.ScheduleTimeBarModel;
42 import de.jaret.examples.timebars.scheduling.swing.renderer.JobRenderer;
43 import de.jaret.examples.timebars.scheduling.swing.renderer.ScheduleingTitleRenderer;
44 import de.jaret.util.date.Interval;
45 import de.jaret.util.date.JaretDate;
46 import de.jaret.util.misc.Pair;
47 import de.jaret.util.ui.timebars.TimeBarViewerInterface;
48 import de.jaret.util.ui.timebars.mod.DefaultIntervalModificator;
49 import de.jaret.util.ui.timebars.model.DefaultTimeBarRowModel;
50 import de.jaret.util.ui.timebars.model.TimeBarRow;
51 import de.jaret.util.ui.timebars.model.TimeBarSelectionListener;
52 import de.jaret.util.ui.timebars.model.TimeBarSelectionModel;
53 import de.jaret.util.ui.timebars.swing.TimeBarViewer;
54 import de.jaret.util.ui.timebars.swing.renderer.BoxTimeScaleRenderer;
55 import de.jaret.util.ui.timebars.swing.renderer.ITitleRenderer;
56
57 public class SchedulingPanel extends JPanel {
58
59 JTable _jobTable;
60 JobTableModel _jobTableModel;
61
62 TimeBarViewer _tbv;
63 ScheduleTimeBarModel _model;
64
65 protected List<Job> _draggedJobs;
66 protected List<Integer> _draggedJobsOffsets;
67
68 protected JaretDate _tbvDragOrigBegin;
69 protected JaretDate _tbvDragOrigEnd;
70 protected DefaultTimeBarRowModel _tbvDragOrigRow;
71
72 public SchedulingPanel() {
73 setLayout(new BorderLayout());
74
75 JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
76 splitPane.setResizeWeight(0.85);
77 add(splitPane, BorderLayout.CENTER);
78
79
80 _model = createTBVModel();
81 _tbv = new TimeBarViewer();
82 _tbv.setModel(_model);
83 splitPane.setTopComponent(_tbv);
84
85
86 _tbv.setDrawRowGrid(true);
87 _tbv.setYAxisWidth(150);
88 _tbv.setTimeScaleRenderer(new BoxTimeScaleRenderer());
89 _tbv.setTimeScalePosition(TimeBarViewerInterface.TIMESCALE_POSITION_TOP);
90 _tbv.setInitialDisplayRange(new JaretDate(), 24 * 60 * 60);
91
92
93 _tbv.addIntervalModificator(new PreventOverlapIntervalModificator());
94
95
96 _tbv.registerTimeBarRenderer(Job.class, new JobRenderer());
97
98 setUpDND(_tbv);
99
100 createActions(_tbv);
101
102
103 _jobTableModel = createJobTableModel();
104 _jobTable = new JTable(_jobTableModel);
105 JScrollPane scroll = new JScrollPane(_jobTable);
106 splitPane.setBottomComponent(scroll);
107
108
109 DragSource dragSource = DragSource.getDefaultDragSource();
110 DragGestureListener dgl = new JobTableDragGestureListener();
111 DragGestureRecognizer dgr = dragSource.createDefaultDragGestureRecognizer(_jobTable, DnDConstants.ACTION_MOVE,
112 dgl);
113
114
115 JPanel controlPanel = createControlPanel(24 * 60 * 60);
116 add(controlPanel, BorderLayout.SOUTH);
117
118
119
120
121 ITitleRenderer trenderer = new ScheduleingTitleRenderer();
122 _tbv.setTitleRenderer(trenderer);
123 _tbv.setUseTitleRendererComponentInPlace(true);
124
125 }
126
127 /***
128 * Setup some actions on the timebar viewer.
129 *
130 * @param tbv
131 */
132 private void createActions(TimeBarViewer tbv) {
133
134 Action action = new AbstractAction("Clear work center schedule") {
135 public void actionPerformed(ActionEvent e) {
136 System.out.println("run " + getValue(NAME));
137 TimeBarRow row = _tbv.getPopUpInformation().getLeft();
138 clearRow(row);
139 }
140
141 };
142 JPopupMenu pop = new JPopupMenu("Operations");
143 pop.add(action);
144 _tbv.setHeaderContextMenu(pop);
145
146
147 action = new AbstractAction("Unschedule") {
148 public void actionPerformed(ActionEvent e) {
149 System.out.println("run " + getValue(NAME));
150 unscheduleSelected();
151 }
152 };
153 pop = new JPopupMenu("Operations");
154 pop.add(action);
155 action = new AbstractAction("Push back") {
156 public void actionPerformed(ActionEvent e) {
157 System.out.println("run " + getValue(NAME));
158 Pair<TimeBarRow, JaretDate> info = _tbv.getPopUpInformation();
159 List<Interval> intervals = info.getLeft().getIntervals(info.getRight());
160 if (intervals.size() == 1) {
161 System.out.println("pushback on "+intervals.get(0));
162 }
163 }
164
165 };
166 pop.add(action);
167 _tbv.registerPopupMenu(Job.class, pop);
168 }
169
170 private void unscheduleSelected() {
171 List<Interval> intervals = new ArrayList<Interval>(_tbv.getSelectionModel().getSelectedIntervals());
172 for (Interval interval : intervals) {
173 TimeBarRow row = _model.getRowForInterval(interval);
174 ((DefaultTimeBarRowModel)row).remInterval(interval);
175 _jobTableModel.addJob((Job)interval);
176 }
177 }
178 private void clearRow(TimeBarRow row) {
179 List<Interval> intervals = new ArrayList<Interval>(row.getIntervals());
180 for (Interval interval : intervals) {
181 ((DefaultTimeBarRowModel)row).remInterval(interval);
182 _jobTableModel.addJob((Job)interval);
183 }
184 }
185
186
187 /***
188 * Setup the droptarget and the drag source on the timebar viewer.
189 *
190 * @param tbv
191 */
192 private void setUpDND(final TimeBarViewer tbv) {
193
194
195
196 DropTarget dropTarget = new DropTarget();
197 tbv.setDropTarget(dropTarget);
198
199 try {
200 dropTarget.addDropTargetListener(new DropTargetListener() {
201
202 public void dropActionChanged(DropTargetDragEvent evt) {
203 }
204
205 public void drop(DropTargetDropEvent evt) {
206 if (_draggedJobs != null) {
207 TimeBarRow overRow = tbv.getRowForXY(evt.getLocation().x, evt.getLocation().y);
208 if (overRow != null) {
209 for (Job job : _draggedJobs) {
210 ((DefaultTimeBarRowModel) overRow).addInterval(job);
211 _jobTableModel.removeJob(job);
212 }
213 tbv.setGhostIntervals(null, null);
214 evt.dropComplete(true);
215 evt.getDropTargetContext().dropComplete(true);
216
217 _tbvDragOrigRow = null;
218 }
219 tbv.deHighlightRow();
220 }
221
222 }
223
224 public void dragOver(DropTargetDragEvent evt) {
225 TimeBarRow overRow = tbv.getRowForXY(evt.getLocation().x, evt.getLocation().y);
226 if (overRow != null) {
227 tbv.highlightRow(overRow);
228
229 JaretDate curDate = tbv.dateForXY(evt.getLocation().x, evt.getLocation().y);
230 correctAndScheduleJobs(_draggedJobs, curDate);
231
232
233 tbv.setGhostIntervals(_draggedJobs, _draggedJobsOffsets);
234 tbv.setGhostOrigin(evt.getLocation().x, evt.getLocation().y);
235 if (dropAllowed(_draggedJobs, overRow)) {
236 evt.acceptDrag(DnDConstants.ACTION_MOVE);
237 } else {
238 evt.rejectDrag();
239 tbv.setGhostIntervals(null, null);
240 }
241 } else {
242 tbv.deHighlightRow();
243 }
244 }
245
246 public void dragExit(DropTargetEvent evt) {
247 tbv.deHighlightRow();
248 }
249
250 public void dragEnter(DropTargetDragEvent evt) {
251 }
252 });
253 } catch (TooManyListenersException e) {
254
255 e.printStackTrace();
256 }
257
258
259
260
261 DragSource dragSource = DragSource.getDefaultDragSource();
262 DragGestureListener dgl = new TimeBarViewerDragGestureListener();
263 DragGestureRecognizer dgr = dragSource.createDefaultDragGestureRecognizer(_tbv._diagram,
264 DnDConstants.ACTION_MOVE, dgl);
265
266 dragSource.addDragSourceListener(new DragSourceListener() {
267
268 public void dropActionChanged(DragSourceDragEvent dsde) {
269
270
271 }
272
273 public void dragOver(DragSourceDragEvent dsde) {
274
275
276 }
277
278 public void dragExit(DragSourceEvent dse) {
279
280
281 }
282
283 public void dragEnter(DragSourceDragEvent dsde) {
284
285
286 }
287
288 public void dragDropEnd(DragSourceDropEvent dsde) {
289 if (!dsde.getDropSuccess() && _tbvDragOrigRow != null) {
290
291 Job job = _draggedJobs.get(0);
292 job.setBegin(_tbvDragOrigBegin);
293 job.setEnd(_tbvDragOrigEnd);
294 _tbvDragOrigRow.addInterval(job);
295 _tbvDragOrigRow = null;
296 }
297 _tbv.setGhostIntervals(null, null);
298 }
299 });
300
301
302 }
303
304 class TimeBarViewerDragGestureListener implements DragGestureListener {
305 public void dragGestureRecognized(DragGestureEvent e) {
306 Component c = e.getComponent();
307 System.out.println("component " + c);
308 System.out.println(e.getDragOrigin());
309
310 boolean markerDragging = _tbv.getDelegate().isMarkerDraggingInProgress();
311 if (markerDragging) {
312 return;
313 }
314
315 List<Interval> intervals = _tbv.getDelegate().getIntervalsAt(e.getDragOrigin().x, e.getDragOrigin().y);
316 if (intervals.size() > 0 && e.getTriggerEvent().isAltDown()) {
317 Interval interval = intervals.get(0);
318 e.startDrag(null, new StringSelection("Drag " + ((Job) interval).getName()));
319 _draggedJobs = new ArrayList<Job>();
320 _draggedJobs.add((Job)interval);
321 _draggedJobsOffsets = new ArrayList<Integer>();
322 _draggedJobsOffsets.add(0);
323 TimeBarRow row = _model.getRowForInterval(interval);
324 ((DefaultTimeBarRowModel)row).remInterval(interval);
325
326
327 _tbvDragOrigRow = (DefaultTimeBarRowModel)row;
328 _tbvDragOrigBegin = interval.getBegin().copy();
329 _tbvDragOrigEnd = interval.getEnd().copy();
330
331 return;
332 }
333
334
335
336
337
338
339
340
341 }
342 }
343
344
345
346 /***
347 * Correct the dates of the dragged jobs and do a simple forward scheduling.
348 *
349 * @param draggedJobs
350 * @param curDate
351 */
352 private void correctAndScheduleJobs(List<Job> draggedJobs, JaretDate curDate) {
353 for (int i = 0; i < draggedJobs.size(); i++) {
354 Interval interval = draggedJobs.get(i);
355 int secs = interval.getSeconds();
356 interval.setBegin(curDate.copy());
357 interval.setEnd(curDate.copy().advanceSeconds(secs));
358 curDate = interval.getEnd().copy();
359 }
360 }
361
362 /***
363 * Check that none of the dragged jobs overlaps with another interval in the row. Brute force approach.
364 *
365 * @param draggedJobs
366 * @param row
367 * @return
368 */
369 private boolean dropAllowed(List<Job> draggedJobs, TimeBarRow row) {
370 for (Job job : draggedJobs) {
371 for (Interval interval : row.getIntervals()) {
372 if (job.intersects(interval)) {
373 return false;
374 }
375 }
376 }
377
378 return true;
379 }
380
381 class JobTableDragGestureListener implements DragGestureListener {
382 public void dragGestureRecognized(DragGestureEvent e) {
383 Component c = e.getComponent();
384 System.out.println("component " + c);
385 System.out.println(e.getDragOrigin());
386
387 _draggedJobs = new ArrayList<Job>();
388 _draggedJobsOffsets = new ArrayList<Integer>();
389 int[] indizes = _jobTable.getSelectedRows();
390 if (indizes.length > 0) {
391 for (int i : indizes) {
392 _draggedJobs.add(_jobTableModel.getJob(i));
393 _draggedJobsOffsets.add(0);
394 }
395 e.startDrag(null, new StringSelection("Drag " + indizes.length + " intervals"));
396 }
397 }
398 }
399
400 private JPanel createControlPanel(int initialSeconds) {
401 JPanel panel = new JPanel();
402
403 panel.setLayout(new FlowLayout());
404
405
406 final JButton unscheduleButton = new JButton("Unschedule");
407 unscheduleButton.setEnabled(false);
408 _tbv.getSelectionModel().addTimeBarSelectionListener(new TimeBarSelectionListener() {
409
410 public void selectionChanged(TimeBarSelectionModel selectionModel) {
411 unscheduleButton.setEnabled(selectionModel.hasIntervalSelection());
412 }
413
414 public void elementRemovedFromSelection(TimeBarSelectionModel selectionModel, Object element) {
415 unscheduleButton.setEnabled(selectionModel.hasIntervalSelection());
416 }
417
418 public void elementAddedToSelection(TimeBarSelectionModel selectionModel, Object element) {
419 unscheduleButton.setEnabled(selectionModel.hasIntervalSelection());
420 }
421 });
422 unscheduleButton.addActionListener(new ActionListener() {
423
424 public void actionPerformed(ActionEvent arg0) {
425 unscheduleSelected();
426 }
427 });
428 panel.add(unscheduleButton);
429
430
431
432 final double min = 1;
433 final double max = 3 * 365 * 24 * 60 * 60;
434 final double slidermax = 1000;
435 final JSlider _timeScaleSlider = new JSlider(0, (int) slidermax);
436
437 _timeScaleSlider.setPreferredSize(new Dimension(_timeScaleSlider.getPreferredSize().width * 4, _timeScaleSlider
438 .getPreferredSize().height));
439 panel.add(_timeScaleSlider);
440
441 final double b = 1.0 / 100.0;
442 final double faktor = (min - max) / (1 - Math.pow(2, slidermax * b));
443 final double c = (min - faktor);
444
445 int initialSliderVal = calcInitialSliderVal(c, b, faktor, initialSeconds);
446 _timeScaleSlider.setValue((int) (slidermax-initialSliderVal));
447
448 _timeScaleSlider.addChangeListener(new ChangeListener() {
449 public void stateChanged(ChangeEvent e) {
450 final double x = slidermax - (double) _timeScaleSlider.getValue();
451 double seconds = c + faktor * Math.pow(2, x * b);
452 _tbv.setSecondsDisplayed((int) seconds, true);
453 }
454 });
455
456 return panel;
457 }
458
459 private int calcInitialSliderVal(double c, double b, double faktor, int seconds) {
460
461 double x = 1 / b * log2((seconds - c) / faktor);
462
463 return (int) x;
464 }
465
466 private double log2(double a) {
467 return Math.log(a) / Math.log(2);
468 }
469
470 protected ScheduleTimeBarModel createTBVModel() {
471 ScheduleTimeBarModel model = new ScheduleTimeBarModel();
472
473 for (int i = 0; i < 20; i++) {
474 model.addRow("WorkCenter #" + i);
475 }
476
477 return model;
478 }
479
480 protected JobTableModel createJobTableModel() {
481 JobTableModel model = new JobTableModel();
482
483 for (int i = 0; i < 200; i++) {
484 Job job = createRandomJob("Job #" + i);
485 model.addJob(job);
486 }
487
488 return model;
489 }
490
491 protected Job createRandomJob(String name) {
492 JaretDate startDate = new JaretDate();
493 startDate.advanceMinutes(Math.random() * 24 * 60 * 10);
494 int duration = (int) ((Math.random() * 7 + 1) * 60);
495 int priority = (int) (Math.random() * 4);
496 Job job = new Job(name, startDate, duration, priority);
497 return job;
498 }
499
500 private class PreventOverlapIntervalModificator extends DefaultIntervalModificator {
501
502 @Override
503 public boolean newBeginAllowed(TimeBarRow row, Interval interval, JaretDate newBegin) {
504 boolean result = true;
505 for (Interval i : row.getIntervals()) {
506 if (i != interval && i.contains(newBegin)) {
507 result = false;
508 break;
509 }
510 }
511
512 return result;
513 }
514
515 @Override
516 public boolean newEndAllowed(TimeBarRow row, Interval interval, JaretDate newBegin) {
517 boolean result = true;
518 for (Interval i : row.getIntervals()) {
519 if (i != interval && i.contains(newBegin)) {
520 result = false;
521 break;
522 }
523 }
524
525 return result;
526 }
527
528 @Override
529 public boolean shiftAllowed(TimeBarRow row, Interval interval, JaretDate newBegin) {
530 boolean result = true;
531 for (Interval i : row.getIntervals()) {
532 if (i != interval && i.contains(newBegin)) {
533 result = false;
534 break;
535 }
536 }
537
538 return result;
539 }
540
541 @Override
542 public boolean isApplicable(TimeBarRow row, Interval interval) {
543 return true;
544 }
545
546 }
547
548 }