forked from LupusNobilis/java-org.hwo.ui
532 lines
13 KiB
Java
532 lines
13 KiB
Java
package org.hwo.ui.gantt;
|
|
|
|
import java.awt.Color;
|
|
import java.awt.Dimension;
|
|
import java.awt.FontMetrics;
|
|
import java.awt.Graphics;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.Point;
|
|
import java.awt.Rectangle;
|
|
import java.awt.event.MouseAdapter;
|
|
import java.awt.event.MouseEvent;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JViewport;
|
|
import javax.swing.Scrollable;
|
|
import javax.swing.SwingConstants;
|
|
|
|
import org.hwo.datetime.Date;
|
|
import org.hwo.datetime.DateTime;
|
|
import org.hwo.datetime.TimeOfDay;
|
|
|
|
public class JGantt extends JComponent {
|
|
|
|
GanttResolver resolver;
|
|
|
|
List<IGanttObject> ganttObjects;
|
|
List<IGanttResource> ganttResources;
|
|
|
|
List<IGanttDrawable> ganttDrawables;
|
|
|
|
Date firstDate;
|
|
int days;
|
|
|
|
int zoom;
|
|
|
|
boolean timeVisible;
|
|
|
|
|
|
boolean relevantChange;
|
|
|
|
JViewport topPane,
|
|
leftPane;
|
|
|
|
|
|
IGanttObject selectedObject;
|
|
|
|
|
|
List<GanttMouseEventListener> ganttMouseEventListener;
|
|
|
|
|
|
public JGantt() {
|
|
ganttMouseEventListener = new ArrayList<GanttMouseEventListener>();
|
|
|
|
resolver = new GanttResolver(this);
|
|
|
|
ganttObjects = new ArrayList<IGanttObject>();
|
|
ganttResources = new ArrayList<IGanttResource>();
|
|
ganttDrawables = new ArrayList<IGanttDrawable>();
|
|
|
|
firstDate = new Date();
|
|
days = 31;
|
|
zoom = 1;
|
|
|
|
timeVisible = false;
|
|
|
|
relevantChange = true;
|
|
|
|
setDoubleBuffered(true);
|
|
|
|
topPane = new JViewport();
|
|
topPane.add(new TopPane());
|
|
leftPane = new JViewport();
|
|
leftPane.add(new Leftpane());
|
|
|
|
addMouseListener(new MouseAdapter() {
|
|
@Override
|
|
public void mouseClicked(MouseEvent e) {
|
|
switch (e.getButton()){
|
|
case MouseEvent.BUTTON1:
|
|
if (e.getClickCount()==1){
|
|
DateTime dt = getDateTimeAt(e.getPoint().x);
|
|
IGanttResource r = getResourceAt(e.getPoint().y);
|
|
IGanttObject o = null;
|
|
if (r != null)
|
|
o = getObjectAt(r, dt);
|
|
|
|
fireGanttMouseClick(JGantt.this, r, dt, o);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
addGanttMouseListener(new GanttMouseEventListener() {
|
|
|
|
@Override
|
|
public void mouseClick(JGantt gantt, IGanttResource ganttResource,
|
|
DateTime dateTime, IGanttObject ganttObject) {
|
|
setSelectedObject(ganttObject);
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
public void addGanttMouseListener(GanttMouseEventListener listener){
|
|
ganttMouseEventListener.add(listener);
|
|
}
|
|
public void removeGanttMouseListener(GanttMouseEventListener listener){
|
|
ganttMouseEventListener.add(listener);
|
|
}
|
|
|
|
public void fireGanttMouseClick(JGantt gantt,IGanttResource ganttResource,DateTime dateTime,IGanttObject ganttObject){
|
|
for (GanttMouseEventListener listener: ganttMouseEventListener){
|
|
listener.mouseClick(this, ganttResource, dateTime, ganttObject);
|
|
}
|
|
};
|
|
|
|
|
|
public int getZoom() {
|
|
return zoom;
|
|
}
|
|
public void setZoom(int zoom) {
|
|
this.zoom = zoom;
|
|
relevantChange = true;
|
|
repaint();
|
|
}
|
|
|
|
public void addGanttObject(IGanttObject object){
|
|
if (!this.ganttObjects.contains(object)){
|
|
this.ganttObjects.add(object);
|
|
|
|
if (IGanttDrawable.class.isInstance(object)){
|
|
ganttDrawables.add((IGanttDrawable)object);
|
|
}
|
|
|
|
resolver.leftOptimize(object);
|
|
object.addNotify(this);
|
|
}
|
|
|
|
for (IGanttResource res: object.getResources()){
|
|
addGanttResource(res);
|
|
}
|
|
|
|
repaint();
|
|
}
|
|
public void removeGanttObject(IGanttObject object){
|
|
if (this.ganttObjects.contains(object)){
|
|
this.ganttObjects.remove(object);
|
|
|
|
if (this.ganttDrawables.contains(object)){
|
|
this.ganttDrawables.remove(object);
|
|
}
|
|
|
|
object.removeNotify(this);
|
|
}
|
|
repaint();
|
|
}
|
|
public IGanttObject[] getGanttObjects(){
|
|
return this.ganttObjects.toArray(new IGanttObject[0]);
|
|
}
|
|
|
|
public void addGanttResource(IGanttResource resource){
|
|
if (!this.ganttResources.contains(resource))
|
|
this.ganttResources.add(resource);
|
|
}
|
|
public void removeGanttResource(IGanttResource resource){
|
|
this.ganttResources.remove(resource);
|
|
}
|
|
public IGanttResource[] getGanttResources(){
|
|
return this.ganttResources.toArray(new IGanttResource[0]);
|
|
}
|
|
|
|
public Date getFirstDate() {
|
|
return new Date(firstDate);
|
|
}
|
|
public void setFirstDate(Date firstDate) {
|
|
this.firstDate = new Date(firstDate);
|
|
}
|
|
|
|
public int getDays() {
|
|
return days;
|
|
}
|
|
public void setDays(int days) {
|
|
this.days = days;
|
|
}
|
|
|
|
public void setTimeVisible(boolean timeVisible) {
|
|
this.timeVisible = timeVisible;
|
|
}
|
|
public boolean isTimeVisible(){
|
|
return this.timeVisible;
|
|
}
|
|
|
|
public IGanttObject getSelectedObject() {
|
|
return selectedObject;
|
|
}
|
|
public void setSelectedObject(IGanttObject selectedObject) {
|
|
this.selectedObject = selectedObject;
|
|
repaint();
|
|
}
|
|
|
|
public IGanttObject findObjectAt(IGanttResource resource,DateTime dateTime){
|
|
System.err.println(String.format("JGantt: Find Object on Resource %s at %s",resource.toString(),dateTime.toString()));
|
|
|
|
for (IGanttObject o: ganttObjects){
|
|
if (o.getStartTime().isBefore(dateTime) && o.getEndTime().isAfter(dateTime) && Arrays.asList(o.getResources()).contains(resource)){
|
|
return o;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public IGanttResource getResourceAt(int y){
|
|
if (y > minHeight)
|
|
return null;
|
|
|
|
IGanttResource res = ganttResources.get((y-6) / lineHeight);
|
|
return res;
|
|
}
|
|
|
|
public DateTime getDateTimeAt(int x){
|
|
Date date = new Date(firstDate);
|
|
date.addDays(x / dayColumnWidth);
|
|
TimeOfDay time = new TimeOfDay( TimeOfDay.SECONDSPERDAY * (x % dayColumnWidth) / dayColumnWidth );
|
|
return new DateTime(date, time);
|
|
}
|
|
|
|
private IGanttObject getObjectAt(IGanttResource resource,DateTime dateTime){
|
|
IGanttObject o = findObjectAt(resource,dateTime);
|
|
|
|
return o;
|
|
}
|
|
|
|
private IGanttObject[] findParallelObjects(IGanttObject object,IGanttResource resource){
|
|
LinkedList<IGanttObject> rl = new LinkedList<IGanttObject>();
|
|
|
|
/* for (IGanttObject o:ganttObjects)
|
|
if (Arrays.asList(o.getResources()).contains(o)
|
|
*/
|
|
return rl.toArray(new IGanttObject[0]);
|
|
}
|
|
|
|
public IGanttObject[] findDependedObjects(IGanttObject object){
|
|
LinkedList<IGanttObject> rl = new LinkedList<IGanttObject>();
|
|
|
|
for (IGanttObject o:ganttObjects)
|
|
if (Arrays.asList(o.getDependencies()).contains(object))
|
|
rl.add(o);
|
|
|
|
return rl.toArray(new IGanttObject[0]);
|
|
}
|
|
|
|
public IGanttObject[] findDependedObjects(IGanttObject object,IGanttResource resource){
|
|
LinkedList<IGanttObject> rl = new LinkedList<IGanttObject>();
|
|
|
|
for (IGanttObject o:ganttObjects)
|
|
if (Arrays.asList(o.getDependencies()).contains(object) && Arrays.asList(o.getResources()).contains(resource))
|
|
rl.add(o);
|
|
|
|
return rl.toArray(new IGanttObject[0]);
|
|
}
|
|
|
|
/*********
|
|
* Layout variables...
|
|
*/
|
|
|
|
int resourceLines;
|
|
int lineHeight,
|
|
headerLineHeight;
|
|
int resourceBaseHeight;
|
|
|
|
int leftColumnWidth;
|
|
|
|
int dayColumnWidth;
|
|
|
|
int minHeight;
|
|
int minWidth;
|
|
|
|
void paintObject(Graphics2D g,Rectangle rect,IGanttObject object){
|
|
|
|
if (ganttDrawables.contains(object)){
|
|
((IGanttDrawable)object).paint(g, rect);
|
|
} else {
|
|
if (selectedObject == object)
|
|
g.setColor(Color.RED);
|
|
else
|
|
g.setColor(new Color(255,192,192));
|
|
|
|
g.fillRoundRect(rect.x,rect.y,rect.width,rect.height, 8, 8);
|
|
g.drawRoundRect(rect.x,rect.y,rect.width,rect.height, 8, 8);
|
|
|
|
if (selectedObject == object)
|
|
g.setColor(Color.WHITE);
|
|
else
|
|
g.setColor(Color.BLACK);
|
|
|
|
g.drawString(object.getShortLabel(), rect.x + 4, rect.y + lineHeight - 8);
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
public void paint(Graphics _g) {
|
|
Graphics2D g = (Graphics2D)_g;
|
|
|
|
int n;
|
|
|
|
updateSizes();
|
|
|
|
g.setColor(getBackground());
|
|
g.fillRect(0, 0, getWidth(), getHeight());
|
|
|
|
g.setColor(Color.BLACK);
|
|
// g.drawLine(leftColumnWidth, 0, leftColumnWidth, getHeight());
|
|
|
|
Date lastDate = getFirstDate();
|
|
lastDate.addDays(days-1);
|
|
|
|
|
|
for (n = 0; n < days; n++){
|
|
int l = ((n) * dayColumnWidth);
|
|
|
|
g.drawLine(((n+1) * dayColumnWidth), 0, ((n+1) * dayColumnWidth), minHeight);
|
|
}
|
|
|
|
|
|
n = 0;
|
|
for (IGanttResource resource: this.ganttResources){
|
|
n += resource.getMaxParallelUsage();
|
|
|
|
g.setColor(Color.LIGHT_GRAY);
|
|
|
|
g.drawLine(0, 4 + (n * lineHeight), minWidth, 4 + (n * lineHeight));
|
|
}
|
|
|
|
for (IGanttObject object: ganttObjects){
|
|
if (object.getEndTime().getDate().isBefore(firstDate) || object.getStartTime().getDate().isAfter(lastDate))
|
|
continue;
|
|
|
|
int pos = 2 + (object.getStartTime().getDate().daysAfter(firstDate) * dayColumnWidth);
|
|
int width = (object.getEndTime().getDate().daysAfter(object.getStartTime().getDate()) * dayColumnWidth) + dayColumnWidth - 4;
|
|
int posOffset = dayColumnWidth * object.getStartTime().getTime().getSecondsOfTheDay() / TimeOfDay.SECONDSPERDAY;
|
|
|
|
pos += posOffset;
|
|
width -= posOffset + dayColumnWidth * (TimeOfDay.SECONDSPERDAY - object.getEndTime().getTime().getSecondsOfTheDay()) / TimeOfDay.SECONDSPERDAY;
|
|
|
|
for (IGanttResource res: object.getResources()){
|
|
int vpos = 6 + (ganttResources.indexOf(res) * lineHeight);
|
|
|
|
Rectangle rect = new Rectangle(pos, vpos, width, lineHeight - 4);
|
|
paintObject(g, rect, object);
|
|
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
private void updateSizes(){
|
|
if (!relevantChange)
|
|
return;
|
|
|
|
Graphics2D g = (Graphics2D)getGraphics();
|
|
FontMetrics fm = getFontMetrics(getFont());
|
|
|
|
//lineHeight = (int)(getFont().getSize2D()*72) + 4;
|
|
lineHeight = getFont().getSize() + 8;
|
|
headerLineHeight = getFont().getSize() + 2;
|
|
|
|
resourceBaseHeight = headerLineHeight * 4;
|
|
|
|
if (isTimeVisible())
|
|
dayColumnWidth = 96;
|
|
else
|
|
dayColumnWidth = ((int)fm.getStringBounds("WWW", g).getWidth()) + 10;
|
|
|
|
dayColumnWidth *= zoom;
|
|
|
|
resourceLines = 0;
|
|
leftColumnWidth = 0;
|
|
for (IGanttResource resource: this.ganttResources){
|
|
int lw = (int)fm.getStringBounds(resource.getLabel(), g).getWidth();
|
|
|
|
if (lw > leftColumnWidth)
|
|
leftColumnWidth = lw;
|
|
|
|
resourceLines += resource.getMaxParallelUsage();
|
|
}
|
|
|
|
leftColumnWidth += 15;
|
|
|
|
|
|
minHeight = 4 + (lineHeight * resourceLines);
|
|
minWidth = ( dayColumnWidth * days );
|
|
|
|
setPreferredSize(new Dimension(minWidth, minHeight));
|
|
|
|
leftPane.setSize(new Dimension(leftColumnWidth,minHeight));
|
|
topPane.setSize(new Dimension(minWidth, resourceBaseHeight));
|
|
|
|
relevantChange = false;
|
|
}
|
|
|
|
|
|
|
|
|
|
public class TopPane extends JComponent {
|
|
|
|
@Override
|
|
public Dimension getPreferredSize() {
|
|
return new Dimension(minWidth,resourceBaseHeight);
|
|
}
|
|
|
|
@Override
|
|
public void paint(Graphics _g) {
|
|
Graphics2D g = (Graphics2D)_g;
|
|
|
|
int n;
|
|
int ayear = 0;
|
|
int aweek = 0;
|
|
|
|
updateSizes();
|
|
|
|
g.setColor(getBackground());
|
|
g.fillRect(0, 0, getWidth(), getHeight());
|
|
|
|
g.setColor(Color.BLACK);
|
|
// g.drawLine(leftColumnWidth, 0, leftColumnWidth, getHeight());
|
|
|
|
g.drawLine(0, resourceBaseHeight, getWidth(), resourceBaseHeight);
|
|
g.drawLine(0, headerLineHeight, minWidth, headerLineHeight);
|
|
g.drawLine(0, 2 * headerLineHeight, minWidth, 2 * headerLineHeight);
|
|
|
|
Date date = getFirstDate();
|
|
|
|
for (n = 0; n < days; n++){
|
|
int l = ((n) * dayColumnWidth);
|
|
|
|
g.drawLine(((n+1) * dayColumnWidth), resourceBaseHeight - (2 * headerLineHeight), ((n+1) * dayColumnWidth), resourceBaseHeight * 4);
|
|
|
|
g.drawString(String.format("%02d.%02d", date.getDay(),date.getMonth()), l + 2, resourceBaseHeight - headerLineHeight - 2);
|
|
g.drawString(String.format("%s", date.weekdays[date.getWeekDay()]), l + 2, resourceBaseHeight - 2);
|
|
|
|
if (ayear != date.getYear()){
|
|
g.drawString(String.format("%02d", date.getYear()), l + 2, resourceBaseHeight - (3 * headerLineHeight) - 2);
|
|
if (ayear != 0){
|
|
g.drawLine(((n) * dayColumnWidth), resourceBaseHeight - (4 * headerLineHeight), ((n) * dayColumnWidth), resourceBaseHeight - (3 * headerLineHeight));
|
|
}
|
|
ayear = date.getYear();
|
|
}
|
|
|
|
int w = date.getWeek();
|
|
|
|
if (aweek != w){
|
|
g.drawString(String.format("%02d", w), l + 2, resourceBaseHeight - (2 * headerLineHeight) - 2);
|
|
if (aweek != 0){
|
|
g.drawLine(((n) * dayColumnWidth), resourceBaseHeight - (3 * headerLineHeight), ((n) * dayColumnWidth), resourceBaseHeight - (2 * headerLineHeight));
|
|
}
|
|
aweek = w;
|
|
}
|
|
|
|
date.addDays(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class Leftpane extends JComponent {
|
|
|
|
public Leftpane() {
|
|
}
|
|
|
|
@Override
|
|
public Dimension getPreferredSize() {
|
|
return new Dimension(leftColumnWidth,minHeight);
|
|
}
|
|
|
|
@Override
|
|
public void paint(Graphics _g) {
|
|
Graphics2D g = (Graphics2D)_g;
|
|
|
|
int n;
|
|
|
|
updateSizes();
|
|
|
|
g.setColor(getBackground());
|
|
g.fillRect(0, 0, getWidth(), getHeight());
|
|
|
|
g.setColor(Color.BLACK);
|
|
g.drawLine(leftColumnWidth - 1, 0, leftColumnWidth - 1, getHeight());
|
|
|
|
// g.drawLine(0, 0, getWidth(), 0);
|
|
|
|
n = 0;
|
|
for (IGanttResource resource: ganttResources){
|
|
n += resource.getMaxParallelUsage();
|
|
|
|
g.setColor(Color.BLACK);
|
|
g.drawString(resource.getLabel(), 2, 1 + (n * lineHeight));
|
|
g.setColor(Color.LIGHT_GRAY);
|
|
|
|
g.drawLine(0, 4 + (n * lineHeight), getWidth(), 4 + (n * lineHeight));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
public void addNotify() {
|
|
super.addNotify();
|
|
|
|
if (JViewport.class.isInstance(getParent()) && JScrollPane.class.isInstance(getParent().getParent()))
|
|
{
|
|
JScrollPane sp = (JScrollPane)getParent().getParent();
|
|
sp.setColumnHeader(topPane);
|
|
sp.setRowHeader(leftPane);
|
|
}
|
|
|
|
|
|
}
|
|
@Override
|
|
public void removeNotify() {
|
|
super.removeNotify();
|
|
}
|
|
|
|
}
|