/*
* 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;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.JComponent;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.undo.UndoManager;
import org.apache.commons.io.FileUtils;
import com.igormaznitsa.mindmap.model.logger.Logger;
import com.igormaznitsa.mindmap.model.logger.LoggerFactory;
import com.igormaznitsa.sciareto.Context;
import com.igormaznitsa.sciareto.preferences.PreferencesManager;
import com.igormaznitsa.sciareto.preferences.SpecificKeys;
import com.igormaznitsa.sciareto.ui.DialogProviderManager;
import com.igormaznitsa.sciareto.ui.SystemUtils;
import com.igormaznitsa.sciareto.ui.tabs.TabTitle;
import com.igormaznitsa.sciareto.ui.FindTextScopeProvider;
public final class TextEditor extends AbstractEditor {
private static final Logger LOGGER = LoggerFactory.getLogger(TextEditor.class);
public static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, 14);
private final JTextArea editor;
private final TabTitle title;
private boolean ignoreChange;
private final UndoManager undoManager = new UndoManager();
private final JScrollPane scrollPane;
public static final FileFilter TXT_FILE_FILTER = new FileFilter() {
@Override
public boolean accept(@Nonnull final File f) {
return f.isDirectory() || f.getName().endsWith(".txt"); //NOI18N
}
@Override
@Nonnull
public String getDescription() {
return "Text document (*.txt)";
}
};
@Override
@Nonnull
public FileFilter getFileFilter() {
return TXT_FILE_FILTER;
}
public TextEditor(@Nonnull final Context context, @Nullable File file) throws IOException {
super();
this.editor = new JTextArea();
this.scrollPane = new JScrollPane(this.editor);
this.editor.getCaret().setSelectionVisible(true);
this.editor.setFont(PreferencesManager.getInstance().getFont(PreferencesManager.getInstance().getPreferences(), SpecificKeys.PROPERTY_TEXT_EDITOR_FONT, DEFAULT_FONT));
this.title = new TabTitle(context, this, file);
this.editor.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(@Nonnull final DocumentEvent e) {
if (!ignoreChange) {
title.setChanged(true);
}
context.notifyUpdateRedoUndo();
}
@Override
public void removeUpdate(@Nonnull final DocumentEvent e) {
if (!ignoreChange) {
title.setChanged(true);
}
context.notifyUpdateRedoUndo();
}
@Override
public void changedUpdate(@Nonnull final DocumentEvent e) {
if (!ignoreChange) {
title.setChanged(true);
}
context.notifyUpdateRedoUndo();
}
});
loadContent(file);
this.editor.getDocument().addUndoableEditListener(this.undoManager);
}
@Override
public void focusToEditor() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
editor.requestFocusInWindow();
}
});
}
@Override
public boolean isRedo() {
return this.undoManager.canRedo();
}
@Override
public boolean isUndo() {
return this.undoManager.canUndo();
}
@Override
public boolean redo() {
if (this.undoManager.canRedo()) {
this.undoManager.redo();
}
return this.undoManager.canRedo();
}
@Override
public boolean undo() {
if (this.undoManager.canUndo()) {
this.undoManager.undo();
}
return this.undoManager.canUndo();
}
@Override
@Nonnull
public JComponent getMainComponent() {
return this.editor;
}
@Override
public boolean isEditable() {
return true;
}
@Override
public boolean isSaveable() {
return true;
}
@Override
public void updateConfiguration() {
this.editor.setFont(PreferencesManager.getInstance().getFont(PreferencesManager.getInstance().getPreferences(), SpecificKeys.PROPERTY_TEXT_EDITOR_FONT, DEFAULT_FONT));
this.editor.revalidate();
this.editor.repaint();
}
@Override
public void loadContent(@Nullable final File file) throws IOException {
this.ignoreChange = true;
try {
if (file != null) {
this.editor.setText(FileUtils.readFileToString(file, "UTF-8")); //NOI18N
this.editor.setCaretPosition(0);
}
}
finally {
this.ignoreChange = false;
}
this.undoManager.discardAllEdits();
this.title.setChanged(false);
this.scrollPane.revalidate();
}
@Override
public boolean saveDocument() throws IOException {
boolean result = false;
if (this.title.isChanged()) {
File file = this.title.getAssociatedFile();
if (file == null) {
file = DialogProviderManager.getInstance().getDialogProvider().msgSaveFileDialog("text-editor", "Save Text document", null, true, getFileFilter(), "Save");
if (file == null) {
return result;
}
}
SystemUtils.saveUTFText(file, this.editor.getText());
this.title.setChanged(false);
result = true;
} else {
result = true;
}
return result;
}
@Override
@Nonnull
public TabTitle getTabTitle() {
return this.title;
}
@Override
@Nonnull
public EditorContentType getEditorContentType() {
return EditorContentType.TEXT;
}
@Override
@Nonnull
public JComponent getContainerToShow() {
return this.scrollPane;
}
@Override
@Nonnull
public AbstractEditor getEditor() {
return this;
}
private boolean searchSubstring(@Nonnull final Pattern pattern, final boolean next) {
final String currentText = this.editor.getText();
int cursorPos = this.editor.getCaretPosition();
final Matcher matcher = pattern.matcher(currentText);
boolean result = false;
if (next) {
if (cursorPos < currentText.length()) {
if (matcher.find(cursorPos) || matcher.find(0)) {
final int foundPosition = matcher.start();
this.editor.select(foundPosition, matcher.end());
this.editor.getCaret().setSelectionVisible(true);
result = true;
}
}
} else {
int lastFound = -1;
int lastFoundEnd = -1;
int maxPos = this.editor.getCaret().getMark() == this.editor.getCaret().getDot() ? this.editor.getCaretPosition() : this.editor.getSelectionStart();
for (int i = 0; i < 2; i++) {
while (matcher.find()) {
final int pos = matcher.start();
if (pos < maxPos) {
lastFound = pos;
lastFoundEnd = matcher.end();
} else {
break;
}
}
if (lastFound >= 0) {
break;
}
maxPos = currentText.length();
}
if (lastFound >= 0) {
this.editor.select(lastFound, lastFoundEnd);
this.editor.getCaret().setSelectionVisible(true);
result = true;
}
}
return result;
}
@Override
public boolean findNext(@Nonnull final Pattern pattern, @Nonnull final FindTextScopeProvider provider) {
return searchSubstring(pattern, true);
}
@Override
public boolean findPrev(@Nonnull final Pattern pattern, @Nonnull final FindTextScopeProvider provider) {
return searchSubstring(pattern, false);
}
@Override
public boolean doesSupportPatternSearch() {
return true;
}
@Override
public boolean doCopy() {
boolean result = false;
final String selected = this.editor.getSelectedText();
if (selected != null && !selected.isEmpty()) {
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(new StringSelection(selected), null);
}
return result;
}
@Override
public boolean doesSupportCutCopyPaste() {
return true;
}
@Override
public boolean isCutAllowed() {
final String selected = this.editor.getSelectedText();
return selected != null && !selected.isEmpty();
}
@Override
public boolean doCut() {
boolean result = false;
final String selected = this.editor.getSelectedText();
if (selected != null && !selected.isEmpty()) {
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(new StringSelection(selected), null);
this.editor.replaceSelection(""); //NOI18N
}
return result;
}
@Override
public boolean doPaste() {
boolean result = false;
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
String text = null;
try {
if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
text = clipboard.getData(DataFlavor.stringFlavor).toString();
}
}
catch (Exception ex) {
LOGGER.warn("Can't get data from clipboard : " + ex.getMessage()); //NOI18N
}
if (text != null) {
this.editor.replaceSelection(text);
result = true;
}
return result;
}
@Override
public boolean isCopyAllowed() {
final String selected = this.editor.getSelectedText();
return selected != null && !selected.isEmpty();
}
@Override
public boolean isPasteAllowed() {
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
return clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor);
}
}