/*
* Copyright 2013 Hewlett-Packard Development Company, L.P
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hp.alm.ali.idea.content.taskboard;
import com.hp.alm.ali.idea.action.ActionUtil;
import com.hp.alm.ali.idea.translate.TranslateService;
import com.hp.alm.ali.idea.translate.Translator;
import com.hp.alm.ali.idea.translate.ValueCallback;
import com.hp.alm.ali.idea.model.type.UserType;
import com.hp.alm.ali.idea.ui.editor.TaskAddInvestedEditor;
import com.hp.alm.ali.idea.ui.event.PopupAdapter;
import com.hp.alm.ali.idea.ui.Highlightable;
import com.hp.alm.ali.idea.ui.SimpleHighlight;
import com.hp.alm.ali.idea.ui.editor.TaskEditor;
import com.hp.alm.ali.idea.model.Entity;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import org.jetbrains.annotations.NonNls;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragSource;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class TaskPanel extends JLayeredPane implements Highlightable, DataProvider {
public static final Dimension SIZE = new Dimension(200, 120);
public static final String TASK_IN_PROGRESS = "In Progress";
public static final String TASK_NEW = "New";
public static final String TASK_COMPLETED = "Completed";
private Entity task;
private JPanel statusPanel;
private JLabel remainingLabel;
private JLabel assigned;
private TimePanel timePanel;
private JPanel content;
private RestrictedTextPane description;
private boolean matched;
private Set<Object> forcedMatch = Collections.synchronizedSet(new HashSet<Object>());
private Gauge gauge;
private JPanel overlay;
private JPanel handlePanel;
private JPanel panelForBorder;
private SimpleHighlight simpleHighlight;
private Project project;
private BacklogItemPanel backlogItemPanel;
private TranslateService translateService;
private Translator userTranslator;
public TaskPanel(final Entity task, final Project project, BacklogItemPanel pBacklogItemPanel) {
this.task = task;
this.project = project;
this.backlogItemPanel = pBacklogItemPanel;
this.translateService = project.getComponent(TranslateService.class);
this.userTranslator = project.getComponent(UserType.class).getTranslator();
content = new JPanel(new BorderLayout());
Color bg = content.getBackground();
boolean darkTheme = isDarkTheme(bg);
if (!darkTheme) {
content.setBackground(new Color(0xFd, 0xF8, 0xCE));
}
content.setBorder(new EmptyBorder(2, 12, 2, 2)); // leave room for the handle
add(content, JLayeredPane.DEFAULT_LAYER);
description = new RestrictedTextPane(SIZE.width - 30, 45);
content.add(description, BorderLayout.CENTER);
statusPanel = new JPanel(new BorderLayout());
statusPanel.setOpaque(false);
JPanel assignedAndTime = new JPanel(new BorderLayout());
assignedAndTime.setOpaque(false);
assigned = new JLabel();
assignedAndTime.add(assigned, BorderLayout.WEST);
timePanel = new TimePanel(project, this);
assignedAndTime.add(timePanel, BorderLayout.EAST);
statusPanel.add(assignedAndTime, BorderLayout.SOUTH);
gauge = new Gauge(0);
JPanel north = new JPanel(new BorderLayout());
north.setOpaque(false);
north.add(gauge, BorderLayout.CENTER);
remainingLabel = new JLabel(IconLoader.getIcon("/general/secondaryGroup.png"));
north.add(remainingLabel, BorderLayout.WEST);
statusPanel.add(north, BorderLayout.NORTH);
content.add(statusPanel, BorderLayout.SOUTH);
// no mouse events due to overlays, must be handled in the upper layer
final JLabel moreLink = new JLabel(IconLoader.getIcon("/actions/forward.png"));
moreLink.setVerticalAlignment(SwingConstants.TOP);
moreLink.setToolTipText("More Actions");
content.add(moreLink, BorderLayout.EAST);
overlay = new JPanel();
overlay.setOpaque(true);
if (darkTheme) {
overlay.setBackground(new Color(bg.getRed(), bg.getGreen(), bg.getBlue(), 128));
} else {
overlay.setBackground(new Color(0xFF, 0xFF, 0xFF, 128));
}
overlay.setVisible(false);
add(overlay, JLayeredPane.PALETTE_LAYER);
panelForBorder = new JPanel();
panelForBorder.setOpaque(false);
panelForBorder.setBorder(createPanelBorder());
add(panelForBorder, new Integer(PALETTE_LAYER - 1));
handlePanel = new JPanel(new BorderLayout());
handlePanel.setOpaque(false);
JLabel handle = new JLabel(IconLoader.getIcon("/handle.png"), JLabel.LEADING);
handle.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
handle.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
handle.addMouseListener(new PopupAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() > 1) {
TaskEditor taskEditor = new TaskEditor(project, getTask());
taskEditor.execute();
} else {
Point point = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), moreLink);
if(moreLink.contains(point)) {
ActionPopupMenu popupMenu = ActionUtil.createActionPopup("hpali.task", "taskboard");
popupMenu.getComponent().show(moreLink, 0, 0);
} else {
timePanel.mouseClickedPropagate(e);
}
}
}
@Override
public void onPopup(MouseEvent e) {
ActionPopupMenu popupMenu = ActionUtil.createActionPopup("hpali.task", "taskboard");
popupMenu.getComponent().show(e.getComponent(), e.getX(), e.getY());
}
});
handlePanel.add(handle, BorderLayout.CENTER);
add(handlePanel, JLayeredPane.DRAG_LAYER);
simpleHighlight = new SimpleHighlight(description);
DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(handle, DnDConstants.ACTION_COPY, new TaskTransferable(this));
}
public static Border createPanelBorder() {
return BorderFactory.createLineBorder(UIManager.getDefaults().getColor("Table.gridColor"));
}
public Dimension getPreferredSize() {
// NOTE: the dimension of the overlay is 1 pixel less otherwise the base layer is not repainted sometimes (e.g. when scrolling)
content.setBounds(new Rectangle(0, 0, SIZE.width, SIZE.height));
overlay.setBounds(new Rectangle(0, 0, SIZE.width - 1, SIZE.height - 1));
handlePanel.setBounds(new Rectangle(0, 0, SIZE.width - 1, SIZE.height - 1));
panelForBorder.setBounds(new Rectangle(0, 0, SIZE.width, SIZE.height));
return SIZE;
}
public void update(Entity task) {
this.task = task;
moveTaskAsNeeded();
String remaining = task.getPropertyValue("remaining");
String invested = task.getPropertyValue("invested");
String status = task.getPropertyValue("status");
String assignedTo = task.getPropertyValue("assigned-to");
if(!assignedTo.isEmpty()) {
translateService.translateAsync(userTranslator, assignedTo, true, new ValueCallback() {
@Override
public void value(String value) {
assigned.setText(value);
}
});
} else {
assigned.setText("<Unassigned>");
}
if(TASK_IN_PROGRESS.equals(status)) {
gauge.setVisible(true);
gauge.setValue(Double.valueOf(invested) / (Double.valueOf(remaining) + Double.valueOf(invested)));
remainingLabel.setVisible(false);
} else if(TASK_NEW.equals(status)) {
gauge.setVisible(false);
remainingLabel.setVisible(true);
remainingLabel.setText(remaining + " hours remaining");
} else {
gauge.setVisible(false);
remainingLabel.setVisible(false);
}
description.setText(task.getPropertyValue("description"));
timePanel.update(task);
getBacklogItemPanel().applyFilter();
}
private void moveTaskAsNeeded() {
TaskContainerPanel oldContainer = (TaskContainerPanel)getParent();
TaskContainerPanel newContainer = backlogItemPanel.getTaskContainer(task.getPropertyValue("status"));
if(oldContainer.equals(newContainer)) {
return;
}
newContainer.addTask(this);
newContainer.revalidate();
newContainer.repaint();
oldContainer.revalidate();
oldContainer.repaint();
}
public String getStatus() {
return task.getPropertyValue("status");
}
public Entity getTask() {
return task;
}
@Override
public void setFilter(String filter) {
simpleHighlight.setFilter(filter);
}
@Override
public void setMatch(boolean match) {
matched = match;
doOverlay();
}
public void addForcedMatch(Object o) {
forcedMatch.add(o);
doOverlay();
}
public void removeForcedMatch(Object o) {
forcedMatch.remove(o);
doOverlay();
}
private void doOverlay() {
overlay.setVisible(!matched && forcedMatch.isEmpty());
repaint(); // otherwise unmatched background is not painted properly
}
private boolean isDarkTheme(Color background) {
double value = 0.21 * background.getRed() + 0.72 * background.getGreen() + 0.07 * background.getBlue();
return value < 128;
}
public BacklogItemPanel getBacklogItemPanel() {
return backlogItemPanel;
}
@Override
public Object getData(@NonNls String s) {
if("entity-list".equals(s)) {
return Arrays.asList(task);
}
return null;
}
public void setDropTargetListener(DropTargetListener dropTargetListener) {
setDropTarget(new DropTarget(this, DnDConstants.ACTION_MOVE, dropTargetListener, true, null));
description.setDropTarget(new DropTarget(description, DnDConstants.ACTION_MOVE, dropTargetListener, true, null));
}
}