/*
* 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.cfg.AliProjectConfiguration;
import com.hp.alm.ali.idea.cfg.TaskBoardConfiguration;
import com.hp.alm.ali.idea.services.ActiveItemService;
import com.hp.alm.ali.idea.ui.EntityLabel;
import com.hp.alm.ali.idea.services.EntityService;
import com.hp.alm.ali.idea.ui.ScrollablePanel;
import com.hp.alm.ali.idea.action.ActionUtil;
import com.hp.alm.ali.idea.services.EntityLabelService;
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.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.ui.components.labels.LinkLabel;
import com.intellij.ui.components.labels.LinkListener;
import com.intellij.util.ui.UIUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NonNls;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class BacklogItemPanel extends ScrollablePanel implements Highlightable, DataProvider {
public static final String ITEM_NEW = "New";
public static final String ITEM_IN_PROGRESS = "In Progress";
public static final String ITEM_IN_TESTING = "In Testing";
public static final String ITEM_DONE = "Done";
public static final Dimension DIMENSION = new Dimension(300, 160);
private Project project;
private EntityLabelService entityLabelService;
private EntityService entityService;
private ActiveItemService activeItemService;
private TaskBoardConfiguration taskBoardConfiguration;
private TaskBoardFlow taskBoardFlow;
private AliProjectConfiguration aliProjectConfiguration;
private Map<String, TaskContainerPanel> taskContainers;
private JComponent taskContent;
private Map<Integer, TaskPanel> tasks;
private Header header;
private Content content;
private JTextPane entityName;
private Entity item;
private SimpleHighlight simpleHighlight;
private TaskBoardFilter filter;
public BacklogItemPanel(Project project, Entity item, TaskBoardFilter filter) {
super(new BorderLayout());
this.project = project;
this.item = item;
this.filter = filter;
entityLabelService = project.getComponent(EntityLabelService.class);
entityService = project.getComponent(EntityService.class);
activeItemService = project.getComponent(ActiveItemService.class);
taskBoardConfiguration = project.getComponent(TaskBoardConfiguration.class);
taskBoardFlow = project.getComponent(TaskBoardFlow.class);
aliProjectConfiguration = project.getComponent(AliProjectConfiguration.class);
header = new Header();
header.setBorder(new EmptyBorder(0, 5, 0, 5));
add(header, BorderLayout.NORTH);
entityName = new JTextPane();
entityName.setBackground(getBackground());
entityName.setEditable(false);
add(entityName, BorderLayout.CENTER);
content = new Content();
content.setBorder(new EmptyBorder(0, 5, 10, 5));
add(content, BorderLayout.SOUTH);
tasks = new HashMap<Integer, TaskPanel>();
taskContainers = new HashMap<String, TaskContainerPanel>();
taskContainers.put(TaskPanel.TASK_NEW, new TaskContainerPanel(this, TaskPanel.TASK_NEW, item));
taskContainers.put(TaskPanel.TASK_IN_PROGRESS, new TaskContainerPanel(this, TaskPanel.TASK_IN_PROGRESS, item));
taskContainers.put(TaskPanel.TASK_COMPLETED, new TaskContainerPanel(this, TaskPanel.TASK_COMPLETED, item));
taskContent = new JPanel(new GridLayout(1, 3));
taskContent.add(getTaskContainer(TaskPanel.TASK_NEW));
taskContent.add(getTaskContainer(TaskPanel.TASK_IN_PROGRESS));
taskContent.add(getTaskContainer(TaskPanel.TASK_COMPLETED));
Color gridColor = UIManager.getDefaults().getColor("Table.gridColor");
taskContent.setBorder(BorderFactory.createMatteBorder(0, 1, 1, 0, gridColor));
setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, gridColor));
// we don't use gap in grid layout to avoid trailing line (on the right)
getTaskContainer(TaskPanel.TASK_IN_PROGRESS).setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, gridColor));
getTaskContainer(TaskPanel.TASK_COMPLETED).setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, gridColor));
simpleHighlight = new SimpleHighlight(entityName);
setPreferredSize(DIMENSION);
update(item);
}
public void update(Entity item) {
this.item = item;
header.update();
content.update();
entityName.setText(item.getPropertyValue("entity-name"));
applyFilter();
}
public void updateTask(final Entity task, final boolean created) {
TaskPanel taskPanel = tasks.get(task.getId());
if(taskPanel == null) {
taskPanel = new TaskPanel(task, project, this);
tasks.put(task.getId(), taskPanel);
getTaskContainer(task.getPropertyValue("status")).addTask(taskPanel);
}
taskPanel.update(task);
applyFilter();
final TaskPanel fTaskPanel = taskPanel;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
// task panel is (most of the time) not painted properly when doing full reload (refresh in the Settings dialog)
// if the task is filtered out, only half of the task rectangle has the correct alpha
// additional repaint (later) seems to fix the issue
fTaskPanel.repaint();
if(created) {
TaskContainerPanel taskContainer = (TaskContainerPanel)fTaskPanel.getParent();
taskContainer.scrollRectToVisible(fTaskPanel.getBounds());
}
}
});
}
public void moveTask(final Entity task, final String newStatus) {
final TaskPanel taskPanel = tasks.get(task.getId());
task.setProperty("status", newStatus);
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
HashSet<String> properties = new HashSet<String>();
properties.add("status");
if (TaskPanel.TASK_IN_PROGRESS.equals(newStatus)) {
// when moving task to progress
if (taskBoardConfiguration.isAssignTask() && task.getPropertyValue("assigned-to").isEmpty()) {
// assign them to me if they are unassigned
task.setProperty("assigned-to", aliProjectConfiguration.getUsername());
properties.add("assigned-to");
}
if (taskBoardConfiguration.isActivateItem()) {
// activate work item
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
Entity workItem = new Entity(item.getPropertyValue("entity-type"), Integer.valueOf(item.getPropertyValue("entity-id")));
activeItemService.activate(workItem, true, false);
}
});
}
}
final Entity updatedTask = entityService.updateEntity(task, properties, false, true);
if(updatedTask == null) {
return; // update failed
}
taskBoardFlow.onTaskUpdated(updatedTask, item);
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
taskPanel.update(updatedTask);
}
});
}
});
}
public TaskContainerPanel getTaskContainer(String status) {
return taskContainers.get(status);
}
private Double getValue(Entity entity, String property) {
try {
return Double.valueOf(entity.getPropertyValue(property));
} catch (NumberFormatException e) {
return 0.0;
}
}
public JComponent getTaskContent() {
return taskContent;
}
public void applyFilter() {
Map<Entity, Boolean> matches = matches();
if(matches.containsValue(true)) {
setHighlight(this, matches.get(item), filter.getFilter());
for(TaskPanel taskPanel: tasks.values()) {
Entity task = taskPanel.getTask();
setHighlight(taskPanel, matches.get(task), filter.getFilter());
}
getTaskContent().setVisible(true);
setVisible(true);
} else {
getTaskContent().setVisible(false);
setVisible(false);
}
}
private void setHighlight(Highlightable panel, boolean match, String filter) {
panel.setFilter(filter);
panel.setMatch(match);
}
private Map<Entity, Boolean> matches() {
HashMap<Entity, Boolean> ret = new HashMap<Entity, Boolean>();
boolean typeMatch;
if(item.getPropertyValue("entity-type").equals("requirement")) {
typeMatch = filter.isUserStories();
} else {
typeMatch = filter.isDefects();
}
if(!filter.getStatus().contains(item.getPropertyValue("status"))) {
typeMatch = false;
}
if (!filter.isBlocked() && !item.getPropertyValue("blocked").isEmpty()) {
typeMatch = false;
}
ret.put(item, typeMatch && filterMatches(filter.getFilter(), item, "entity-name") && ownerMatches(filter.getAssignedTo(), item, "owner"));
for(TaskPanel taskPanel: tasks.values()) {
Entity task = taskPanel.getTask();
ret.put(task, typeMatch && filterMatches(filter.getFilter(), task, "description") && ownerMatches(filter.getAssignedTo(), task, "assigned-to"));
}
return ret;
}
private boolean filterMatches(String filter, Entity entity, String property) {
if(filter == null || filter.isEmpty()) {
return true;
}
if(StringUtils.containsIgnoreCase(entity.getPropertyValue(property), filter)) {
return true;
}
return false;
}
private boolean ownerMatches(String assignedTo, Entity entity, String property) {
if(assignedTo == null) {
return true;
}
return assignedTo.equals(entity.getPropertyValue(property));
}
@Override
public void setFilter(String filter) {
simpleHighlight.setFilter(filter);
}
@Override
public void setMatch(boolean match) {
}
public TaskPanel getTaskPanel(Entity task) {
return getTaskPanel(task.getId());
}
public TaskPanel getTaskPanel(int id) {
return tasks.get(id);
}
public void retainTasks(Set<Integer> taskIds) {
boolean modified = false;
for(Iterator<Integer> it = tasks.keySet().iterator(); it.hasNext(); ) {
int taskId = it.next();
if(!taskIds.contains(taskId)) {
TaskPanel taskPanel = tasks.get(taskId);
TaskContainerPanel taskContainer = getTaskContainer(taskPanel.getStatus());
taskContainer.remove(taskPanel);
taskContainer.repaint();
it.remove();
modified = true;
}
}
if(modified) {
applyFilter();
}
}
public void removeTaskPanel(TaskPanel taskPanel) {
TaskContainerPanel taskContainer = getTaskContainer(taskPanel.getStatus());
taskContainer.remove(taskPanel);
taskContainer.repaint();
tasks.remove(taskPanel.getTask().getId());
applyFilter();
}
public Entity getItem() {
return item;
}
@Override
public Object getData(@NonNls String s) {
if("entity-list".equals(s)) {
Entity entity = new Entity(item.getPropertyValue("entity-type"), Integer.valueOf(item.getPropertyValue("entity-id")));
entity.mergeRelatedEntity(item);
return Arrays.asList(entity);
}
return null;
}
private class RbiStatus extends JPanel {
private JLabel value;
public RbiStatus() {
super(new FlowLayout(FlowLayout.LEFT, 0, 0));
setOpaque(false);
add(new JLabel("Status: "));
value = new JLabel();
add(value);
}
public void setValue(String status) {
value.setText(status);
}
}
private class Content extends JPanel {
private Gauge gauge;
private RbiStatus status;
private JLabel remainingLabel;
public Content() {
super(new BorderLayout());
setOpaque(false);
JPanel north = new JPanel(new BorderLayout());
north.setOpaque(false);
gauge = new Gauge(0);
north.add(gauge, BorderLayout.CENTER);
add(north, BorderLayout.NORTH);
JPanel south = new JPanel(new BorderLayout());
south.setOpaque(false);
status = new RbiStatus();
south.add(status, BorderLayout.WEST);
remainingLabel = new JLabel();
remainingLabel.setBorder(new EmptyBorder(0, 0, 0, 3));
south.add(remainingLabel, BorderLayout.EAST);
add(south, BorderLayout.SOUTH);
update();
}
public void update() {
Double estimated = getValue(item, "estimated");
Double remaining = getValue(item, "remaining");
if(estimated > 0.0) {
gauge.setValue((estimated - remaining) / estimated);
} else {
gauge.setValue(0);
}
status.setValue(item.getPropertyValue("status"));
if(remaining > 0.0) {
remainingLabel.setText(Math.round(remaining) + " hours remaining");
remainingLabel.setVisible(true);
} else {
remainingLabel.setVisible(false);
}
}
}
private class Header extends JPanel {
private JLabel blocked;
public Header() {
super(new BorderLayout());
setOpaque(false);
JPanel westPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
blocked = new JLabel(IconLoader.getIcon("/blocked.png"));
blocked.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 2));
westPanel.add(blocked);
westPanel.add(new EntityLabel(item.getPropertyValue("entity-type"), entityLabelService, " | ID " + item.getPropertyValue("entity-id")));
add(westPanel, BorderLayout.WEST);
JPanel toolbar = new JPanel();
toolbar.setOpaque(false);
LinkLabel addLink = new LinkLabel("", IconLoader.getIcon("/general/add.png"));
addLink.setToolTipText("Add Task");
addLink.setListener(new LinkListener() {
@Override
public void linkSelected(LinkLabel linkLabel, Object o) {
TaskEditor editor = new TaskEditor(project, item.getId());
editor.execute();
}
}, null);
toolbar.add(addLink);
final LinkLabel moreLink = new LinkLabel("", IconLoader.getIcon("/actions/forward.png"));
moreLink.setToolTipText("More Actions");
moreLink.setListener(new LinkListener() {
@Override
public void linkSelected(LinkLabel linkLabel, Object o) {
ActionPopupMenu actionPopup = ActionUtil.createEntityActionPopup("taskboard");
actionPopup.getComponent().show(moreLink, 0, 0);
}
}, null);
toolbar.add(moreLink);
add(toolbar, BorderLayout.EAST);
update();
}
public void update() {
String reason = item.getPropertyValue("blocked");
if (!reason.isEmpty()) {
blocked.setToolTipText("Blocking reason: " + reason);
blocked.setVisible(true);
} else {
blocked.setVisible(false);
}
}
}
}