1 19 20 package taskblocks.graph; 21 22 import java.awt.AlphaComposite ; 23 import java.awt.BasicStroke ; 24 import java.awt.Color ; 25 import java.awt.Cursor ; 26 import java.awt.FontMetrics ; 27 import java.awt.Graphics ; 28 import java.awt.Graphics2D ; 29 import java.awt.Insets ; 30 import java.awt.Rectangle ; 31 import java.awt.RenderingHints ; 32 import java.awt.event.AdjustmentEvent ; 33 import java.awt.event.AdjustmentListener ; 34 import java.awt.event.ComponentEvent ; 35 import java.awt.event.ComponentListener ; 36 import java.awt.geom.Line2D ; 37 import java.text.DateFormat ; 38 import java.text.SimpleDateFormat ; 39 import java.util.Date ; 40 41 import javax.swing.BorderFactory ; 42 import javax.swing.JComponent ; 43 import javax.swing.JScrollBar ; 44 import javax.swing.ToolTipManager ; 45 import javax.swing.event.ChangeListener ; 46 47 import taskblocks.Colors; 48 import taskblocks.Pair; 49 import taskblocks.Utils; 50 51 public class TaskGraphComponent extends JComponent implements ComponentListener , AdjustmentListener { 52 53 static int ROW_HEIGHT = 30; 54 private static int DEFAULT_DAY_WIDTH = 10; 55 static int CONN_PADDING_FACTOR = 6; 56 private static int HEADER_HEIGHT = 20; 57 58 private static final int TOLERANCE = 5; 60 static Integer LEFT = new Integer (0); 62 static Integer RIGHT = new Integer (1); 64 65 69 TaskGraphModel _model; 70 71 75 TaskGraphRepresentation _builder; 76 77 78 TaskGraphPainter _painter; 79 80 83 GraphActionListener _grActListener; 84 85 86 int _dayWidth = DEFAULT_DAY_WIDTH; 87 88 89 long _firstDay; 90 91 92 int _graphLeft; 93 94 private int _graphTop; 95 96 private int _graphWidth; 97 98 int _graphHeight; 99 100 int _headerWidth; 101 102 GraphMouseHandler _mouseHandler; 103 JScrollBar _verticalScroll; 104 105 109 Rectangle _contentBounds = new Rectangle (); 110 111 int _scrollTop; 112 113 public TaskGraphComponent(TaskGraphModel model, TaskGraphPainter painter) { 114 _painter = painter; 115 116 setModel(model); 117 _mouseHandler = new GraphMouseHandler(this); 118 _verticalScroll = new JScrollBar (JScrollBar.VERTICAL); 119 this.add(_verticalScroll); 120 _verticalScroll.addAdjustmentListener(this); 121 122 setBorder(BorderFactory.createEmptyBorder(0,0,15,0)); 123 this.addMouseMotionListener(_mouseHandler); 124 this.addMouseListener(_mouseHandler); 125 this.addMouseWheelListener(_mouseHandler); 126 this.addKeyListener(_mouseHandler); 127 this.addComponentListener(this); 128 this.setFocusable(true); 129 ToolTipManager.sharedInstance().setDismissDelay(8000); 130 ToolTipManager.sharedInstance().setReshowDelay(3000); 131 } 132 133 public TaskGraphRepresentation getGraphRepresentation() { 134 return _builder; 135 } 136 137 public void moveRight() { 138 _firstDay +=2; 139 _builder.setPaintDirty(); 140 repaint(); 141 } 142 143 public void moveLeft() { 144 _firstDay-=2; 145 _builder.setPaintDirty(); 146 repaint(); 147 } 148 149 void recountBounds() { 150 Insets insets = getInsets(); 151 _graphTop = HEADER_HEIGHT; 152 _graphTop += insets.top; 153 _graphHeight = getHeight() - HEADER_HEIGHT; 154 _graphLeft = _headerWidth; 155 _graphWidth = getWidth() - _headerWidth; 156 157 _graphLeft += insets.left; 158 _graphHeight -= insets.top + insets.bottom; 159 _graphWidth -= insets.left + insets.right; 160 161 } 162 163 public void paint(Graphics g) { 164 Graphics2D g2 = (Graphics2D )g; 165 166 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 167 168 g2.setColor(Color.white); 170 g2.fillRect(0,0,getWidth(), getHeight()); 171 172 synchronized(_builder) { 173 Insets insets = getInsets(); 174 175 recountBounds(); 176 if(_builder.isPaintDirty()) { 178 TaskLayouter.recountBounds(_graphTop, ROW_HEIGHT, _builder, this, g2); 179 } 180 181 g2.clipRect(insets.left, insets.top, getWidth()-insets.left-insets.right, getHeight()-insets.top-insets.bottom); 182 183 _contentBounds.y = Integer.MAX_VALUE; 185 _contentBounds.height = -1; 186 187 for(TaskRow row: _builder._rows) { 189 row._bounds.x = insets.left; 190 row._bounds.y = row._topPosition-3; 191 row._bounds.width = row._selected ? _headerWidth + _graphWidth : _headerWidth; 192 row._bounds.height = ROW_HEIGHT+6; 193 _painter.paintRowHeader(row._userManObject, g2, row._bounds, row._selected); 194 195 if(row._index > 0) { 196 g2.setColor(Color.lightGray); 197 int lineY = row._topPosition-row._topPadding*CONN_PADDING_FACTOR; 198 g2.drawLine(insets.left, lineY, 2000, lineY); 199 } 200 201 if(_contentBounds.y > row._bounds.y) { 203 if(_contentBounds.height != -1) { 204 _contentBounds.height += (_contentBounds.y - row._bounds.y); 205 } 206 _contentBounds.y = row._bounds.y; 207 } 208 if(_contentBounds.y + _contentBounds.height < row._bounds.y + row._bounds.height + CONN_PADDING_FACTOR) { 209 _contentBounds.height = row._bounds.y + row._bounds.height - _contentBounds.y + CONN_PADDING_FACTOR; 210 } 211 } 212 213 g2.setColor(Color.DARK_GRAY); 215 g2.drawLine(_graphLeft, _graphTop-HEADER_HEIGHT, _graphLeft, _graphTop + _graphHeight+HEADER_HEIGHT); 216 Color lightHeaderCol = Colors.TASKS_TOP_HEADER_COLOR.brighter().brighter(); 217 g2.setColor(lightHeaderCol); 218 g2.drawLine(_graphLeft+1, _graphTop-HEADER_HEIGHT, _graphLeft+1, _graphTop-1); 219 221 paintWorkerHeader(g2); 222 223 g2.clipRect(_graphLeft+2, _graphTop - HEADER_HEIGHT, _graphWidth-3, _graphHeight+HEADER_HEIGHT); 225 Task t; 226 for(int i = _builder._tasks.length-1; i >= 0; i--) { 227 t = _builder._tasks[i]; 228 _painter.paintTask(t._userObject, g2, t._bounds, t._selected || t == _mouseHandler._pressedTask); 229 231 if(_contentBounds.y > t._bounds.y) {_contentBounds.y = t._bounds.y;} 232 if(_contentBounds.y + _contentBounds.height < t._bounds.y + t._bounds.height) { 233 _contentBounds.height = t._bounds.y + t._bounds.height - _contentBounds.y; 234 } 235 } 236 paintHeaderAndWeekends(g2); 238 239 for(Connection c: _builder._connections) { 241 paintConnection(g2, c); 242 } 243 244 paintCursor(g2); 246 247 if(_mouseHandler._dragMode == 4) { 249 g2.setColor(Color.RED); 250 if(_mouseHandler._destTask != null && _mouseHandler._destTask != _mouseHandler._pressedTask) { 251 _painter.paintTask(_mouseHandler._destTask._userObject, g2, _mouseHandler._destTask._bounds, true); 252 } 253 g2.drawLine(_mouseHandler._pressX, _mouseHandler._pressY, _mouseHandler.getLastMouseX(), _mouseHandler.getLastMouseY()); 254 } 255 } 256 257 adjustScrolls(); 258 259 paintChildren(g); 261 } 262 263 public void scaleDown() { 264 long mouseDay = xToTime(_mouseHandler.getLastMouseX()); 265 266 _dayWidth -=1; 267 if(_dayWidth < 4) { 268 _dayWidth = 4; 269 } 270 271 long newMouseDay = xToTime(_mouseHandler.getLastMouseX()); 272 if(newMouseDay != mouseDay) { 273 _firstDay -= newMouseDay-mouseDay; 274 } 275 276 _builder.setPaintDirty(); 277 repaint(); 278 } 279 280 public void scaleUp() { 281 long mouseDay = xToTime(_mouseHandler.getLastMouseX()); 282 283 _dayWidth += 1; 284 if(_dayWidth > 50) { 285 _dayWidth = 50; 286 } 287 288 long newMouseDay = xToTime(_mouseHandler.getLastMouseX()); 289 if(newMouseDay != mouseDay) { 290 _firstDay -= newMouseDay-mouseDay; 291 } 292 _builder.setPaintDirty(); 293 repaint(); 294 } 295 296 public void setGraphActionListener(GraphActionListener list) { 297 _grActListener = list; 298 } 299 300 public void setGraphChangeListener(ChangeListener changeListener) { 301 _builder.setGraphChangeListener(changeListener); 302 } 303 304 public void focusOnToday() { 305 _firstDay = System.currentTimeMillis()/Utils.MILLISECONDS_PER_DAY; 306 _builder.setPaintDirty(); 307 repaint(); 308 } 309 310 public void scrollToTaskVisible(Object task) { 311 Task taskToFocus = null; 313 for(Task t: _builder._tasks) { 314 if(t._userObject == task) { 315 taskToFocus = t; 316 break; 317 } 318 } 319 if(taskToFocus == null) { 320 return; 322 } 323 324 long lastVisibleDay = xToTime(getWidth()-getInsets().right); 327 328 if(taskToFocus.getFinishTime() > lastVisibleDay) { 330 _firstDay += (taskToFocus.getFinishTime() - lastVisibleDay); 331 _builder.setPaintDirty(); 332 } 333 if(taskToFocus.getStartTime() < _firstDay) { 335 _firstDay = taskToFocus.getStartTime(); 336 _builder.setPaintDirty(); 337 } 338 339 _mouseHandler.clearSelection(); 341 _mouseHandler._selection.add(taskToFocus); 342 taskToFocus._selected = true; 343 344 repaint(); 345 } 346 347 public void setModel(TaskGraphModel model) { 348 if(model == _model) { 349 _builder.buildFromModel(); 351 return; 352 } 353 TaskGraphRepresentation oldBuilder = _builder; 354 _model = model; 355 _builder = new TaskGraphRepresentation(_model); 356 if(oldBuilder != null) { 357 _builder.setGraphChangeListener(oldBuilder.getGraphChangeListener()); 358 oldBuilder.setGraphChangeListener(null); } 360 _builder.buildFromModel(); 361 362 _firstDay = Long.MAX_VALUE; 364 for(Task t: _builder._tasks) { 365 if(_firstDay > t.getStartTime()) { 366 _firstDay = t.getStartTime(); 367 } 368 } 369 if(_firstDay == Long.MAX_VALUE) { 370 _firstDay = System.currentTimeMillis()/Utils.MILLISECONDS_PER_DAY; 371 } 372 373 repaint(); 374 } 375 376 TaskRow findRow(int y) { 377 for(TaskRow row: _builder._rows) { 378 if(y >= row._topPosition-row._topPadding*CONN_PADDING_FACTOR && y <= row._topPosition + ROW_HEIGHT + row._bottomPadding*CONN_PADDING_FACTOR) { 379 return row; 380 } 381 } 382 return null; 383 } 384 385 TaskRow findNearestRow(int y) { 386 TaskRow myRow = null; 387 int minDist = Integer.MAX_VALUE; 388 for(TaskRow row: _builder._rows) { 389 if(y > row._topPosition && y < row._topPosition + ROW_HEIGHT) { 390 myRow = row; 391 break; 392 } else { 393 int dist = Math.min( 394 Math.abs(y-row._topPosition), 395 Math.abs(y-(row._topPosition + ROW_HEIGHT)) 396 ); 397 if(dist < minDist) { 398 myRow = row; 399 minDist = dist; 400 } 401 } 402 } 403 return myRow; 404 } 405 406 void changeCursor(Object o) { 407 if(o instanceof Task || o instanceof Connection) { 408 this.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 409 } else if(o instanceof Pair) { 410 this.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); 411 } else { 412 this.setCursor(Cursor.getDefaultCursor()); 413 } 414 } 415 416 Object findObjectOnPo(int x, int y) { 417 418 if(x < _graphLeft) { 419 return findRow(y); 420 } 421 422 for(Task t: _builder._tasks) { 423 424 if(y > t._bounds.y && y < (t._bounds.y + t._bounds.height)) { 425 if(x > (t._bounds.x-TOLERANCE) && x < (t._bounds.x + TOLERANCE)) { 426 return new Pair<Task, Integer >(t, LEFT); 427 } 428 if(x > (t._bounds.x+t._bounds.width-TOLERANCE) && x < (t._bounds.x + t._bounds.width + TOLERANCE)) { 429 return new Pair<Task, Integer >(t, RIGHT); 430 } 431 } 432 433 if(t._bounds.contains(x, y)) { 434 return t; 435 } 436 } 437 438 for(Connection c: _builder._connections) { 439 double d = Line2D.ptSegDistSq(c._path.xpoints[0], c._path.ypoints[0], c._path.xpoints[1], c._path.ypoints[1], x, y); 441 d = Math.min(d, Line2D.ptSegDistSq(c._path.xpoints[1], c._path.ypoints[1], c._path.xpoints[2], c._path.ypoints[2], x, y)); 442 d = Math.min(d, Line2D.ptSegDistSq(c._path.xpoints[2], c._path.ypoints[2], c._path.xpoints[3], c._path.ypoints[3], x, y)); 443 if(d < 5*5) { 444 return c; 445 } 446 } 447 448 return null; 449 } 450 451 private void paintConnection(Graphics2D g2, Connection c) { 452 if(c._selected) { 453 g2.setColor(Colors.SELECTOIN_COLOR); 454 } else { 455 g2.setColor(Colors.CONNECTION_COLOR); 456 } 457 458 int x2 = c._path.xpoints[3]; 459 int y2 = c._path.ypoints[3]; 460 if(y2 > c._path.ypoints[0]) { 461 g2.drawLine(x2-3, y2-5, x2, y2); 462 g2.drawLine(x2+3, y2-5, x2, y2); 463 } else { 464 g2.drawLine(x2-3, y2+5, x2, y2); 465 g2.drawLine(x2+3, y2+5, x2, y2); 466 } 467 g2.drawLine(c._path.xpoints[0], c._path.ypoints[0], c._path.xpoints[1], c._path.ypoints[1]); 468 g2.drawLine(c._path.xpoints[1], c._path.ypoints[1], c._path.xpoints[2], c._path.ypoints[2]); 469 g2.drawLine(c._path.xpoints[2], c._path.ypoints[2], c._path.xpoints[3], c._path.ypoints[3]); 470 } 471 472 private void paintCursor(Graphics2D g2) { 473 if(_mouseHandler._cursorTaskRow != null && _mouseHandler._cursorTime >= 0 && _mouseHandler._pressedTask != null) { 474 475 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7f)); 476 int y = _mouseHandler._cursorTaskRow._topPosition; 477 478 int transX = (int)((_mouseHandler._cursorTime - _mouseHandler._pressedTask.getStartTime())*_dayWidth); 479 int transY = y - _mouseHandler._pressedTask._row._topPosition; 480 g2.translate(transX, transY); 481 _painter.paintTask(_mouseHandler._pressedTask._userObject, g2, _mouseHandler._pressedTask._bounds, true); 482 g2.setColor(Color.BLACK); 483 g2.setStroke(new BasicStroke (3)); 484 g2.drawLine(_mouseHandler._pressedTask._bounds.x, _mouseHandler._pressedTask._bounds.y+3, _mouseHandler._pressedTask._bounds.x, _mouseHandler._pressedTask._bounds.y + _mouseHandler._pressedTask._bounds.height-4); 485 g2.translate(-transX, -transY); 486 } 487 } 488 489 private void paintWorkerHeader(Graphics2D g2) { 490 g2.setColor(Colors.TASKS_TOP_HEADER_COLOR); 491 492 FontMetrics fm = g2.getFontMetrics(); 493 494 g2.fillRect(_graphLeft-_headerWidth, _graphTop-HEADER_HEIGHT, _headerWidth, HEADER_HEIGHT); 495 g2.setColor(Color.WHITE); 496 g2.drawString("Worker", _graphLeft-_headerWidth + 8, _graphTop-HEADER_HEIGHT + (fm.getHeight() + HEADER_HEIGHT)/2 - fm.getDescent() ); 497 498 Color darkHeaderCol = Colors.TASKS_TOP_HEADER_COLOR.darker().darker(); 499 Color lightHeaderCol = Colors.TASKS_TOP_HEADER_COLOR.brighter().brighter(); 500 g2.setColor(darkHeaderCol); 501 g2.drawRect(_graphLeft-_headerWidth, _graphTop-HEADER_HEIGHT, _graphWidth+_headerWidth-1, HEADER_HEIGHT+_graphHeight-1); 502 g2.drawLine(_graphLeft-_headerWidth, _graphTop, _graphLeft+_graphWidth, _graphTop); 503 g2.setColor(lightHeaderCol); 504 g2.drawLine(_graphLeft-_headerWidth+1, _graphTop-1, _graphLeft-_headerWidth+1, _graphTop-HEADER_HEIGHT+1); 505 g2.drawLine(_graphLeft-_headerWidth+1, _graphTop-HEADER_HEIGHT+1, _graphLeft-1, _graphTop-HEADER_HEIGHT+1); 506 } 507 508 private void paintHeaderAndWeekends(Graphics2D g2) { 509 Color lightHeaderCol = Colors.TASKS_TOP_HEADER_COLOR.brighter().brighter(); 510 Color darkHeaderCol = Colors.TASKS_TOP_HEADER_COLOR.darker().darker(); 511 FontMetrics fm = g2.getFontMetrics(); 512 513 g2.setColor(Colors.TASKS_TOP_HEADER_COLOR); 514 g2.fillRect(_graphLeft+1, _graphTop-HEADER_HEIGHT+1, _graphWidth-2, HEADER_HEIGHT-1); 515 g2.setColor(Color.WHITE); 516 517 int skip = 1; 518 if(_dayWidth < 10) { 519 skip = 2; 520 } 521 522 Color weekEndColor = new Color (50,50,50,50); int x = 0, x1, x2; 524 int mostRight = _graphLeft + _graphWidth; 525 526 int firstDayInWeek = Utils.getDayInWeek(_firstDay); 527 for(int i = -firstDayInWeek; x < mostRight; i+=7) { 528 long time = _firstDay + i; 529 x = timeToX(time); 530 if(x >= mostRight) { 531 break; 532 } 533 534 g2.setColor(weekEndColor); 536 x1 = timeToX(time+5); 537 x2 = timeToX(time+7); 538 g2.fillRect(x1, _graphTop, x2-x1, _graphHeight); 539 540 if((time/7) % skip == 0) { 542 DateFormat df = new SimpleDateFormat ("d.M."); 543 String timeFormatted = df.format(new Date (time*Utils.MILLISECONDS_PER_DAY)); 544 g2.setColor(Color.WHITE); 545 g2.drawString(timeFormatted, x+4, _graphTop-HEADER_HEIGHT + (fm.getHeight() + HEADER_HEIGHT)/2 - fm.getDescent() -1 ); 546 g2.setColor(darkHeaderCol); 547 } 548 } 549 550 g2.setColor(darkHeaderCol); 553 x = 0; 554 for(int i = 0; x < mostRight; i++) { 555 long time = _firstDay + i; 556 x = timeToX(time); 557 x2 = timeToX(time + skip*7); 558 int dayInWeek = Utils.getDayInWeek(time); 559 g2.setColor(darkHeaderCol); 560 g2.drawLine(x, _graphTop-3, x, _graphTop-1); 561 if(dayInWeek == 0 && (time/7) % skip == 0) { 562 g2.drawLine(x, _graphTop-HEADER_HEIGHT, x, _graphTop-1); 563 g2.setColor(lightHeaderCol); 564 g2.drawLine(x+1, _graphTop-1, x+1, _graphTop-HEADER_HEIGHT+1); 565 g2.drawLine(x+1, _graphTop - HEADER_HEIGHT+1, x2-1, _graphTop-HEADER_HEIGHT+1); 566 } 567 } 568 569 g2.setColor(darkHeaderCol); 570 572 573 long time = System.currentTimeMillis()/ Utils.MILLISECONDS_PER_DAY; 575 g2.setColor(new Color (255,0,0,100)); 576 x = timeToX(time); 577 g2.drawLine(x, _graphTop, x, _graphTop + _graphHeight); 578 579 } 580 581 582 int timeToX(long time) { 583 int relativeTime = (int)(time - _firstDay); 584 return _graphLeft + relativeTime * _dayWidth; 585 } 586 587 long xToTime(int x) { 588 x+= _dayWidth/2; return (x - _graphLeft) / _dayWidth + _firstDay; 590 } 591 592 public void componentHidden(ComponentEvent e) { 593 } 594 595 public void componentMoved(ComponentEvent e) { 596 } 597 598 public void componentResized(ComponentEvent e) { 599 recountBounds(); 600 Insets insets = getInsets(); 601 _verticalScroll.setBounds( 602 getWidth() - _verticalScroll.getWidth() - insets.right, 603 _graphTop+1, 604 _verticalScroll.getPreferredSize().width, 605 getHeight() - _graphTop - insets.bottom-2 606 ); 607 } 608 609 public void componentShown(ComponentEvent e) { 610 } 611 612 boolean _adjustingScrolls; 613 public void adjustmentValueChanged(AdjustmentEvent e) { 614 if(_adjustingScrolls) { 615 return; 616 } 617 if(_contentBounds.height == -1) { 618 return; 619 } 620 if(e.getSource() == _verticalScroll) { 621 Rectangle b = new Rectangle (_contentBounds); 623 b.y-=10; 624 b.height+=20; 625 int top = _graphTop; 626 int bottom = getHeight() - getInsets().bottom; int canScrollUp = Math.max(0, Math.max(bottom - b.y - b.height, top - b.y)); 628 629 int diff = _verticalScroll.getValue() - canScrollUp; 630 _scrollTop -= diff; 631 _builder.setPaintDirty(); 632 repaint(); 633 } 634 } 635 private void adjustScrolls() { 636 _adjustingScrolls = true; 638 if(_contentBounds.height == -1) { 639 return; 640 } 641 try { 642 Rectangle b = new Rectangle (_contentBounds); 643 b.y-=10; 644 b.height+=20; 645 int top = _graphTop; 646 int bottom = getHeight() - getInsets().bottom; 648 int canScrollDown = Math.max(0, Math.max(b.y-top, b.y + b.height - bottom)); 649 int canScrollUp = Math.max(0, Math.max(bottom - (b.y + b.height), top - b.y)); 650 651 _verticalScroll.setMaximum(canScrollUp + canScrollDown); 653 _verticalScroll.setBlockIncrement((canScrollUp + canScrollDown) / 5); 654 _verticalScroll.setValue(canScrollUp); 655 } finally { 657 _adjustingScrolls = false; 658 } 659 } 660 661 public void deleteSelection() { 662 _mouseHandler.deleteSelection(); 663 } 664 665 } 666 | Popular Tags |