com.hp.alm.ali.idea.content.taskboard.TaskBoardPanel.java Source code

Java tutorial

Introduction

Here is the source code for com.hp.alm.ali.idea.content.taskboard.TaskBoardPanel.java

Source

/*
 * 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.TaskBoardConfiguration;
import com.hp.alm.ali.idea.entity.EntityQuery;
import com.hp.alm.ali.idea.entity.EntityRef;
import com.hp.alm.ali.idea.entity.queue.QueryQueue;
import com.hp.alm.ali.idea.entity.queue.QueryTarget;
import com.hp.alm.ali.idea.ui.ComboItem;
import com.hp.alm.ali.idea.ui.MultiValueSelectorLabel;
import com.hp.alm.ali.idea.ui.QuickSearchPanel;
import com.hp.alm.ali.idea.services.EntityService;
import com.hp.alm.ali.idea.action.ActionUtil;
import com.hp.alm.ali.idea.entity.EntityListener;
import com.hp.alm.ali.idea.content.AliContentFactory;
import com.hp.alm.ali.idea.ui.ReleaseChooser;
import com.hp.alm.ali.idea.ui.SprintChooser;
import com.hp.alm.ali.idea.services.SprintService;
import com.hp.alm.ali.idea.ui.entity.EntityStatusPanel;
import com.hp.alm.ali.idea.model.Entity;
import com.hp.alm.ali.idea.model.parser.EntityList;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.ui.components.labels.BoldLabel;
import com.intellij.util.ui.UIUtil;
import org.apache.commons.lang.StringUtils;

import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SortOrder;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class TaskBoardPanel extends JPanel implements SprintService.Listener, EntityListener {

    public static final int BACKLOG_ITEM_PAGE_SIZE = 200;
    public static final int TASK_PAGE_SIZE = 1000;
    public static final int MIN_COLUMN_WIDTH = 250;
    public static final String PLACE = "HPALI.TaskBoard";

    private static final List<String> allItemStatuses = Arrays.asList(BacklogItemPanel.ITEM_NEW,
            BacklogItemPanel.ITEM_IN_PROGRESS, BacklogItemPanel.ITEM_IN_TESTING, BacklogItemPanel.ITEM_DONE);

    private Project project;
    private EntityService entityService;
    private SprintService sprintService;
    private QueryQueue queue;

    private EntityStatusPanel status;

    private Header header;
    private ColumnHeader columnHeader;
    private Content content;
    private TaskPanel forcedTaskPanel;

    public TaskBoardPanel(final Project project) {
        super(new BorderLayout());

        this.project = project;

        status = new EntityStatusPanel(project);
        queue = new QueryQueue(project, status, false);

        entityService = project.getComponent(EntityService.class);
        entityService.addEntityListener(this);
        sprintService = project.getComponent(SprintService.class);
        sprintService.addListener(this);

        loadTasks();

        header = new Header();
        columnHeader = new ColumnHeader();

        content = new Content();
        add(content, BorderLayout.NORTH);

        header.assignedTo.reload();

        // force mouse-over task as visible (otherwise events are captured by the overlay and repaint quirks)
        Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
            @Override
            public void eventDispatched(AWTEvent event) {
                if (isShowing() && event.getID() == MouseEvent.MOUSE_MOVED) {
                    MouseEvent m = (MouseEvent) event;
                    TaskPanel currentPanel = locateContainer(m, TaskPanel.class);
                    if (currentPanel != null) {
                        if (forcedTaskPanel == currentPanel) {
                            return;
                        } else if (forcedTaskPanel != null) {
                            forcedTaskPanel.removeForcedMatch(this);
                        }
                        forcedTaskPanel = currentPanel;
                        forcedTaskPanel.addForcedMatch(this);
                    } else if (forcedTaskPanel != null) {
                        forcedTaskPanel.removeForcedMatch(this);
                        forcedTaskPanel = null;
                    }
                }
            }
        }, AWTEvent.MOUSE_MOTION_EVENT_MASK);

        Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
            @Override
            public void eventDispatched(AWTEvent event) {
                if (isShowing()) {
                    MouseEvent m = (MouseEvent) event;
                    switch (event.getID()) {
                    case MouseEvent.MOUSE_PRESSED:
                    case MouseEvent.MOUSE_RELEASED:
                        // implement backlog item popup
                        if (m.isPopupTrigger()) {
                            final BacklogItemPanel itemPanel = locateContainer(m, BacklogItemPanel.class);
                            if (itemPanel != null) {
                                ActionPopupMenu popupMenu = ActionUtil.createEntityActionPopup("taskboard");
                                Point p = SwingUtilities.convertPoint(m.getComponent(), m.getPoint(), itemPanel);
                                popupMenu.getComponent().show(itemPanel, p.x, p.y);
                            }
                        }
                        break;

                    case MouseEvent.MOUSE_CLICKED:
                        // implement backlog item double click
                        if (m.getClickCount() > 1) {
                            BacklogItemPanel itemPanel = locateContainer(m, BacklogItemPanel.class);
                            if (itemPanel != null) {
                                Entity backlogItem = itemPanel.getItem();
                                Entity workItem = new Entity(backlogItem.getPropertyValue("entity-type"),
                                        Integer.valueOf(backlogItem.getPropertyValue("entity-id")));
                                AliContentFactory.loadDetail(project, workItem, true, true);
                            }
                        }
                    }
                }

            }
        }, AWTEvent.MOUSE_EVENT_MASK);
    }

    private <T extends Component> T locateContainer(MouseEvent event, Class<T> clazz) {
        Point p = SwingUtilities.convertPoint((Component) event.getSource(), event.getPoint(), TaskBoardPanel.this);
        Component comp = SwingUtilities.getDeepestComponentAt(this, p.x, p.y);
        return (T) SwingUtilities.getAncestorOfClass(clazz, comp);
    }

    private void loadTasks() {
        Entity sprint = sprintService.getSprint();
        Entity team = sprintService.getTeam();
        if (sprint != null && team != null) {
            loadTasks(sprint, team, Collections.<Entity>emptyList(), Collections.<Entity>emptyList());
        }
    }

    private void loadTasks(final Entity sprint, final Entity team, final List<Entity> previousBacklogItems,
            final List<Entity> previousTasks) {
        EntityQuery query = new EntityQuery("release-backlog-item");
        query.setStartIndex(previousBacklogItems.size() + 1);
        query.setPageSize(BACKLOG_ITEM_PAGE_SIZE);
        query.setValue("is-leaf", "Y");
        query.setValue("team-id", team.getPropertyValue("id"));
        query.setValue("sprint-id", String.valueOf(sprint.getId()));
        query.setPropertyResolved("is-leaf", true);
        query.setPropertyResolved("team-id", true);
        query.addOrder("rank", SortOrder.ASCENDING);
        query.addOrder("id", SortOrder.ASCENDING);
        queue.query(query, new QueryTarget() {
            @Override
            public void handleResult(EntityList list) {
                updateBacklogItems(sprint, team, list, previousBacklogItems, previousTasks);
            }
        });
    }

    private void updateBacklogItems(Entity sprint, Entity team, final EntityList items,
            final List<Entity> previousBacklogItems, final List<Entity> previousTasks) {
        final Runnable redo = new Runnable() {
            @Override
            public void run() {
                loadTasks();
            }
        };
        UIUtil.invokeLaterIfNeeded(new Runnable() {
            @Override
            public void run() {
                EntityList merged = EntityList.empty();
                merged.addAll(previousBacklogItems);
                merged.addAll(items);

                content.retain(merged);
                if (merged.isEmpty()) {
                    EntityList empty = EntityList.empty();
                    content.retainTasks(empty);
                    status.loaded(merged, redo);
                } else if (items.isEmpty()) {
                    // no more items loaded (only possible when paging is inconsistent)
                    String backlogItemsCount = EntityStatusPanel.getItemCountString(previousBacklogItems.size(),
                            previousBacklogItems.size(), "backlog items");
                    String tasksCount = EntityStatusPanel.getItemCountString(previousTasks.size(),
                            previousTasks.size(), "tasks");
                    status.info("Loaded " + backlogItemsCount + " and " + tasksCount, null, redo, null);
                } else {
                    status.info("Loaded "
                            + EntityStatusPanel.getItemCountString(items.getTotal(), merged.size(), "backlog items")
                            + ", loading tasks...", null, redo, null);
                    for (int i = previousBacklogItems.size(); i < merged.size(); i++) {
                        updateBacklogItem(merged.get(i), false, i);
                    }
                }
            }
        });
        if (!items.isEmpty()) {
            loadTasksChunk(sprint, team, items, previousBacklogItems, previousTasks.size(), previousTasks, 1, redo);
        }
    }

    private void loadTasksChunk(final Entity sprint, final Entity team, final EntityList backlogItems,
            final List<Entity> previousBacklogItems, final int previousTasksTotalCount,
            final List<Entity> previousTasks, final int startIndex, final Runnable redo) {
        EntityQuery query = new EntityQuery("project-task");
        query.addOrder("id", SortOrder.ASCENDING);
        query.setOrValues("release-backlog-item-id", backlogItems.getIdStrings());
        query.setStartIndex(startIndex);
        query.setPageSize(TASK_PAGE_SIZE);
        final EntityList tasks = entityService.query(query);
        UIUtil.invokeLaterIfNeeded(new Runnable() {
            @Override
            public void run() {
                final EntityList merged = EntityList.empty();
                merged.addAll(previousTasks);
                merged.addAll(tasks);

                content.retainTasks(merged);

                for (Entity task : tasks) {
                    updateTask(task, false);
                }
                Runnable more = null;
                if (tasks.getTotal() > tasks.size() + startIndex - 1) {
                    more = new Runnable() {
                        @Override
                        public void run() {
                            loadTasksChunk(sprint, team, backlogItems, previousBacklogItems,
                                    previousTasksTotalCount, merged, startIndex + tasks.size(), redo);
                        }
                    };
                } else if (backlogItems.getTotal() > previousBacklogItems.size() + backlogItems.size()) {
                    more = new Runnable() {
                        @Override
                        public void run() {
                            ArrayList<Entity> mergedItems = new ArrayList<Entity>();
                            mergedItems.addAll(previousBacklogItems);
                            mergedItems.addAll(backlogItems);
                            loadTasks(sprint, team, mergedItems, merged);
                        }
                    };
                }
                String backlogItemsCount = EntityStatusPanel.getItemCountString(backlogItems.getTotal(),
                        previousBacklogItems.size() + backlogItems.size(), "backlog items");
                String tasksCount = EntityStatusPanel.getItemCountString(previousTasksTotalCount + tasks.getTotal(),
                        merged.size(), "tasks");
                status.info("Loaded " + backlogItemsCount + " and " + tasksCount, null, redo, more);
            }
        });
    }

    private void loadTasksOfNewlyAddedBli(Entity entity) {
        EntityQuery query = new EntityQuery("project-task");
        query.setValue("release-backlog-item-id", entity.getPropertyValue("id"));
        final EntityList tasks = entityService.query(query);
        UIUtil.invokeLaterIfNeeded(new Runnable() {
            @Override
            public void run() {
                for (Entity task : tasks) {
                    updateTask(task, false);
                }
            }
        });
    }

    private void updateTask(Entity task, boolean created) {
        ApplicationManager.getApplication().assertIsDispatchThread();

        content.updateTask(task, created);
    }

    private void removeTask(int id) {
        ApplicationManager.getApplication().assertIsDispatchThread();

        content.removeTask(id);
    }

    private boolean updateBacklogItem(Entity item, boolean validate, int index) {
        ApplicationManager.getApplication().assertIsDispatchThread();

        return content.updateItem(item, validate, index);
    }

    @Override
    public void onReleaseSelected(Entity release) {
    }

    @Override
    public void onSprintSelected(Entity sprint) {
        loadTasks();
    }

    @Override
    public void onTeamSelected(Entity team) {
        loadTasks();
    }

    public Component getStatusComponent() {
        return status;
    }

    public JComponent getHeader() {
        return header;
    }

    public JComponent getColumnHeader() {
        return columnHeader;
    }

    @Override
    public void entityLoaded(final Entity entity, final Event event) {
        if ("project-task".equals(entity.getType())) {
            UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                    updateTask(entity, event == Event.CREATE);
                }
            });
        } else if ("release-backlog-item".equals(entity.getType())) {
            UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                    if (inThisSprint(entity)) {
                        int index = content.getItemIndex(entity);
                        if (updateBacklogItem(entity, true, index) && event != Event.CREATE) {
                            // only load tasks for non-created BLIs only - events will follow for those we create ourselves
                            ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
                                @Override
                                public void run() {
                                    loadTasksOfNewlyAddedBli(entity);
                                }
                            });
                        }
                    } else {
                        content.removeBacklogItem(entity, true);
                    }
                }
            });
        }
    }

    @Override
    public void entityNotFound(final EntityRef ref, boolean removed) {
        if ("project-task".equals(ref.type)) {
            UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                    removeTask(ref.id);
                }
            });
        } else if ("defect".equals(ref.type) || "requirement".equals(ref.type)) {
            UIUtil.invokeLaterIfNeeded(new Runnable() {
                @Override
                public void run() {
                    content.removeBacklogItemOfWorkItem(ref);
                }
            });
        }
    }

    private boolean inThisSprint(Entity entity) {
        Entity sprint = sprintService.getSprint();
        Entity team = sprintService.getTeam();
        return sprint != null && sprint.getPropertyValue("id").equals(entity.getPropertyValue("sprint-id"))
                && team != null && team.getPropertyValue("id").equals(entity.getPropertyValue("team-id"));
    }

    private class Header extends JPanel implements TaskBoardFilter {

        private QuickSearchPanel quickSearchPanel;
        private AssignedToComboBox assignedTo;
        private JCheckBox stories;
        private JCheckBox defects;
        private JCheckBox blocked;
        private MultiValueSelectorLabel statusFilter;

        public Header() {
            super(new BorderLayout());

            final TaskBoardConfiguration conf = project.getComponent(TaskBoardConfiguration.class);
            JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0)); // using vertical spacing makes the bottom gap too wide (not sure why)
            toolbar.setBorder(BorderFactory.createEtchedBorder());
            toolbar.add(new ReleaseChooser(project));
            quickSearchPanel = new QuickSearchPanel(conf.getFilter(), new QuickSearchPanel.Target() {
                @Override
                public void executeFilter(String value) {
                    conf.setFilter(value);
                    content.applyFilter();
                }
            }, true);
            quickSearchPanel.setBorder(new EmptyBorder(2, 2, 2, 0)); // ad-hoc fix to achieve same layout backlog content has
            toolbar.add(quickSearchPanel);
            SprintChooser sprintChooser = new SprintChooser(project);
            toolbar.add(sprintChooser);
            assignedTo = new AssignedToComboBox(project, conf.getAssignedTo());
            assignedTo.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    if (e.getStateChange() == ItemEvent.SELECTED) {
                        conf.setAssignedTo(((ComboItem) assignedTo.getSelectedItem()).getKey().toString());
                        content.applyFilter();
                    }
                }
            });
            toolbar.add(new JLabel("Assigned To:"));
            toolbar.add(assignedTo);

            String showStatuses = conf.getShowStatuses();
            List<String> selectedItems;
            if (TaskBoardConfiguration.ALL_STATUSES.equals(showStatuses)) {
                selectedItems = allItemStatuses;
            } else {
                selectedItems = Arrays.asList(showStatuses.split(";"));
            }
            statusFilter = new MultiValueSelectorLabel(project, "Status", selectedItems, allItemStatuses);
            statusFilter.addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    Set<String> selectedValues = statusFilter.getSelectedValues();
                    if (selectedValues.size() == allItemStatuses.size()) {
                        conf.setShowStatuses(TaskBoardConfiguration.ALL_STATUSES);
                    } else {
                        conf.setShowStatuses(StringUtils.join(selectedValues, ";"));
                    }
                    content.applyFilter();
                }
            });
            toolbar.add(statusFilter);

            toolbar.add(new JLabel("Show:"));
            stories = new JCheckBox("User stories", conf.isShowUserStories());
            stories.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    conf.setShowUserStories(stories.isSelected());
                    content.applyFilter();
                }
            });
            toolbar.add(stories);
            defects = new JCheckBox("Defects", conf.isShowDefects());
            defects.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    conf.setShowDefects(defects.isSelected());
                    content.applyFilter();
                }
            });
            toolbar.add(defects);
            blocked = new JCheckBox("Blocked", conf.isShowBlocked());
            blocked.addItemListener(new ItemListener() {
                @Override
                public void itemStateChanged(ItemEvent e) {
                    conf.setShowBlocked(blocked.isSelected());
                    content.applyFilter();
                }
            });
            toolbar.add(blocked);

            add(toolbar, BorderLayout.NORTH);
            add(sprintChooser.getWarningPanel());
        }

        @Override
        public String getFilter() {
            return quickSearchPanel.getValue();
        }

        @Override
        public String getAssignedTo() {
            Object selectedItem = assignedTo.getSelectedItem();
            if (selectedItem == null || AssignedToComboBox.ALL.equals(selectedItem)) {
                return null;
            } else if (AssignedToComboBox.UNASSIGNED.equals(selectedItem)) {
                return "";
            } else {
                return ((ComboItem) selectedItem).getKey().toString();
            }
        }

        @Override
        public boolean isUserStories() {
            return stories.isSelected();
        }

        @Override
        public boolean isDefects() {
            return defects.isSelected();
        }

        @Override
        public boolean isBlocked() {
            return blocked.isSelected();
        }

        @Override
        public Set<String> getStatus() {
            return statusFilter.getSelectedValues();
        }
    }

    private class ColumnHeader extends JPanel {

        public ColumnHeader() {
            super(new GridBagLayout());

            GridBagConstraints c = new GridBagConstraints();
            c.fill = GridBagConstraints.HORIZONTAL;
            c.gridx = 0;
            c.gridy = 0;
            c.gridwidth = 1;
            c.weightx = 0;
            c.anchor = GridBagConstraints.NORTHWEST;
            JComponent rbiHeader = columnHeader("Backlog Item");
            rbiHeader.setPreferredSize(new Dimension(BacklogItemPanel.DIMENSION.width, 26));
            add(rbiHeader, c);
            c.fill = GridBagConstraints.BOTH;
            c.gridx++;
            c.weightx = 1;
            JPanel taskHeader = new JPanel(new GridLayout(1, 3));
            taskHeader.add(columnHeader(TaskPanel.TASK_NEW));
            taskHeader.add(columnHeader(TaskPanel.TASK_IN_PROGRESS));
            taskHeader.add(columnHeader(TaskPanel.TASK_COMPLETED));
            add(taskHeader, c);
        }
    }

    private class Content extends JPanel {

        private Map<Entity, BacklogItemPanel> items = new HashMap<Entity, BacklogItemPanel>();

        public Content() {
            super(new GridBagLayout());
        }

        public boolean updateItem(Entity item, boolean validate, int index) {
            GridBagConstraints c = new GridBagConstraints();
            c.gridy = index;
            c.gridx = 0;
            c.fill = GridBagConstraints.VERTICAL;
            c.anchor = GridBagConstraints.NORTHEAST;
            c.insets = new Insets(0, 0, 1, 1);
            BacklogItemPanel backlogItemPanel = items.get(item);
            boolean created;
            if (backlogItemPanel == null) {
                backlogItemPanel = new BacklogItemPanel(project, item, header);
                items.put(item, backlogItemPanel);
                backlogItemPanel.applyFilter();
                if (validate) {
                    revalidate();
                    repaint();

                    final BacklogItemPanel newItemPanel = backlogItemPanel;
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            scrollRectToVisible(newItemPanel.getBounds());
                        }
                    });
                }
                created = true;
            } else {
                backlogItemPanel.update(item);
                created = false;
            }
            add(backlogItemPanel, c);

            c.gridx++;
            c.fill = GridBagConstraints.BOTH;
            c.weightx = 1;
            c.insets = new Insets(0, 0, 1, 0);
            add(backlogItemPanel.getTaskContent(), c);

            return created;
        }

        public int getItemIndex(Entity item) {
            BacklogItemPanel backlogItemPanel = items.get(item);
            if (backlogItemPanel != null) {
                return ((GridBagLayout) getLayout()).getConstraints(backlogItemPanel).gridy;
            } else {
                return items.size();
            }
        }

        public void updateTask(Entity task, boolean created) {
            int itemId = Integer.valueOf(task.getPropertyValue("release-backlog-item-id"));
            BacklogItemPanel backlogItemPanel = items.get(new Entity("release-backlog-item", itemId));
            if (backlogItemPanel != null) {
                backlogItemPanel.updateTask(task, created);
            }
        }

        public void removeTask(int id) {
            for (final BacklogItemPanel backlogItemPanel : items.values()) {
                TaskPanel taskPanel = backlogItemPanel.getTaskPanel(id);
                if (taskPanel != null) {
                    backlogItemPanel.removeTaskPanel(taskPanel);
                }
            }
        }

        public void removeBacklogItem(Entity backlogItem, boolean validate) {
            BacklogItemPanel backlogItemPanel = items.remove(backlogItem);
            if (backlogItemPanel != null) {
                GridBagLayout gridBagLayout = (GridBagLayout) getLayout();
                GridBagConstraints c = gridBagLayout.getConstraints(backlogItemPanel);
                remove(backlogItemPanel);
                remove(backlogItemPanel.getTaskContent());
                // shift all items bellow us one row up
                for (BacklogItemPanel item : items.values()) {
                    GridBagConstraints cc = gridBagLayout.getConstraints(item);
                    if (cc.gridy-- > c.gridy) {
                        gridBagLayout.setConstraints(item, cc);
                        cc = gridBagLayout.getConstraints(item.getTaskContent());
                        cc.gridy--;
                        gridBagLayout.setConstraints(item.getTaskContent(), cc);
                    }
                }
                if (validate) {
                    revalidate();
                    repaint();
                }
            }
        }

        public void removeBacklogItemOfWorkItem(EntityRef entity) {
            for (Entity item : items.keySet()) {
                if (new EntityRef(item.getPropertyValue("entity-type"),
                        Integer.valueOf(item.getPropertyValue("entity-id"))).equals(entity)) {
                    removeBacklogItem(item, true);
                    break;
                }
            }
        }

        public void retain(EntityList blis) {
            for (Entity bli : new LinkedList<Entity>(items.keySet())) {
                if (!blis.contains(bli)) {
                    removeBacklogItem(bli, false);
                }
            }
        }

        public void retainTasks(EntityList tasks) {
            HashSet<Integer> ids = new HashSet<Integer>(tasks.getIds());
            for (BacklogItemPanel bliPanel : items.values()) {
                bliPanel.retainTasks(ids);
            }
        }

        public void applyFilter() {
            for (BacklogItemPanel rbiPanel : items.values()) {
                rbiPanel.applyFilter();
            }
        }
    }

    private JComponent columnHeader(String name) {
        BoldLabel label = new BoldLabel(name);
        label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 1, 0, 0),
                BorderFactory.createEtchedBorder()));
        label.setPreferredSize(new Dimension(MIN_COLUMN_WIDTH, 26));
        return label;
    }

}