/* * 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.Cursor; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Set; import javax.swing.Action; import javax.swing.JOptionPane; import taskblocks.utils.Pair; import taskblocks.utils.Utils; public class GraphMouseHandler implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener { public static final int DM_NOTHING = 0; public static final int DM_WHOLE_TASK = 1; public static final int DM_TASK_LEFT_BOUNDARY = 2; public static final int DM_TASK_RIGHT_BOUNDARY= 3; public static final int DM_NEW_CONNECTION = 4; public static final int DM_CANVAS = 5; /** * drag mode * 0 - nothing, * 1 - dragging whole selected tasks, * 2 - dragging task left boundary, * 3 - dragging task right boundary * 4 - dragging new connection * 5 - dragging the whole canvas */ int _dragMode; /** Used when dragging. Task on which the mouse was pressed*/ Task _pressedTask; /** Last mouse-pressed position */ int _pressX; /** Last mouse-pressed position */ int _pressY; /** Used when dragging - on this position will be shown the moved task shadow*/ long _cursorTime; /** Used when dragging */ TaskRow _cursorTaskRow; /** Used when dragging - task to which the new connection is to be created */ Task _destTask; /** Used when dragging - last mouse x */ private int _dragX; /** Used when dragging - last mouse y */ private int _dragY; /** Remembered firstDay when mouse was pressed and drag mode is 5 (dragging whole canvas) */ private long _pressFirstDay; /** The day on which the mouse was last pressed */ private long _pressDay; /** Graph component for which this handler works */ private TaskGraphComponent _graph; /** The set of currently selected tasks */ Set<Object> _selection = new HashSet<Object>(); GraphMouseHandler(TaskGraphComponent graph) { _graph = graph; } public void mouseClicked(MouseEvent e) { Point p = e.getPoint(); Object o = _graph.findObjectOnPo(p.x, p.y); Task t = null; TaskRow r = null; GraphObject go = null; if(o instanceof GraphObject) { go = (GraphObject)o; } if(o instanceof Task) { t = (Task)o; } else if(o instanceof TaskRow) { r = (TaskRow)o; } else if(o instanceof Pair) { Pair<Task, Integer> pressedObj = (Pair<Task, Integer>)o; t = (Task)pressedObj.fst; go = (Task)pressedObj.fst; } boolean selectionChanged = false; if(e.getButton() == MouseEvent.BUTTON1 && ( (e.getModifiers() & MouseEvent.CTRL_MASK) != 0 || (e.getModifiers() & MouseEvent.META_MASK) != 0 || (e.getModifiers() & MouseEvent.SHIFT_MASK) != 0 ) ) { // left mouse but. pressed with ctr or meta or shift => multi-selection if(go != null) { go._selected = !go._selected; selectionChanged = true; if(go._selected) { _selection.add(go); _dragMode = DM_WHOLE_TASK; // moving whole task(s) } else { _selection.remove(go); } } } else { if(go == null) { // outside task -> clear selection if(_selection.size() > 0) { clearSelection(); selectionChanged = true; } } else { // if selection contains exactly t, do nothing if(_selection.size() != 1 || !_selection.contains(go)) { clearSelection(); _selection.add(go); go._selected = true; selectionChanged = true; } } } if(selectionChanged) { _graph.repaint(); } if(_graph._grActListener != null) { if(t != null) { _graph._grActListener.taskClicked(t._userObject, e); } else if (r != null) { _graph._grActListener.manClicked(r._userManObject, e); } else { _graph._grActListener.graphClicked(e); } } } public void mouseDragged(MouseEvent e) { Point p = e.getPoint(); _dragX = p.x; _dragY = p.y; switch(_dragMode) { case DM_WHOLE_TASK: // dragging whole pressed task if(_pressedTask != null) { // find the destination position. // 1. find row TaskRow myRow = _graph.findNearestRow(p.y); if(myRow != null) { // 2. find time int x = p.x - (_pressX - _pressedTask._bounds.x); _cursorTime = _graph.xToTime(x); _cursorTaskRow = myRow; _graph.repaint(); } else { // nothing found, let the cursor be the last one. } } break; case DM_TASK_LEFT_BOUNDARY: // dragging left task boundary if(_pressedTask != null) { long wantedTaskStart = _graph.xToTime(p.x); long oldTaskStart = _pressedTask.getStartTime(); long oldTaskFinish = _pressedTask.getFinishTime(); if(oldTaskStart != wantedTaskStart) { long t1 = Math.min(oldTaskStart, wantedTaskStart); long t2 = Math.max(oldTaskStart, wantedTaskStart); long workingDaysDiff = Utils.countWorkDuration(t1, t2); // we check this, because even if old != wanted, the workingDaysDiff can be 0 // for example if old = monday and wanted = sunday. if(workingDaysDiff > 0) { // Try new start and count effort according to it, so the finish will be not over old finish. // Then move the start to such place so that old finish = new finish _pressedTask.setStartTime(wantedTaskStart); // start with effort=1. Increase it until the real duration is over for(long newEffort = 2; true; newEffort++) { _pressedTask.setEffort(newEffort); if(_pressedTask.getFinishTime() > oldTaskFinish) { // we are over, step back _pressedTask.setEffort(newEffort-1); break; } } // start with wanted start time. increase it until the finish is over old finish. for(long newStart = wantedTaskStart+1; true; newStart++) { _pressedTask.setStartTime(newStart); if(_pressedTask.getFinishTime() > oldTaskFinish) { // we are over, step back _pressedTask.setStartTime(newStart-1); break; } } _graph.repaint(); } } } break; case DM_TASK_RIGHT_BOUNDARY: // dragging right task boundary if(_pressedTask != null) { long wantedTaskFinish = _graph.xToTime(p.x); long oldTaskFinish = _pressedTask.getFinishTime(); if(wantedTaskFinish != oldTaskFinish) { long t1 = Math.min(wantedTaskFinish, oldTaskFinish); long t2 = Math.max(wantedTaskFinish, oldTaskFinish); long workingDaysDiff = Utils.countWorkDuration(t1, t2); if(workingDaysDiff > 0) { // find biggest effort for which finish is not over wanted. for(long newEffort = 2; true; newEffort++) { _pressedTask.setEffort(_pressedTask.getEffort()+1); _pressedTask.setEffort(newEffort); if(_pressedTask.getFinishTime() > wantedTaskFinish) { // we are over, step back _pressedTask.setEffort(newEffort-1); break; } } _graph.repaint(); } } } break; case DM_NEW_CONNECTION: // new connection if(_pressedTask != null) { Object o = _graph.findObjectOnPo(p.x, p.y); if(o instanceof Task) { _destTask = (Task)o; } else if(o instanceof Pair) { _destTask = ((Pair<Task, Integer>)o).fst; } else { _destTask = null; } } _graph.repaint(); break; case DM_CANVAS: // Scrolling the Frame long tmpFirstDay = _graph._firstDay; _graph._firstDay = _pressFirstDay; long mouseDay = _graph.xToTime(p.x); _graph._firstDay = tmpFirstDay; long newFirstDay = _pressFirstDay - (mouseDay - _pressDay); if(newFirstDay != _graph._firstDay) { _graph._firstDay = newFirstDay; _graph.repaint(); _graph._builder.setPaintDirty(); } break; } _graph._builder.updateModel(); } public void mouseEntered(MouseEvent arg0) { } public void mouseExited(MouseEvent arg0) { _graph.setCursor(Cursor.getDefaultCursor()); } public void mouseMoved(MouseEvent e) { Point p = e.getPoint(); _dragX = p.x; _dragY = p.y; Object o = _graph.findObjectOnPo(p.x, p.y); _graph.changeCursor(o); if(o instanceof Task || o instanceof Pair) { Task t; if(o instanceof Task) { t = (Task)o; } else { t = ((Pair<Task, Integer>)o).fst; } String taskName = _graph._model.getTaskName(t._userObject); DateFormat df = new SimpleDateFormat("d.M."); String start = df.format(new Date(t.getStartTime() * Utils.MILLISECONDS_PER_DAY)); String end = df.format(new Date(t.getFinishTimeForTooltip() * Utils.MILLISECONDS_PER_DAY)); String effort = String.valueOf(t.getEffort()); String duration = String.valueOf((long)((double)t.getEffort() / t.getWorkload())); String comment = t.getComment(); _graph.setToolTipText("<html><p style=\"padding:2 5 2 5;\"><b>" + taskName + "</b>" + "<br>Start: " + start + "   End: " + end + "<br>Effort: " + effort + " days" + "<br>Duration: " + duration + " days" + "<br>Comment: "+comment); } else { _graph.setToolTipText(null); } } public void mousePressed(MouseEvent e) { Point p = e.getPoint(); Object o = _graph.findObjectOnPo(p.x, p.y); _graph.requestFocus(); _pressX = p.x; _pressY = p.y; if(e.getButton() == MouseEvent.BUTTON1 && o instanceof Pair) { // presed on task left/right boundary Pair<Task,Integer> pressedObj = (Pair<Task,Integer>)o; Task t = pressedObj.fst; Integer direction = pressedObj.snd; _pressedTask = t; if(TaskGraphComponent.LEFT.equals(direction)) { _dragMode = DM_TASK_LEFT_BOUNDARY; } else if(TaskGraphComponent.RIGHT.equals(direction)) { _dragMode = DM_TASK_RIGHT_BOUNDARY; } _graph._builder.beginUpdateModelGroup(_pressedTask == null ? "" : "change '" + _graph._model.getTaskName(_pressedTask._userObject) + "'"); return; } GraphObject go = null; if(o instanceof GraphObject) { go = (GraphObject)o; } if(go instanceof Task) { _pressedTask = (Task)go; } else { _pressedTask = null; } if(e.getButton() != MouseEvent.BUTTON1) { _pressedTask = null; } _graph._builder.beginUpdateModelGroup(_pressedTask == null ? "" : "change '" + _graph._model.getTaskName(_pressedTask._userObject) + "'"); if(_pressedTask != null && (e.getModifiers() & MouseEvent.SHIFT_MASK) != 0) { // mouse pressed with shift => start defining new connection _dragMode = DM_NEW_CONNECTION; // draggin new connection } else { if(go == null || e.getButton() == MouseEvent.BUTTON3) { // outside task/connection or with right button -> drag the canvas if(p.x > _graph._graphLeft) { _pressFirstDay = _graph._firstDay; _pressDay = _graph.xToTime(p.x); _dragMode = DM_CANVAS; } return; } else { // if selection contains exactly t, do nothing _graph.repaint(); _dragMode = DM_WHOLE_TASK; // moving whole tasks } } } public void mouseReleased(MouseEvent e) { if(_cursorTaskRow != null && _cursorTime >= 0 && _pressedTask != null) { if(_cursorTaskRow != _pressedTask.getRow()) { // change man of the task _graph._builder.changeTaskRow(_pressedTask, _cursorTaskRow); } // start time must be set after the (possibly new) man is set, // because of different workloads => setting start time recounts also // finish time, duration etc. _pressedTask.setStartTime(_cursorTime); _graph._builder.recountStartingTimes(); } if(_dragMode == DM_TASK_LEFT_BOUNDARY || _dragMode == DM_TASK_RIGHT_BOUNDARY) { if(_pressedTask != null) { _graph._builder.recountStartingTimes(); _graph.repaint(); } } // if creating new connection if(_dragMode == DM_NEW_CONNECTION) { if(_pressedTask != null && _destTask != null && _pressedTask != _destTask) { try { _graph._builder.createConnection(_pressedTask, _destTask); } catch(Exception e1) { JOptionPane.showMessageDialog(_graph, "<html><b>Can't set dependency</b><br><br>" + e1.getMessage()); } _graph._builder.recountStartingTimes(); _graph.repaint(); } } _graph._builder.updateModel(); _graph._builder.endUpdateModelGroup(); _graph.changeCursor(e.getPoint()); _dragMode = DM_NOTHING; _cursorTaskRow = null; _cursorTime = -1; _destTask = null; _pressedTask = null; _graph.repaint(); } public void mouseWheelMoved(MouseWheelEvent e) { int rot = e.getWheelRotation(); if(rot < 0) { for(int i = 0; i < -rot; i++) { _graph.scaleUp(); } } else if(rot > 0) { for(int i = 0; i < rot; i++) { _graph.scaleDown(); } } } int getLastMouseX() { return _dragX; } int getLastMouseY() { return _dragY; } void clearSelection() { for(Object o: _selection) { if(o instanceof GraphObject) { ((GraphObject)o)._selected = false; } } _selection.clear(); } public void keyPressed(KeyEvent e) { /*if(e.getKeyCode() == KeyEvent.VK_DELETE || e.getKeyCode() == KeyEvent.VK_BACK_SPACE) { deleteSelection(); } else */if(e.getKeyCode() == KeyEvent.VK_PAGE_DOWN) { _graph._verticalScroll.setValue(_graph._verticalScroll.getValue() + _graph._graphHeight/2); } else if(e.getKeyCode() == KeyEvent.VK_PAGE_UP) { _graph._verticalScroll.setValue(_graph._verticalScroll.getValue() - _graph._graphHeight/2); } else if(e.getKeyCode() == KeyEvent.VK_UP) { _graph._verticalScroll.setValue(_graph._verticalScroll.getValue() - 10); } else if(e.getKeyCode() == KeyEvent.VK_DOWN) { _graph._verticalScroll.setValue(_graph._verticalScroll.getValue() + 10); } } public void keyReleased(KeyEvent e) { } public void keyTyped(KeyEvent e) { } void deleteSelection() { _graph._model.beginUpdateGroup("Delete"); try { boolean changed = false; for(Object o: _selection) { if(o instanceof Connection) { _graph._builder.removeConnection((Connection)o); changed = true; } } for(Object o: _selection) { if(o instanceof Task) { _graph._builder.removeTask((Task)o); changed = true; } } for(Object o: _selection) { if(o instanceof TaskRow) { _graph._builder.removeRow((TaskRow)o); changed = true; } } if(changed) { _graph.repaint(); } } finally { _graph._model.endUpdateGroup(); } } }