/* * Copyright (C) Jakub Neubauer, 2007 * * This file is part of TaskBlocks * * TaskBlocks is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * TaskBlocks is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package taskblocks.graph; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.util.ArrayList; import java.util.List; /** * This class is used to count real positions of tasks on the component * * @author jakub * */ class TaskLayouter { /** * Recounts tasks and rows bounds */ static void recountBounds(int _graphTop, int _rowHeight, TaskGraphRepresentation _builder, TaskGraphComponent _graph, Graphics2D g2) { int rowIndex = 0; FontMetrics fm = g2.getFontMetrics(); int maxRowWidth = fm.stringWidth("Worker"); for (TaskRow row : _builder._rows) { maxRowWidth = Math.max(maxRowWidth, fm.stringWidth(row.getLabel())); } _graph._headerWidth = maxRowWidth + 20; _graph.recountBounds(); // count rows and tasks boundaries int cummulatedRowAdd = _graph._scrollTop; for (TaskRow row : _builder._rows) { int rowTop = (int) (_graphTop + rowIndex * _rowHeight + cummulatedRowAdd); // count connections paddings and modify cummulatedRowAdd and rowTop // according to them. int maxUpPadding = 0; int maxDownPadding = 0; List<Connection> processedConnections = new ArrayList<Connection>(); for (Task t : row._tasks) { for (int i = 0; i < t._outgoingConnections.length; i++) { Connection c = t._outgoingConnections[i]; // recognize, if connection is going up or down boolean goingDown = row._index <= c._toTask.getRow()._index; int connPadding = 1; // simple hack - if connection is in the same row, padding // should be // greater that 1, so the arrow will look nicer. if (c._fromTask.getRow() == c._toTask.getRow()) { connPadding = 2; } // now check if the connection crosses something in the same // direction. // if yes, increase connPadding until nothings crosses. // Note, that we must check only already processed // connections. LOOP: while (true) { for (Connection c2 : processedConnections) { boolean c2GoingDown = row._index <= c2._toTask .getRow()._index; if (goingDown == c2GoingDown && connPadding == c2._padding && crosses(c, c2)) { connPadding++; // padding increased, lets check again. continue LOOP; } } // all checks OK => stop break LOOP; } c._padding = connPadding; processedConnections.add(c); if (goingDown) { maxDownPadding = Math.max(maxDownPadding, connPadding); } else { maxUpPadding = Math.max(maxUpPadding, connPadding); } } } rowTop += maxUpPadding * TaskGraphComponent.CONN_PADDING_FACTOR; cummulatedRowAdd += (maxDownPadding + 1) * TaskGraphComponent.CONN_PADDING_FACTOR + maxUpPadding * TaskGraphComponent.CONN_PADDING_FACTOR; row._topPosition = rowTop; row._topPadding = maxUpPadding; row._bottomPadding = maxDownPadding+1; double top = rowTop + TaskGraphComponent.CONN_PADDING_FACTOR; double height = _rowHeight - TaskGraphComponent.CONN_PADDING_FACTOR; for (Task t : row._tasks) { double left = _graph.timeToX(t.getStartTime()); double widthPlaned = t.getRealDuration() * _graph._dayWidth; t._bounds.setBounds((int) left+2, (int) top, (int) widthPlaned-4, (int) height); } // if this is row of cursor, recount also cursor. if(_graph._cursorTempTask != null && _graph._cursorTempTask._row == row) { double left = _graph.timeToX(_graph._cursorTempTask.getStartTime()); double widthPlaned = _graph._cursorTempTask.getRealDuration() * _graph._dayWidth; _graph._cursorTempTask._bounds.setBounds((int) left+2, (int) top, (int) widthPlaned-4, (int) height); } rowIndex++; } // for all rows for(Connection c: _builder._connections) { recountConnectionBounds(c, _graph); } _builder.clearPaintDirtyFlag(); } /** * Checks if the given connections crosses each other (without padding) * * @param c1 * @param c2 * @return */ static boolean crosses(Connection c1, Connection c2) { long c1Left = c1._fromTask.getFinishTime(); long c1Right = c1._toTask.getStartTime(); long c2Left = c2._fromTask.getFinishTime(); long c2Right = c2._toTask.getStartTime(); // c1.left inside c2 if (c1Left >= c2Left && c1Left <= c2Right) { return true; } // c1.right inside c2 if (c1Right >= c2Left && c1Right <= c2Right) { return true; } // c2.left inside c1 if (c2Left >= c1Left && c2Left <= c1Right) { return true; } // c2.right inside c1 if (c2Right >= c1Left && c2Right <= c1Right) { return true; } return false; } static void recountConnectionBounds(Connection c, TaskGraphComponent _graph) { long fromTime = c._fromTask.getFinishTime(); long toTime = c._toTask.getStartTime(); int x1 = _graph.timeToX(fromTime); int x2 = _graph.timeToX(toTime); int y1, y2; boolean goingDown = c._fromTask.getRow()._index <= c._toTask.getRow()._index; if(goingDown) { y1 = c._fromTask.getRow()._topPosition + TaskGraphComponent.ROW_HEIGHT; } else { y1 = c._fromTask.getRow()._topPosition + TaskGraphComponent.CONN_PADDING_FACTOR; } boolean commingFromUp = c._fromTask.getRow()._index < c._toTask.getRow()._index; if(commingFromUp) { y2 = c._toTask.getRow()._topPosition + TaskGraphComponent.CONN_PADDING_FACTOR; } else { y2 = c._toTask.getRow()._topPosition + TaskGraphComponent.ROW_HEIGHT; } int mediumY; if(y2 >= y1) { mediumY = y1 + c._padding*TaskGraphComponent.CONN_PADDING_FACTOR; } else { mediumY = y1 - c._padding*TaskGraphComponent.CONN_PADDING_FACTOR; } c._path.xpoints[0] = x1; c._path.ypoints[0] = y1; c._path.xpoints[1] = x1; c._path.ypoints[1] = mediumY; c._path.xpoints[2] = x2; c._path.ypoints[2] = mediumY; c._path.xpoints[3] = x2; c._path.ypoints[3] = y2; } }