123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- /*
- * Copyright (c) 2006-2017 DMDirc Developers
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
- * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
- * permit persons to whom the Software is furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
- * Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
- * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
- * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
- * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- */
-
- package com.dmdirc.addons.ui_swing.textpane;
-
- import com.dmdirc.addons.ui_swing.UIUtilities;
- import com.dmdirc.interfaces.WindowModel;
- import com.dmdirc.config.provider.ConfigChangeListener;
- import com.dmdirc.ui.messages.CachingDocument;
- import com.dmdirc.ui.messages.Document;
- import com.dmdirc.ui.messages.DocumentListener;
- import com.dmdirc.ui.messages.LinePosition;
- import com.dmdirc.ui.messages.StyledMessageUtils;
- import com.dmdirc.util.StringUtils;
- import com.dmdirc.util.URLBuilder;
- import java.awt.Adjustable;
- import java.awt.Color;
- import java.awt.Point;
- import java.awt.datatransfer.Clipboard;
- import java.awt.datatransfer.StringSelection;
- import java.awt.event.AdjustmentEvent;
- import java.awt.event.AdjustmentListener;
- import java.awt.event.InputEvent;
- import java.awt.event.MouseEvent;
- import java.awt.event.MouseMotionAdapter;
- import java.awt.event.MouseMotionListener;
- import java.awt.event.MouseWheelEvent;
- import java.awt.event.MouseWheelListener;
- import javax.swing.BoundedRangeModel;
- import javax.swing.DefaultBoundedRangeModel;
- import javax.swing.JComponent;
- import javax.swing.JLabel;
- import javax.swing.JLayer;
- import javax.swing.JScrollBar;
- import javax.swing.SwingConstants;
- import net.miginfocom.swing.MigLayout;
-
- import static com.google.common.base.Preconditions.checkArgument;
-
- /**
- * Styled, scrollable text pane.
- */
- public final class TextPane extends JComponent implements MouseWheelListener,
- AdjustmentListener, DocumentListener, ConfigChangeListener {
-
- /** A version number for this class. */
- private static final long serialVersionUID = 5;
- /** Scrollbar model. */
- private final BoundedRangeModel scrollModel;
- /** Canvas object, used to draw text. */
- private final TextPaneCanvas canvas;
- /** IRCDocument. */
- private final Document document;
- /** Parent window. */
- private final WindowModel window;
- /** Indicator to show whether new lines have been added. */
- private final JLabel newLineIndicator;
- /** Background painter. */
- private final BackgroundPainter backgroundPainter;
- /** The domain to read configuration from. */
- private final String configDomain;
- /** Clipboard to handle copy and paste cations. */
- private final Clipboard clipboard;
- /** Style utilities class. */
- private final StyledMessageUtils styleUtils;
- /** Last seen line. */
- private int lastSeenLine;
- /** Show new line notifications. */
- private boolean showNotification;
-
- /**
- * Creates a new instance of TextPane.
- *
- * @param configDomain The domain to read configuration from.
- * @param urlBuilder The builder to use to construct URLs for resources.
- * @param clipboard The clipboard to handle copy and paste actions
- * @param window Parent window
- */
- public TextPane(
- final String configDomain,
- final URLBuilder urlBuilder, final Clipboard clipboard,
- final WindowModel window) {
- this.window = window;
- this.configDomain = configDomain;
- this.clipboard = clipboard;
- styleUtils = new StyledMessageUtils(); // TODO: inject this
-
- setUI(new TextPaneUI());
- document = window.getBackBuffer().getDocument();
- newLineIndicator = new JLabel("", SwingConstants.CENTER);
- newLineIndicator.setBackground(Color.RED);
- newLineIndicator.setForeground(Color.WHITE);
- newLineIndicator.setOpaque(true);
- newLineIndicator.setVisible(false);
-
- setLayout(new MigLayout("fill, hidemode 3"));
- backgroundPainter = new BackgroundPainter(window.getConfigManager(),
- urlBuilder, configDomain, "textpanebackground",
- "textpanebackgroundoption", "textpanebackgroundopacity");
- canvas = new TextPaneCanvas(this,
- new CachingDocument<>(document, new AttributedStringMessageMaker()));
- final JLayer<JComponent> layer = new JLayer<>(canvas);
- layer.setUI(backgroundPainter);
- add(layer, "dock center");
- add(newLineIndicator, "dock south, center, grow");
- scrollModel = new DefaultBoundedRangeModel();
- scrollModel.setMaximum(0);
- scrollModel.setExtent(0);
- final JScrollBar scrollBar = new JScrollBar(Adjustable.VERTICAL);
- scrollBar.setModel(scrollModel);
- add(scrollBar, "dock east");
- scrollBar.addAdjustmentListener(this);
- scrollBar.addAdjustmentListener(canvas);
- window.getConfigManager().addChangeListener(configDomain, "textpanelinenotification", this);
- configChanged("", "textpanelinenotification");
-
- addMouseWheelListener(this);
- document.addIRCDocumentListener(this);
- setAutoscrolls(true);
-
- final MouseMotionListener doScrollRectToVisible = new MouseMotionAdapter() {
-
- @Override
- public void mouseDragged(final MouseEvent e) {
- if (e.getXOnScreen() > getLocationOnScreen().getX()
- && e.getXOnScreen() < getLocationOnScreen().getX() + getWidth()
- && e.getModifiersEx() == InputEvent.BUTTON1_DOWN_MASK) {
- if (getLocationOnScreen().getY() > e.getYOnScreen()) {
- scrollModel.setValue(scrollBar.getValue() - 1);
- } else if (getLocationOnScreen().getY() + getHeight()
- < e.getYOnScreen()) {
- scrollModel.setValue(scrollBar.getValue() + 1);
- }
- canvas.highlightEvent(MouseEventType.DRAG, e);
- }
- }
- };
- addMouseMotionListener(doScrollRectToVisible);
-
- setRangeProperties(document.getNumLines() - 1, document.getNumLines() - 1);
- }
-
- /**
- * Sets the range properties of the scroll model. This method takes into account the scroll
- * model working with 0 indexed line numbers.
- *
- * @param max Total number of lines
- * @param value Current line
- */
- private void setRangeProperties(final int max, final int value) {
- if (max == 1) {
- scrollModel.setRangeProperties(1, 0, 1, 1, false);
- } else {
- scrollModel.setRangeProperties(value, 0, 0, max - 1, false);
- }
- }
-
- @Override
- public void updateUI() {
- setUI(new TextPaneUI());
- }
-
- /**
- * Returns the last visible line in the TextPane.
- *
- * @return Last visible line index
- */
- public int getLastVisibleLine() {
- return scrollModel.getValue();
- }
-
- /**
- * Sets the new position for the scrollbar and the associated position to render the text from.
- *
- * @param position new position of the scrollbar
- */
- public void setScrollBarPosition(final int position) {
- scrollModel.setValue(position);
- }
-
- @Override
- public void adjustmentValueChanged(final AdjustmentEvent e) {
- if (showNotification && e.getValue() >= scrollModel.getMaximum()) {
- newLineIndicator.setVisible(false);
- }
-
- lastSeenLine = Math.max(lastSeenLine, e.getValue());
-
- final int lines = scrollModel.getMaximum() - lastSeenLine;
- newLineIndicator.setText("↓ " + lines + " new line"
- + (lines == 1 ? "" : "s") + " ↓");
- }
-
- @Override
- public void mouseWheelMoved(final MouseWheelEvent e) {
- if (e.getWheelRotation() > 0) {
- scrollModel.setValue(scrollModel.getValue() + e.getScrollAmount());
- } else {
- scrollModel.setValue(scrollModel.getValue() - e.getScrollAmount());
- }
- }
-
- /**
- *
- * Returns the line information from a mouse click inside the TextPane.
- *
- * @param point mouse position
- *
- * @return line number, line part, position in whole line
- */
- public LineInfo getClickPosition(final Point point) {
- return canvas.getClickPosition(point, true);
- }
-
- /**
- *
- * Returns the line information from a mouse click inside the TextPane.
- *
- * @param point mouse position
- * @param selection Are we selecting text?
- *
- * @return line number, line part, position in whole line
- *
- * @since 0.6.3
- */
- public LineInfo getClickPosition(final Point point,
- final boolean selection) {
- return canvas.getClickPosition(point, selection);
- }
-
- /**
- * Returns the selected text.
- *
- * @param styled Return styled text?
- *
- * @return Selected text
- */
- public String getSelectedText(final boolean styled) {
- final StringBuilder selectedText = new StringBuilder();
- final LinePosition selectedRange = canvas.getSelectedRange();
-
- if (selectedRange.getStartLine() == -1) {
- return null;
- }
-
- for (int i = selectedRange.getStartLine(); i <= selectedRange.getEndLine(); i++) {
- if (i != selectedRange.getStartLine()) {
- selectedText.append('\n');
- }
- if (scrollModel.getMaximum() < i) {
- return selectedText.toString();
- }
- final String line;
- if (styled) {
- line = document.getLine(i).getStyledText();
- } else {
- line = document.getLine(i).getText();
- }
- if (!line.isEmpty()) {
- if (selectedRange.getEndLine() == selectedRange.getStartLine()) {
- //loop through range
- if (selectedRange.getStartPos() != -1 && selectedRange.getEndPos() != -1) {
- selectedText.append(getText(line,
- selectedRange.getStartPos(),
- selectedRange.getEndPos(), styled));
- }
- } else if (i == selectedRange.getStartLine()) {
- //loop from start of range to the end
- final int length = styleUtils.stripControlCodes(line).length();
- if (selectedRange.getStartPos() != -1 && selectedRange.getStartPos() < length) {
- // Ensure that we're actually selecting some text on this line
- selectedText.append(getText(line, selectedRange.getStartPos(), length,
- styled));
- }
- } else if (i == selectedRange.getEndLine()) {
- //loop from start to end of range
- if (selectedRange.getEndPos() > 0) {
- selectedText.append(getText(line, 0, selectedRange.getEndPos(), styled));
- }
- } else {
- //loop the whole line
- final int length = styleUtils.stripControlCodes(line).length();
- selectedText.append(getText(line, 0, length, styled));
- }
- }
- }
-
- return selectedText.toString();
- }
-
- /**
- * Gets a range of text (styled or unstyled) from the given text.
- *
- * @param text Text to extract text from
- * @param start Start index
- * @param end End index
- * @param styled Styled text?
- *
- * @return Requested text range as a String
- */
- private String getText(final String text, final int start, final int end,
- final boolean styled) {
- checkArgument(start < end, "'start' (" + start + ") must be less than 'end' (" + end + ')');
- checkArgument(start >= 0, "'start' (" + start + ") must be non-negative");
-
- if (styled) {
- return styleUtils.getStyledText(text, start, end);
- } else {
- return text.substring(start, end);
- }
- }
-
- /**
- * Returns the selected range.
- *
- * @return selected range
- */
- public LinePosition getSelectedRange() {
- return canvas.getSelectedRange();
- }
-
- /**
- * Returns whether there is a selected range.
- *
- * @return true iif there is a selected range
- */
- public boolean hasSelectedRange() {
- final LinePosition selectedRange = canvas.getSelectedRange();
- return !(selectedRange.getStartLine() == selectedRange.getEndLine()
- && selectedRange.getStartPos() == selectedRange.getEndPos());
- }
-
- /**
- * Selects the specified region of text.
- *
- * @param position Line position
- */
- public void setSelectedText(final LinePosition position) {
- canvas.setSelectedRange(position);
- }
-
- /**
- * Returns the type of text this click represents.
- *
- * @param lineInfo Line info of click.
- *
- * @return Click type for specified position
- */
- public ClickTypeValue getClickType(final LineInfo lineInfo) {
- return canvas.getClickType(lineInfo);
- }
-
- /**
- * Returns the surrounding word at the specified position.
- *
- * @param lineNumber Line number to get word from
- * @param index Position to get surrounding word
- *
- * @return Surrounding word
- */
- public String getWordAtIndex(final int lineNumber, final int index) {
- if (lineNumber == -1) {
- return "";
- }
- final int[] indexes = StringUtils.indiciesOfWord(document.getLine(lineNumber).getText(),
- index);
- return document.getLine(lineNumber).getText().substring(indexes[0], indexes[1]);
- }
-
- /** Adds the selected text to the clipboard. */
- public void copy() {
- copy(false);
- }
-
- /**
- * Adds the selected text to the clipboard.
- *
- * @param copyControlCharacters Should we copy control codes, or strip them?
- */
- public void copy(final boolean copyControlCharacters) {
- if (getSelectedText(false) != null && !getSelectedText(false).isEmpty()) {
- clipboard.setContents(new StringSelection(getSelectedText(copyControlCharacters)), null);
- }
- }
-
- /** Clears the TextPane. */
- public void clear() {
- UIUtilities.invokeLater(document::clear);
- }
-
- /** Clears the selection. */
- public void clearSelection() {
- canvas.clearSelection();
- }
-
- /** Scrolls one page up in the TextPane. */
- public void pageDown() {
- scrollModel.setValue(scrollModel.getValue() + 10);
- }
-
- /** Scrolls one page down in the TextPane. */
- public void pageUp() {
- scrollModel.setValue(scrollModel.getValue() - 10);
- }
-
- /** Scrolls to the beginning of the TextPane. */
- public void goToHome() {
- scrollModel.setValue(0);
- }
-
- /** Scrolls to the end of the TextPane. */
- public void goToEnd() {
- scrollModel.setValue(scrollModel.getMaximum());
- }
-
- @Override
- public void trimmed(final int newSize, final int numTrimmed) {
- UIUtilities.invokeLater(() -> {
- lastSeenLine -= numTrimmed;
- final LinePosition selectedRange = getSelectedRange();
- selectedRange.setStartLine(selectedRange.getStartLine() - numTrimmed);
- selectedRange.setEndLine(selectedRange.getEndLine() - numTrimmed);
- if (selectedRange.getStartLine() < 0) {
- selectedRange.setStartLine(0);
- }
- if (selectedRange.getEndLine() < 0) {
- selectedRange.setEndLine(0);
- }
- setSelectedText(selectedRange);
- if (scrollModel.getValue() == scrollModel.getMaximum()) {
- setRangeProperties(newSize, newSize);
- } else {
- setRangeProperties(newSize, scrollModel.getValue() - numTrimmed);
- }
- });
- }
-
- @Override
- public void cleared() {
- UIUtilities.invokeLater(() -> {
- scrollModel.setMaximum(0);
- scrollModel.setValue(0);
- canvas.recalc();
- });
- }
-
- @Override
- public void linesAdded(final int line, final int length, final int size) {
- UIUtilities.invokeLater(() -> {
- if (scrollModel.getValue() == scrollModel.getMaximum()) {
- setRangeProperties(size, size);
- } else {
- setRangeProperties(size, scrollModel.getValue());
- if (showNotification) {
- newLineIndicator.setVisible(true);
- }
- }
- });
- }
-
- @Override
- public void repaintNeeded() {
- UIUtilities.invokeLater(canvas::recalc);
- }
-
- /**
- * Retrieves this TextPane's IRCDocument.
- *
- * @return This TextPane's IRC document
- */
- public Document getDocument() {
- return document;
- }
-
- /**
- * Retrieves the parent window for this TextPane.
- *
- * @return Parent window
- */
- public WindowModel getWindow() {
- return window;
- }
-
- /**
- * Adds a TextPane listener to this TextPane.
- *
- * @param listener Listener to add
- */
- public void addTextPaneListener(final TextPaneListener listener) {
- canvas.addTextPaneListener(listener);
- }
-
- /**
- * Removes a TextPane listener from this TextPane.
- *
- * @param listener Listener to remove
- */
- public void removeTextPaneListener(final TextPaneListener listener) {
- canvas.removeTextPaneListener(listener);
- }
-
- @Override
- public void configChanged(final String domain, final String key) {
- showNotification = window.getConfigManager().getOptionBool(configDomain, "textpanelinenotification");
- if (!showNotification) {
- UIUtilities.invokeLater(() -> newLineIndicator.setVisible(false));
- }
- }
-
- /**
- * Called to close this TextPane and any associated resources.
- */
- public void close() {
- backgroundPainter.unbind();
- }
-
- }
|