enable save buttons only when text was changed

This commit is contained in:
2015-05-08 13:16:12 +02:00
parent 13ea982705
commit fa912d01ea
17 changed files with 5605 additions and 5518 deletions
@@ -0,0 +1,374 @@
/*
* Copyright (C) 2010-2015 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui.editor;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.gui.AppStrings;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.KeyEventPostProcessor;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.plaf.TextUI;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Highlighter.HighlightPainter;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import javax.swing.text.View;
import jsyntaxpane.SyntaxDocument;
import jsyntaxpane.Token;
import jsyntaxpane.actions.ActionUtils;
/**
*
* @author JPEXS
*/
public class LineMarkedEditorPane extends UndoFixedEditorPane implements LinkHandler {
private static final int truncateLimit = 2 * 1024 * 1024;
private int lastLine = -1;
private boolean error = false;
private Token lastUnderlined = null;
private static final HighlightPainter underLinePainter = new UnderLinePainter(new Color(0, 0, 255));
private LinkHandler linkHandler = this;
public int getLine() {
return lastLine;
}
public void markError() {
error = true;
}
public void gotoLine(int line) {
setCaretPosition(ActionUtils.getDocumentPosition(this, line, 0));
}
public void selectLine(int line) {
Document d = getDocument();
String text = "";
try {
text = d.getText(0, d.getLength());
} catch (BadLocationException ex) {
//ignore
}
int lineCnt = 1;
int lineStart = 0;
int lineEnd = -1;
for (int i = 0; i < text.length(); i++) {
if (text.charAt(i) == '\n') {
lineCnt++;
if (lineCnt == line) {
lineStart = i + 1;
}
if (lineCnt == line + 1) {
lineEnd = i;
}
}
}
if (lineCnt == 1) {
lineEnd = text.length() - 1;
if (line > 1) {
lineStart = text.length() - 1;
}
}
select(lineStart, lineEnd);
requestFocus();
}
public LineMarkedEditorPane() {
setOpaque(false);
addCaretListener(new CaretListener() {
@Override
public void caretUpdate(CaretEvent e) {
int caretPosition = getCaretPosition();
Element root = getDocument().getDefaultRootElement();
int currentLine = root.getElementIndex(caretPosition);
if (currentLine != lastLine) {
lastLine = currentLine;
error = false;
repaint();
}
}
});
final LinkAdapter la = new LinkAdapter();
addMouseMotionListener(la);
addMouseListener(la);
//No standard AddKeyListener as we want to catch Ctrl globally no matter of focus
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addKeyEventPostProcessor(new KeyEventPostProcessor() {
@Override
public boolean postProcessKeyEvent(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
la.keyPressed(e);
}
if (e.getID() == KeyEvent.KEY_RELEASED) {
la.keyReleased(e);
}
if (e.getID() == KeyEvent.KEY_TYPED) {
la.keyTyped(e);
}
return false;
}
});
}
private class LinkAdapter extends MouseAdapter implements KeyListener {
private Point lastPos = new Point(0, 0);
private boolean ctrlDown = false;
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
ctrlDown = true;
update();
}
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_CONTROL) {
ctrlDown = false;
update();
}
}
private void update() {
if (ctrlDown) {
Document d = getDocument();
if (d instanceof SyntaxDocument) {
SyntaxDocument sd = (SyntaxDocument) d;
int pos = viewToModel(lastPos);
if (pos <= 0) {
return;
}
Token t = sd.getTokenAt(pos);
if (t != lastUnderlined) {
if (t == null || lastUnderlined == null || !t.equals(lastUnderlined)) {
MyMarkers.removeMarkers(LineMarkedEditorPane.this, underLinePainter);
if (t != null && linkHandler.isLink(t)) {
lastUnderlined = t;
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
lastUnderlined = null;
}
} else {
lastUnderlined = null;
}
}
if (lastUnderlined != null) {
MyMarkers.markToken(LineMarkedEditorPane.this, lastUnderlined, underLinePainter);
} else {
setCursor(Cursor.getDefaultCursor());
}
repaint();
}
} else {
lastUnderlined = null;
MyMarkers.removeMarkers(LineMarkedEditorPane.this, underLinePainter);
setCursor(Cursor.getDefaultCursor());
repaint();
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (ctrlDown) {
SyntaxDocument sd = (SyntaxDocument) getDocument();
int pos = viewToModel(e.getPoint());
if (pos < 0) {
return;
}
Token t = sd.getTokenAt(pos + 1);
if (t != null && linkHandler.isLink(t)) {
linkHandler.handleLink(t);
}
}
}
@Override
public void mouseMoved(MouseEvent e) {
ctrlDown = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) > 0;
lastPos = e.getPoint();
update();
}
}
public void setLinkHandler(LinkHandler linkHandler) {
if (linkHandler == null) {
linkHandler = this;
}
this.linkHandler = linkHandler;
}
public LinkHandler getLinkHandler() {
return linkHandler;
}
@Override
public HighlightPainter linkPainter() {
return underLinePainter;
}
@Override
public boolean isLink(Token token) {
return false;
}
@Override
public void handleLink(Token token) {
}
@Override
public void setText(String t) {
lastLine = -1;
error = false;
if (Configuration.debugMode.get() && t.length() > truncateLimit) {
t = t.substring(0, truncateLimit) + "\r\n" + AppStrings.translate("editorTruncateWarning").replace("%chars%", Integer.toString(truncateLimit));
}
super.setText(t);
setCaretPosition(0); //scroll to top
}
public static class UnderLinePainter extends DefaultHighlighter.DefaultHighlightPainter {
public UnderLinePainter(Color color) {
super(color);
}
@Override
public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) {
try {
// --- determine locations ---
TextUI mapper = c.getUI();
Color col = getColor();
if (col == null) {
col = Color.black;
}
g.setColor(col);
for (int i = offs0; i < offs1; i++) {
Rectangle r = mapper.modelToView(c, i, Position.Bias.Forward);
Rectangle r1 = mapper.modelToView(c, i + 1, Position.Bias.Forward);
if (r1.y == r.y) {
g.drawLine(r.x, r.y + r.height - 3, r1.x, r.y + r.height - 3);
}
}
} catch (BadLocationException e) {
// can't render
}
}
@Override
public Shape paintLayer(Graphics g, int offs0, int offs1,
Shape bounds, JTextComponent c, View view) {
g.setColor(c.getSelectionColor());
Rectangle r;
if (offs0 == view.getStartOffset()
&& offs1 == view.getEndOffset()) {
// Contained in view, can just use bounds.
if (bounds instanceof Rectangle) {
r = (Rectangle) bounds;
} else {
r = bounds.getBounds();
}
} else {
// Should only render part of View.
try {
// --- determine locations ---
Shape shape = view.modelToView(offs0, Position.Bias.Forward,
offs1, Position.Bias.Backward,
bounds);
r = (shape instanceof Rectangle)
? (Rectangle) shape : shape.getBounds();
} catch (BadLocationException e) {
// can't render
r = null;
}
}
if (r != null) {
r.width = Math.max(r.width, 1);
paint(g, offs0, offs1, r, c);
}
return r;
}
}
@Override
public void paint(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
if (lastLine > 0) {
FontMetrics fontMetrics = g.getFontMetrics();
int lh = fontMetrics.getHeight();
int d = fontMetrics.getDescent();
if (error) {
g.setColor(new Color(255, 200, 200));
} else {
g.setColor(new Color(0xee, 0xee, 0xee));
}
g.fillRect(0, d + lh * lastLine - 1, getWidth(), lh);
}
super.paint(g);
}
}
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2010-2015 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui.editor;
import javax.swing.text.Highlighter;
import jsyntaxpane.Token;
/**
*
* @author JPEXS
*/
public interface LinkHandler {
public boolean isLink(Token token);
public void handleLink(Token token);
public Highlighter.HighlightPainter linkPainter();
}
@@ -0,0 +1,127 @@
/*
* Copyright (C) 2010-2015 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui.editor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.BadLocationException;
import javax.swing.text.Highlighter;
import javax.swing.text.JTextComponent;
import jsyntaxpane.SyntaxDocument;
import jsyntaxpane.Token;
import jsyntaxpane.actions.ActionUtils;
/**
*
* @author JPEXS
*/
public class MyMarkers {
/**
* Removes only our private highlights This is public so that we can remove
* the highlights when the editorKit is unregistered. SimpleMarker can be
* null, in which case all instances of our Markers are removed.
*
* @param component the text component whose markers are to be removed
* @param marker the SimpleMarker to remove
*/
public static void removeMarkers(JTextComponent component, Highlighter.HighlightPainter marker) {
Highlighter hilite = component.getHighlighter();
Highlighter.Highlight[] hilites = hilite.getHighlights();
for (int i = 0; i < hilites.length; i++) {
Highlighter.HighlightPainter hMarker = hilites[i].getPainter();
if (marker == null || hMarker.equals(marker)) {
hilite.removeHighlight(hilites[i]);
}
}
}
/**
* Remove all the markers from an JEditorPane
*
* @param editorPane Editor
*/
public static void removeMarkers(JTextComponent editorPane) {
removeMarkers(editorPane, null);
}
/**
* add highlights for the given Token on the given pane
*
* @param pane Editor
* @param token Token
* @param marker Marker
*/
public static void markToken(JTextComponent pane, Token token, Highlighter.HighlightPainter marker) {
markText(pane, token.start, token.end(), marker);
}
/**
* add highlights for the given region on the given pane
*
* @param pane Editor
* @param start Start index
* @param end End index
* @param marker Marker
*/
public static void markText(JTextComponent pane, int start, int end, Highlighter.HighlightPainter marker) {
try {
Highlighter hiliter = pane.getHighlighter();
int selStart = pane.getSelectionStart();
int selEnd = pane.getSelectionEnd();
// if there is no selection or selection does not overlap
if (selStart == selEnd || end < selStart || start > selStart) {
hiliter.addHighlight(start, end, marker);
return;
}
// selection starts within the highlight, highlight before slection
if (selStart > start && selStart < end) {
hiliter.addHighlight(start, selStart, marker);
}
// selection ends within the highlight, highlight remaining
if (selEnd > start && selEnd < end) {
hiliter.addHighlight(selEnd, end, marker);
}
} catch (BadLocationException ex) {
}
}
/**
* Mark all text in the document that matches the given pattern
*
* @param pane control to use
* @param pattern pattern to match
* @param marker marker to use for highlighting
*/
public static void markAll(JTextComponent pane, Pattern pattern, Highlighter.HighlightPainter marker) {
SyntaxDocument sDoc = ActionUtils.getSyntaxDocument(pane);
if (sDoc == null || pattern == null) {
return;
}
Matcher matcher = sDoc.getMatcher(pattern);
// we may not have any matcher (due to undo or something, so don't do anything.
if (matcher == null) {
return;
}
while (matcher.find()) {
markText(pane, matcher.start(), matcher.end(), marker);
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (C) 2010-2015 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui.editor;
/**
*
* @author JPEXS
*/
@FunctionalInterface
public interface TextChangedListener {
public void textChanged();
}
@@ -0,0 +1,170 @@
/*
* Copyright (C) 2010-2015 JPEXS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.jpexs.decompiler.flash.gui.editor;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.gui.View;
import com.jpexs.helpers.Stopwatch;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JEditorPane;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import jsyntaxpane.SyntaxDocument;
/**
*
* @author JPEXS
*/
public class UndoFixedEditorPane extends JEditorPane {
private static final Object setTextLock = new Object();
private final List<TextChangedListener> textChangedListeners = new ArrayList<>();
private String originalContentType;
private DocumentListener documentListener;
public UndoFixedEditorPane() {
addDocumentListener();
}
private void fireTextChanged() {
for (TextChangedListener listener : textChangedListeners) {
listener.textChanged();
}
}
private void addDocumentListener() {
documentListener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
fireTextChanged();
}
@Override
public void removeUpdate(DocumentEvent e) {
fireTextChanged();
}
@Override
public void changedUpdate(DocumentEvent e) {
fireTextChanged();
}
};
getDocument().addDocumentListener(documentListener);
}
private void removeDocumentListener() {
getDocument().removeDocumentListener(documentListener);
}
public void changeContentType(String type) {
if (!type.equals(getContentType())) {
removeDocumentListener();
setContentType(type);
addDocumentListener();
}
}
@Override
public void setText(final String t) {
View.execInEventDispatch(() -> {
setText(t, getContentType());
});
}
private void setText(String t, String contentType) {
synchronized (setTextLock) {
if (!t.equals(getText())) {
boolean plain = t.length() > Configuration.syntaxHighlightLimit.get();
if (plain) {
contentType = "text/plain";
originalContentType = getContentType();
changeContentType(contentType);
} else {
if (originalContentType != null) {
changeContentType(originalContentType);
originalContentType = null;
}
}
Stopwatch sw = Stopwatch.startNew();
try {
Document doc = getDocument();
setDocument(new SyntaxDocument(null));
doc.remove(0, doc.getLength());
Reader r = new StringReader(t);
EditorKit kit = createEditorKitForContentType(contentType);
kit.read(r, doc, 0);
setDocument(doc);
} catch (BadLocationException | IOException ex) {
Logger.getLogger(UndoFixedEditorPane.class.getName()).log(Level.SEVERE, null, ex);
}
sw.stop();
if (!plain && sw.getElapsedMilliseconds() > 5000) {
Logger.getLogger(UndoFixedEditorPane.class.getName()).log(Level.WARNING, "Syntax highlightig took long time. You can try to decrease the syntax highlight limit in advanced settings.");
}
clearUndos();
}
}
}
@Override
protected void processKeyEvent(KeyEvent ke) {
if (!isEditable()) {
// disable Ctrl-E: delete line
// and Ctrl-H: Search and replace
if ((ke.getKeyCode() == KeyEvent.VK_E && ke.isControlDown())
|| (ke.getKeyCode() == KeyEvent.VK_H && ke.isControlDown())) {
return;
}
}
super.processKeyEvent(ke);
}
public void clearUndos() {
Document doc = getDocument();
if (doc instanceof SyntaxDocument) {
SyntaxDocument sdoc = (SyntaxDocument) doc;
sdoc.clearUndos();
}
}
public void addTextChangedListener(TextChangedListener l) {
textChangedListeners.add(l);
}
public void removeTextChangedListener(TextChangedListener l) {
textChangedListeners.remove(l);
}
}