/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions). * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.nbgit.ui.log; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Rectangle2D; import java.io.File; import java.io.IOException; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.AbstractAction; import javax.swing.AbstractListModel; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTextPane; import javax.swing.KeyStroke; import javax.swing.ListCellRenderer; import javax.swing.UIManager; import javax.swing.text.AttributeSet; import javax.swing.text.BadLocationException; import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import org.nbgit.Git; import org.nbgit.GitModuleConfig; import org.nbgit.GitProgressSupport; import org.nbgit.ui.diff.DiffSetupSource; import org.nbgit.ui.update.RevertModificationsAction; import org.nbgit.util.GitUtils; import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.settings.FontColorSettings; import org.openide.ErrorManager; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.RequestProcessor; import org.openide.windows.TopComponent; /** * Shows Search History results in a JList. * * @author Maros Sandor */ class SummaryView implements MouseListener, ComponentListener, MouseMotionListener, DiffSetupSource { private static final String SUMMARY_DIFF_PROPERTY = "Summary-Diff-"; private static final String SUMMARY_REVERT_PROPERTY = "Summary-Revert-"; private static final String SUMMARY_EXPORTDIFFS_PROPERTY = "Summary-ExportDiffs-"; private final SearchHistoryPanel master; private JList resultsList; private JScrollPane scrollPane; private final List dispResults; private String message; private AttributeSet searchHiliteAttrs; private List<RepositoryRevision> results; public SummaryView(SearchHistoryPanel master, List<RepositoryRevision> results) { this.master = master; this.results = results; this.dispResults = expandResults(results); FontColorSettings fcs = MimeLookup.getLookup(MimePath.get("text/x-java")).lookup(FontColorSettings.class); // NOI18N searchHiliteAttrs = fcs.getFontColors("highlight-search"); // NOI18N message = master.getCriteria().getCommitMessage(); resultsList = new JList(new SummaryListModel()); resultsList.setFixedCellHeight(-1); resultsList.addMouseListener(this); resultsList.addMouseMotionListener(this); resultsList.setCellRenderer(new SummaryCellRenderer()); resultsList.getAccessibleContext().setAccessibleName(NbBundle.getMessage(SummaryView.class, "ACSN_SummaryView_List")); // NOI18N resultsList.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(SummaryView.class, "ACSD_SummaryView_List")); // NOI18N scrollPane = new JScrollPane(resultsList, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); master.addComponentListener(this); resultsList.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put( KeyStroke.getKeyStroke(KeyEvent.VK_F10, KeyEvent.SHIFT_DOWN_MASK), "org.openide.actions.PopupAction"); resultsList.getActionMap().put("org.openide.actions.PopupAction", new AbstractAction() { public void actionPerformed(ActionEvent e) { onPopup(org.netbeans.modules.versioning.util.Utils.getPositionForPopup(resultsList)); } }); } public void componentResized(ComponentEvent e) { int[] selection = resultsList.getSelectedIndices(); resultsList.setModel(new SummaryListModel()); resultsList.setSelectedIndices(selection); } public void componentHidden(ComponentEvent e) { // not interested } public void componentMoved(ComponentEvent e) { // not interested } public void componentShown(ComponentEvent e) { // not interested } @SuppressWarnings("unchecked") private List expandResults(List<RepositoryRevision> results) { ArrayList newResults = new ArrayList(results.size()); for (RepositoryRevision repositoryRevision : results) { newResults.add(repositoryRevision); List<RepositoryRevision.Event> events = repositoryRevision.getEvents(); for (RepositoryRevision.Event event : events) { newResults.add(event); } } return newResults; } public void mouseClicked(MouseEvent e) { int idx = resultsList.locationToIndex(e.getPoint()); if (idx == -1) { return; } Rectangle rect = resultsList.getCellBounds(idx, idx); Point p = new Point(e.getX() - rect.x, e.getY() - rect.y); Rectangle diffBounds = (Rectangle) resultsList.getClientProperty(SUMMARY_DIFF_PROPERTY + idx); // NOI18N if (diffBounds != null && diffBounds.contains(p)) { diffPrevious(idx); } diffBounds = (Rectangle) resultsList.getClientProperty(SUMMARY_REVERT_PROPERTY + idx); // NOI18N if (diffBounds != null && diffBounds.contains(p)) { revertModifications(new int[]{idx}); } } public void mouseEntered(MouseEvent e) { // not interested } public void mouseExited(MouseEvent e) { // not interested } public void mousePressed(MouseEvent e) { if (!master.isIncomingSearch() && e.isPopupTrigger()) { onPopup(e); } } public void mouseReleased(MouseEvent e) { if (!master.isIncomingSearch() && e.isPopupTrigger()) { onPopup(e); } } public void mouseDragged(MouseEvent e) { } public void mouseMoved(MouseEvent e) { int idx = resultsList.locationToIndex(e.getPoint()); if (idx == -1) { return; } Rectangle rect = resultsList.getCellBounds(idx, idx); Point p = new Point(e.getX() - rect.x, e.getY() - rect.y); Rectangle diffBounds = (Rectangle) resultsList.getClientProperty(SUMMARY_DIFF_PROPERTY + idx); // NOI18N if (diffBounds != null && diffBounds.contains(p)) { resultsList.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); return; } diffBounds = (Rectangle) resultsList.getClientProperty(SUMMARY_REVERT_PROPERTY + idx); // NOI18N if (diffBounds != null && diffBounds.contains(p)) { resultsList.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); return; } diffBounds = (Rectangle) resultsList.getClientProperty(SUMMARY_EXPORTDIFFS_PROPERTY + idx); // NOI18N if (diffBounds != null && diffBounds.contains(p)) { resultsList.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); return; } resultsList.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); } public Collection getSetups() { Node[] nodes = TopComponent.getRegistry().getActivatedNodes(); if (nodes.length == 0) { return master.getSetups(results.toArray(new RepositoryRevision[results.size()]), new RepositoryRevision.Event[0]); } Set<RepositoryRevision.Event> events = new HashSet<RepositoryRevision.Event>(); Set<RepositoryRevision> revisions = new HashSet<RepositoryRevision>(); int[] sel = resultsList.getSelectedIndices(); for (int i : sel) { Object revCon = dispResults.get(i); if (revCon instanceof RepositoryRevision) { revisions.add((RepositoryRevision) revCon); } else { events.add((RepositoryRevision.Event) revCon); } } return master.getSetups(revisions.toArray(new RepositoryRevision[revisions.size()]), events.toArray(new RepositoryRevision.Event[events.size()])); } public String getSetupDisplayName() { return null; } private void onPopup(MouseEvent e) { onPopup(e.getPoint()); } private void onPopup(Point p) { int[] sel = resultsList.getSelectedIndices(); if (sel.length == 0) { int idx = resultsList.locationToIndex(p); if (idx == -1) { return; } resultsList.setSelectedIndex(idx); sel = new int[]{idx}; } final int[] selection = sel; JPopupMenu menu = new JPopupMenu(); String previousRevision = null; RepositoryRevision container = null; final RepositoryRevision.Event[] drev; Object revCon = dispResults.get(selection[0]); boolean noExDeletedExistingFiles = true; boolean revisionSelected; boolean missingFile = false; boolean oneRevisionMultiselected = true; if (revCon instanceof RepositoryRevision) { revisionSelected = true; container = (RepositoryRevision) dispResults.get(selection[0]); drev = new RepositoryRevision.Event[0]; oneRevisionMultiselected = true; noExDeletedExistingFiles = true; } else { revisionSelected = false; drev = new RepositoryRevision.Event[selection.length]; for (int i = 0; i < selection.length; i++) { drev[i] = (RepositoryRevision.Event) dispResults.get(selection[i]); if (!missingFile && drev[i].getFile() == null) { missingFile = true; } if (oneRevisionMultiselected && i > 0 && drev[0].getLogInfoHeader().getRevision().equals(drev[i].getLogInfoHeader().getRevision())) { oneRevisionMultiselected = false; } if (drev[i].getFile() != null && drev[i].getFile().exists() && drev[i].getChangedPath().getAction() == 'D') { noExDeletedExistingFiles = false; } } container = drev[0].getLogInfoHeader(); } long revision = 0; //Long.parseLong(container.getLog().getRevision()); final boolean rollbackToEnabled = !missingFile && !revisionSelected && oneRevisionMultiselected; final boolean rollbackChangeEnabled = !missingFile && oneRevisionMultiselected && (drev.length == 0 || noExDeletedExistingFiles); // drev.length == 0 => the whole revision was selected final boolean viewEnabled = selection.length == 1 && !revisionSelected && drev[0].getFile() != null && drev[0].getFile().exists() && !drev[0].getFile().isDirectory(); final boolean diffToPrevEnabled = selection.length == 1; if (revision > 0) { menu.add(new JMenuItem(new AbstractAction(NbBundle.getMessage(SummaryView.class, "CTL_SummaryView_DiffToPrevious", "" + previousRevision)) { // NOI18N { setEnabled(diffToPrevEnabled); } public void actionPerformed(ActionEvent e) { diffPrevious(selection[0]); } })); /* TBD - Support Rollback Changes: * Need to figure out how to implement SVN functionality to allow you to rollback a change * currently for svn this cmd runs: merge -R <revX>:<revY> it basically diffs the two revs * and creates a patch which is then applied ot the file in the working dir, effectively removing * the change applied between revX and revY * In hg we'd need to do a hg diff -r revX -r revY, generate a patch and then apply it to the working dir copy * / menu.add(new JMenuItem(new AbstractAction(NbBundle.getMessage(SummaryView.class, "CTL_SummaryView_RollbackChange")) { // NOI18N { setEnabled(false); // rollbackChangeEnabled); } public void actionPerformed(ActionEvent e) { revertModifications(selection); } })); */ } if (!revisionSelected) { menu.add(new JMenuItem(new AbstractAction(NbBundle.getMessage(SummaryView.class, "CTL_SummaryView_RollbackTo", "" + revision)) { // NOI18N { setEnabled(rollbackToEnabled); } public void actionPerformed(ActionEvent e) { revertModifications(selection); } /*public void actionPerformed(ActionEvent e) { RequestProcessor.getDefault().post(new Runnable() { public void run() { rollback(drev); } }); }*/ })); menu.add(new JMenuItem(new AbstractAction(NbBundle.getMessage(SummaryView.class, "CTL_SummaryView_View")) { // NOI18N { setEnabled(viewEnabled); } public void actionPerformed(ActionEvent e) { RequestProcessor.getDefault().post(new Runnable() { public void run() { view(selection[0]); } }); } })); } menu.show(resultsList, p.x, p.y); } /** * Overwrites local file with this revision. * * @param event */ static void rollback(RepositoryRevision.Event event) { rollback(new RepositoryRevision.Event[]{event}); } /** * Overwrites local file with this revision. * * @param event */ static void rollback(final RepositoryRevision.Event[] events) { String repository = events[0].getLogInfoHeader().getRepositoryRootUrl(); RequestProcessor rp = Git.getInstance().getRequestProcessor(repository); GitProgressSupport support = new GitProgressSupport() { public void perform() { for (RepositoryRevision.Event event : events) { rollback(event, this); } } }; support.start(rp, repository, NbBundle.getMessage(SummaryView.class, "MSG_Rollback_Progress")); // NOI18N } private static void rollback(RepositoryRevision.Event event, GitProgressSupport progress) { File file = event.getFile(); File parent = file.getParentFile(); parent.mkdirs(); File oldFile = null; try { oldFile = GitUtils.getFileRevision(file, event.getLogInfoHeader().getRevision()); file.delete(); FileUtil.copyFile(FileUtil.toFileObject(oldFile), FileUtil.toFileObject(parent), file.getName(), ""); } catch (IOException e) { ErrorManager.getDefault().notify(e); } finally { if (oldFile != null && oldFile != file) oldFile.delete(); } } private void revertModifications(int[] selection) { Set<RepositoryRevision.Event> events = new HashSet<RepositoryRevision.Event>(); Set<RepositoryRevision> revisions = new HashSet<RepositoryRevision>(); for (int idx : selection) { Object o = dispResults.get(idx); if (o instanceof RepositoryRevision) { revisions.add((RepositoryRevision) o); } else { events.add((RepositoryRevision.Event) o); } } revert(master, revisions.toArray(new RepositoryRevision[revisions.size()]), events.toArray(new RepositoryRevision.Event[events.size()])); } static void revert(final SearchHistoryPanel master, final RepositoryRevision[] revisions, final RepositoryRevision.Event[] events) { String url = master.getSearchRepositoryRootUrl(); RequestProcessor rp = Git.getInstance().getRequestProcessor(url); GitProgressSupport support = new GitProgressSupport() { public void perform() { revertImpl(master, revisions, events, this); } }; support.start(rp, url, NbBundle.getMessage(SummaryView.class, "MSG_Revert_Progress")); // NOI18N } private static void revertImpl(SearchHistoryPanel master, RepositoryRevision[] revisions, RepositoryRevision.Event[] events, GitProgressSupport progress) { List<File> revertFiles = new ArrayList<File>(); boolean doBackup = GitModuleConfig.getDefault().getBackupOnRevertModifications(); for (RepositoryRevision revision : revisions) { File root = new File(revision.getRepositoryRootUrl()); for (RepositoryRevision.Event event : revision.getEvents()) { if (event.getFile() == null) { continue; } revertFiles.add(event.getFile()); } RevertModificationsAction.performRevert( root, revision.getRevision(), revertFiles, doBackup, progress.getLogger()); revertFiles.clear(); } Map<File, List<RepositoryRevision.Event>> revertMap = new HashMap<File, List<RepositoryRevision.Event>>(); for (RepositoryRevision.Event event : events) { if (event.getFile() == null) { continue; } File root = Git.getInstance().getTopmostManagedParent(event.getFile()); if (revertMap == null) { revertMap = new HashMap<File, List<RepositoryRevision.Event>>(); } List<RepositoryRevision.Event> revEvents = revertMap.get(root); if (revEvents == null) { revEvents = new ArrayList<RepositoryRevision.Event>(); revertMap.put(root, revEvents); } revEvents.add(event); } if (events != null && events.length > 0 && revertMap != null && !revertMap.isEmpty()) { Set<File> roots = revertMap.keySet(); for (File root : roots) { List<RepositoryRevision.Event> revEvents = revertMap.get(root); for (RepositoryRevision.Event event : revEvents) { if (event.getFile() == null) { continue; } revertFiles.add(event.getFile()); } if (revEvents != null && !revEvents.isEmpty()) { // Assuming all files in a given repository reverting to same revision /* RevertModificationsAction.performRevert( root, revEvents.get(0).getLogInfoHeader().getLog().getRevision(), revertFiles, doBackup, progress.getLogger()); */ } } } } private void view(int idx) { Object o = dispResults.get(idx); if (o instanceof RepositoryRevision.Event) { try { RepositoryRevision.Event drev = (RepositoryRevision.Event) o; File file = GitUtils.getFileRevision(drev.getFile(), drev.getLogInfoHeader().getRevision()); FileObject fo = FileUtil.toFileObject(FileUtil.normalizeFile(file)); org.netbeans.modules.versioning.util.Utils.openFile(fo, drev.getLogInfoHeader().getRevision()); } catch (IOException ex) { // Ignore if file not available in cache } } } private void diffPrevious(int idx) { Object o = dispResults.get(idx); if (o instanceof RepositoryRevision.Event) { RepositoryRevision.Event drev = (RepositoryRevision.Event) o; master.showDiff(drev); } else { RepositoryRevision container = (RepositoryRevision) o; master.showDiff(container); } } public JComponent getComponent() { return scrollPane; } private class SummaryListModel extends AbstractListModel { public int getSize() { return dispResults.size(); } public Object getElementAt(int index) { return dispResults.get(index); } } private class SummaryCellRenderer extends JPanel implements ListCellRenderer { private static final String FIELDS_SEPARATOR = " "; // NOI18N private static final double DARKEN_FACTOR = 0.95; private Style selectedStyle; private Style normalStyle; private Style filenameStyle; private Style indentStyle; private Style noindentStyle; private Style hiliteStyle; private JTextPane textPane = new JTextPane(); private JPanel actionsPane = new JPanel(); private DateFormat defaultFormat; private int index; private HyperlinkLabel diffLink; private HyperlinkLabel revertLink; public SummaryCellRenderer() { selectedStyle = textPane.addStyle("selected", null); // NOI18N StyleConstants.setForeground(selectedStyle, UIManager.getColor("List.selectionForeground")); // NOI18N normalStyle = textPane.addStyle("normal", null); // NOI18N StyleConstants.setForeground(normalStyle, UIManager.getColor("List.foreground")); // NOI18N filenameStyle = textPane.addStyle("filename", normalStyle); // NOI18N StyleConstants.setBold(filenameStyle, true); indentStyle = textPane.addStyle("indent", null); // NOI18N StyleConstants.setLeftIndent(indentStyle, 50); noindentStyle = textPane.addStyle("noindent", null); // NOI18N StyleConstants.setLeftIndent(noindentStyle, 0); defaultFormat = DateFormat.getDateTimeInstance(); hiliteStyle = textPane.addStyle("hilite", normalStyle); // NOI18N Color c = (Color) searchHiliteAttrs.getAttribute(StyleConstants.Background); if (c != null) { StyleConstants.setBackground(hiliteStyle, c); } c = (Color) searchHiliteAttrs.getAttribute(StyleConstants.Foreground); if (c != null) { StyleConstants.setForeground(hiliteStyle, c); } setLayout(new BorderLayout()); add(textPane); add(actionsPane, BorderLayout.PAGE_END); actionsPane.setLayout(new FlowLayout(FlowLayout.TRAILING, 2, 5)); diffLink = new HyperlinkLabel(); diffLink.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 8)); actionsPane.add(diffLink); revertLink = new HyperlinkLabel(); revertLink.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 8)); //actionsPane.add(revertLink); textPane.setBorder(null); } public Color darker(Color c) { return new Color(Math.max((int) (c.getRed() * DARKEN_FACTOR), 0), Math.max((int) (c.getGreen() * DARKEN_FACTOR), 0), Math.max((int) (c.getBlue() * DARKEN_FACTOR), 0)); } public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (value instanceof RepositoryRevision) { renderContainer(list, (RepositoryRevision) value, index, isSelected); } else { renderRevision(list, (RepositoryRevision.Event) value, index, isSelected); } return this; } private void renderContainer(JList list, RepositoryRevision container, int index, boolean isSelected) { StyledDocument sd = textPane.getStyledDocument(); Style style; Color backgroundColor; Color foregroundColor; if (isSelected) { foregroundColor = UIManager.getColor("List.selectionForeground"); // NOI18N backgroundColor = UIManager.getColor("List.selectionBackground"); // NOI18N style = selectedStyle; } else { foregroundColor = UIManager.getColor("List.foreground"); // NOI18N backgroundColor = UIManager.getColor("List.background"); // NOI18N backgroundColor = darker(backgroundColor); style = normalStyle; } textPane.setBackground(backgroundColor); actionsPane.setBackground(backgroundColor); this.index = index; try { sd.remove(0, sd.getLength()); sd.setParagraphAttributes(0, sd.getLength(), noindentStyle, false); sd.insertString(0, container.getRevision(), null); // NOI18N sd.setCharacterAttributes(0, sd.getLength(), filenameStyle, false); sd.insertString(sd.getLength(), FIELDS_SEPARATOR + container.getAuthor(), null); sd.insertString(sd.getLength(), FIELDS_SEPARATOR + defaultFormat.format(container.getDate()), null); String commitMessage = container.getMessage(); if (commitMessage.endsWith("\n")) { commitMessage = commitMessage.substring(0, commitMessage.length() - 1); // NOI18N } sd.insertString(sd.getLength(), "\n", null); sd.insertString(sd.getLength(), commitMessage, null); if (message != null && !isSelected) { int idx = commitMessage.indexOf(message); if (idx != -1) { int len = commitMessage.length(); int doclen = sd.getLength(); sd.setCharacterAttributes(doclen - len + idx, message.length(), hiliteStyle, false); } } resizePane(commitMessage, list.getFontMetrics(list.getFont())); sd.setCharacterAttributes(0, Integer.MAX_VALUE, style, false); } catch (BadLocationException e) { ErrorManager.getDefault().notify(e); } actionsPane.setVisible(true); if (!master.isIncomingSearch()) { diffLink.set(NbBundle.getMessage(SummaryView.class, "CTL_Action_Diff"), foregroundColor, backgroundColor);// NOI18N revertLink.set(NbBundle.getMessage(SummaryView.class, "CTL_Action_Revert"), foregroundColor, backgroundColor); // NOI18N } } private void renderRevision(JList list, RepositoryRevision.Event dispRevision, final int index, boolean isSelected) { Style style; StyledDocument sd = textPane.getStyledDocument(); Color backgroundColor; Color foregroundColor; if (isSelected) { foregroundColor = UIManager.getColor("List.selectionForeground"); // NOI18N backgroundColor = UIManager.getColor("List.selectionBackground"); // NOI18N style = selectedStyle; } else { foregroundColor = UIManager.getColor("List.foreground"); // NOI18N backgroundColor = UIManager.getColor("List.background"); // NOI18N style = normalStyle; } textPane.setBackground(backgroundColor); actionsPane.setVisible(false); this.index = -1; try { sd.remove(0, sd.getLength()); sd.setParagraphAttributes(0, sd.getLength(), indentStyle, false); sd.insertString(sd.getLength(), String.valueOf(dispRevision.getChangedPath().getAction()), null); sd.insertString(sd.getLength(), FIELDS_SEPARATOR + dispRevision.getChangedPath().getPath(), null); sd.setCharacterAttributes(0, Integer.MAX_VALUE, style, false); resizePane(sd.getText(0, sd.getLength() - 1), list.getFontMetrics(list.getFont())); } catch (BadLocationException e) { ErrorManager.getDefault().notify(e); } } @SuppressWarnings("empty-statement") private void resizePane(String text, FontMetrics fm) { if (text == null) { text = ""; } int width = master.getWidth(); if (width > 0) { Rectangle2D rect = fm.getStringBounds(text, textPane.getGraphics()); int nlc, i; for (nlc = -1 , i = 0; i != -1; i = text.indexOf('\n', i + 1), nlc++); nlc++; int lines = (int) (rect.getWidth() / (width - 80) + 1); int ph = fm.getHeight() * (lines + nlc) + 0; textPane.setPreferredSize(new Dimension(width - 50, ph)); } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (index == -1) { return; } Rectangle apb = actionsPane.getBounds(); { Rectangle bounds = diffLink.getBounds(); bounds.setBounds(bounds.x, bounds.y + apb.y, bounds.width, bounds.height); resultsList.putClientProperty(SUMMARY_DIFF_PROPERTY + index, bounds); // NOI18N } Rectangle bounds = revertLink.getBounds(); bounds.setBounds(bounds.x, bounds.y + apb.y, bounds.width, bounds.height); resultsList.putClientProperty(SUMMARY_REVERT_PROPERTY + index, bounds); // NOI18N } } private static class HyperlinkLabel extends JLabel { public HyperlinkLabel() { setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); } public void set(String text, Color foreground, Color background) { StringBuilder sb = new StringBuilder(100); if (foreground.equals(UIManager.getColor("List.foreground"))) { // NOI18N sb.append("<html><a href=\"\">"); // NOI18N sb.append(text); sb.append("</a>"); // NOI18N } else { sb.append("<html><a href=\"\" style=\"color:"); // NOI18N sb.append("rgb("); // NOI18N sb.append(foreground.getRed()); sb.append(","); // NOI18N sb.append(foreground.getGreen()); sb.append(","); // NOI18N sb.append(foreground.getBlue()); sb.append(")"); // NOI18N sb.append("\">"); // NOI18N sb.append(text); sb.append("</a>"); // NOI18N } setText(sb.toString()); setBackground(background); } } }