blob: 31019a79552efb31ff771ba25863f0e34cb9b58b [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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.intellij.history.integration.ui.views;
import com.intellij.history.core.revisions.Revision;
import com.intellij.history.integration.LocalHistoryBundle;
import com.intellij.history.integration.ui.models.HistoryDialogModel;
import com.intellij.history.integration.ui.models.RevisionItem;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.ui.ExpandableItemsHandler;
import com.intellij.ui.JBColor;
import com.intellij.ui.SeparatorWithText;
import com.intellij.ui.TableCell;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.table.JBTable;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.util.ui.AbstractLayoutManager;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.util.*;
import java.util.List;
public class RevisionsList {
public static final int RECENT_PERIOD = 12;
private final JBTable table;
public RevisionsList(SelectionListener l) {
table = new JBTable();
table.setModel(new MyModel(Collections.<RevisionItem>emptyList(), Collections.<RevisionItem, Period>emptyMap()));
table.setTableHeader(null);
table.setShowGrid(false);
table.setRowMargin(0);
table.getColumnModel().setColumnMargin(0);
table.resetDefaultFocusTraversalKeys();
table.setDefaultRenderer(Object.class, new MyCellRenderer(table));
table.getEmptyText().setText(VcsBundle.message("history.empty"));
addSelectionListener(l);
}
public JComponent getComponent() {
return table;
}
private void addSelectionListener(SelectionListener listener) {
final SelectionListener l = listener;
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
private int mySelectedRow1 = 0;
private int mySelectedRow2 = 0;
private final SelectionListener mySelectionListener = l;
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) return;
ListSelectionModel sm = table.getSelectionModel();
mySelectedRow1 = sm.getMinSelectionIndex();
mySelectedRow2 = sm.getMaxSelectionIndex();
mySelectionListener.revisionsSelected(mySelectedRow1, mySelectedRow2);
}
});
}
public void updateData(HistoryDialogModel model) {
Set<Long> sel = new THashSet<Long>();
MyModel m = (MyModel)table.getModel();
for (int i : table.getSelectedRows()) {
if (i >= m.getRowCount()) continue;
sel.add(m.getValueAt(i, 0).revision.getChangeSetId());
}
List<RevisionItem> newRevs = model.getRevisions();
Date today = new Date();
Map<RevisionItem, Period> periods = new THashMap<RevisionItem, Period>();
for (int i = 0; i < newRevs.size(); i++) {
RevisionItem each = newRevs.get(i);
boolean recent = today.getTime() - each.revision.getTimestamp() < 1000 * 60 * 60 * RECENT_PERIOD;
if (recent) {
if (i == 0) {
periods.put(each, Period.RECENT);
}
}
else {
periods.put(each, periods.isEmpty() ? Period.OLD : Period.OLDER);
break;
}
}
table.setModel(new MyModel(newRevs, periods));
for (int i = 0; i < newRevs.size(); i++) {
RevisionItem r = newRevs.get(i);
if (sel.contains(r.revision.getChangeSetId())) {
table.getSelectionModel().addSelectionInterval(i, i);
}
}
if (table.getSelectionModel().isSelectionEmpty()) {
table.getSelectionModel().addSelectionInterval(0, 0);
}
}
public interface SelectionListener {
void revisionsSelected(int first, int last);
}
private enum Period {
RECENT(LocalHistoryBundle.message("revisions.table.period.recent", RECENT_PERIOD)),
OLDER(LocalHistoryBundle.message("revisions.table.period.older")),
OLD(LocalHistoryBundle.message("revisions.table.period.old"));
private final String myDisplayString;
private Period(String displayString) {
myDisplayString = displayString;
}
public String getDisplayString() {
return myDisplayString;
}
}
public static class MyModel extends AbstractTableModel {
private final List<RevisionItem> myRevisions;
private final Map<RevisionItem, Period> myPeriods;
public MyModel(List<RevisionItem> revisions, Map<RevisionItem, Period> periods) {
myRevisions = revisions;
myPeriods = periods;
}
public int getColumnCount() {
return 1;
}
public int getRowCount() {
return myRevisions.size();
}
public RevisionItem getValueAt(int rowIndex, int columnIndex) {
return myRevisions.get(rowIndex);
}
public Period getPeriod(RevisionItem r) {
return myPeriods.get(r);
}
}
public static class MyCellRenderer implements TableCellRenderer {
private static final Color USER_LABEL_COLOR = new JBColor(new Color(230, 230, 250), new Color(89, 96, 74));
private static final Insets BORDER_INSETS = new Insets(2, 5, 2, 5);
private final DefaultTableCellRenderer myTemplate = new DefaultTableCellRenderer();
private final JPanel myWrapperPanel = new JPanel();
private final JPanel myItemPanel = new JPanel();
private final MyBorder myBorder = new MyBorder(BORDER_INSETS);
private final SeparatorWithText myPeriodLabel = new SeparatorWithText();
private final JBLabel myDateLabel = new JBLabel();
private final JBLabel myFilesCountLabel = new JBLabel();
private final JBLabel myTitleLabel = new JBLabel();
private final JPanel myLabelPanel = new JPanel();
private final MyLabelContainer myLabelContainer = new MyLabelContainer();
private final JBLabel myLabelLabel = new JBLabel();
private final ExpandableItemsHandler<TableCell> myToolTipHandler;
public MyCellRenderer(JBTable table) {
myToolTipHandler = table.getExpandableItemsHandler();
JPanel headersPanel = new JPanel(new BorderLayout());
headersPanel.setOpaque(false);
headersPanel.add(myPeriodLabel, BorderLayout.NORTH);
headersPanel.add(myLabelPanel, BorderLayout.CENTER);
myLabelContainer.add(myLabelLabel);
myLabelPanel.setLayout(new AbstractLayoutManager() {
@Override
public Dimension preferredLayoutSize(Container parent) {
Insets i = parent.getInsets();
Dimension pref = myLabelContainer.getPreferredSize();
return new Dimension(pref.width + i.left + i.right, pref.height + i.top + i.bottom);
}
@Override
public void layoutContainer(Container parent) {
Insets i = parent.getInsets();
int pw = parent.getWidth() - i.left - i.right;
Dimension pref = myLabelContainer.getPreferredSize();
int w = Math.min(pw, pref.width);
int h = pref.height;
int x = i.left + pw - w;
int y = i.top;
myLabelContainer.setBounds(x, y, w, h);
}
});
myLabelPanel.setOpaque(false);
myLabelPanel.add(myLabelContainer);
final JPanel layoutPanel = new JPanel(new BorderLayout());
layoutPanel.setOpaque(false);
layoutPanel.add(headersPanel, BorderLayout.NORTH);
layoutPanel.add(myItemPanel, BorderLayout.CENTER);
myWrapperPanel.setLayout(new AbstractLayoutManager() {
@Override
public Dimension preferredLayoutSize(Container parent) {
return layoutPanel.getPreferredSize();
}
@Override
public void layoutContainer(Container parent) {
Dimension size = parent.getSize();
Insets i = parent.getInsets();
Dimension pref = layoutPanel.getPreferredSize();
layoutPanel.setBounds(i.left, i.top, size.width - i.left - i.right, pref.height);
}
});
myWrapperPanel.add(layoutPanel);
myItemPanel.setBorder(myBorder);
myItemPanel.setLayout(new BorderLayout());
JPanel north = new JPanel(new BorderLayout());
north.setOpaque(false);
north.add(myDateLabel, BorderLayout.WEST);
north.add(myFilesCountLabel, BorderLayout.EAST);
JPanel south = new JPanel(new BorderLayout());
south.add(myTitleLabel, BorderLayout.CENTER);
south.setOpaque(false);
myItemPanel.add(north, BorderLayout.NORTH);
myItemPanel.add(south, BorderLayout.SOUTH);
myLabelLabel.setBorder(new EmptyBorder(0, 5, 1, 5));
myLabelPanel.setBorder(new MyBorder(new Insets(4, 20, 3, 1)));
myWrapperPanel.setOpaque(false);
myItemPanel.setOpaque(true);
myWrapperPanel.setBackground(table.getBackground());
myDateLabel.setComponentStyle(UIUtil.ComponentStyle.SMALL);
myFilesCountLabel.setComponentStyle(UIUtil.ComponentStyle.SMALL);
myLabelLabel.setComponentStyle(UIUtil.ComponentStyle.REGULAR);
myTitleLabel.setComponentStyle(UIUtil.ComponentStyle.REGULAR);
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value == null) return myWrapperPanel; // null erroneously comes from JPanel.getAccessibleChild
RevisionItem r = (RevisionItem)value;
LabelsAndColor labelsAndColor = getLabelsAndColor(r);
final Period p = ((MyModel)table.getModel()).getPeriod(r);
if (p == null) {
myPeriodLabel.setVisible(false);
}
else {
myPeriodLabel.setVisible(true);
myPeriodLabel.setCaption(p.getDisplayString());
}
myBorder.set(row == table.getModel().getRowCount() - 1);
myDateLabel.setText(ensureString(DateFormatUtil.formatPrettyDateTime(r.revision.getTimestamp())));
myFilesCountLabel.setText(ensureString(labelsAndColor.filesCount));
myTitleLabel.setFont(myTitleLabel.getFont().deriveFont(labelsAndColor.isNamed ? Font.BOLD : Font.PLAIN));
myTitleLabel.setText(ensureString(labelsAndColor.title));
JComponent orig = (JComponent)myTemplate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (labelsAndColor.label == null) {
myLabelPanel.setVisible(false);
}
else {
myLabelPanel.setVisible(true);
myLabelLabel.setBackground(labelsAndColor.label.second);
myLabelContainer.set(labelsAndColor.label.second);
myLabelLabel.setText(ensureString(labelsAndColor.label.first));
}
Color fg = orig.getForeground();
Color bg = isSelected && !table.isFocusOwner() ? UIUtil.getListUnfocusedSelectionBackground() : orig.getBackground();
myDateLabel.setForeground(isSelected ? fg : JBColor.GRAY);
myFilesCountLabel.setForeground(myDateLabel.getForeground());
myTitleLabel.setForeground(isSelected || labelsAndColor.isNamed ? fg : JBColor.DARK_GRAY);
myItemPanel.setBackground(bg);
myWrapperPanel.doLayout();
int height = myWrapperPanel.getPreferredSize().height;
//table.setRowHeight causes extra repaint of the table, so we try to avoid it.
if (table.getRowHeight(row) != height && height > 0) {
table.setRowHeight(row, height);
}
return myWrapperPanel;
}
private String ensureString(String s) {
return StringUtil.isEmpty(s) ? " " : s;
}
private LabelsAndColor getLabelsAndColor(RevisionItem item) {
Revision r = item.revision;
final Pair<List<String>, Integer> affected = r.getAffectedFileNames();
String title = r.getChangeSetName();
boolean named = title != null;
if (title == null) {
title = StringUtil.join(affected.first, ", ");
if (affected.first.size() < affected.second) title += "...";
}
String filesCount = StringUtil.pluralize(LocalHistoryBundle.message("revisions.table.filesCount", affected.second), affected.second);
Pair<String, Color> label = null;
if (!item.labels.isEmpty()) {
Revision first = item.labels.getFirst();
label = Pair.create(first.getLabel(), first.getLabelColor() == -1 ? USER_LABEL_COLOR : new Color(first.getLabelColor()));
}
return new LabelsAndColor(named, title, filesCount, label);
}
private static class LabelsAndColor {
final boolean isNamed;
final String title;
final String filesCount;
final Pair<String, Color> label;
private LabelsAndColor(boolean isNamed, String title, String filesCount, Pair<String, Color> label) {
this.isNamed = isNamed;
this.title = title;
this.filesCount = filesCount;
this.label = label;
}
}
private static class MyBorder extends EmptyBorder {
private boolean isLast;
private MyBorder(Insets insets) {
super(insets);
}
public void set(boolean isLast) {
this.isLast = isLast;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2d = (Graphics2D)g.create();
g2d.setColor(UIUtil.getBorderColor());
g2d.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{1}, 1));
g2d.drawLine(x, y, x + width, y);
if (isLast) {
g2d.drawLine(x, y + height - 1, x + width, y + height - 1);
}
g2d.dispose();
}
}
private static class MyLabelContainer extends JPanel {
private MyLabelContainer() {
super(new BorderLayout());
}
public void set(Color c) {
setBackground(c);
}
@Override
public Dimension getMinimumSize() {
return super.getMinimumSize();
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g.create();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(getBackground());
g2d.fillRoundRect(0, 0, getWidth(), getHeight(), getHeight() - 2, getHeight() - 2);
g2d.setColor(getBackground().darker());
g2d.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, getHeight() - 2, getHeight() - 2);
g2d.dispose();
}
}
}
}