View Javadoc

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          // split pane for tbv/table
75          JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
76          splitPane.setResizeWeight(0.85);
77          add(splitPane, BorderLayout.CENTER);
78  
79          // TBV
80          _model = createTBVModel();
81          _tbv = new TimeBarViewer();
82          _tbv.setModel(_model);
83          splitPane.setTopComponent(_tbv);
84  
85          // do some configurations on the timebarviewer
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          // Interval modificator preventing intervals from beeing overlapped by shifting or sizing
93          _tbv.addIntervalModificator(new PreventOverlapIntervalModificator());
94  
95          // register the renderer
96          _tbv.registerTimeBarRenderer(Job.class, new JobRenderer());
97          
98          setUpDND(_tbv);
99  
100         createActions(_tbv);
101 
102         // table
103         _jobTableModel = createJobTableModel();
104         _jobTable = new JTable(_jobTableModel);
105         JScrollPane scroll = new JScrollPane(_jobTable);
106         splitPane.setBottomComponent(scroll);
107 
108         // add dnd support
109         DragSource dragSource = DragSource.getDefaultDragSource();
110         DragGestureListener dgl = new JobTableDragGestureListener();
111         DragGestureRecognizer dgr = dragSource.createDefaultDragGestureRecognizer(_jobTable, DnDConstants.ACTION_MOVE,
112                 dgl);
113 
114         // controls at the bottom
115         JPanel controlPanel = createControlPanel(24 * 60 * 60);
116         add(controlPanel, BorderLayout.SOUTH);
117 
118         
119         // setup title renderer
120         // use the component directly
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         // add a popup menu for the header
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         // add a popup menu for EventIntervals
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         // create and setup drop target
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                             // TODO mystic problem with drop success
217                             _tbvDragOrigRow = null; // mark the drag successful ...
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                         // tell the timebar viewer
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             // TODO Auto-generated catch block
255             e.printStackTrace();
256         }
257 
258         
259         
260         // add drag source
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                 // TODO Auto-generated method stub
270                 
271             }
272             
273             public void dragOver(DragSourceDragEvent dsde) {
274                 // TODO Auto-generated method stub
275                 
276             }
277             
278             public void dragExit(DragSourceEvent dse) {
279                 // TODO Auto-generated method stub
280                 
281             }
282             
283             public void dragEnter(DragSourceDragEvent dsde) {
284                 // TODO Auto-generated method stub
285                 
286             }
287             
288             public void dragDropEnd(DragSourceDropEvent dsde) {
289                 if (!dsde.getDropSuccess() && _tbvDragOrigRow != null) {
290                     // drag did not suceed -> restore original position
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                 // save orig data
327                 _tbvDragOrigRow = (DefaultTimeBarRowModel)row;
328                 _tbvDragOrigBegin = interval.getBegin().copy();
329                 _tbvDragOrigEnd = interval.getEnd().copy();
330                 
331                 return;
332             }
333 //            Point origin = e.getDragOrigin();
334 //            if (_tbv.getDelegate().getYAxisRect().contains(origin)) {
335 //                TimeBarRow row = _tbv.getRowForXY(origin.x, origin.y);
336 //                if (row != null) {
337 //                    e.startDrag(null, new StringSelection("Drag ROW " + row));
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         // simple layout
403         panel.setLayout(new FlowLayout());
404 
405         // unschedule button
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         /// scaling slider
432         final double min = 1; // minimum value for seconds displayed
433         final double max = 3 * 365 * 24 * 60 * 60; // max nummber of seconds displayed (3 years in seconds)
434         final double slidermax = 1000; // slider maximum (does not really matter)
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; // additional factor to reduce the absolut values in the exponent
442         final double faktor = (min - max) / (1 - Math.pow(2, slidermax * b)); // factor for the exp function
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(); // reverse x
451                 double seconds = c + faktor * Math.pow(2, x * b); // calculate the seconds to display
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); // 10 days range
494         int duration = (int) ((Math.random() * 7 + 1) * 60); // min 1h max 8h duration
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 }