diff --git a/versioning.util/src/org/netbeans/modules/versioning/history/AbstractSummaryView.java b/versioning.util/src/org/netbeans/modules/versioning/history/AbstractSummaryView.java --- a/versioning.util/src/org/netbeans/modules/versioning/history/AbstractSummaryView.java +++ b/versioning.util/src/org/netbeans/modules/versioning/history/AbstractSummaryView.java @@ -78,6 +78,7 @@ private JScrollPane scrollPane; private VCSHyperlinkSupport linkerSupport = new VCSHyperlinkSupport(); + private int initialDelay; public AbstractSummaryView(SummaryViewMaster master, final List results, Map kenaiUsersMap) { this.master = master; @@ -176,14 +177,17 @@ @Override public void mouseMoved(MouseEvent e) { - resultsList.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); resultsList.setToolTipText(null); 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); - linkerSupport.mouseMoved(p, resultsList, getLinkerIdentFor(idx)); + boolean moved = linkerSupport.mouseMoved(p, resultsList, getLinkerIdentFor(idx)); + if (!moved) { + //prevent flickering cursor while hovering + resultsList.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } } String getLinkerIdentFor (int index) { @@ -193,12 +197,15 @@ @Override public void mouseEntered(MouseEvent e) { - // not interested + // see http://tech.chitgoks.com/2010/05/31/disable-tooltip-delay-in-java-swing/ + initialDelay = ToolTipManager.sharedInstance().getInitialDelay(); + ToolTipManager.sharedInstance().setInitialDelay(0); + } @Override public void mouseExited(MouseEvent e) { - // not interested + ToolTipManager.sharedInstance().setInitialDelay(initialDelay); } @Override diff --git a/versioning.util/src/org/netbeans/modules/versioning/history/RevisionItemCell.java b/versioning.util/src/org/netbeans/modules/versioning/history/RevisionItemCell.java new file mode 100644 --- /dev/null +++ b/versioning.util/src/org/netbeans/modules/versioning/history/RevisionItemCell.java @@ -0,0 +1,141 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle 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]" + * + * 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. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.versioning.history; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Rectangle; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTextPane; +import javax.swing.colorchooser.AbstractColorChooserPanel; + +/** + * Composite panel for displaying revision, author, commit message and other + * info. + */ +public class RevisionItemCell extends JPanel { + private JTextPane authorControl=new JTextPane(); + private JTextPane dateControl=new JTextPane(); + private JTextPane revisionControl=new JTextPane(); + private JTextPane commitMessageControl=new JTextPane(); + private JPanel northPanel=new JPanel(); + private JPanel authorDatePanel=new JPanel(); + + public RevisionItemCell () { + this.setBorder(null); + this.setLayout(new BorderLayout(0, 0)); + this.add(commitMessageControl, java.awt.BorderLayout.CENTER); + this.add(northPanel, java.awt.BorderLayout.NORTH); + northPanel.setBorder(BorderFactory.createEmptyBorder()); + northPanel.setLayout(new BorderLayout(5, 0)); + northPanel.add(authorDatePanel, java.awt.BorderLayout.EAST); + northPanel.add(revisionControl, java.awt.BorderLayout.CENTER); + authorDatePanel.setLayout(new BorderLayout(5, 0)); + authorDatePanel.setBorder(BorderFactory.createEmptyBorder()); + authorDatePanel.add(authorControl, BorderLayout.CENTER); + authorDatePanel.add(dateControl, BorderLayout.EAST); + + // + initTextPane(dateControl); + initTextPane(authorControl); + initTextPane(revisionControl); + initTextPane(commitMessageControl); + northPanel.setOpaque(false); + authorDatePanel.setOpaque(false); + this.setFocusable(false); + northPanel.setFocusable(false); + authorDatePanel.setFocusable(false); + } + /** + * Corrects the bounding rectangle of nested textpanes. + * @param startComponent + * @param r + */ + public void correctTranslation (final Container startComponent, final Rectangle r) { + if (null == startComponent) { + return; + } + if (null == r) { + return; + } + + final RevisionItemCell stopComponent = this; + Container current=startComponent; + while (current != stopComponent) { + r.translate(current.getX(), current.getY()); + current = current.getParent(); + } + r.translate(current.getX(), current.getY()); + } + + public JTextPane getAuthorControl () { + return authorControl; + } + + public JTextPane getDateControl () { + return dateControl; + } + + public JTextPane getRevisionControl () { + return revisionControl; + } + + public JTextPane getCommitMessageControl () { + return commitMessageControl; + } + + public JPanel getNorthPanel () { + return northPanel; + } + + private void initTextPane (JTextPane pane) { + pane.setBorder(null); + pane.setLayout(null); + //fix for nimbus laf + pane.setOpaque(false); + pane.setBackground(new Color(0, 0, 0, 0)); + } + +} diff --git a/versioning.util/src/org/netbeans/modules/versioning/history/SummaryCellRenderer.java b/versioning.util/src/org/netbeans/modules/versioning/history/SummaryCellRenderer.java --- a/versioning.util/src/org/netbeans/modules/versioning/history/SummaryCellRenderer.java +++ b/versioning.util/src/org/netbeans/modules/versioning/history/SummaryCellRenderer.java @@ -44,6 +44,7 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; +import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.EventQueue; @@ -85,14 +86,18 @@ import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.settings.FontColorSettings; +import org.netbeans.modules.versioning.history.AbstractSummaryView.LogEntry; import org.netbeans.modules.versioning.history.AbstractSummaryView.LogEntry.Event; import org.netbeans.modules.versioning.history.AbstractSummaryView.RevisionItem; import org.netbeans.modules.versioning.history.AbstractSummaryView.SummaryViewMaster.SearchHighlight; import org.netbeans.modules.versioning.util.Utils; import org.netbeans.modules.versioning.util.VCSHyperlinkProvider; import org.netbeans.modules.versioning.util.VCSHyperlinkSupport; +import org.netbeans.modules.versioning.util.VCSHyperlinkSupport.AuthorLinker; +import org.netbeans.modules.versioning.util.VCSHyperlinkSupport.IssueLinker; import org.netbeans.modules.versioning.util.VCSKenaiAccessor; import org.openide.ErrorManager; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle; @@ -253,18 +258,9 @@ private class RevisionRenderer extends JPanel implements ListCellRenderer { private String id; - private final Style selectedStyle; - private final Style normalStyle; - private final Style indentStyle; - private final Style noindentStyle; - private final Style issueHyperlinkStyle; - private final Style linkStyle; - private final Style authorStyle; - private final Style hiliteStyle; private boolean lastSelection = false; - private final JTextPane textPane; + private final RevisionItemCell revisionCell = new RevisionItemCell(); private final JButton expandButton; - private String commitMessage = ""; //NOI18N private boolean lastMessageExpanded; private boolean lastRevisionExpanded; private int lastWidth; @@ -272,46 +268,16 @@ public RevisionRenderer() { selectionForeground = new JList().getSelectionForeground(); - textPane = new JTextPane(); expandButton = new LinkButton(ICON_COLLAPSED); expandButton.setBorder(BorderFactory.createEmptyBorder()); - selectedStyle = textPane.addStyle("selected", null); //NOI18N - StyleConstants.setForeground(selectedStyle, selectionForeground); - StyleConstants.setBackground(selectedStyle, selectionBackground); - normalStyle = textPane.addStyle("normal", null); //NOI18N - StyleConstants.setForeground(normalStyle, UIManager.getColor("List.foreground")); //NOI18N - indentStyle = textPane.addStyle("indent", null); //NOI18N - StyleConstants.setLeftIndent(indentStyle, 50); - noindentStyle = textPane.addStyle("noindent", null); //NOI18N - StyleConstants.setLeftIndent(noindentStyle, 0); + this.setBorder(BorderFactory.createMatteBorder(3, 0, 0, 0, UIManager.getColor("List.background"))); //NOI18N + this.setLayout(new BorderLayout(3, 0)); - issueHyperlinkStyle = textPane.addStyle("issuehyperlink", normalStyle); //NOI18N - StyleConstants.setForeground(issueHyperlinkStyle, Color.BLUE); - StyleConstants.setUnderline(issueHyperlinkStyle, true); - - linkStyle = textPane.addStyle("link", normalStyle); //NOI18N - StyleConstants.setForeground(linkStyle, Color.BLUE); - StyleConstants.setBold(linkStyle, true); - - authorStyle = textPane.addStyle("author", normalStyle); //NOI18N - StyleConstants.setForeground(authorStyle, Color.BLUE); - - 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); - - textPane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); - setLayout(new BorderLayout(0, 0)); - setBorder(BorderFactory.createMatteBorder(3, 0, 0, 0, UIManager.getColor("List.background"))); //NOI18N - - add(expandButton, BorderLayout.WEST); expandButton.setMaximumSize(expandButton.getPreferredSize()); expandButton.setMinimumSize(expandButton.getPreferredSize()); - add(textPane, BorderLayout.CENTER); + this.add(expandButton, BorderLayout.WEST); + this.add(revisionCell, BorderLayout.CENTER); } @Override @@ -319,32 +285,25 @@ AbstractSummaryView.RevisionItem item = (AbstractSummaryView.RevisionItem) value; AbstractSummaryView.LogEntry entry = item.getUserData(); - StyledDocument sd = textPane.getStyledDocument(); Collection highlights = summaryView.getMaster().getSearchHighlights(); - if (sd.getLength() == 0 || selected != lastSelection || item.messageExpanded != lastMessageExpanded || item.revisionExpanded != lastRevisionExpanded + if (revisionCell.getRevisionControl().getStyledDocument().getLength() == 0 || revisionCell.getDateControl().getStyledDocument().getLength() == 0 || revisionCell.getAuthorControl().getStyledDocument().getLength() == 0 || revisionCell.getCommitMessageControl().getStyledDocument().getLength() == 0 || selected != lastSelection || item.messageExpanded != lastMessageExpanded || item.revisionExpanded != lastRevisionExpanded || !highlights.equals(lastHighlights)) { lastSelection = selected; lastMessageExpanded = item.messageExpanded; lastRevisionExpanded = item.revisionExpanded; lastHighlights = highlights; - Style style; Color backgroundColor; - Color foregroundColor; if (selected) { - foregroundColor = selectionForeground; backgroundColor = selectionBackground; - style = selectedStyle; } else { - foregroundColor = UIManager.getColor("List.foreground"); //NOI18N backgroundColor = UIManager.getColor("List.background"); //NOI18N backgroundColor = entry.isLessInteresting() ? darkerUninteresting(backgroundColor) : darker(backgroundColor); - style = normalStyle; } - textPane.setOpaque(false); - textPane.setBackground(new Color(0, 0, 0, 0)); - setBackground(backgroundColor); + this.setBackground(backgroundColor); + revisionCell.setBackground(backgroundColor); + if (item.revisionExpanded) { expandButton.setIcon(ICON_EXPANDED); } else { @@ -357,166 +316,22 @@ } try { - // clear document - sd.remove(0, sd.getLength()); - sd.setParagraphAttributes(0, sd.getLength(), noindentStyle, false); - - // add revision - sd.insertString(0, item.getUserData().getRevision(), null); - sd.setCharacterAttributes(0, sd.getLength(), normalStyle, false); - if (!selected) { - for (AbstractSummaryView.LogEntry.RevisionHighlight highlight : item.getUserData().getRevisionHighlights()) { - Style s = textPane.addStyle(null, normalStyle); - StyleConstants.setForeground(s, highlight.getForeground()); - StyleConstants.setBackground(s, highlight.getBackground()); - sd.setCharacterAttributes(highlight.getStart(), highlight.getLength(), s, false); - } - for (SearchHighlight highlight : highlights) { - if (highlight.getKind() == SearchHighlight.Kind.REVISION) { - int doclen = sd.getLength(); - String highlightMessage = highlight.getSearchText(); - String revisionText = item.getUserData().getRevision().toLowerCase(); - int idx = revisionText.indexOf(highlightMessage); - if (idx > -1) { - sd.setCharacterAttributes(doclen - revisionText.length() + idx, highlightMessage.length(), hiliteStyle, false); - } - } - } - } - - // add author - sd.insertString(sd.getLength(), FIELDS_SEPARATOR, style); - String author = entry.getAuthor(); - VCSHyperlinkSupport.StyledDocumentHyperlink l = linkerSupport.getLinker(VCSHyperlinkSupport.AuthorLinker.class, id); - if(l == null) { - VCSKenaiAccessor.KenaiUser kenaiUser = getKenaiUser(author); - if (kenaiUser != null) { - l = new VCSHyperlinkSupport.AuthorLinker(kenaiUser, authorStyle, sd, author); - linkerSupport.add(l, id); - } - } - int pos = sd.getLength(); - if(l != null) { - l.insertString(sd, selected ? style : null); - } else { - sd.insertString(sd.getLength(), author, style); - } - if (!selected) { - for (SearchHighlight highlight : highlights) { - if (highlight.getKind() == SearchHighlight.Kind.AUTHOR) { - int doclen = sd.getLength(); - String highlightMessage = highlight.getSearchText(); - String authorText = sd.getText(pos, doclen - pos).toLowerCase(); - int idx = authorText.indexOf(highlightMessage); - if (idx > -1) { - sd.setCharacterAttributes(doclen - authorText.length() + idx, highlightMessage.length(), hiliteStyle, false); - } - } - } - } - - // add date - sd.insertString(sd.getLength(), FIELDS_SEPARATOR + entry.getDate(), null); - - // add commit msg - boolean messageChanged = !entry.getMessage().equals(commitMessage); - commitMessage = entry.getMessage(); - if (commitMessage.endsWith("\n")) commitMessage = commitMessage.substring(0, commitMessage.length() - 1); //NOI18N - sd.insertString(sd.getLength(), "\n", null); //NOI18N - int nlc, i; - for (i = 0, nlc = -1; i != -1 ; i = commitMessage.indexOf('\n', i + 1), nlc++); - if (nlc > 0 && !item.messageExpanded) { - commitMessage = commitMessage.substring(0, commitMessage.indexOf("\n")); //NOI18N - } - - // compute issue hyperlinks - l = linkerSupport.getLinker(VCSHyperlinkSupport.IssueLinker.class, id); - if (messageChanged) { - lastWidth = -1; - if (l != null) { - // must reinitialize issue linker to paint the new message - linkerSupport.remove(l, id); - l = null; - } - } - if(l == null) { - for (VCSHyperlinkProvider hp : getHyperlinkProviders()) { - l = VCSHyperlinkSupport.IssueLinker.create(hp, issueHyperlinkStyle, summaryView.getRoot(), sd, commitMessage); - if(l != null) { - linkerSupport.add(l, id); - break; // get the first one - } - } - } - pos = sd.getLength(); - if(l != null) { - l.insertString(sd, style); - } else { - sd.insertString(sd.getLength(), commitMessage, style); - } - - // tooltip for message - MessageTooltip mtt = linkerSupport.getLinker(MessageTooltip.class, id); - if (messageChanged) { - linkerSupport.remove(mtt, id); - mtt = null; - } - if (mtt == null) { - linkerSupport.add(new MessageTooltip(entry.getMessage(), pos, sd.getLength()), id); - } - - // paint first line of commit message bold - int lineEnd = sd.getText(pos, sd.getLength() - pos).indexOf("\n"); - if (lineEnd == -1) { - lineEnd = sd.getLength() - pos; - } - Style s = textPane.addStyle(null, style); - StyleConstants.setBold(s, true); - sd.setCharacterAttributes(pos, lineEnd, s, false); - int msglen = commitMessage.length(); - int doclen = sd.getLength(); - - if (nlc > 0 && !item.messageExpanded) { - l = linkerSupport.getLinker(ExpandMsgHyperlink.class, id); - if (l == null) { - l = new ExpandMsgHyperlink(item, sd.getLength(), id); - linkerSupport.add(l, id); - } - l.insertString(sd, linkStyle); - } - - - if (!selected) { - for (SearchHighlight highlight : highlights) { - if (highlight.getKind() == SearchHighlight.Kind.MESSAGE) { - String highlightMessage = highlight.getSearchText(); - int idx = commitMessage.toLowerCase().indexOf(highlightMessage); - if (idx == -1) { - if (nlc > 0 && !item.messageExpanded && entry.getMessage().toLowerCase().contains(highlightMessage)) { - sd.setCharacterAttributes(doclen, sd.getLength(), hiliteStyle, false); - } - } else { - sd.setCharacterAttributes(doclen - msglen + idx, highlightMessage.length(), hiliteStyle, false); - } - } - } - } - - if (selected) { - sd.setCharacterAttributes(0, Integer.MAX_VALUE, style, false); - } + addRevision(revisionCell.getRevisionControl(), item, selected, highlights); + addCommitMessage(revisionCell.getCommitMessageControl(), item, selected, highlights); + addAuthor(revisionCell.getAuthorControl(), item, selected, highlights); + addDate(revisionCell.getDateControl(), item, selected, highlights); } catch (BadLocationException e) { ErrorManager.getDefault().notify(e); } } - lastWidth = resizePane(textPane.getText(), list, lastWidth); + lastWidth = resizePane(revisionCell.getCommitMessageControl().getText(), list, lastWidth); return this; } - + @SuppressWarnings("empty-statement") - private int resizePane(String text, JList list, int lastWidth) { - if(text == null) { + private int resizePane (String text, JList list, int lastWidth) { + if (text == null) { text = ""; //NOI18N } int width = summaryView.getMaster().getComponent().getWidth(); @@ -525,25 +340,306 @@ FontMetrics fm = list.getFontMetrics(list.getFont()); int lines = 0; for (String row : rows) { - Rectangle2D rect = fm.getStringBounds(row, textPane.getGraphics()); + Rectangle2D rect = fm.getStringBounds(row, revisionCell.getGraphics()); lines += (int) (rect.getWidth() / (width - 80) + 1); } - int ph = fm.getHeight() * lines + 9; - textPane.setPreferredSize(new Dimension(width - 50 - ICON_COLLAPSED.getIconWidth(), ph)); - setPreferredSize(textPane.getPreferredSize()); + int ph = fm.getHeight() * (lines + 1) + 4; + revisionCell.setPreferredSize(new Dimension(width - 50 - ICON_COLLAPSED.getIconWidth(), ph)); + setPreferredSize(revisionCell.getPreferredSize()); } return width; } + + private void addRevision (JTextPane pane, RevisionItem item, boolean selected, Collection highlights) throws BadLocationException { + StyledDocument sd = pane.getStyledDocument(); + // clear document + clearSD(pane, sd); + + Style selectedStyle = createSelectedStyle(pane); + Style normalStyle = createNormalStyle(pane); + Style hiliteStyle = createHiliteStyleStyle(pane, normalStyle, searchHiliteAttrs); + Style style; + if (selected) { + style = selectedStyle; + } else { + style = normalStyle; + } + + + // add revision + sd.insertString(0, item.getUserData().getRevision(), style); + if (!selected) { + for (AbstractSummaryView.LogEntry.RevisionHighlight highlight : item.getUserData().getRevisionHighlights()) { + Style s = pane.addStyle(null, normalStyle); + StyleConstants.setForeground(s, highlight.getForeground()); + StyleConstants.setBackground(s, highlight.getBackground()); + sd.setCharacterAttributes(highlight.getStart(), highlight.getLength(), s, false); + } + for (SearchHighlight highlight : highlights) { + if (highlight.getKind() == SearchHighlight.Kind.REVISION) { + int doclen = sd.getLength(); + String highlightMessage = highlight.getSearchText(); + String revisionText = item.getUserData().getRevision().toLowerCase(); + int idx = revisionText.indexOf(highlightMessage); + if (idx > -1) { + sd.setCharacterAttributes(doclen - revisionText.length() + idx, highlightMessage.length(), hiliteStyle, false); + } + } + } + } + } + + private void addAuthor (JTextPane pane, RevisionItem item, boolean selected, Collection highlights) throws BadLocationException { + LogEntry entry = item.getUserData(); + StyledDocument sd = pane.getStyledDocument(); + clearSD(pane, sd); + Style selectedStyle = createSelectedStyle(pane); + Style normalStyle = createNormalStyle(pane); + Style style; + if (selected) { + style = selectedStyle; + } else { + style = normalStyle; + } + Style authorStyle = createAuthorStyle(pane, normalStyle); + Style hiliteStyle = createHiliteStyleStyle(pane, normalStyle, searchHiliteAttrs); + String author = entry.getAuthor(); + VCSHyperlinkSupport.StyledDocumentHyperlink l = linkerSupport.getLinker(VCSHyperlinkSupport.AuthorLinker.class, id); + if (l == null) { + VCSKenaiAccessor.KenaiUser kenaiUser = getKenaiUser(author); + if (kenaiUser != null) { + AuthorLinker a = new VCSHyperlinkSupport.AuthorLinker(kenaiUser, authorStyle, sd, author); + a.setReferenceComponent(revisionCell); + l=a; + linkerSupport.add(l, id); + } + } + int pos = sd.getLength(); + if (l != null) { + l.insertString(sd, selected ? style : null); + } else { + sd.insertString(sd.getLength(), author, style); + } + if (!selected) { + for (SearchHighlight highlight : highlights) { + if (highlight.getKind() == SearchHighlight.Kind.AUTHOR) { + int doclen = sd.getLength(); + String highlightMessage = highlight.getSearchText(); + String authorText = sd.getText(pos, doclen - pos).toLowerCase(); + int idx = authorText.indexOf(highlightMessage); + if (idx > -1) { + sd.setCharacterAttributes(doclen - authorText.length() + idx, highlightMessage.length(), hiliteStyle, false); + } + } + } + } + } + + private void addDate (JTextPane pane, RevisionItem item, boolean selected, Collection highlights) throws BadLocationException { + + LogEntry entry = item.getUserData(); + StyledDocument sd = pane.getStyledDocument(); + // clear document + clearSD(pane, sd); + + Style selectedStyle = createSelectedStyle(pane); + Style normalStyle = createNormalStyle(pane); + Style style; + if (selected) { + style = selectedStyle; + } else { + style = normalStyle; + } + + // add date + sd.insertString(sd.getLength(), entry.getDate(), style); + } + + private void addCommitMessage (JTextPane pane, RevisionItem item, boolean selected, Collection highlights) throws BadLocationException { + LogEntry entry = item.getUserData(); + StyledDocument sd = pane.getStyledDocument(); + clearSD(pane, sd); + Style selectedStyle = createSelectedStyle(pane); + Style normalStyle = createNormalStyle(pane); + Style linkStyle = createLinkStyle(pane, normalStyle); + Style hiliteStyle = createHiliteStyleStyle(pane, normalStyle, searchHiliteAttrs); + Style issueHyperlinkStyle = createIssueHyperlinkStyle(pane, normalStyle); + Style style; + if (selected) { + style = selectedStyle; + } else { + style = normalStyle; + } + boolean messageChanged = !entry.getMessage().isEmpty(); + String commitMessage = entry.getMessage().trim(); + int nlc; + int i; + for (i = 0, nlc = -1; i != -1; i = commitMessage.indexOf('\n', i + 1), nlc++); + + if (nlc > 0 && !item.messageExpanded) { + //get first line of comment if collapsed + commitMessage = commitMessage.substring(0, commitMessage.indexOf("\n")); //NOI18N + } + IssueLinker l = linkerSupport.getLinker(VCSHyperlinkSupport.IssueLinker.class, id); + if (messageChanged) { + lastWidth = -1; + if (l != null) { + // must reinitialize issue linker to paint the new message + linkerSupport.remove(l, id); + l = null; + } + } + if (l == null) { + for (VCSHyperlinkProvider hp : getHyperlinkProviders()) { + l = VCSHyperlinkSupport.IssueLinker.create(hp, issueHyperlinkStyle, summaryView.getRoot(), sd, commitMessage); + if (l != null) { + l.setReferenceComponent(revisionCell); + + linkerSupport.add(l, id); + break; // got the first one + } + } + } + if (l != null) { + l.insertString(sd, style); + } else { + sd.insertString(0, commitMessage, style); + } + + { + //make the first line bold + int lineEnd = sd.getText(0, sd.getLength()).indexOf("\n"); + if (lineEnd == -1) { + lineEnd = sd.getLength(); + } + Style s = pane.addStyle(null, style); + StyleConstants.setBold(s, true); + sd.setCharacterAttributes(0, lineEnd, s, false); + } + + int msglen = commitMessage.length(); + int doclen = sd.getLength(); + + // remove previous tooltips + { + MessageTooltip mtt = linkerSupport.getLinker(MessageTooltip.class, id); + linkerSupport.remove(mtt, id); + } + + // insert message tooltip and expand link, only if the commit message has more than one line + // AND if it is not fully visible + if (nlc > 0 && !item.messageExpanded) { + //insert expand link + ExpandMsgHyperlink el = linkerSupport.getLinker(ExpandMsgHyperlink.class, id); + if (el == null) { + el = new ExpandMsgHyperlink(item, sd.getLength(), id); + el.setReferenceComponent(revisionCell); + linkerSupport.add(el, id); + } + el.insertString(sd, linkStyle); + + //insert commit message tooltip + MessageTooltip messageTooltip = new MessageTooltip(entry.getMessage(), 0, sd.getLength()); + messageTooltip.setReferenceComponent(revisionCell); + linkerSupport.add(messageTooltip, id); + } + + if (!selected) { + for (SearchHighlight highlight : highlights) { + if (highlight.getKind() == SearchHighlight.Kind.MESSAGE) { + String highlightMessage = highlight.getSearchText(); + int idx = commitMessage.toLowerCase().indexOf(highlightMessage); + if (idx == -1) { + if (nlc > 0 && !item.messageExpanded && entry.getMessage().toLowerCase().contains(highlightMessage)) { + sd.setCharacterAttributes(doclen, sd.getLength(), hiliteStyle, false); + } + } else { + sd.setCharacterAttributes(doclen - msglen + idx, highlightMessage.length(), hiliteStyle, false); + } + } + } + } + if (selected) { + sd.setCharacterAttributes(0, Integer.MAX_VALUE, style, false); + } + } + + private Style createNormalStyle (JTextPane textPane) { + Style normalStyle = textPane.addStyle("normal", null); //NOI18N + StyleConstants.setForeground(normalStyle, UIManager.getColor("List.foreground")); //NOI18N + return normalStyle; + } + + private Style createIssueHyperlinkStyle (JTextPane textPane, Style normalStyle) { + Style issueHyperlinkStyle = textPane.addStyle("issuehyperlink", normalStyle); //NOI18N + StyleConstants.setForeground(issueHyperlinkStyle, Color.BLUE); + StyleConstants.setUnderline(issueHyperlinkStyle, true); + return issueHyperlinkStyle; + } + + private Style createAuthorStyle (JTextPane textPane, Style normalStyle) { + Style authorStyle = textPane.addStyle("author", normalStyle); //NOI18N + StyleConstants.setForeground(authorStyle, Color.BLUE); + return authorStyle; + } + + private Style createLinkStyle (JTextPane textPane, Style normalStyle) { + Style linkStyle = textPane.addStyle("link", normalStyle); //NOI18N + StyleConstants.setForeground(linkStyle, Color.BLUE); + StyleConstants.setBold(linkStyle, true); + return linkStyle; + } + + private Style createNoindentStyle (JTextPane textPane) { + Style noindentStyle = textPane.addStyle("noindent", null); //NOI18N + StyleConstants.setLeftIndent(noindentStyle, 0); + return noindentStyle; + } + + private Style createSelectedStyle (JTextPane textPane) { + Style selectedStyle = textPane.addStyle("selected", null); //NOI18N + StyleConstants.setForeground(selectedStyle, selectionForeground); + StyleConstants.setBackground(selectedStyle, selectionBackground); + return selectedStyle; + } + + private Style createHiliteStyleStyle (JTextPane textPane, Style normalStyle, AttributeSet searchHiliteAttrs) { + Style 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); + } + + return hiliteStyle; + } @Override public void paint(Graphics g) { super.paint(g); - linkerSupport.computeBounds(textPane, id); + linkerSupport.computeBounds(revisionCell.getCommitMessageControl(), id); + ExpandLink link = linkerSupport.getLinker(ExpandLink.class, id); if (link != null) { + link.computeBounds(expandButton); } } + + private void clearSD (JTextPane pane, StyledDocument sd) { + try { + Style noindentStyle = createNoindentStyle(pane); + + sd.remove(0, sd.getLength()); + sd.setParagraphAttributes(0, sd.getLength(), noindentStyle, false); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + } } private static class MessageTooltip extends VCSHyperlinkSupport.Hyperlink { @@ -551,6 +647,11 @@ private final int start; private final int end; private final String text; + private RevisionItemCell referenceComponent; + + public void setReferenceComponent(RevisionItemCell referenceComponent) { + this.referenceComponent = referenceComponent; + } private MessageTooltip (String text, int start, int end) { this.start = start; @@ -560,7 +661,7 @@ @Override public boolean mouseMoved (Point p, JComponent component) { - if (bounds != null && component.getToolTipText() == null) { + if (bounds != null /*&& component.getToolTipText() == null*/) { for (Rectangle b : bounds) { if (b.contains(p)) { component.setToolTipText(text); @@ -568,6 +669,7 @@ } } } + component.setToolTipText(null); return false; } @@ -578,6 +680,9 @@ @Override public void computeBounds (JTextPane textPane) { + if (null != referenceComponent) { + textPane = referenceComponent.getCommitMessageControl(); + } Rectangle tpBounds = textPane.getBounds(); TextUI tui = textPane.getUI(); try { @@ -588,6 +693,9 @@ for (int pos = start; pos <= end; ++pos) { Rectangle startr = tui.modelToView(textPane, pos, Position.Bias.Forward); Rectangle endr = tui.modelToView(textPane, pos + 1, Position.Bias.Backward); + //prevent NPE if width is too small + if (null == startr) {continue;} + if (null == endr) {continue;} if (startr.y > lastY) { rects.add(rec); rec = new Rectangle(tpBounds.x + startr.x, startr.y, endr.x - startr.x, startr.height); @@ -596,6 +704,13 @@ rec.setSize(rec.width + endr.x - startr.x, rec.height); } } + // NOTE the textPane is positioned within a parent panel so the relative bound has to be modified too + // FIXME this is an ugly hack because the public API of org.netbeans.modules.versioning.util.VCSHyperlinkSupport should stay stable + // SOLUTION: provide a reference component which is injected via setter and + // translate from bottom to top of the component hiearchy + if (null != referenceComponent){ + referenceComponent.correctTranslation(textPane, rec); + } rects.add(rec); rects.remove(0); bounds = rects.toArray(new Rectangle[rects.size()]); @@ -1000,13 +1115,18 @@ } } - private static final String LINK_STRING = "..."; //NOI18N + private static final String LINK_STRING = " ..."; //NOI18N private static final int LINK_STRING_LEN = LINK_STRING.length(); private class ExpandMsgHyperlink extends VCSHyperlinkSupport.StyledDocumentHyperlink { private Rectangle bounds; private final int startoffset; private final AbstractSummaryView.RevisionItem item; private final String revision; + private RevisionItemCell referenceComponent; + + public void setReferenceComponent (RevisionItemCell referenceComponent) { + this.referenceComponent = referenceComponent; + } public ExpandMsgHyperlink (AbstractSummaryView.RevisionItem item, int startoffset, String revision) { this.startoffset = startoffset; @@ -1037,6 +1157,9 @@ @Override public void computeBounds(JTextPane textPane) { + if (null != referenceComponent) { + textPane=referenceComponent.getCommitMessageControl(); + } Rectangle tpBounds = textPane.getBounds(); TextUI tui = textPane.getUI(); bounds = new Rectangle(); @@ -1049,6 +1172,9 @@ Rectangle endr = mtv.getBounds(); bounds = new Rectangle(tpBounds.x + startr.x, startr.y, endr.x - startr.x, startr.height); + if (null != referenceComponent) { + referenceComponent.correctTranslation(textPane, bounds); + } } catch (BadLocationException ex) { throw new RuntimeException(ex); } diff --git a/versioning.util/src/org/netbeans/modules/versioning/util/VCSHyperlinkSupport.java b/versioning.util/src/org/netbeans/modules/versioning/util/VCSHyperlinkSupport.java --- a/versioning.util/src/org/netbeans/modules/versioning/util/VCSHyperlinkSupport.java +++ b/versioning.util/src/org/netbeans/modules/versioning/util/VCSHyperlinkSupport.java @@ -60,6 +60,7 @@ import javax.swing.text.Style; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; +import org.netbeans.modules.versioning.history.RevisionItemCell; import org.netbeans.modules.versioning.util.VCSKenaiAccessor.KenaiUser; import org.openide.util.Exceptions; import org.openide.util.NbBundle; @@ -184,6 +185,11 @@ private final File root; private final int length; private final Style issueHyperlinkStyle; + private RevisionItemCell referenceComponent = null; + + public void setReferenceComponent (RevisionItemCell referenceComponent) { + this.referenceComponent = referenceComponent; + } private IssueLinker(VCSHyperlinkProvider hp, Style issueHyperlinkStyle, File root, StyledDocument sd, String text, int[] spans) { this.length = spans.length / 2; @@ -232,6 +238,10 @@ @Override public void computeBounds(JTextPane textPane) { + //use correct textpane + if (null!=referenceComponent){ + textPane=referenceComponent.getCommitMessageControl(); + } Rectangle tpBounds = textPane.getBounds(); TextUI tui = textPane.getUI(); this.bounds = new Rectangle[length]; @@ -240,6 +250,10 @@ Rectangle startr = tui.modelToView(textPane, docstart[i], Position.Bias.Forward).getBounds(); Rectangle endr = tui.modelToView(textPane, docend[i], Position.Bias.Backward).getBounds(); this.bounds[i] = new Rectangle(tpBounds.x + startr.x, startr.y, endr.x - startr.x, startr.height); + //NOTE the textPane is positioned within a parent panel so the origin has to be modified too + if (null != referenceComponent) { + referenceComponent.correctTranslation(textPane, this.bounds[i]); + } } catch (BadLocationException ex) { } } } @@ -285,7 +299,11 @@ private final String author; private final Style authorStyle; private final String insertToChat; + private RevisionItemCell referenceComponent = null; + public void setReferenceComponent(RevisionItemCell referenceComponent) { + this.referenceComponent = referenceComponent; + } public AuthorLinker(KenaiUser kenaiUser, Style authorStyle, StyledDocument sd, String author) throws BadLocationException { this(kenaiUser, authorStyle, sd, author, null); } @@ -305,6 +323,10 @@ @Override public void computeBounds(JTextPane textPane) { + //use correct textpane + if (null != referenceComponent) { + textPane = referenceComponent.getAuthorControl(); + } Rectangle tpBounds = textPane.getBounds(); TextUI tui = textPane.getUI(); this.bounds = new Rectangle(); @@ -315,6 +337,10 @@ endr.x += kenaiUser.getIcon().getIconWidth(); } this.bounds = new Rectangle(tpBounds.x + startr.x, startr.y, endr.x - startr.x, startr.height); + + if (null != referenceComponent) { + referenceComponent.correctTranslation(textPane, this.bounds); + } } catch (BadLocationException ex) { Exceptions.printStackTrace(ex); }