| /* |
| * 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(); |
| } |
| } |
| } |
| } |