package de.uni_passau.fim.infosun.prophet.plugin.plugins.codeViewerPlugin.codeViewerPlugins; import java.awt.Component; import java.awt.event.KeyEvent; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.HashSet; import java.util.Set; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.KeyStroke; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import de.uni_passau.fim.infosun.prophet.plugin.plugins.codeViewerPlugin.CodeViewer; import de.uni_passau.fim.infosun.prophet.plugin.plugins.codeViewerPlugin.Plugin; import de.uni_passau.fim.infosun.prophet.plugin.plugins.codeViewerPlugin.tabbedPane.EditorPanel; import de.uni_passau.fim.infosun.prophet.plugin.plugins.codeViewerPlugin.tabbedPane.EditorTabbedPane; import de.uni_passau.fim.infosun.prophet.util.qTree.Attribute; import de.uni_passau.fim.infosun.prophet.util.settings.Setting; import de.uni_passau.fim.infosun.prophet.util.settings.components.CheckBoxSetting; import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import static de.uni_passau.fim.infosun.prophet.util.language.UIElementNames.getLocalized; import static java.awt.event.InputEvent.CTRL_MASK; import static java.awt.event.InputEvent.SHIFT_MASK; /** * A <code>Plugin</code> that enables editing the contents of the <code>CodeViewer</code>s <code>EditorPanel</code>s. * Menu items and keyboard shortcuts to save the currently active panel or all panels will be added to the * <code>CodeViewer</code>. The edited versions of the files will be saved in a directory under the * <code>CodeViewer</code>s save directory. */ public class EditAndSavePlugin implements Plugin { private static final String KEY = "editable"; private static final String DIR_NAME = "savedFiles"; private File saveDir; private EditorTabbedPane tabbedPane; private Set<EditorPanel> changed; private boolean enabled; /** * Constructs a new <code>EditAndSavePlugin</code>. */ public EditAndSavePlugin() { changed = new HashSet<>(); } @Override public Setting getSetting(Attribute mainAttribute) { Attribute attribute = mainAttribute.getSubAttribute(KEY); Setting setting = new CheckBoxSetting(attribute, getClass().getSimpleName()); setting.setCaption(getLocalized("EDIT_AND_SAVE_EDITABLE_CODE")); return setting; } @Override public void onCreate(CodeViewer viewer) { Attribute attr = viewer.getAttribute(); enabled = attr.containsSubAttribute(KEY) && Boolean.parseBoolean(attr.getSubAttribute(KEY).getValue()); if (!enabled) { return; } tabbedPane = viewer.getTabbedPane(); saveDir = new File(viewer.getSaveDir(), DIR_NAME); if (!saveDir.mkdirs()) { System.err.println("Could not create the directory to save files in."); } JMenuItem saveMenuItem = new JMenuItem(getLocalized("EDIT_AND_SAVE_SAVE")); saveMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, CTRL_MASK)); saveMenuItem.addActionListener(event -> saveActiveEditorPanel()); viewer.addMenuItemToFileMenu(saveMenuItem); JMenuItem saveAllMenuItem = new JMenuItem(getLocalized("EDIT_AND_SAVE_SAVE_ALL")); saveAllMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, CTRL_MASK | SHIFT_MASK)); saveAllMenuItem.addActionListener(event -> saveAllEditorPanels()); viewer.addMenuItemToFileMenu(saveAllMenuItem); } @Override public void onEditorPanelCreate(CodeViewer codeViewer, EditorPanel editorPanel) { if (!enabled) { return; } RSyntaxTextArea textArea = editorPanel.getTextArea(); File savedFile = getSaveFile(editorPanel); if (savedFile.exists()) { Document doc = textArea.getDocument(); DocumentListener[] listeners = removeListeners((RSyntaxDocument) doc); try { doc.remove(0, doc.getLength()); } catch (BadLocationException e) { System.err.println("Could not clear the document."); System.err.println(e.getMessage()); } try { doc.insertString(0, readFile(savedFile), null); } catch (BadLocationException e) { System.err.println("Could not insert the contents of the saveFile."); System.err.println(e.getMessage()); } catch (IOException e) { System.err.println("Could not read the saveFile."); System.err.println(e.getMessage()); } textArea.setCaretPosition(0); addListeners((RSyntaxDocument) doc, listeners); } textArea.setEditable(true); textArea.getDocument().addDocumentListener(new DocumentListener() { private void changeOccurred() { changed.add(editorPanel); } @Override public void changedUpdate(DocumentEvent event) { } @Override public void insertUpdate(DocumentEvent event) { changeOccurred(); } @Override public void removeUpdate(DocumentEvent event) { changeOccurred(); } }); } @Override public void onEditorPanelClose(CodeViewer codeViewer, EditorPanel editorPanel) { if (!enabled) { return; } if (changed.contains(editorPanel)) { String msg = getLocalized("EDIT_AND_SAVE_DIALOG_SAVE_CHANGES") + "?"; String title = getLocalized("EDIT_AND_SAVE_SAVE") + "?"; if (JOptionPane.showConfirmDialog(null, msg, title, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { saveEditorPanel(editorPanel); } changed.remove(editorPanel); } } @Override public void onClose(CodeViewer codeViewer) { if (!enabled) { return; } if (!changed.isEmpty()) { String msg = getLocalized("EDIT_AND_SAVE_DIALOG_SAVE_CHANGES") + "?"; String title = getLocalized("EDIT_AND_SAVE_SAVE") + "?"; if (JOptionPane.showConfirmDialog(null, msg, title, JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { changed.forEach(this::saveEditorPanel); } } saveDir = null; tabbedPane = null; changed.clear(); } /** * Saves the <code>EditorPanel</code> that is currently selected in the <code>tabbedPane</code>. */ private void saveActiveEditorPanel() { Component activeComp = tabbedPane.getSelectedComponent(); if (activeComp != null && activeComp instanceof EditorPanel) { saveEditorPanel((EditorPanel) activeComp); } } /** * Saves all <code>EditorPanel</code>s that are currently open in the <code>tabbedPane</code>. */ private void saveAllEditorPanels() { for (int i = 0; i < tabbedPane.getTabCount(); i++) { Component myComp = tabbedPane.getComponentAt(i); if (myComp instanceof EditorPanel) { saveEditorPanel((EditorPanel) myComp); } } } /** * Saves the contents of the given <code>editorPanel</code> to a <code>File</code> (of the original name) in the * directory <code>saveDir</code>. * * @param editorPanel * the <code>EditorPanel</code> whose contents are to be saved */ private void saveEditorPanel(EditorPanel editorPanel) { File file = getSaveFile(editorPanel); try (Writer w = new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8)) { w.write(editorPanel.getTextArea().getText()); changed.remove(editorPanel); } catch (IOException e) { System.err.println("Could not save an EditorPanels text."); System.err.println(e.getMessage()); } } /** * Returns the <code>File</code> in which the contents of the given <code>EditorPanel</code> should be saved * if they are changed. * * @param editorPanel * the <code>EditorPanel</code> whose <code>savedFile</code> is to be returned * * @return the <code>savedFile</code> for the <code>editorPanel</code> */ private File getSaveFile(EditorPanel editorPanel) { return new File(saveDir, editorPanel.getFile().getName()); } /** * Removes all <code>DocumentListener</code>s from the given <code>RSyntaxDocument</code> and returns them. * * @param doc * the <code>RSyntaxDocument</code> to remove listeners from * * @return the removed listeners */ private DocumentListener[] removeListeners(RSyntaxDocument doc) { DocumentListener[] listeners = doc.getDocumentListeners(); for (DocumentListener listener : listeners) { doc.removeDocumentListener(listener); } return listeners; } /** * Adds all given <code>DocumentListener</code>s to the <code>RSyntaxDocument</code>. * * @param doc * the <code>RSyntaxDocument</code> to add the <code>DocumentListener</code>s to * @param listeners * the <code>DocumentListener</code>s to add */ private void addListeners(RSyntaxDocument doc, DocumentListener[] listeners) { for (DocumentListener listener : listeners) { doc.addDocumentListener(listener); } } /** * Returns the contents of the given <code>File</code> as a <code>String</code>. This assumes UTF-8 encoding. * * @param f * the <code>File</code> to read * * @return the contents of the <code>File</code> * * @throws IOException * if there is an <code>IOException</code> reading the <code>File</code> */ private String readFile(File f) throws IOException { return new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8).intern(); } }