/*
* Copyright 2016 Igor Maznitsa.
*
* Licensed 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 com.igormaznitsa.sciareto.ui.editors.mmeditors;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Locale;
import javax.annotation.Nonnull;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.UndoableEditEvent;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.TextAction;
import javax.swing.text.Utilities;
import javax.swing.undo.UndoManager;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import com.igormaznitsa.mindmap.model.logger.Logger;
import com.igormaznitsa.mindmap.model.logger.LoggerFactory;
import com.igormaznitsa.sciareto.preferences.PreferencesManager;
import com.igormaznitsa.sciareto.preferences.SpecificKeys;
import com.igormaznitsa.sciareto.ui.DialogProviderManager;
import com.igormaznitsa.sciareto.ui.UiUtils;
import com.igormaznitsa.sciareto.ui.editors.TextEditor;
public final class NoteEditor extends javax.swing.JPanel {
private static final long serialVersionUID = -1715683034655322518L;
private static final Logger LOGGER = LoggerFactory.getLogger(NoteEditor.class);
private final UndoManager undoManager = new UndoManager(){
private static final long serialVersionUID = -239961738072597268L;
@Override
public void undoableEditHappened(@Nonnull final UndoableEditEvent e) {
super.undoableEditHappened(e);
updateRedoUndoState();
}
};
private enum Wrapping {
NONE("none", "off"), //NOI18N
CHAR_WRAP("char", "char"), //NOI18N
WORD_WRAP("word", "word"); //NOI18N
private final String value;
private final String display;
private Wrapping(@Nonnull final String val, @Nonnull final String display) {
this.value = val;
this.display = display;
}
@Nonnull
public String getValue() {
return this.value;
}
@Nonnull
public String getDisplay() {
return this.display;
}
@Nonnull
public Wrapping next() {
final int index = this.ordinal() + 1;
if (index >= Wrapping.values().length) {
return NONE;
} else {
return Wrapping.values()[index];
}
}
@Nonnull
public static Wrapping findFor(@Nonnull final String text) {
for (final Wrapping w : Wrapping.values()) {
if (w.value.equalsIgnoreCase(text)) {
return w;
}
}
return NONE;
}
}
private static final FileFilter TEXT_FILE_FILTER = new FileFilter() {
@Override
public boolean accept(final File f) {
return f.isDirectory() || f.getName().toLowerCase(Locale.ENGLISH).endsWith(".txt"); //NOI18N
}
@Override
public String getDescription() {
return java.util.ResourceBundle.getBundle("com/igormaznitsa/nbmindmap/i18n/Bundle").getString("PlainTextEditor.fileFilter.description");
}
};
private Wrapping wrapping;
private static boolean isWhitespaceOrControl(final char c) {
return Character.isISOControl(c) || Character.isWhitespace(c);
}
private void updateRedoUndoState(){
this.buttonUndo.setEnabled(this.undoManager.canUndo());
this.buttonRedo.setEnabled(this.undoManager.canRedo());
}
private void doUndo(){
if (this.undoManager.canUndo()){
this.undoManager.undo();
}
updateRedoUndoState();
}
private void doRedo(){
if (this.undoManager.canRedo()){
this.undoManager.redo();
}
updateRedoUndoState();
}
public NoteEditor(@Nonnull final String text) {
initComponents();
this.buttonRedo.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() | KeyEvent.SHIFT_MASK), "do-redo"); //NOI18N
this.buttonRedo.getActionMap().put("do-redo", new AbstractAction(){ //NOI18N
private static final long serialVersionUID = -5644390861803492172L;
@Override
public void actionPerformed(@Nonnull final ActionEvent e) {
doRedo();
}
});
this.buttonUndo.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),"do-undo"); //NOI18N
this.buttonUndo.getActionMap().put("do-undo", new AbstractAction() { //NOI18N
private static final long serialVersionUID = -5644390861803492172L;
@Override
public void actionPerformed(@Nonnull final ActionEvent e) {
doUndo();
}
});
this.editorPane.getActionMap().put(DefaultEditorKit.selectWordAction, new TextAction(DefaultEditorKit.selectWordAction) {
private static final long serialVersionUID = -6477916799997545798L;
private final Action start = new TextAction("wordStart") { //NOI18N
private static final long serialVersionUID = 4377386270269629176L;
@Override
public void actionPerformed(@Nonnull final ActionEvent e) {
final JTextComponent target = getTextComponent(e);
try {
if (target != null) {
int offs = target.getCaretPosition();
final Document doc = target.getDocument();
final String text = doc.getText(0, doc.getLength());
int startOffs = offs;
if (startOffs < text.length()) {
for (int i = offs; i >= 0; i--) {
if (!isWhitespaceOrControl(text.charAt(i))) {
startOffs = i;
} else {
break;
}
}
target.setCaretPosition(startOffs);
}
}
} catch (BadLocationException ex) {
UIManager.getLookAndFeel().provideErrorFeedback(target);
}
}
};
private final Action end = new TextAction("wordEnd") { //NOI18N
private static final long serialVersionUID = 4377386270269629176L;
@Override
public void actionPerformed(@Nonnull final ActionEvent e) {
final JTextComponent target = getTextComponent(e);
try {
if (target != null) {
int offs = target.getCaretPosition();
final Document doc = target.getDocument();
final String text = doc.getText(0, doc.getLength());
int endOffs = offs;
for (int i = offs; i < text.length(); i++) {
endOffs = i;
if (isWhitespaceOrControl(text.charAt(i))) {
break;
}
}
if (endOffs < text.length() && !isWhitespaceOrControl(text.charAt(endOffs))) {
endOffs++;
}
target.moveCaretPosition(endOffs);
}
} catch (BadLocationException ex) {
UIManager.getLookAndFeel().provideErrorFeedback(target);
}
}
};
@Override
public void actionPerformed(@Nonnull final ActionEvent e) {
this.start.actionPerformed(e);
this.end.actionPerformed(e);
}
});
this.setPreferredSize(new Dimension(640, 480));
this.editorPane.setFont(PreferencesManager.getInstance().getFont(PreferencesManager.getInstance().getPreferences(), SpecificKeys.PROPERTY_TEXT_EDITOR_FONT, TextEditor.DEFAULT_FONT));
this.editorPane.setText(text);
this.addAncestorListener(new AncestorListener() {
@Override
public void ancestorAdded(@Nonnull final AncestorEvent event) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
editorPane.grabFocus();
updateCaretPos();
}
});
}
@Override
public void ancestorRemoved(@Nonnull final AncestorEvent event) {
}
@Override
public void ancestorMoved(@Nonnull final AncestorEvent event) {
}
});
this.editorPane.addCaretListener(new CaretListener() {
@Override
public void caretUpdate(@Nonnull final CaretEvent e) {
updateCaretPos();
}
});
this.wrapping = Wrapping.WORD_WRAP;
editorPane.setCaretPosition(0);
updateWrapping();
this.editorPane.getDocument().addUndoableEditListener(this.undoManager);
updateRedoUndoState();
UiUtils.makeOwningDialogResizable(this);
}
private void updateCaretPos() {
final int pos = this.editorPane.getCaretPosition();
final int col = getColumn(pos, this.editorPane);
final int row = getRow(pos, this.editorPane);
this.labelCursorPos.setText(row + ":" + col); //NOI18N
final String selectedText = this.editorPane.getSelectedText();
if (StringUtils.isEmpty(selectedText)) {
this.buttonCopy.setEnabled(false);
this.buttonBrowse.setEnabled(false);
} else {
this.buttonCopy.setEnabled(true);
try {
final URI uri = URI.create(selectedText.trim());
this.buttonBrowse.setEnabled(uri.isAbsolute());
} catch (Exception ex) {
this.buttonBrowse.setEnabled(false);
}
}
}
private static int getRow(final int pos, final JTextComponent editor) {
int rn = (pos == 0) ? 1 : 0;
try {
int offs = pos;
while (offs > 0) {
offs = Utilities.getRowStart(editor, offs) - 1;
rn++;
}
} catch (BadLocationException e) {
LOGGER.error("Bad location", e); //NOI18N
}
return rn;
}
private static int getColumn(final int pos, final JTextComponent editor) {
try {
return pos - Utilities.getRowStart(editor, pos) + 1;
} catch (BadLocationException e) {
LOGGER.error("Bad location", e); //NOI18N
}
return -1;
}
@Nonnull
public String getText() {
return this.editorPane.getText();
}
public void dispose() {
}
/**
* This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The content of this method is always regenerated by the Form
* Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jToolBar1 = new javax.swing.JToolBar();
buttonUndo = new javax.swing.JButton();
buttonRedo = new javax.swing.JButton();
buttonImport = new javax.swing.JButton();
buttonExport = new javax.swing.JButton();
buttonCopy = new javax.swing.JButton();
buttonPaste = new javax.swing.JButton();
buttonBrowse = new javax.swing.JButton();
buttonClear = new javax.swing.JButton();
jPanel1 = new javax.swing.JPanel();
labelCursorPos = new javax.swing.JLabel();
jSeparator1 = new javax.swing.JSeparator();
labelWrapMode = new javax.swing.JLabel();
filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(16, 0), new java.awt.Dimension(16, 0), new java.awt.Dimension(16, 32767));
jScrollPane2 = new javax.swing.JScrollPane();
editorPane = new javax.swing.JTextArea();
setLayout(new java.awt.BorderLayout());
jToolBar1.setFloatable(false);
jToolBar1.setRollover(true);
buttonUndo.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/undo.png"))); // NOI18N
buttonUndo.setMnemonic('u');
buttonUndo.setText("Undo");
buttonUndo.setFocusable(false);
buttonUndo.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
buttonUndo.setNextFocusableComponent(buttonRedo);
buttonUndo.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
buttonUndo.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonUndoActionPerformed(evt);
}
});
jToolBar1.add(buttonUndo);
buttonRedo.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/redo.png"))); // NOI18N
buttonRedo.setMnemonic('r');
buttonRedo.setText("Redo");
buttonRedo.setFocusable(false);
buttonRedo.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
buttonRedo.setNextFocusableComponent(buttonImport);
buttonRedo.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
buttonRedo.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonRedoActionPerformed(evt);
}
});
jToolBar1.add(buttonRedo);
buttonImport.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/disk16.png"))); // NOI18N
buttonImport.setMnemonic('i');
buttonImport.setText("Import");
buttonImport.setToolTipText("Import text content from UTF8 encoded text file");
buttonImport.setFocusable(false);
buttonImport.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
buttonImport.setNextFocusableComponent(buttonExport);
buttonImport.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
buttonImport.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonImportActionPerformed(evt);
}
});
jToolBar1.add(buttonImport);
buttonExport.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/file_save16.png"))); // NOI18N
buttonExport.setMnemonic('e');
buttonExport.setText("Export");
buttonExport.setToolTipText("Export text content into UTF8 encoded file");
buttonExport.setFocusable(false);
buttonExport.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
buttonExport.setNextFocusableComponent(buttonCopy);
buttonExport.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
buttonExport.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonExportActionPerformed(evt);
}
});
jToolBar1.add(buttonExport);
buttonCopy.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/page_copy16.png"))); // NOI18N
buttonCopy.setMnemonic('c');
buttonCopy.setText("Copy");
buttonCopy.setToolTipText("Copy selected text content into clipboard");
buttonCopy.setFocusable(false);
buttonCopy.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
buttonCopy.setNextFocusableComponent(buttonPaste);
buttonCopy.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
buttonCopy.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonCopyActionPerformed(evt);
}
});
jToolBar1.add(buttonCopy);
buttonPaste.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/paste_plain16.png"))); // NOI18N
buttonPaste.setMnemonic('p');
buttonPaste.setText("Paste");
buttonPaste.setToolTipText("Paste text content from clipboard into current position");
buttonPaste.setFocusable(false);
buttonPaste.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
buttonPaste.setNextFocusableComponent(buttonBrowse);
buttonPaste.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
buttonPaste.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonPasteActionPerformed(evt);
}
});
jToolBar1.add(buttonPaste);
buttonBrowse.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/link16.png"))); // NOI18N
buttonBrowse.setMnemonic('b');
buttonBrowse.setText("Browse");
buttonBrowse.setToolTipText("Open selected link in browser");
buttonBrowse.setFocusable(false);
buttonBrowse.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
buttonBrowse.setNextFocusableComponent(buttonClear);
buttonBrowse.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
buttonBrowse.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonBrowseActionPerformed(evt);
}
});
jToolBar1.add(buttonBrowse);
buttonClear.setIcon(new javax.swing.ImageIcon(getClass().getResource("/icons/cross16.png"))); // NOI18N
buttonClear.setMnemonic('a');
buttonClear.setText("Clear All");
buttonClear.setToolTipText("Clear all text content");
buttonClear.setFocusable(false);
buttonClear.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
buttonClear.setNextFocusableComponent(editorPane);
buttonClear.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
buttonClear.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
buttonClearActionPerformed(evt);
}
});
jToolBar1.add(buttonClear);
add(jToolBar1, java.awt.BorderLayout.NORTH);
jPanel1.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.RIGHT));
labelCursorPos.setText("...:...");
labelCursorPos.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
labelCursorPosMouseClicked(evt);
}
});
jPanel1.add(labelCursorPos);
jSeparator1.setOrientation(javax.swing.SwingConstants.VERTICAL);
jSeparator1.setPreferredSize(new java.awt.Dimension(8, 16));
jPanel1.add(jSeparator1);
labelWrapMode.setText("...");
labelWrapMode.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
labelWrapMode.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
labelWrapModeMouseClicked(evt);
}
});
jPanel1.add(labelWrapMode);
jPanel1.add(filler1);
add(jPanel1, java.awt.BorderLayout.PAGE_END);
editorPane.setColumns(20);
editorPane.setRows(5);
editorPane.setNextFocusableComponent(buttonUndo);
jScrollPane2.setViewportView(editorPane);
add(jScrollPane2, java.awt.BorderLayout.CENTER);
}// </editor-fold>//GEN-END:initComponents
private void labelCursorPosMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_labelCursorPosMouseClicked
}//GEN-LAST:event_labelCursorPosMouseClicked
private void labelWrapModeMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_labelWrapModeMouseClicked
this.wrapping = this.wrapping.next();
updateWrapping();
}//GEN-LAST:event_labelWrapModeMouseClicked
private void updateWrapping() {
this.editorPane.setWrapStyleWord(this.wrapping != Wrapping.CHAR_WRAP);
this.editorPane.setLineWrap(this.wrapping != Wrapping.NONE);
updateBottomPanel();
}
private void buttonImportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonImportActionPerformed
final File toOpen = DialogProviderManager.getInstance().getDialogProvider().msgOpenFileDialog("note-editor", UiUtils.BUNDLE.getString("PlainTextEditor.buttonLoadActionPerformed.title"), null, true, TEXT_FILE_FILTER, "Open"); //NOI18N
if (toOpen != null) {
try {
final String text = FileUtils.readFileToString(toOpen, "UTF-8"); //NOI18N
this.editorPane.setText(text);
} catch (Exception ex) {
LOGGER.error("Error during text file loading", ex); //NOI18N
DialogProviderManager.getInstance().getDialogProvider().msgError(UiUtils.BUNDLE.getString("PlainTextEditor.buttonLoadActionPerformed.msgError"));
}
}
}//GEN-LAST:event_buttonImportActionPerformed
private void buttonExportActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonExportActionPerformed
final File toSave = DialogProviderManager.getInstance().getDialogProvider().msgSaveFileDialog("note-editor", UiUtils.BUNDLE.getString("PlainTextEditor.buttonSaveActionPerformed.saveTitle"), null, true, TEXT_FILE_FILTER, "Save"); //NOI18N
if (toSave != null) {
try {
final String text = getText();
FileUtils.writeStringToFile(toSave, text, "UTF-8"); //NOI18N
} catch (Exception ex) {
LOGGER.error("Error during text file saving", ex); //NOI18N
DialogProviderManager.getInstance().getDialogProvider().msgError(UiUtils.BUNDLE.getString("PlainTextEditor.buttonSaveActionPerformed.msgError"));
}
}
}//GEN-LAST:event_buttonExportActionPerformed
private void buttonCopyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonCopyActionPerformed
StringSelection stringSelection = new StringSelection(this.editorPane.getSelectedText());
final Clipboard clpbrd = Toolkit.getDefaultToolkit().getSystemClipboard();
clpbrd.setContents(stringSelection, null);
}//GEN-LAST:event_buttonCopyActionPerformed
private void buttonPasteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonPasteActionPerformed
try {
this.editorPane.replaceSelection((String) Toolkit.getDefaultToolkit().getSystemClipboard().getData(DataFlavor.stringFlavor));
} catch (UnsupportedFlavorException ex) {
// no text data in clipboard
} catch (IOException ex) {
LOGGER.error("Error during paste from clipboard", ex); //NOI18N
}
}//GEN-LAST:event_buttonPasteActionPerformed
private void buttonClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonClearActionPerformed
this.editorPane.setText(""); //NOI18N
}//GEN-LAST:event_buttonClearActionPerformed
private void buttonBrowseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonBrowseActionPerformed
final String selectedText = this.editorPane.getSelectedText().trim();
try {
UiUtils.browseURI(URI.create(selectedText), false);
} catch (Exception ex) {
LOGGER.error("Can't open link : " + selectedText); //NOI18N
DialogProviderManager.getInstance().getDialogProvider().msgError("Can't browse link : " + selectedText);
}
}//GEN-LAST:event_buttonBrowseActionPerformed
private void buttonUndoActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonUndoActionPerformed
doUndo();
}//GEN-LAST:event_buttonUndoActionPerformed
private void buttonRedoActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_buttonRedoActionPerformed
doRedo();
}//GEN-LAST:event_buttonRedoActionPerformed
private void updateBottomPanel() {
this.labelWrapMode.setText("Wrap: " + this.wrapping.getDisplay());
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton buttonBrowse;
private javax.swing.JButton buttonClear;
private javax.swing.JButton buttonCopy;
private javax.swing.JButton buttonExport;
private javax.swing.JButton buttonImport;
private javax.swing.JButton buttonPaste;
private javax.swing.JButton buttonRedo;
private javax.swing.JButton buttonUndo;
private javax.swing.JTextArea editorPane;
private javax.swing.Box.Filler filler1;
private javax.swing.JPanel jPanel1;
private javax.swing.JScrollPane jScrollPane2;
private javax.swing.JSeparator jSeparator1;
private javax.swing.JToolBar jToolBar1;
private javax.swing.JLabel labelCursorPos;
private javax.swing.JLabel labelWrapMode;
// End of variables declaration//GEN-END:variables
}