| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.netbeans.modules.editor.errorstripe; |
| |
| import java.awt.Color; |
| import java.awt.Component; |
| import java.awt.Cursor; |
| import java.awt.Dimension; |
| import java.awt.Graphics; |
| import java.awt.Insets; |
| import java.awt.Point; |
| import java.awt.Rectangle; |
| import java.awt.event.MouseEvent; |
| import java.awt.event.MouseListener; |
| import java.awt.event.MouseMotionListener; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.lang.reflect.Field; |
| import java.text.MessageFormat; |
| import javax.accessibility.Accessible; |
| import javax.accessibility.AccessibleContext; |
| import javax.accessibility.AccessibleRole; |
| import javax.swing.Icon; |
| import javax.swing.ImageIcon; |
| import javax.swing.JComponent; |
| import javax.swing.JLayeredPane; |
| import javax.swing.JScrollPane; |
| import javax.swing.SwingUtilities; |
| import javax.swing.UIManager; |
| import javax.swing.event.DocumentEvent; |
| import javax.swing.event.DocumentListener; |
| import javax.swing.plaf.TextUI; |
| import javax.swing.text.AbstractDocument; |
| import javax.swing.text.BadLocationException; |
| import javax.swing.text.Document; |
| import javax.swing.text.JTextComponent; |
| import javax.swing.text.StyledDocument; |
| import javax.swing.text.View; |
| import org.netbeans.api.editor.fold.FoldHierarchy; |
| import org.netbeans.api.editor.fold.FoldHierarchyEvent; |
| import org.netbeans.api.editor.fold.FoldHierarchyListener; |
| |
| import org.netbeans.editor.BaseDocument; |
| import org.netbeans.editor.BaseTextUI; |
| import org.netbeans.editor.Utilities; |
| import org.netbeans.lib.editor.util.StringEscapeUtils; |
| import org.netbeans.modules.editor.errorstripe.caret.CaretMark; |
| import org.netbeans.modules.editor.errorstripe.privatespi.Mark; |
| import org.netbeans.spi.editor.errorstripe.UpToDateStatus; |
| import org.openide.ErrorManager; |
| import org.netbeans.modules.editor.errorstripe.privatespi.Status; |
| import org.openide.text.NbDocument; |
| import org.openide.util.NbBundle; |
| import org.openide.util.RequestProcessor; |
| import org.openide.util.WeakListeners; |
| |
| |
| /** |
| * |
| * @author Jan Lahoda |
| */ |
| public class AnnotationView extends JComponent implements FoldHierarchyListener, MouseListener, MouseMotionListener, DocumentListener, PropertyChangeListener, Accessible { |
| |
| /*package private*/ static final ErrorManager ERR = ErrorManager.getDefault().getInstance("org.netbeans.modules.editor.errorstripe.AnnotationView"); // NOI18N |
| |
| /*package private*/ static final ErrorManager TIMING_ERR = ErrorManager.getDefault().getInstance("org.netbeans.modules.editor.errorstripe.AnnotationView.timing"); // NOI18N |
| |
| private static final int STATUS_BOX_SIZE = 7; |
| private static final int THICKNESS = STATUS_BOX_SIZE + 6; |
| /*package private*/ static final int PIXELS_FOR_LINE = 3/*height / lines*/; |
| /*package private*/ static final int LINE_SEPARATOR_SIZE = 1/*2*/; |
| /*package private*/ static final int HEIGHT_OFFSET = 20; |
| |
| /*package private*/ static final int UPPER_HANDLE = 4; |
| /*package private*/ static final int LOWER_HANDLE = 4; |
| |
| private BaseDocument doc; |
| private final JTextComponent pane; |
| |
| private static final Color STATUS_UP_PART_COLOR = Color.WHITE; |
| private static final Color STATUS_DOWN_PART_COLOR = new Color(0xCDCABB); |
| |
| private static final int QUIET_TIME = 100; |
| |
| private static final RequestProcessor WORKER = new RequestProcessor(AnnotationView.class.getName(), 1, false, false); //NOI18N |
| private final RequestProcessor.Task repaintTask; |
| private final RepaintTask repaintTaskRunnable; |
| private final Insets scrollBar; |
| private final AnnotationViewData data; |
| |
| private static final Icon busyIcon; |
| |
| private DocumentListener weakDocL; |
| |
| static { |
| busyIcon = new ImageIcon(AnnotationView.class.getResource("resources/hodiny.gif")); |
| } |
| |
| // public AnnotationView(JTextComponent pane) { |
| // this(pane, null); |
| // } |
| |
| /** Creates a new instance of AnnotationViewBorder */ |
| public AnnotationView(JTextComponent pane/*, List/ *<MarkProviderCreator>* / creators*/) { |
| this.pane = pane; |
| // Set the name to be able to check for this component when "errorStripeOnly" property |
| // is turned on for the pane in CustomizableSideBar. |
| setName("errorStripe"); |
| |
| repaintTask = WORKER.create(repaintTaskRunnable = new RepaintTask()); |
| this.data = new AnnotationViewDataImpl(this, pane); |
| this.scrollBar = UIManager.getInsets("Nb.Editor.ErrorStripe.ScrollBar.Insets"); // NOI18N |
| |
| FoldHierarchy fh = FoldHierarchy.get(pane); |
| fh.addFoldHierarchyListener(WeakListeners.create(FoldHierarchyListener.class, this, fh)); |
| pane.addPropertyChangeListener(WeakListeners.propertyChange(this, pane)); |
| |
| updateForNewDocument(); |
| |
| addMouseListener(this); |
| addMouseMotionListener(this); |
| |
| setOpaque(true); |
| |
| setToolTipText(NbBundle.getMessage(AnnotationView.class,"TP_ErrorStripe")); |
| } |
| |
| /*package private for tests*/AnnotationViewData getData() { |
| return data; |
| } |
| |
| private synchronized void updateForNewDocument() { |
| data.unregister(); |
| Document newDocument = pane.getDocument(); |
| |
| if (weakDocL != null && this.doc != null) { |
| this.doc.removeDocumentListener(weakDocL); |
| this.doc = null; |
| } |
| |
| if (newDocument instanceof BaseDocument) { |
| this.doc = (BaseDocument) pane.getDocument(); |
| weakDocL = WeakListeners.document(this, this.doc); |
| this.doc.addDocumentListener(weakDocL); |
| } |
| |
| data.register(this.doc); |
| } |
| |
| /*package private for tests*/int[] getLinesSpan(int currentLine) { |
| AbstractDocument adoc = doc; |
| if (adoc != null) |
| adoc.readLock(); |
| try { |
| double componentHeight = getComponentHeight(); |
| double usableHeight = getUsableHeight(); |
| |
| double position = _modelToView(currentLine, componentHeight, usableHeight); |
| |
| if (position == (-1)) |
| return new int[] {currentLine, currentLine}; |
| |
| int startLine = currentLine; |
| int endLine = currentLine; |
| |
| while (position == _modelToView(startLine - 1, componentHeight, usableHeight) && startLine > 0) |
| startLine--; |
| |
| while ((endLine + 1) < Utilities.getRowCount(doc) && position == _modelToView(endLine + 1, componentHeight, usableHeight)) |
| endLine++; |
| |
| return new int[] {startLine, endLine}; |
| } finally { |
| if (adoc != null) |
| adoc.readUnlock(); |
| } |
| } |
| |
| private void drawOneColorGlobalStatus(Graphics g, Color color) { |
| g.setColor(color); |
| |
| int x = (THICKNESS - STATUS_BOX_SIZE) / 2; |
| int y = (topOffset() - STATUS_BOX_SIZE) / 2; |
| |
| g.fillRect(x, y, STATUS_BOX_SIZE, STATUS_BOX_SIZE); |
| |
| g.setColor(STATUS_DOWN_PART_COLOR); |
| |
| g.drawLine(x - 1, y - 1, x + STATUS_BOX_SIZE, y - 1 ); |
| g.drawLine(x - 1, y - 1, x - 1, y + STATUS_BOX_SIZE); |
| |
| g.setColor(STATUS_UP_PART_COLOR); |
| |
| g.drawLine(x - 1, y + STATUS_BOX_SIZE, x + STATUS_BOX_SIZE, y + STATUS_BOX_SIZE); |
| g.drawLine(x + STATUS_BOX_SIZE, y - 1, x + STATUS_BOX_SIZE, y + STATUS_BOX_SIZE); |
| } |
| |
| private void drawInProgressGlobalStatus(Graphics g, Color color) { |
| int x = (THICKNESS - STATUS_BOX_SIZE) / 2; |
| int y = (topOffset() - STATUS_BOX_SIZE) / 2; |
| |
| busyIcon.paintIcon(this, g, x, y); // NOI18N |
| |
| g.setColor(STATUS_DOWN_PART_COLOR); |
| |
| g.drawLine(x - 1, y - 1, x + STATUS_BOX_SIZE, y - 1 ); |
| g.drawLine(x - 1, y - 1, x - 1, y + STATUS_BOX_SIZE); |
| |
| g.setColor(STATUS_UP_PART_COLOR); |
| |
| g.drawLine(x - 1, y + STATUS_BOX_SIZE, x + STATUS_BOX_SIZE, y + STATUS_BOX_SIZE); |
| g.drawLine(x + STATUS_BOX_SIZE, y - 1, x + STATUS_BOX_SIZE, y + STATUS_BOX_SIZE); |
| |
| } |
| |
| private static final Color GLOBAL_RED = new Color(0xFF2A1C); |
| private static final Color GLOBAL_YELLOW = new Color(0xE1AA00); |
| private static final Color GLOBAL_GREEN = new Color(0x65B56B); |
| |
| private Color getColorForGlobalStatus(Status status) { |
| if (Status.STATUS_ERROR == status) |
| return GLOBAL_RED; |
| |
| if (Status.STATUS_WARNING == status) |
| return GLOBAL_YELLOW; |
| |
| return GLOBAL_GREEN; |
| } |
| |
| private void drawGlobalStatus(Graphics g) { |
| UpToDateStatus type = data.computeTotalStatusType(); |
| Color resultingColor; |
| |
| if (type == UpToDateStatus.UP_TO_DATE_DIRTY) { |
| drawOneColorGlobalStatus(g, UIManager.getColor("Panel.background")); // NOI18N |
| } else { |
| if (type == UpToDateStatus.UP_TO_DATE_PROCESSING) { |
| // Status totalStatus = data.computeTotalStatus(); |
| // |
| drawInProgressGlobalStatus(g, null/*Status.getDefaultColor(totalStatus)*/); |
| } else { |
| if (type == UpToDateStatus.UP_TO_DATE_OK) { |
| Status totalStatus = data.computeTotalStatus(); |
| |
| drawOneColorGlobalStatus(g, getColorForGlobalStatus(totalStatus)); |
| } else { |
| throw new IllegalStateException("Unknown up-to-date type: " + type); // NOI18N |
| } |
| } |
| } |
| } |
| |
| private int getCurrentLine() { |
| Document doc = pane.getDocument(); |
| int line = -1; |
| |
| if (doc instanceof StyledDocument && pane.getCaret() != null) { |
| int offset = pane.getCaretPosition(); //TODO: AWT? |
| line = NbDocument.findLineNumber((StyledDocument) doc, offset); |
| } |
| |
| return line; |
| } |
| |
| private static Field currWriterField; |
| private static boolean isWriteLocked(AbstractDocument doc) { |
| if (currWriterField == null) { |
| Field f = null; |
| try { |
| f = AbstractDocument.class.getDeclaredField("currWriter"); // NOI18N |
| } catch (NoSuchFieldException ex) { |
| throw new IllegalStateException(ex); |
| } |
| f.setAccessible(true); |
| synchronized (doc) { |
| currWriterField = f; |
| } |
| } |
| try { |
| synchronized (doc) { |
| return currWriterField.get(doc) != null; |
| } |
| } catch (IllegalAccessException ex) { |
| throw new IllegalStateException(ex); |
| } |
| } |
| |
| |
| @Override |
| public void paintComponent(Graphics g) { |
| if (isWriteLocked(doc)) { |
| // Try a little bit later ;-) |
| repaint(100); |
| return; |
| } |
| // Thread.dumpStack(); |
| long startTime = System.currentTimeMillis(); |
| super.paintComponent(g); |
| |
| Color oldColor = g.getColor(); |
| |
| Color backColor = UIManager.getColor("NbEditorGlyphGutter.background"); //NOI18N |
| if( null == backColor ) |
| backColor = UIManager.getColor("Panel.background"); // NOI18N |
| g.setColor(backColor); |
| |
| g.fillRect(0, 0, getWidth(), getHeight()); |
| |
| // SortedMap marks = getMarkMap(); |
| int currentline = getCurrentLine(); |
| int annotatedLine = data.findNextUsedLine(-1); |
| |
| AbstractDocument adoc = doc; |
| // Try once again for the case if it was locked during the painting: |
| if (isWriteLocked(doc)) { |
| // Try a little bit later ;-) |
| repaint(100); |
| return; |
| } |
| |
| if (adoc != null) { |
| adoc.readLock(); |
| } |
| try { |
| while (annotatedLine != Integer.MAX_VALUE) { |
| // System.err.println("annotatedLine = " + annotatedLine ); |
| int[] lineSpan = getLinesSpan(annotatedLine); |
| int startLine = lineSpan[0]; |
| int endLine = lineSpan[1]; |
| |
| Mark m = data.getMainMarkForBlock(startLine, endLine); |
| |
| if (m != null) { |
| Status s = m.getStatus(); |
| double start = modelToView(annotatedLine); |
| |
| if (s != null) { |
| // System.err.println("m = " + m ); |
| Color color = m.getEnhancedColor(); |
| |
| if (color == null) { |
| color = Status.getDefaultColor(s); |
| } |
| |
| assert color != null; |
| |
| g.setColor(color); |
| |
| |
| //g.fillRect(1, (int) start, THICKNESS - 2, PIXELS_FOR_LINE); |
| //* 3D Version |
| if (m.getType() != Mark.TYPE_CARET) { |
| g.fillRect(1, (int) start, THICKNESS - 2, PIXELS_FOR_LINE); |
| //g.draw3DRect(1, (int) start, THICKNESS - 3, PIXELS_FOR_LINE - 1, true); |
| } |
| //*/ |
| if ((startLine <= currentline && currentline <= endLine) || m.getType() == Mark.TYPE_CARET) { |
| drawCurrentLineMark(g, (int) start); |
| } |
| } |
| } |
| |
| annotatedLine = data.findNextUsedLine(endLine); |
| } |
| |
| drawGlobalStatus(g); |
| } finally { |
| if (adoc != null) |
| adoc.readUnlock(); |
| } |
| |
| g.setColor(oldColor); |
| |
| long end = System.currentTimeMillis(); |
| |
| if (TIMING_ERR.isLoggable(ErrorManager.INFORMATIONAL)) { |
| TIMING_ERR.log("AnnotationView.paintComponent consumed: " + (end - startTime)); |
| } |
| } |
| |
| private void drawCurrentLineMark(Graphics g, int start) { |
| g.setColor( CaretMark.getCaretMarkColor()); |
| g.drawLine(2, start + PIXELS_FOR_LINE / 2, THICKNESS - 3, start + PIXELS_FOR_LINE / 2 ); |
| g.fillRect( THICKNESS / 2 - PIXELS_FOR_LINE / 2, start, PIXELS_FOR_LINE, PIXELS_FOR_LINE ); |
| g.draw3DRect( THICKNESS / 2 - PIXELS_FOR_LINE / 2, start, PIXELS_FOR_LINE - 1, PIXELS_FOR_LINE - 1, true ); |
| |
| } |
| |
| /*private*/ void fullRepaint() { |
| fullRepaint(false); |
| } |
| |
| /*private*/ void fullRepaint(final boolean clearMarksCache) { |
| fullRepaint(clearMarksCache, false); |
| } |
| |
| /*private*/ void fullRepaint(final boolean clearMarksCache, final boolean clearModelToViewCache) { |
| synchronized (repaintTaskRunnable) { |
| repaintTaskRunnable.setClearMarksCache(clearMarksCache); |
| repaintTaskRunnable.setClearModelToViewCache(clearModelToViewCache); |
| repaintTask.schedule(QUIET_TIME); |
| } |
| } |
| |
| private class RepaintTask implements Runnable { |
| private boolean clearMarksCache; |
| private boolean clearModelToViewCache; |
| |
| public void setClearMarksCache(boolean clearMarksCache) { |
| this.clearMarksCache |= clearMarksCache; |
| } |
| |
| public void setClearModelToViewCache(boolean clearModelToViewCache) { |
| this.clearModelToViewCache |= clearModelToViewCache; |
| } |
| |
| private synchronized boolean readAndDestroyClearMarksCache() { |
| boolean result = clearMarksCache; |
| |
| clearMarksCache = false; |
| |
| return result; |
| } |
| |
| private synchronized boolean readAndDestroyClearModelToViewCache() { |
| boolean result = clearModelToViewCache; |
| |
| clearModelToViewCache = false; |
| |
| return result; |
| } |
| |
| @Override |
| public void run() { |
| final boolean clearMarksCache = readAndDestroyClearMarksCache(); |
| final boolean clearModelToViewCache= readAndDestroyClearModelToViewCache(); |
| |
| //Fix for #54193: |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| synchronized (AnnotationView.this) { |
| if (clearMarksCache) { |
| data.clear(); |
| } |
| if (clearModelToViewCache) { |
| modelToViewCache = null; |
| } |
| } |
| |
| invalidate(); |
| repaint(); |
| } |
| }); |
| } |
| } |
| |
| private void documentChange() { |
| fullRepaint(lines != Utilities.getRowCount(doc)); |
| } |
| |
| private double getComponentHeight() { |
| final double[] ret = new double[1]; |
| pane.getDocument().render(new Runnable() { |
| @Override |
| public void run() { |
| ret[0] = pane.getUI().getRootView(pane).getPreferredSpan(View.Y_AXIS); |
| } |
| }); |
| return ret[0]; |
| } |
| |
| double getUsableHeight() { |
| //fix for issue #54080: |
| //find the scrollpane which contains the pane: |
| Component scrollPaneCandidade = pane.getParent(); |
| |
| if (scrollPaneCandidade instanceof JLayeredPane) { |
| scrollPaneCandidade = scrollPaneCandidade.getParent(); |
| } |
| |
| if (scrollPaneCandidade != null && !(scrollPaneCandidade instanceof JScrollPane)) { |
| scrollPaneCandidade = scrollPaneCandidade.getParent(); |
| } |
| |
| if (scrollPaneCandidade == null || !(scrollPaneCandidade instanceof JScrollPane) || scrollBar == null) { |
| //no help for #54080: |
| return getHeight() - HEIGHT_OFFSET; |
| } |
| |
| JScrollPane scrollPane = (JScrollPane) scrollPaneCandidade; |
| int visibleHeight = scrollPane.getViewport().getExtentSize().height; |
| |
| int topButton = topOffset(); |
| int bottomButton = scrollBar.bottom; |
| |
| return visibleHeight - topButton - bottomButton; |
| } |
| |
| int topOffset() { |
| if (scrollBar == null) { |
| //no help for #54080: |
| return HEIGHT_OFFSET; |
| } |
| |
| return (HEIGHT_OFFSET > scrollBar.top ? HEIGHT_OFFSET : scrollBar.top) + PIXELS_FOR_LINE; |
| } |
| |
| private int[] modelToViewCache = null; |
| private int lines = -1; |
| private int height = -1; |
| |
| private int getYFromPos(int offset) throws BadLocationException { |
| TextUI ui = pane.getUI(); |
| int result; |
| |
| // For some reason the offset may become -1; uncomment following line to see that |
| offset = Math.max(offset, 0); |
| if (ui instanceof BaseTextUI) { |
| result = ((BaseTextUI) ui).getYFromPos(offset); |
| } else { |
| Rectangle r = pane.modelToView(offset); |
| |
| result = r != null ? r.y : 0; |
| } |
| |
| if (result == 0) { |
| return -1; |
| } else { |
| return result; |
| } |
| } |
| |
| private synchronized int getModelToViewImpl(int line) throws BadLocationException { |
| int docLines = Utilities.getRowCount(doc); |
| |
| if (modelToViewCache == null || height != pane.getHeight() || lines != docLines) { |
| modelToViewCache = new int[Utilities.getRowCount(doc) + 2]; |
| lines = Utilities.getRowCount(doc); |
| height = pane.getHeight(); |
| } |
| |
| if (line >= docLines) |
| return -1; |
| |
| int result = modelToViewCache[line + 1]; |
| |
| if (result == 0) { |
| int lineOffset = Utilities.getRowStartFromLineOffset((BaseDocument) pane.getDocument(), line); |
| |
| modelToViewCache[line + 1] = result = getYFromPos(lineOffset); |
| } |
| |
| if (result == (-1)) |
| result = 0; |
| |
| return result; |
| } |
| |
| /*package private*/ double modelToView(int line) { |
| return _modelToView(line, getComponentHeight(), getUsableHeight()); |
| } |
| |
| private double _modelToView(int line, double componentHeight, double usableHeight) { |
| try { |
| int r = getModelToViewImpl(line); |
| |
| if (r == (-1)) |
| return -1.0; |
| |
| if (ERR.isLoggable(ErrorManager.INFORMATIONAL)) { |
| ERR.log(ErrorManager.INFORMATIONAL, "AnnotationView.modelToView: line=" + line); // NOI18N |
| // ERR.log(ErrorManager.INFORMATIONAL, "AnnotationView.modelToView: lineOffset=" + lineOffset); // NOI18N |
| ERR.log(ErrorManager.INFORMATIONAL, "AnnotationView.modelToView: r=" + r); // NOI18N |
| ERR.log(ErrorManager.INFORMATIONAL, "AnnotationView.modelToView: getComponentHeight()=" + getComponentHeight()); // NOI18N |
| ERR.log(ErrorManager.INFORMATIONAL, "AnnotationView.modelToView: getUsableHeight()=" + getUsableHeight()); // NOI18N |
| } |
| |
| if (componentHeight <= usableHeight) { |
| //1:1 mapping: |
| return r + topOffset(); |
| } else { |
| double position = r / componentHeight; |
| int blocksCount = (int) (usableHeight / (PIXELS_FOR_LINE + LINE_SEPARATOR_SIZE)); |
| int block = (int) (position * blocksCount); |
| |
| return block * (PIXELS_FOR_LINE + LINE_SEPARATOR_SIZE) + topOffset(); |
| } |
| } catch (BadLocationException e) { |
| ErrorManager.getDefault().notify(e); |
| return -1.0; |
| } |
| } |
| |
| private static final int VIEW_TO_MODEL_IMPORTANCE = ErrorManager.INFORMATIONAL; |
| |
| /*package private*/ int[] viewToModel(double offset) { |
| try { |
| if (getComponentHeight() <= getUsableHeight()) { |
| //1:1 mapping: |
| int positionOffset = pane.viewToModel(new Point(1, (int) (offset - topOffset()))); |
| if (positionOffset == -1) { |
| return null; |
| } |
| int line = Utilities.getLineOffset(doc, positionOffset); |
| |
| if (ERR.isLoggable(VIEW_TO_MODEL_IMPORTANCE)) { |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: line=" + line); // NOI18N |
| } |
| |
| double position = modelToView(line); |
| |
| if (offset < position || offset >= (position + PIXELS_FOR_LINE)) |
| return null; |
| |
| return getLinesSpan(line); |
| } else { |
| int blocksCount = (int) (getUsableHeight() / (PIXELS_FOR_LINE + LINE_SEPARATOR_SIZE)); |
| int block = (int) ((offset - topOffset()) / (PIXELS_FOR_LINE + LINE_SEPARATOR_SIZE)); |
| double yPos = (getComponentHeight() * block) / blocksCount; |
| |
| if (yPos == (int) yPos) |
| yPos -= 1; |
| |
| int positionOffset = pane.viewToModel(new Point(0, (int) yPos)); |
| if (positionOffset == -1) { |
| return null; |
| } |
| int line = Utilities.getLineOffset(doc, positionOffset) + 1; |
| int[] span = getLinesSpan(line); |
| double normalizedOffset = modelToView(span[0]); |
| |
| if (ERR.isLoggable(VIEW_TO_MODEL_IMPORTANCE)) { |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: offset=" + offset); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: block=" + block); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: blocksCount=" + blocksCount); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: pane.getHeight()=" + pane.getHeight()); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: yPos=" + yPos); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: positionOffset=" + positionOffset); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: line=" + line); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.viewToModel: normalizedOffset=" + normalizedOffset); // NOI18N |
| } |
| |
| if (offset < normalizedOffset || offset >= (normalizedOffset + PIXELS_FOR_LINE)) { |
| return null; |
| } |
| |
| if (block < 0) |
| return null; |
| |
| return span; |
| } |
| } catch (BadLocationException e) { |
| ErrorManager.getDefault().notify(e); |
| return null; |
| } |
| } |
| |
| private Mark getMarkForPointImpl(double point) { |
| int[] lineSpan = viewToModel(point); |
| |
| if (lineSpan == null) |
| return null; |
| |
| int startLine = lineSpan[0]; |
| int endLine = lineSpan[1]; |
| |
| if (startLine != (-1)) { |
| return data.getMainMarkForBlock(startLine, endLine); |
| } |
| |
| return null; |
| } |
| |
| /*package private*/ Mark getMarkForPoint(double point) { |
| //Normalize the point: |
| point = ((int) (point / (PIXELS_FOR_LINE + LINE_SEPARATOR_SIZE))) * (PIXELS_FOR_LINE + LINE_SEPARATOR_SIZE); |
| |
| Mark a = getMarkForPointImpl(point); |
| |
| if (ERR.isLoggable(VIEW_TO_MODEL_IMPORTANCE)) { |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.getAnnotationForPoint: point=" + point); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.getAnnotationForPoint: a=" + a); // NOI18N |
| } |
| |
| int relativeMax = Math.max(UPPER_HANDLE + 1, LOWER_HANDLE + 1); |
| |
| for (short relative = 1; relative < relativeMax && a == null; relative++) { |
| if (relative <= UPPER_HANDLE) { |
| a = getMarkForPointImpl(point + relative); |
| |
| if (ERR.isLoggable(VIEW_TO_MODEL_IMPORTANCE)) { |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.getAnnotationForPoint: a=" + a); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.getAnnotationForPoint: relative=" + relative); // NOI18N |
| } |
| } |
| |
| if (relative <= LOWER_HANDLE && a == null) { |
| a = getMarkForPointImpl(point - relative); |
| |
| if (ERR.isLoggable(VIEW_TO_MODEL_IMPORTANCE)) { |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.getAnnotationForPoint: a=" + a); // NOI18N |
| ERR.log(VIEW_TO_MODEL_IMPORTANCE, "AnnotationView.getAnnotationForPoint: relative=-" + relative); // NOI18N |
| } |
| } |
| } |
| |
| return a; |
| } |
| |
| @Override |
| public Dimension getMaximumSize() { |
| return new Dimension(THICKNESS, Integer.MAX_VALUE); |
| } |
| |
| @Override |
| public Dimension getMinimumSize() { |
| return new Dimension(THICKNESS, Integer.MIN_VALUE); |
| } |
| |
| @Override |
| public Dimension getPreferredSize() { |
| return new Dimension(THICKNESS, Integer.MIN_VALUE); |
| } |
| |
| @Override |
| public void mouseReleased(MouseEvent e) { |
| //NOTHING: |
| resetCursor(); |
| } |
| |
| @Override |
| public void mousePressed(MouseEvent e) { |
| resetCursor(); |
| } |
| |
| @Override |
| public void mouseMoved(MouseEvent e) { |
| checkCursor(e); |
| } |
| |
| @Override |
| public void mouseExited(MouseEvent e) { |
| resetCursor(); |
| } |
| |
| @Override |
| public void mouseEntered(MouseEvent e) { |
| checkCursor(e); |
| } |
| |
| @Override |
| public void mouseDragged(MouseEvent e) { |
| } |
| |
| @Override |
| public void mouseClicked(MouseEvent e) { |
| resetCursor(); |
| |
| Mark mark = getMarkForPoint(e.getPoint().getY()); |
| |
| if (mark!= null) { |
| pane.setCaretPosition(Utilities.getRowStartFromLineOffset(doc, mark.getAssignedLines()[0])); |
| } |
| } |
| |
| private void resetCursor() { |
| setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); |
| } |
| |
| private void checkCursor(MouseEvent e) { |
| Mark mark = getMarkForPoint(e.getPoint().getY()); |
| |
| if (mark == null) { |
| resetCursor(); |
| return ; |
| } |
| |
| setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); |
| } |
| |
| @Override |
| public String getToolTipText(MouseEvent event) { |
| if (ERR.isLoggable(ErrorManager.INFORMATIONAL)) { |
| ERR.log(ErrorManager.INFORMATIONAL, "getToolTipText: event=" + event); // NOI18N |
| } |
| int y = event.getY(); |
| |
| if (y <= topOffset()) { |
| int[] errWar = data.computeErrorsAndWarnings(); |
| int errors = errWar[0]; |
| int warnings = errWar[1]; |
| |
| if (errors == 0 && warnings == 0) { |
| return NbBundle.getBundle(AnnotationView.class).getString("TP_NoErrors"); // NOI18N |
| } |
| |
| if (errors == 0 && warnings != 0) { |
| return MessageFormat.format(NbBundle.getBundle(AnnotationView.class).getString("TP_X_warning(s)"), new Object[] {Integer.valueOf(warnings)}); // NOI18N |
| } |
| |
| if (errors != 0 && warnings == 0) { |
| return MessageFormat.format(NbBundle.getBundle(AnnotationView.class).getString("TP_X_error(s)"), new Object[] {Integer.valueOf(errors)}); // NOI18N |
| } |
| |
| return MessageFormat.format(NbBundle.getBundle(AnnotationView.class).getString("TP_X_error(s)_Y_warning(s)"), new Object[] {Integer.valueOf(errors), Integer.valueOf(warnings)}); // NOI18N |
| } |
| |
| Mark mark = getMarkForPoint(y); |
| |
| if (mark != null) { |
| String description = mark.getShortDescription(); |
| |
| if (description != null) { |
| if (description != null) { |
| // #122422 - some descriptions are intentionaly a valid HTML and don't want to be escaped |
| if (description.startsWith(HTML_PREFIX_LOWERCASE) || description.startsWith(HTML_PREFIX_UPPERCASE)) { |
| return description; |
| } else { |
| return "<html><body>" + StringEscapeUtils.escapeHtml(description); // NOI18N |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| private static final String HTML_PREFIX_LOWERCASE = "<html"; //NOI18N |
| private static final String HTML_PREFIX_UPPERCASE = "<HTML"; //NOI18N |
| |
| @Override |
| public void foldHierarchyChanged(FoldHierarchyEvent evt) { |
| //fix for #63402: clear the modelToViewCache after folds changed: |
| //#64498: do not take monitor on this here: |
| fullRepaint(false, true); |
| } |
| |
| @Override |
| public void removeUpdate(DocumentEvent e) { |
| documentChange(); |
| } |
| |
| @Override |
| public void insertUpdate(DocumentEvent e) { |
| documentChange(); |
| } |
| |
| @Override |
| public void changedUpdate(DocumentEvent e) { |
| //ignored... |
| } |
| |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| if (evt.getSource() == this.pane && "document".equals(evt.getPropertyName())) { |
| updateForNewDocument(); |
| return ; |
| } |
| |
| fullRepaint(); |
| } |
| |
| @Override |
| public AccessibleContext getAccessibleContext() { |
| if (accessibleContext == null) { |
| accessibleContext = new AccessibleJComponent() { |
| @Override |
| public AccessibleRole getAccessibleRole() { |
| return AccessibleRole.PANEL; |
| } |
| }; |
| accessibleContext.setAccessibleName(NbBundle.getMessage(AnnotationView.class, "ACSN_AnnotationView")); //NOI18N |
| accessibleContext.setAccessibleDescription(NbBundle.getMessage(AnnotationView.class, "ACSD_AnnotationView")); //NOI18N |
| } |
| return accessibleContext; |
| } |
| } |