| /* |
| * 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.core.output2.ui; |
| |
| import javax.swing.plaf.TextUI; |
| import javax.swing.*; |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ChangeListener; |
| import javax.swing.event.DocumentEvent; |
| import javax.swing.event.DocumentListener; |
| import javax.swing.text.*; |
| import java.awt.*; |
| import java.awt.event.*; |
| import org.netbeans.core.output2.FoldingSideBar; |
| import org.netbeans.core.output2.Lines; |
| import org.netbeans.core.output2.OutputDocument; |
| import org.netbeans.core.output2.options.OutputOptions; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| |
| /** |
| * A scroll pane containing an editor pane, with special handling of the caret |
| * and scrollbar - until a keyboard or mouse event, after a call to setDocument(), |
| * the caret and scrollbar are locked to the last line of the document. This avoids |
| * "jumping" scrollbars as the position of the caret (and thus the scrollbar) get updated |
| * to reposition them at the bottom of the document on every document change. |
| * |
| * @author Tim Boudreau |
| */ |
| public abstract class AbstractOutputPane extends JScrollPane implements DocumentListener, MouseListener, MouseMotionListener, KeyListener, ChangeListener, MouseWheelListener, Runnable { |
| private boolean locked = true; |
| |
| private int fontHeight = -1; |
| private int fontWidth = -1; |
| protected JEditorPane textView; |
| private FoldingSideBar foldingSideBar; |
| int lastCaretLine = 0; |
| int caretBlinkRate = 500; |
| boolean hadSelection = false; |
| boolean recentlyReset = false; |
| private static boolean copyingOfLargeParts = false; |
| |
| public AbstractOutputPane() { |
| textView = createTextView(); |
| init(); |
| } |
| |
| //#114290 |
| public void doUpdateCaret() { |
| Caret car = textView.getCaret(); |
| if (car instanceof DefaultCaret) { |
| ((DefaultCaret)car).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); |
| } |
| } |
| |
| public void dontUpdateCaret() { |
| Caret car = textView.getCaret(); |
| if (car instanceof DefaultCaret) { |
| ((DefaultCaret)car).setUpdatePolicy(DefaultCaret.NEVER_UPDATE); |
| } |
| } |
| |
| @Override |
| public void requestFocus() { |
| textView.requestFocus(); |
| } |
| |
| @Override |
| public boolean requestFocusInWindow() { |
| return textView.requestFocusInWindow(); |
| } |
| |
| public Font getViewFont() { |
| return textView.getFont(); |
| } |
| |
| public void setViewFont (Font f) { |
| textView.setFont(f); |
| updateFont(getGraphics()); |
| } |
| |
| protected abstract JEditorPane createTextView(); |
| |
| protected void documentChanged() { |
| lastLength = -1; |
| if (lineToScroll != -1) { |
| if (scrollToLine(lineToScroll)) { |
| lineToScroll = -1; |
| } |
| } else { |
| ensureCaretPosition(); |
| } |
| |
| if (recentlyReset && isShowing()) { |
| recentlyReset = false; |
| } |
| if (locked) { |
| resetCursor(); |
| } |
| if (isWrapped()) { |
| //Saves having OutputEditorKit have to do its own listening |
| getViewport().revalidate(); |
| getViewport().repaint(); |
| } |
| } |
| |
| public abstract boolean isWrapped(); |
| public abstract void setWrapped (boolean val); |
| |
| public boolean hasSelection() { |
| return textView.getSelectionStart() != textView.getSelectionEnd(); |
| } |
| |
| public boolean isScrollLocked() { |
| return locked; |
| } |
| |
| /** |
| * Ensure that the document is scrolled all the way to the bottom (unless |
| * some user event like scrolling or placing the caret has unlocked it). |
| * <p> |
| * Note that this method is always called on the event queue, since |
| * OutputDocument only fires changes on the event queue. |
| */ |
| public final void ensureCaretPosition() { |
| if (!enqueued) { |
| //Make sure the scrollbar is updated *after* the document change |
| //has been processed and the scrollbar model's maximum updated |
| enqueued = true; |
| SwingUtilities.invokeLater(this); |
| } |
| } |
| |
| /** True when invokeLater has already been called on this instance */ |
| private boolean enqueued = false; |
| /** |
| * Scrolls the pane to the bottom, invokeLatered to ensure all state has |
| * been updated, so the bottom really *is* the bottom. |
| */ |
| public void run() { |
| enqueued = false; |
| if (locked) { |
| getVerticalScrollBar().setValue(getVerticalScrollBar().getModel().getMaximum()); |
| getHorizontalScrollBar().setValue(getHorizontalScrollBar().getModel().getMinimum()); |
| } |
| ensureCaretAtVisiblePosition(); |
| } |
| |
| /** |
| * Ensure that the caret is at a visible position, not inside a collapsed |
| * fold. If not, move it up to nearest visible line above the current |
| * (hidden) line. |
| */ |
| private void ensureCaretAtVisiblePosition() { |
| assert EventQueue.isDispatchThread(); |
| final Lines lines = getLines(); |
| if (lines != null) { |
| int caretLine = lines.getLineAt(getCaretPos()); |
| int origCaretLine = caretLine; |
| while (caretLine >= 0 && !lines.isVisible(caretLine)) { |
| caretLine = lines.getParentFoldStart(caretLine); |
| } |
| if (caretLine != origCaretLine && caretLine >= 0) { |
| getCaret().setDot(lines.getLineStart(caretLine)); |
| } |
| } |
| } |
| |
| public int getSelectionStart() { |
| return textView.getSelectionStart(); |
| } |
| |
| public int getSelectionEnd() { |
| return textView.getSelectionEnd(); |
| } |
| |
| public String getSelectedText() { |
| int start = getSelectionStart(); |
| int end = getSelectionEnd(); |
| String str = null; |
| if (start > 0 && end > start) { |
| try { |
| str = getDocument().getText(start, end - start); |
| } catch (BadLocationException ex) { |
| ex.printStackTrace(); |
| } |
| } |
| return str; |
| } |
| |
| public void setSelection (int start, int end) { |
| int rstart = Math.min (start, end); |
| int rend = Math.max (start, end); |
| if (rstart == rend) { |
| getCaret().setDot(rstart); |
| } else { |
| textView.setSelectionStart(rstart); |
| textView.setSelectionEnd(rend); |
| } |
| } |
| |
| @NbBundle.Messages({ |
| "MSG_TooMuchTextSelected=Selecting large parts of text can cause " |
| + "Out-Of-Memory errors. Do you want to continue?" |
| }) |
| public void selectAll() { |
| unlockScroll(); |
| getCaret().setVisible(true); |
| int start = 0; |
| int end = getLength(); |
| if (end - start > 20000000 && !copyingOfLargeParts) { // 40 MB |
| NotifyDescriptor nd = new NotifyDescriptor.Confirmation( |
| Bundle.MSG_TooMuchTextSelected(), |
| NotifyDescriptor.YES_NO_OPTION); |
| Object result = DialogDisplayer.getDefault().notify(nd); |
| if (result == NotifyDescriptor.YES_OPTION) { |
| copyingOfLargeParts = true; |
| } else { |
| return; |
| } |
| } |
| textView.setSelectionStart(start); |
| textView.setSelectionEnd(end); |
| } |
| |
| public boolean isAllSelected() { |
| return textView.getSelectionStart() == 0 && textView.getSelectionEnd() == getLength(); |
| } |
| |
| protected void init() { |
| setRowHeaderView(foldingSideBar = new FoldingSideBar(textView, this)); |
| setViewportView(textView); |
| textView.setEditable(false); |
| |
| textView.addMouseListener(this); |
| textView.addMouseMotionListener(this); |
| textView.addKeyListener(this); |
| textView.addMouseWheelListener(this); |
| //#107354 |
| OCaret oc = new OCaret(); |
| oc.setUpdatePolicy(DefaultCaret.NEVER_UPDATE); |
| textView.setCaret (oc); |
| |
| getCaret().setSelectionVisible(true); |
| |
| getVerticalScrollBar().getModel().addChangeListener(this); |
| getVerticalScrollBar().addMouseMotionListener(this); |
| |
| getViewport().addMouseListener(this); |
| getVerticalScrollBar().addMouseListener(this); |
| setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED); |
| setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS); |
| addMouseListener(this); |
| |
| getCaret().addChangeListener(this); |
| textView.setFont(OutputOptions.getDefault().getFont(isWrapped())); |
| setBorder (BorderFactory.createEmptyBorder()); |
| setViewportBorder (BorderFactory.createEmptyBorder()); |
| |
| Color c = UIManager.getColor("nb.output.selectionBackground"); |
| if (c != null) { |
| textView.setSelectionColor(c); |
| } |
| } |
| |
| /** |
| * Accessed reflectively from org.netbeans.jellytools.OutputTabOperator. |
| */ |
| public final Document getDocument() { |
| return textView.getDocument(); |
| } |
| |
| /** |
| * This method is here for use *only* by unit tests. |
| */ |
| public final JTextComponent getTextView() { |
| return textView; |
| } |
| |
| public final FoldingSideBar getFoldingSideBar() { |
| return foldingSideBar; |
| } |
| |
| public final void copy() { |
| if (getCaret().getDot() != getCaret().getMark()) { |
| textView.copy(); |
| } else { |
| Toolkit.getDefaultToolkit().beep(); |
| } |
| } |
| |
| public final void paste() { |
| textView.paste(); |
| } |
| |
| protected void setDocument (Document doc) { |
| if (hasSelection()) { |
| hasSelectionChanged(false); |
| } |
| hadSelection = false; |
| lastCaretLine = 0; |
| lastLength = -1; |
| lineToScroll = -1; |
| Document old = textView.getDocument(); |
| old.removeDocumentListener(this); |
| if (doc != null) { |
| textView.setDocument(doc); |
| doc.addDocumentListener(this); |
| lockScroll(); |
| recentlyReset = true; |
| } else { |
| textView.setDocument (new PlainDocument()); |
| textView.setEditorKit(new DefaultEditorKit()); |
| } |
| } |
| |
| protected void setEditorKit(EditorKit kit) { |
| Document doc = textView.getDocument(); |
| |
| textView.setEditorKit(kit); |
| textView.setDocument(doc); |
| updateKeyBindings(); |
| } |
| |
| /** |
| * Setting the editor kit will clear the action map/key map connection |
| * to the TopComponent, so we reset it here. |
| */ |
| protected final void updateKeyBindings() { |
| Keymap keymap = textView.getKeymap(); |
| keymap.removeKeyStrokeBinding(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); |
| } |
| |
| protected EditorKit getEditorKit() { |
| return textView.getEditorKit(); |
| } |
| |
| public final int getLineCount() { |
| return textView.getDocument().getDefaultRootElement().getElementCount(); |
| } |
| |
| private int lastLength = -1; |
| public final int getLength() { |
| if (lastLength == -1) { |
| lastLength = textView.getDocument().getLength(); |
| } |
| return lastLength; |
| } |
| |
| public void scrollTo(int pos) { |
| getCaret().setDot(pos); |
| try { |
| Rectangle rect = textView.modelToView(pos); |
| if (rect != null) { |
| int spaceAround |
| = (textView.getVisibleRect().height - rect.height) / 2; |
| Rectangle centeredRect = new Rectangle( |
| rect.x, Math.max(0, rect.y - spaceAround + rect.height), |
| rect.width, spaceAround * 2 + rect.height); |
| textView.scrollRectToVisible(centeredRect); |
| } |
| locked = false; |
| } catch (BadLocationException ex) { |
| } |
| } |
| |
| public final void sendCaretToPos(int startPos, int endPos, boolean select) { |
| inSendCaretToLine = true; |
| try { |
| getCaret().setVisible(true); |
| getCaret().setSelectionVisible(true); |
| if (select) { |
| scrollTo(endPos); |
| getCaret().setDot(endPos); |
| getCaret().moveDot(startPos); |
| textView.repaint(); |
| } else { |
| getCaret().setDot(startPos); |
| } |
| } catch (Error sie) { |
| if (sie.getClass().getName().equals("javax.swing.text.StateInvariantError")) { |
| Exceptions.attachMessage(sie, "sendCaretToPos("+startPos+", "+endPos+", "+select+"), caret = "+getCaret()+", highlighter = "+textView.getHighlighter()+", document length = "+textView.getDocument().getLength()); |
| } |
| Exceptions.printStackTrace(sie); |
| } finally { |
| locked = false; |
| inSendCaretToLine = false; |
| } |
| } |
| |
| private boolean inSendCaretToLine = false; |
| private int lineToScroll = -1; |
| public final boolean sendCaretToLine(int idx, boolean select) { |
| int lastLine = getLineCount() - 1; |
| if (idx > lastLine) { |
| idx = lastLine; |
| } |
| inSendCaretToLine = true; |
| try { |
| getCaret().setVisible(true); |
| getCaret().setSelectionVisible(true); |
| Element el = textView.getDocument().getDefaultRootElement().getElement(idx); |
| int position = el.getStartOffset(); |
| if (select) { |
| getCaret().setDot(el.getEndOffset() - 1); |
| getCaret().moveDot(position); |
| textView.repaint(); |
| } else { |
| getCaret().setDot(position); |
| } |
| |
| if (!scrollToLine(idx + 3) && isScrollLocked()) { |
| lineToScroll = idx + 3; |
| } |
| } catch (Error sie) { |
| if (sie.getClass().getName().equals("javax.swing.text.StateInvariantError")) { |
| Exceptions.attachMessage(sie, "sendCaretToLine("+idx+", "+select+"), caret = "+getCaret()+", highlighter = "+textView.getHighlighter()+", document length = "+textView.getDocument().getLength()); |
| } |
| Exceptions.printStackTrace(sie); |
| } finally { |
| locked = false; |
| inSendCaretToLine = false; |
| } |
| return true; |
| } |
| |
| boolean scrollToLine(int line) { |
| int lineIdx = Math.min(getLineCount() - 1, line); |
| Rectangle rect = null; |
| try { |
| rect = textView.modelToView(textView.getDocument().getDefaultRootElement().getElement(lineIdx).getStartOffset()); |
| } catch (BadLocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| if (rect == null) { |
| return false; |
| } |
| |
| boolean oldLocked = locked; |
| textView.scrollRectToVisible(rect); |
| locked = oldLocked; |
| |
| Rectangle visRect = textView.getVisibleRect(); |
| return line == lineIdx && visRect.y + visRect.height == rect.y + rect.height; |
| } |
| |
| boolean scrollToPos(int pos) { |
| Rectangle rect = null; |
| try { |
| rect = textView.modelToView(pos); |
| } catch (BadLocationException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| |
| if (rect == null) { |
| return false; |
| } |
| |
| boolean oldLocked = locked; |
| textView.scrollRectToVisible(rect); |
| locked = oldLocked; |
| |
| return true; |
| } |
| |
| public final void lockScroll() { |
| if (!locked) { |
| locked = true; |
| } |
| } |
| |
| public final void unlockScroll() { |
| if (locked) { |
| locked = false; |
| } |
| lineToScroll = -1; |
| } |
| |
| protected abstract void caretPosChanged(int pos); |
| |
| protected abstract void lineClicked (int line, int pos); |
| |
| protected abstract void enterPressed(); |
| |
| protected abstract void postPopupMenu (Point p, Component src); |
| |
| public final int getCaretLine() { |
| int result = 0; |
| int charPos = getCaret().getDot(); |
| if (charPos > 0) { |
| result = textView.getDocument().getDefaultRootElement().getElementIndex(charPos); |
| } |
| return result; |
| } |
| |
| public final boolean isLineSelected(int idx) { |
| Element line = textView.getDocument().getDefaultRootElement().getElement(idx); |
| return line.getStartOffset() == getSelectionStart() && line.getEndOffset()-1 == getSelectionEnd(); |
| } |
| |
| public final int getCaretPos() { |
| return getCaret().getDot(); |
| } |
| |
| @Override |
| public final void paint (Graphics g) { |
| if (fontHeight == -1) { |
| updateFont(g); |
| } |
| super.paint(g); |
| } |
| |
| void updateFont(Graphics g) { |
| if (g == null) { |
| fontHeight = fontWidth = -1; |
| return; |
| } |
| fontHeight = g.getFontMetrics(textView.getFont()).getHeight(); |
| fontWidth = g.getFontMetrics(textView.getFont()).charWidth('m'); //NOI18N |
| getVerticalScrollBar().setUnitIncrement(fontHeight); |
| getHorizontalScrollBar().setUnitIncrement(fontWidth); |
| } |
| |
| //***********************Listener implementations***************************** |
| |
| public void stateChanged(ChangeEvent e) { |
| if (e.getSource() == getVerticalScrollBar().getModel()) { |
| if (!locked) { //XXX check if doc is still being written? |
| BoundedRangeModel mdl = getVerticalScrollBar().getModel(); |
| if (mdl.getValue() + mdl.getExtent() == mdl.getMaximum()) { |
| lockScroll(); |
| } |
| } |
| } else { |
| if (!locked) { |
| maybeSendCaretEnteredLine(); |
| } |
| boolean hasSelection = textView.getSelectionStart() != textView.getSelectionEnd(); |
| if (hasSelection != hadSelection) { |
| hadSelection = hasSelection; |
| hasSelectionChanged (hasSelection); |
| } |
| } |
| } |
| |
| private void maybeSendCaretEnteredLine() { |
| if (EventQueue.getCurrentEvent() instanceof MouseEvent) { |
| //User may have clicked a hyperlink, in which case, we'll test |
| //it and see if it's really in the text of the hyperlink - so |
| //don't do anything here |
| return; |
| } |
| //Don't message the controller if we're programmatically setting |
| //the selection, or if the caret moved because output was written - |
| //it can cause the controller to send events to OutputListeners which |
| //should only happen for user events |
| if (!locked && !inSendCaretToLine) { |
| boolean sel = textView.getSelectionStart() != textView.getSelectionEnd(); |
| if (!sel) { |
| caretPosChanged(getCaretPos()); |
| } |
| if (sel != hadSelection) { |
| hadSelection = sel; |
| hasSelectionChanged (sel); |
| } |
| } |
| } |
| |
| |
| private void hasSelectionChanged(boolean sel) { |
| AbstractOutputTab parent = (AbstractOutputTab) getParent(); |
| if (parent != null) { // #243686 |
| parent.hasSelectionChanged(sel); |
| } |
| } |
| |
| public final void changedUpdate(DocumentEvent e) { |
| //Ensure it is consumed |
| e.getLength(); |
| documentChanged(); |
| if (e.getOffset() + e.getLength() >= getCaretPos() && (locked || !(e instanceof OutputDocument.DO))) { |
| //#119985 only move caret when not in editable section |
| OutputDocument doc = (OutputDocument)e.getDocument(); |
| if (! (e instanceof OutputDocument.DO) && getCaretPos() >= doc.getOutputLength()) { |
| return ; |
| } |
| |
| getCaret().setDot(e.getOffset() + e.getLength()); |
| } |
| } |
| |
| public final void insertUpdate(DocumentEvent e) { |
| //Ensure it is consumed |
| e.getLength(); |
| documentChanged(); |
| if (e.getOffset() + e.getLength() >= getCaretPos() && (locked || !(e instanceof OutputDocument.DO))) { |
| //#119985 only move caret when not in editable section |
| OutputDocument doc = (OutputDocument)e.getDocument(); |
| if (! (e instanceof OutputDocument.DO) && getCaretPos() >= doc.getOutputLength()) { |
| return ; |
| } |
| |
| getCaret().setDot(e.getOffset() + e.getLength()); |
| } |
| } |
| |
| public final void removeUpdate(DocumentEvent e) { |
| //Ensure it is consumed |
| e.getLength(); |
| documentChanged(); |
| } |
| |
| public void mouseClicked(MouseEvent e) { |
| if (e.isAltDown() || e.isAltGraphDown() || e.isControlDown() |
| && SwingUtilities.isMiddleMouseButton(e)) { |
| int currentSize = getViewFont().getSize(); |
| int defaultSize = OutputOptions.getDefaultFont().getSize(); |
| changeFontSizeBy(defaultSize - currentSize); |
| } |
| } |
| |
| public void mouseEntered(MouseEvent e) { |
| } |
| |
| public void mouseExited(MouseEvent e) { |
| resetCursor(); |
| } |
| |
| boolean isOnHyperlink(Point p) { |
| Document doc = getDocument(); |
| if (doc instanceof OutputDocument) { |
| int pos = textView.viewToModel(p); |
| if (pos >= getLength()) { |
| return false; |
| } |
| int line = getDocument().getDefaultRootElement().getElementIndex(pos); |
| int lineStart = getDocument().getDefaultRootElement().getElement(line).getStartOffset(); |
| int lineLength = getDocument().getDefaultRootElement().getElement(line).getEndOffset() - lineStart; |
| try { |
| Rectangle r = textView.modelToView(lineStart + lineLength - 1); |
| boolean onLine = p.x <= r.x + r.width || (isWrapped() && p.y < r.y); |
| if (onLine) { |
| return ((OutputDocument) doc).getLines().getListener(pos, null) != null; |
| } |
| } catch (BadLocationException ex) { |
| } |
| } |
| return false; |
| } |
| |
| private void resetCursor() { |
| Cursor txtCursor = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); |
| if (textView.getCursor() != txtCursor) { |
| textView.setCursor(txtCursor); |
| } |
| } |
| |
| public void mouseMoved(MouseEvent e) { |
| if (e.getSource() == textView) { |
| if (isOnHyperlink(e.getPoint())) { |
| Cursor hand = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); |
| if (textView.getCursor() != hand) { |
| textView.setCursor(hand); |
| } |
| } else { |
| resetCursor(); |
| } |
| } |
| } |
| |
| public void mouseDragged(MouseEvent e) { |
| if (e.getSource() == getVerticalScrollBar()) { |
| int y = e.getY(); |
| if (y > getVerticalScrollBar().getHeight()) { |
| lockScroll(); |
| } |
| } |
| } |
| |
| /** last pressed position for hyperlink test */ |
| private int lastPressedPos = -1; |
| |
| public void mousePressed(MouseEvent e) { |
| if (e.getSource() == textView && SwingUtilities.isLeftMouseButton(e)) { |
| lastPressedPos = textView.viewToModel(e.getPoint()); |
| } |
| if (locked && !e.isPopupTrigger()) { |
| Element el = getDocument().getDefaultRootElement().getElement(getLineCount()-1); |
| getCaret().setDot(el.getStartOffset()); |
| unlockScroll(); |
| //We should now set the caret position so the caret doesn't |
| //seem to ignore the first click |
| if (e.getSource() == textView) { |
| getCaret().setDot (textView.viewToModel(e.getPoint())); |
| } |
| } |
| if (e.isPopupTrigger()) { |
| //Convert immediately to our component space - if the |
| //text view scrolls before the component is opened, popup can |
| //appear above the top of the screen |
| Point p = SwingUtilities.convertPoint((Component) e.getSource(), |
| e.getPoint(), this); |
| |
| postPopupMenu (p, this); |
| } |
| } |
| |
| public final void mouseReleased(MouseEvent e) { |
| if (e.getSource() == textView && SwingUtilities.isLeftMouseButton(e)) { |
| int pos = textView.viewToModel(e.getPoint()); |
| if (pos != -1 && pos == lastPressedPos) { |
| int line = textView.getDocument().getDefaultRootElement().getElementIndex(pos); |
| if (line >= 0) { |
| lineClicked(line,pos); |
| e.consume(); //do NOT allow this window's caret to steal the focus from editor window |
| } |
| } |
| lastPressedPos = -1; |
| } |
| if (e.isPopupTrigger()) { |
| Point p = SwingUtilities.convertPoint((Component) e.getSource(), |
| //Convert immediately to our component space - if the |
| //text view scrolls before the component is opened, popup can |
| //appear above the top of the screen |
| e.getPoint(), this); |
| |
| postPopupMenu (p, this); |
| } |
| } |
| |
| public void keyPressed(KeyEvent keyEvent) { |
| switch (keyEvent.getKeyCode()) { |
| case KeyEvent.VK_END: |
| if (keyEvent.isControlDown()) { |
| lockScroll(); |
| } |
| break; |
| case KeyEvent.VK_UP: |
| case KeyEvent.VK_DOWN: |
| case KeyEvent.VK_LEFT: |
| case KeyEvent.VK_RIGHT: |
| case KeyEvent.VK_HOME: |
| case KeyEvent.VK_PAGE_UP: |
| case KeyEvent.VK_PAGE_DOWN: |
| unlockScroll(); |
| break; |
| case KeyEvent.VK_ENTER: |
| enterPressed(); |
| break; |
| case KeyEvent.VK_C: |
| if (keyEvent.isControlDown()) { |
| askTerminate(keyEvent); // see bug 250245 |
| } |
| break; |
| } |
| } |
| |
| @NbBundle.Messages({ |
| "MSG_TerminateProcess=Terminate the process?" |
| }) |
| private void askTerminate(KeyEvent keyEvent) { |
| Container parent = getParent(); |
| if (parent instanceof AbstractOutputTab) { |
| Caret c = getCaret(); |
| if (c.getDot() != c.getMark()) { |
| return; // some text is selected, copy action will handle this |
| } |
| AbstractOutputTab tab = (AbstractOutputTab) parent; |
| Action[] actions = tab.getToolbarActions(); |
| for (Action a : actions) { |
| if ("stop".equals(a.getValue("OUTPUT_ACTION_TYPE")) //NOI18N |
| && a.isEnabled()) { |
| NotifyDescriptor desc = new NotifyDescriptor.Confirmation( |
| Bundle.MSG_TerminateProcess(), |
| NotifyDescriptor.YES_NO_OPTION); |
| Object res = DialogDisplayer.getDefault().notify(desc); |
| if (NotifyDescriptor.YES_OPTION.equals(res)) { |
| a.actionPerformed( |
| new ActionEvent(this, 0, "stop")); //NOI18N |
| } |
| keyEvent.consume(); |
| break; |
| } |
| } |
| } |
| } |
| |
| public void keyReleased(KeyEvent keyEvent) { |
| } |
| |
| public void keyTyped(KeyEvent keyEvent) { |
| } |
| |
| protected abstract void changeFontSizeBy(int change); |
| |
| public final void mouseWheelMoved(MouseWheelEvent e) { |
| if (e.isAltDown() || e.isAltGraphDown() || e.isControlDown()) { |
| int change = -e.getWheelRotation(); |
| changeFontSizeBy(change); |
| e.consume(); |
| return; |
| } else if (e.isShiftDown()) { // horizontal scrolling |
| BoundedRangeModel sbmodel = getHorizontalScrollBar().getModel(); |
| int currPosition = sbmodel.getValue(); |
| int newPosition = Math.max(0, Math.min(sbmodel.getMaximum(), currPosition |
| + (int) (e.getPreciseWheelRotation() * e.getScrollAmount() * fontWidth))); |
| sbmodel.setValue (newPosition); |
| return; |
| } |
| BoundedRangeModel sbmodel = getVerticalScrollBar().getModel(); |
| int max = sbmodel.getMaximum(); |
| int range = sbmodel.getExtent(); |
| |
| int currPosition = sbmodel.getValue(); |
| if (e.getSource() == textView) { |
| int newPosition = Math.max(0, Math.min(sbmodel.getMaximum(), currPosition |
| + (int) (e.getPreciseWheelRotation() * e.getScrollAmount() * fontHeight))); |
| // height is a magic constant because of #57532 |
| sbmodel.setValue (newPosition); |
| if (newPosition + range >= max) { |
| lockScroll(); |
| return; |
| } |
| } |
| unlockScroll(); |
| } |
| |
| Caret getCaret() { |
| return textView.getCaret(); |
| } |
| |
| public void collapseFold() { |
| Lines l = getLines(); |
| if (l != null) { |
| l.hideFold(l.getFoldStart(l.visibleToRealLine(getCaretLine()))); |
| } |
| } |
| |
| public void expandFold() { |
| Lines l = getLines(); |
| if (l != null) { |
| l.showFold(l.getFoldStart(l.visibleToRealLine(getCaretLine()))); |
| } |
| } |
| |
| public void collapseAllFolds() { |
| Lines l = getLines(); |
| if (l != null) { |
| l.hideAllFolds(); |
| } |
| } |
| |
| public void expandAllFolds() { |
| Lines l = getLines(); |
| if (l != null) { |
| l.showAllFolds(); |
| } |
| } |
| |
| public void collapseFoldTree() { |
| Lines l = getLines(); |
| if (l != null) { |
| l.hideFoldTree(l.getFoldStart(l.visibleToRealLine(getCaretLine()))); |
| } |
| } |
| |
| public void expandFoldTree() { |
| Lines l = getLines(); |
| if (l != null) { |
| l.showFoldTree(l.getFoldStart(l.visibleToRealLine(getCaretLine()))); |
| } |
| } |
| |
| private Lines getLines() { |
| Document d = getDocument(); |
| if (d instanceof OutputDocument) { |
| return ((OutputDocument) d).getLines(); |
| } |
| return null; |
| } |
| |
| private class OCaret extends DefaultCaret { |
| @Override |
| public void paint(Graphics g) { |
| JTextComponent component = textView; |
| if(isVisible() && y >= 0) { |
| try { |
| TextUI mapper = component.getUI(); |
| Rectangle r = mapper.modelToView(component, getDot(), Position.Bias.Forward); |
| |
| if ((r == null) || ((r.width == 0) && (r.height == 0))) { |
| return; |
| } |
| if (width > 0 && height > 0 && |
| !this._contains(r.x, r.y, r.width, r.height)) { |
| // We seem to have gotten out of sync and no longer |
| // contain the right location, adjust accordingly. |
| Rectangle clip = g.getClipBounds(); |
| |
| if (clip != null && !clip.contains(this)) { |
| // Clip doesn't contain the old location, force it |
| // to be repainted lest we leave a caret around. |
| repaint(); |
| } |
| // System.err.println("WRONG! Caret dot m2v = " + r + " but my bounds are " + x + "," + y + "," + width + "," + height); |
| |
| // This will potentially cause a repaint of something |
| // we're already repainting, but without changing the |
| // semantics of damage we can't really get around this. |
| damage(r); |
| } |
| g.setColor(component.getCaretColor()); |
| g.drawLine(r.x, r.y, r.x, r.y + r.height - 1); |
| g.drawLine(r.x+1, r.y, r.x+1, r.y + r.height - 1); |
| |
| } catch (BadLocationException e) { |
| // can't render I guess |
| // System.err.println("Can't render cursor"); |
| } |
| } |
| } |
| |
| private boolean _contains(int X, int Y, int W, int H) { |
| int w = this.width; |
| int h = this.height; |
| if ((w | h | W | H) < 0) { |
| // At least one of the dimensions is negative... |
| return false; |
| } |
| // Note: if any dimension is zero, tests below must return false... |
| int x = this.x; |
| int y = this.y; |
| if (X < x || Y < y) { |
| return false; |
| } |
| if (W > 0) { |
| w += x; |
| W += X; |
| if (W <= X) { |
| // X+W overflowed or W was zero, return false if... |
| // either original w or W was zero or |
| // x+w did not overflow or |
| // the overflowed x+w is smaller than the overflowed X+W |
| if (w >= x || W > w) { |
| return false; |
| } |
| } else { |
| // X+W did not overflow and W was not zero, return false if... |
| // original w was zero or |
| // x+w did not overflow and x+w is smaller than X+W |
| if (w >= x && W > w) { |
| //This is the bug in DefaultCaret - returns false here |
| return true; |
| } |
| } |
| } |
| else if ((x + w) < X) { |
| return false; |
| } |
| if (H > 0) { |
| h += y; |
| H += Y; |
| if (H <= Y) { |
| if (h >= y || H > h) return false; |
| } else { |
| if (h >= y && H > h) return false; |
| } |
| } |
| else if ((y + h) < Y) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void mouseReleased(MouseEvent e) { |
| if( !e.isConsumed() ) { |
| super.mouseReleased(e); |
| } |
| } |
| |
| @Override |
| public void focusGained(FocusEvent e) { |
| getCaret().setBlinkRate(caretBlinkRate); |
| getCaret().setVisible(true); |
| } |
| |
| @Override |
| public void focusLost(FocusEvent e) { |
| getCaret().setVisible(false); |
| } |
| |
| @Override |
| public void setSelectionVisible(boolean vis) { |
| if (vis) { |
| super.setSelectionVisible(vis); |
| } |
| } |
| } |
| } |