Gridarta Editor
ScriptEditControl.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2023 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.textedit.scripteditor;
21 
22 import java.awt.Frame;
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStreamReader;
28 import java.io.OutputStreamWriter;
29 import java.io.Writer;
30 import java.lang.ProcessBuilder.Redirect;
31 import java.nio.charset.Charset;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.prefs.Preferences;
35 import javax.swing.Action;
36 import javax.swing.JFileChooser;
37 import javax.swing.JOptionPane;
38 import javax.swing.filechooser.FileFilter;
43 import net.sf.gridarta.utils.Exiter;
45 import net.sf.japi.swing.action.ActionBuilder;
46 import net.sf.japi.swing.action.ActionBuilderFactory;
47 import net.sf.japi.swing.action.ActionMethod;
48 import org.apache.log4j.Category;
49 import org.apache.log4j.Logger;
50 import org.jetbrains.annotations.NotNull;
51 import org.jetbrains.annotations.Nullable;
52 
59 public class ScriptEditControl implements EditorAction {
60 
64  @NotNull
65  private static final Category LOG = Logger.getLogger(ScriptEditControl.class);
66 
70  @NotNull
71  private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
72 
73  // last active popup is stored here
74 
78  @Nullable
79  private static CFPythonPopup activePopup;
80 
81  @NotNull
82  private final ScriptEditView view; // view (window with text areas)
83 
84  @NotNull
85  private final List<String> tabs; // open tabs, contains absolute filenames (or "<>") in order left to right
86 
90  @NotNull
91  private final JFileChooser openFileChooser;
92 
93  @NotNull
94  private final FileFilter scriptFileFilter;
95 
96  @NotNull
97  private final String scriptSuffix;
98 
102  @NotNull
104 
109  @NotNull
111 
118  public ScriptEditControl(@NotNull final FileFilter scriptFileFilter, @NotNull final String scriptSuffix, @NotNull final Frame owner, final File defaultScriptDir, @NotNull final Preferences preferences, @NotNull final Exiter exiter, @NotNull final AppPreferencesModel appPreferencesModel, @NotNull final ScriptEditControlCallback callback) {
119  this.scriptFileFilter = scriptFileFilter;
120  this.scriptSuffix = scriptSuffix;
121  this.appPreferencesModel = appPreferencesModel;
122  this.callback = callback;
123  tabs = new ArrayList<>(); // start with empty vector
124  view = new ScriptEditView(this, owner, preferences, exiter); // initialize window
125  openFileChooser = createOpenFileChooser(defaultScriptDir);
126  }
127 
128  @Deprecated
129  public void setTextAreaDefaults(@NotNull final TextAreaDefaults textAreaDefaults) {
130  view.setTextAreaDefaults(textAreaDefaults);
131  }
132 
138  public static void registerActivePopup(@NotNull final CFPythonPopup activePopup) {
140  }
141 
145  @ActionMethod
146  public void newScript() {
147  tabs.add("<>"); // this script has no filename assigned yet
148  view.addTab("<New Script>", null);
149  }
150 
155  public void openScriptFile(@NotNull final File file) {
156  final File absoluteFile = file.getAbsoluteFile();
157  if (!absoluteFile.exists() || !absoluteFile.isFile()) {
158  if (LOG.isInfoEnabled()) {
159  LOG.info("Error in ScriptEditControl.openScriptFile():");
160  LOG.info(" File '" + absoluteFile + "' doesn't exist.");
161  }
162  return;
163  }
164 
165  final String editor = appPreferencesModel.getEditor();
166  if (editor.isEmpty()) {
167  tabs.add(absoluteFile.getPath());
168  view.addTab(absoluteFile.getName(), file);
169  } else {
170  try {
171  final List<String> command = AppPreferencesModel.splitCommand(editor);
172  command.add(file.toString());
173  final ProcessBuilder builder = new ProcessBuilder(command);
174  builder.directory(absoluteFile.getParentFile());
175  builder.redirectError(Redirect.PIPE);
176  builder.redirectOutput(Redirect.PIPE);
177  builder.redirectInput(Redirect.PIPE);
178  final Process process = builder.start();
179  try {
180  process.getOutputStream().close();
181  } catch (final IOException ignored) {
182  }
183  try {
184  process.getInputStream().close();
185  } catch (final IOException ignored) {
186  }
187  final Runnable runnable = () -> {
188  final StringBuilder sb = new StringBuilder();
189  try {
190  try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream(), Charset.defaultCharset()))) {
191  final char[] tmp = new char[1024];
192  while (true) {
193  if (reader.ready()) {
194  final int ch = reader.read();
195  if (ch == -1 || ch == '\n' || ch == '\r') {
196  break;
197  }
198 
199  sb.append((char) ch);
200  }
201  }
202  }
203  } catch (final IOException ignored) {
204  }
205  final int exitCode;
206  try {
207  if (process.waitFor() != 0) {
208  if (sb.length() == 0) {
209  sb.append("(no output)");
210  }
211  callback.statusMessage("Editor failed: " + sb);
212  }
213  } catch (final InterruptedException ignored) {
214  Thread.currentThread().interrupt();
215  process.destroy();
216  }
217  };
218  final Thread thread = new Thread(runnable, "failed editor checker");
219  thread.setDaemon(true);
220  thread.start();
221  } catch (final IOException ex) {
222  callback.statusMessage("Cannot start editor: " + ex);
223  }
224  }
225  }
226 
233  @NotNull
234  private JFileChooser createOpenFileChooser(@NotNull final File defaultScriptDir) {
235  final JFileChooser fileChooser = new JFileChooser();
236  fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
237  fileChooser.setMultiSelectionEnabled(false);
238  fileChooser.setFileFilter(scriptFileFilter);
239 
240  // set default folder for new scripts
241  if (defaultScriptDir.exists() && defaultScriptDir.isDirectory()) {
242  FileChooserUtils.setCurrentDirectory(fileChooser, defaultScriptDir);
243  } else {
245  }
246  return fileChooser;
247  }
248 
252  public void openUser() {
253  openFileChooser.setDialogTitle("Open Script File");
254 
256  final int returnVal = openFileChooser.showOpenDialog(view);
257  if (returnVal != JFileChooser.APPROVE_OPTION) {
258  return;
259  }
260 
261  final File file = openFileChooser.getSelectedFile();
262  if (file.exists() && !file.isDirectory()) {
263  // everything okay do far, now open up that script file
264  openScriptFile(file);
265  } else {
266  // user entered a filename which doesn't yet exist -> open new script
267  newScript();
268  }
269  }
270 
276  public boolean closeActiveTab() {
277  final String title = view.getActiveTitle();
278  if (title == null) {
279  return true;
280  }
281 
282  final JEditTextArea activeTextArea = getActiveTextArea();
283  if (activeTextArea != null && activeTextArea.isModified() && !ACTION_BUILDER.showQuestionDialog(view, "scriptEdit.confirmClose", title)) {
284  return false;
285  }
286 
287  if (view.getSelectedIndex() >= 0 && !tabs.isEmpty()) {
288  tabs.remove(view.getSelectedIndex()); // dump the filename
289  }
290  view.closeActiveTab(); // close tab in the view
291 
292  // hide view when last tab has been closed
293  if (view.getTabCount() <= 0) {
294  if (activePopup != null && (activePopup.isShowing() || activePopup.getMenu().isShowing())) {
295  activePopup.getMenu().setVisible(false);
296  }
297  view.setVisible(false);
298  }
299  return true;
300  }
301 
307  public boolean closeAllTabs() {
308  // simply keep closing active tabs till none are left
309  while (view.getSelectedIndex() >= 0 || !tabs.isEmpty()) {
310  if (!closeActiveTab()) {
311  return false;
312  }
313  }
314  return true;
315  }
316 
321  public void saveAsActiveTab() {
322  final String activePath = getActiveFilePath(); // active file path ('null' if undefined)
323  final JEditTextArea activeTextArea = getActiveTextArea();
324  if (activeTextArea == null) {
325  return;
326  }
327  final String text = activeTextArea.getText(); // Store text data to ensure that the right text is saved later.
328  // User could switch tabs or type text in the meantime, who knows.
329  final int tabIndex = view.getSelectedIndex(); // save tab-index of this script
330 
331  openFileChooser.setDialogTitle("Save Script File As");
332 
333  // if file already exists, select it
334  if (activePath != null) {
335  final File file = new File(activePath);
336 
337  if (file.getParentFile().exists() && file.getParentFile().isDirectory()) {
338  openFileChooser.setCurrentDirectory(file.getParentFile());
339  openFileChooser.setSelectedFile(file); // select this name
340  }
341  }
342 
343  final int returnVal = openFileChooser.showSaveDialog(view);
344  if (returnVal != JFileChooser.APPROVE_OPTION) {
345  return;
346  }
347 
348  File file = openFileChooser.getSelectedFile();
349  if (!file.getName().endsWith(scriptSuffix)) {
350  final String fileName = file.getAbsolutePath();
351  file = new File(fileName + scriptSuffix);
352  }
353 
354  // now it is our duty to double check if user attempts to overwrite
355  if (!file.exists() || (activePath != null && file.getAbsolutePath().equals(activePath)) || view.askConfirm("Overwrite?", "A file named \"" + file.getName() + "\" already exists.\n" + "Are you sure you want to overwrite it?")) {
356  // looks like we can finally save the data
357  if (saveTextToFile(file, text)) {
358  activeTextArea.resetModified();
359  }
360 
361  // now update the file path for this open script tab
362  if (tabIndex >= 0 && tabs.size() > tabIndex) {
363  // it might be nice here to check the content of the document at 'index',
364  // to make sure it stayed the same
365  //String path = (String)(tabs.elementAt(tabIndex));
366 
367  // set new path
368  tabs.set(tabIndex, file.getAbsolutePath());
369  view.setTitleAt(tabIndex, file.getName());
370  }
371  }
372  }
373 
377  public void saveActiveTab() {
378  if (getActiveFilePath() == null) {
379  LOG.error("ScriptEditControl.saveActiveTab(): Cannot save file without name!");
380  // Path is missing? This shouldn't happen, but let's do a saveAs instead...
381  saveAsActiveTab();
382  } else {
383  final File file = new File(getActiveFilePath()); // get active path
384  final JEditTextArea activeTextArea = getActiveTextArea();
385  if (activeTextArea != null && saveTextToFile(file, activeTextArea.getText())) {
386  activeTextArea.resetModified();
387  }
388  }
389  }
390 
398  private boolean saveTextToFile(@NotNull final File file, @NotNull final String text) {
399  try {
400  try (FileOutputStream fos = new FileOutputStream(file)) {
401  try (Writer osw = new OutputStreamWriter(fos)) {
402  osw.write(text);
403  }
404  }
405  } catch (final IOException e) {
406  // tell the user because it is important to know that saving failed
407  view.showMessage("Write Error", "The file \"" + file.getName() + "\" could not be written.\nPlease use the 'Save As...' menu.", JOptionPane.ERROR_MESSAGE);
408  return false;
409  }
410 
411  return true;
412  }
413 
417  @Nullable
419  return view.getActiveTextArea();
420  }
421 
425  @Nullable
426  String getActiveFilePath() {
427  if (view.getSelectedIndex() < 0 || tabs.size() <= 0) {
428  return null;
429  }
430 
431  // get stored path
432  final String path = tabs.get(view.getSelectedIndex());
433 
434  return path != null && !path.isEmpty() && !path.equals("<>") ? path : null;
435  }
436 
437  @Override
438  public void setAction(@NotNull final Action action, @NotNull final String name) {
439  }
440 
441 }
name
name
Definition: ArchetypeTypeSetParserTest-ignoreDefaultAttribute1-result.txt:2
net.sf.gridarta.textedit.textarea.TextAreaDefaults
Encapsulates default settings for a text area.
Definition: TextAreaDefaults.java:26
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.getActiveFilePath
String getActiveFilePath()
Definition: ScriptEditControl.java:426
net.sf.gridarta.utils.AppPreferencesModel
Maintains the application preferences state.
Definition: AppPreferencesModel.java:32
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.saveActiveTab
void saveActiveTab()
Save the active script-tab to the stored file path.
Definition: ScriptEditControl.java:377
net.sf.gridarta.textedit.textarea.JEditTextArea
jEdit's text area component.
Definition: JEditTextArea.java:91
net.sf.gridarta.textedit.scripteditor.ScriptEditView.showMessage
void showMessage(@NotNull final String title, @NotNull final String message, final int messageType)
Shows the given message in the UI.
Definition: ScriptEditView.java:341
net.sf.gridarta.textedit.scripteditor.ScriptEditView
The script editor frame.
Definition: ScriptEditView.java:59
net.sf.gridarta.textedit.textarea
This package contains the other part of the script editor.
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.closeAllTabs
boolean closeAllTabs()
Close all opened script-tabs.
Definition: ScriptEditControl.java:307
net.sf.gridarta
Base package of all Gridarta classes.
net.sf.gridarta.utils.FileChooserUtils.setCurrentDirectory
static void setCurrentDirectory(@NotNull final JFileChooser fileChooser, @Nullable final File dir)
Calls JFileChooser#setCurrentDirectory(File).
Definition: FileChooserUtils.java:48
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.view
final ScriptEditView view
Definition: ScriptEditControl.java:82
net.sf.gridarta.textedit.scripteditor.ScriptEditView.getActiveTitle
String getActiveTitle()
Returns the title of the active tab.
Definition: ScriptEditView.java:318
net.sf.gridarta.textedit.scripteditor.ScriptEditView.setTitleAt
void setTitleAt(final int index, @NotNull final String title)
Sets the title of the tab at specified index.
Definition: ScriptEditView.java:309
net.sf
net.sf.gridarta.textedit.textarea.JEditTextArea.isModified
boolean isModified()
Return whether the text content has been modified from the "unmodified" state.
Definition: JEditTextArea.java:955
net.sf.gridarta.utils.FileChooserUtils.sanitizeCurrentDirectory
static void sanitizeCurrentDirectory(@NotNull final JFileChooser fileChooser)
Makes sure the current directory of a JFileChooser is valid.
Definition: FileChooserUtils.java:56
net.sf.gridarta.textedit.textarea.JEditTextArea.getText
String getText()
Returns the entire text of this text area.
Definition: JEditTextArea.java:543
net.sf.gridarta.textedit.scripteditor.ScriptEditView.getActiveTextArea
JEditTextArea getActiveTextArea()
Definition: ScriptEditView.java:286
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.tabs
final List< String > tabs
Definition: ScriptEditControl.java:85
net.sf.gridarta.utils.AppPreferencesModel.splitCommand
static List< String > splitCommand(@NotNull final String command)
Splits a command string into an array.
Definition: AppPreferencesModel.java:145
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.getActiveTextArea
JEditTextArea getActiveTextArea()
Definition: ScriptEditControl.java:418
net.sf.gridarta.textedit.scripteditor.ScriptEditControlCallback.statusMessage
void statusMessage(@NotNull String message)
A message that should be reported to the user.
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.callback
final ScriptEditControlCallback callback
The callback function for reporting problems when starting an external editor.
Definition: ScriptEditControl.java:110
net.sf.gridarta.textedit
net.sf.gridarta.textedit.textarea.JEditTextArea.resetModified
void resetModified()
Reset the "modified" state.
Definition: JEditTextArea.java:962
net.sf.gridarta.textedit.scripteditor.ScriptEditView.setTextAreaDefaults
void setTextAreaDefaults(@NotNull final TextAreaDefaults textAreaDefaults)
Definition: ScriptEditView.java:184
net
net.sf.gridarta.textedit.scripteditor.ScriptEditControlCallback
Callback functions to be implemented by users of ScriptEditControl.
Definition: ScriptEditControlCallback.java:27
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.openUser
void openUser()
Open a file which is chosen by the user.
Definition: ScriptEditControl.java:252
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.setTextAreaDefaults
void setTextAreaDefaults(@NotNull final TextAreaDefaults textAreaDefaults)
Definition: ScriptEditControl.java:129
net.sf.gridarta.textedit.scripteditor.ScriptEditView.addTab
void addTab(@NotNull final String title, @Nullable final File file)
Adds a new TextArea Panel to the TabbedPane.
Definition: ScriptEditView.java:193
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.createOpenFileChooser
JFileChooser createOpenFileChooser(@NotNull final File defaultScriptDir)
Creates the JFileChooser for opening a script file.
Definition: ScriptEditControl.java:234
net.sf.gridarta.textedit.scripteditor.CFPythonPopup
This class implements a popup window which shows all python methods in the 'CFPython' package.
Definition: CFPythonPopup.java:44
net.sf.gridarta.textedit.scripteditor.ScriptEditView.getTabCount
int getTabCount()
Definition: ScriptEditView.java:300
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.saveAsActiveTab
void saveAsActiveTab()
Open a file browser and prompt the user for a location/name to store this file.
Definition: ScriptEditControl.java:321
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.openScriptFile
void openScriptFile(@NotNull final File file)
Opens a script file.
Definition: ScriptEditControl.java:155
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.openFileChooser
final JFileChooser openFileChooser
JFileChooser for opening script files.
Definition: ScriptEditControl.java:91
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.ScriptEditControl
ScriptEditControl(@NotNull final FileFilter scriptFileFilter, @NotNull final String scriptSuffix, @NotNull final Frame owner, final File defaultScriptDir, @NotNull final Preferences preferences, @NotNull final Exiter exiter, @NotNull final AppPreferencesModel appPreferencesModel, @NotNull final ScriptEditControlCallback callback)
Definition: ScriptEditControl.java:118
net.sf.gridarta.textedit.scripteditor.ScriptEditControl
ScriptEditControl - Manages events and data flow for the script editor entity.
Definition: ScriptEditControl.java:59
net.sf.gridarta.utils.AppPreferencesModel.getEditor
String getEditor()
Returns the editor setting.
Definition: AppPreferencesModel.java:135
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.activePopup
static CFPythonPopup activePopup
The currently active popup.
Definition: ScriptEditControl.java:79
net.sf.gridarta.textedit.scripteditor.ScriptEditView.askConfirm
boolean askConfirm(@NotNull final String title, @NotNull final String message)
Shows the given confirmation message as popup frame.
Definition: ScriptEditView.java:330
net.sf.gridarta.utils.FileChooserUtils
Utility class for JFileChooser related functions.
Definition: FileChooserUtils.java:31
editor
Daimonin Editor like your to identify any bugs in the editor
Definition: ReadMe.txt:5
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.ACTION_BUILDER
static final ActionBuilder ACTION_BUILDER
Action Builder.
Definition: ScriptEditControl.java:71
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.setAction
void setAction(@NotNull final Action action, @NotNull final String name)
Sets the Action instance for this editor action.
Definition: ScriptEditControl.java:438
net.sf.gridarta.textedit.scripteditor.CFPythonPopup.getMenu
JPopupMenu getMenu()
Definition: CFPythonPopup.java:118
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.scriptSuffix
final String scriptSuffix
Definition: ScriptEditControl.java:97
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.scriptFileFilter
final FileFilter scriptFileFilter
Definition: ScriptEditControl.java:94
net.sf.gridarta.utils.EditorAction
A global editor action.
Definition: EditorAction.java:29
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.appPreferencesModel
final AppPreferencesModel appPreferencesModel
The global AppPreferencesModel instance.
Definition: ScriptEditControl.java:103
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.LOG
static final Category LOG
The Logger for printing log messages.
Definition: ScriptEditControl.java:65
net.sf.gridarta.textedit.scripteditor.ScriptEditView.closeActiveTab
void closeActiveTab()
Closes the active script-tab.
Definition: ScriptEditView.java:267
net.sf.gridarta.utils
Definition: ActionBuilderUtils.java:20
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.newScript
void newScript()
Open a new empty script document.
Definition: ScriptEditControl.java:146
net.sf.gridarta.textedit.scripteditor.ScriptEditView.getSelectedIndex
int getSelectedIndex()
Definition: ScriptEditView.java:293
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.saveTextToFile
boolean saveTextToFile(@NotNull final File file, @NotNull final String text)
Write the given text into the specified file.
Definition: ScriptEditControl.java:398
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.registerActivePopup
static void registerActivePopup(@NotNull final CFPythonPopup activePopup)
Register last active popup.
Definition: ScriptEditControl.java:138
net.sf.gridarta.utils.Exiter
Exits the application.
Definition: Exiter.java:28
net.sf.gridarta.textedit.scripteditor.ScriptEditControl.closeActiveTab
boolean closeActiveTab()
Close the active script-tab.
Definition: ScriptEditControl.java:276