001/* 002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games. 003 * Copyright (C) 2000-2010 The Gridarta Developers. 004 * 005 * This program is free software; you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation; either version 2 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License along 016 * with this program; if not, write to the Free Software Foundation, Inc., 017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 018 */ 019 020package net.sf.gridarta.textedit.scripteditor; 021 022import java.awt.Frame; 023import java.awt.Rectangle; 024import java.awt.geom.RectangularShape; 025import java.io.BufferedReader; 026import java.io.File; 027import java.io.FileInputStream; 028import java.io.FileNotFoundException; 029import java.io.IOException; 030import java.io.InputStreamReader; 031import java.util.ArrayList; 032import java.util.List; 033import java.util.prefs.Preferences; 034import javax.swing.JDialog; 035import javax.swing.JOptionPane; 036import javax.swing.JTabbedPane; 037import javax.swing.SwingConstants; 038import javax.swing.event.ChangeEvent; 039import javax.swing.event.ChangeListener; 040import javax.swing.text.BadLocationException; 041import net.sf.gridarta.textedit.textarea.JEditTextArea; 042import net.sf.gridarta.textedit.textarea.SyntaxDocument; 043import net.sf.gridarta.textedit.textarea.TextAreaDefaults; 044import net.sf.gridarta.textedit.textarea.tokenmarker.TokenMarkerFactory; 045import net.sf.gridarta.utils.Exiter; 046import net.sf.gridarta.utils.ExiterListener; 047import net.sf.japi.swing.action.ActionBuilder; 048import net.sf.japi.swing.action.ActionBuilderFactory; 049import org.apache.log4j.Category; 050import org.apache.log4j.Logger; 051import org.jetbrains.annotations.NotNull; 052import org.jetbrains.annotations.Nullable; 053 054/** 055 * The script editor frame. This class should only exist in ScriptEditControl. 056 * No other class should refer to it. 057 * @author <a href="mailto:andi.vogl@gmx.net">Andreas Vogl</a> 058 */ 059public class ScriptEditView extends JDialog { 060 061 /** 062 * The Logger for printing log messages. 063 */ 064 @NotNull 065 private static final Category log = Logger.getLogger(ScriptEditView.class); 066 067 /** 068 * Action Builder. 069 */ 070 @NotNull 071 private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta"); 072 073 /** 074 * The {@link TextAreaDefaults} for tabs. 075 */ 076 @NotNull 077 private TextAreaDefaults textAreaDefaults; 078 079 /** 080 * Serial Version UID. 081 */ 082 private static final long serialVersionUID = 1L; 083 084 /** 085 * The key used to store the editor window x-coordinate in preferences 086 * file. 087 */ 088 @NotNull 089 private static final String WINDOW_X = "ScriptEditWindow.x"; 090 091 /** 092 * The key used to store the editor window y-coordinate in preferences 093 * file. 094 */ 095 @NotNull 096 private static final String WINDOW_Y = "ScriptEditWindow.y"; 097 098 /** 099 * The key used to store the editor window width in preferences file. 100 */ 101 @NotNull 102 private static final String WINDOW_WIDTH = "ScriptEditWindow.width"; 103 104 /** 105 * The key used to store the editor window height in preferences file. 106 */ 107 @NotNull 108 private static final String WINDOW_HEIGHT = "ScriptEditWindow.height"; 109 110 /** 111 * The actions for the script editor. 112 */ 113 @NotNull 114 private final Actions actions; 115 116 /** 117 * The undo related actions for the script editor. 118 */ 119 @NotNull 120 private final ScriptEditUndoActions scriptEditUndoActions; 121 122 @NotNull 123 private final JTabbedPane tabPane; // tab pane 124 125 @NotNull 126 private final List<JEditTextArea> textAreas; // list of 'JEditTextArea' objects, same order as tabs 127 128 /** 129 * Builds a frame but keep it hidden (it is shown when first file is 130 * opened). 131 * @param owner the owner of this view 132 * @param preferences the preferences to use 133 * @param exiter the exiter instance 134 */ 135 public ScriptEditView(@NotNull final ScriptEditControl control, @NotNull final Frame owner, @NotNull final Preferences preferences, @NotNull final Exiter exiter) { 136 super(owner, "Script Pad"); 137 setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); 138 139 textAreas = new ArrayList<JEditTextArea>(); 140 actions = new Actions(control); 141 scriptEditUndoActions = new ScriptEditUndoActions(); 142 setJMenuBar(ACTION_BUILDER.createMenuBar(true, "scriptEditMenu")); 143 144 tabPane = new JTabbedPane(SwingConstants.TOP); // init tab pane 145 tabPane.addChangeListener(new EditTabListener(this)); 146 147 getContentPane().add(tabPane); 148 addWindowListener(new EditWindowListener(control)); // add listener for close box 149 150 // calculate some default values in case there is no settings file 151 final RectangularShape screen = getGraphicsConfiguration().getBounds(); 152 final int width = preferences.getInt(WINDOW_WIDTH, (int) (0.8 * screen.getWidth())); 153 final int height = preferences.getInt(WINDOW_HEIGHT, (int) (0.8 * screen.getHeight())); 154 final int x = preferences.getInt(WINDOW_X, (int) (screen.getX() + (screen.getWidth() - (double) width) / 2.0)); 155 final int y = preferences.getInt(WINDOW_Y, (int) (screen.getY() + (screen.getHeight() - (double) height) / 2.0)); 156 setBounds(x, y, width, height); 157 158 final ExiterListener exiterListener = new ExiterListener() { 159 160 @Override 161 public void preExitNotify() { 162 // ignore 163 } 164 165 @Override 166 public void appExitNotify() { 167 final Rectangle bounds = getBounds(); 168 preferences.putInt(WINDOW_X, bounds.x); 169 preferences.putInt(WINDOW_Y, bounds.y); 170 preferences.putInt(WINDOW_WIDTH, bounds.width); 171 preferences.putInt(WINDOW_HEIGHT, bounds.height); 172 } 173 174 @Override 175 public void waitExitNotify() { 176 // ignore 177 } 178 179 }; 180 exiter.addExiterListener(exiterListener); 181 } 182 183 @Deprecated 184 public void setTextAreaDefaults(@NotNull final TextAreaDefaults textAreaDefaults) { 185 this.textAreaDefaults = textAreaDefaults; 186 } 187 188 /** 189 * Adds a new TextArea Panel to the TabbedPane. 190 * @param title title of this script (filename) 191 * @param file file where this script is stored, null if new script opened 192 */ 193 public void addTab(@NotNull final String title, @Nullable final File file) { 194 final JEditTextArea ta = new JEditTextArea(textAreaDefaults); // open new TextArea 195 //ta.setFont(new Font("Courier New", Font.PLAIN, 12)); 196 final SyntaxDocument syntaxDocument = new SyntaxDocument(); 197 ta.setDocument(syntaxDocument); 198 textAreas.add(ta); 199 ta.getDocument().setTokenMarker(TokenMarkerFactory.createTokenMarker(file)); 200 scriptEditUndoActions.addDocument(syntaxDocument); 201 tabPane.addTab(title, ta); 202 if (getTabCount() <= 1 || !isShowing()) { 203 setVisible(true); 204 } 205 206 scriptEditUndoActions.setCurrentDocument(syntaxDocument); 207 208 tabPane.setSelectedIndex(getTabCount() - 1); 209 210 // very important: There must be a drawing update after showing the frame, to make 211 // sure the graphics context is fully initialized before calling 'setEditingFocus()' 212 //if (isFirstTimeShowing) { 213 update(getGraphics()); 214 //} 215 216 if (file != null && file.exists()) { 217 // print file into this document 218 try { 219 final FileInputStream fis = new FileInputStream(file); 220 try { 221 final InputStreamReader isr = new InputStreamReader(fis); 222 try { 223 final BufferedReader in = new BufferedReader(isr); 224 try { 225 boolean firstLine = true; 226 final StringBuilder buff = new StringBuilder(""); 227 while (true) { 228 final String line = in.readLine(); 229 if (line == null) { 230 break; 231 } 232 if (firstLine) { 233 firstLine = false; 234 } else { 235 buff.append('\n'); 236 } 237 buff.append(line); 238 } 239 ta.getDocument().insertString(0, buff.toString(), null); 240 } finally { 241 in.close(); 242 } 243 } finally { 244 isr.close(); 245 } 246 } finally { 247 fis.close(); 248 } 249 // insert buffer into the document 250 } catch (final FileNotFoundException e) { 251 log.info("addTab(): File '" + file.getName() + "' not found."); 252 } catch (final IOException e) { 253 log.info("addTab(): I/O-Error while reading '" + file.getName() + "'."); 254 } catch (final BadLocationException e) { 255 log.info("addTab(): Bad Location in Document!"); 256 } 257 scriptEditUndoActions.resetUndo(syntaxDocument); 258 } 259 260 ta.setEditingFocus(); // set focus to TextArea in order to respond to key press events 261 //ta.scrollToCaret(); // make sure the window shows caret (top left corner) 262 263 ta.resetModified(); 264 265 toFront(); // bring window to front 266 } 267 268 /** 269 * Closes the active script-tab. 270 */ 271 public void closeActiveTab() { 272 if (textAreas.isEmpty()) { 273 setVisible(false); 274 } else { 275 final int oldIndex = tabPane.getSelectedIndex(); 276 final int newIndex = oldIndex >= tabPane.getTabCount() - 1 ? oldIndex - 1 : oldIndex; 277 tabPane.setSelectedIndex(newIndex); 278 final JEditTextArea textArea = textAreas.get(oldIndex); 279 scriptEditUndoActions.removeDocument(textArea.getDocument()); 280 textAreas.remove(oldIndex); 281 tabPane.remove(oldIndex); 282 actions.refresh(); 283 scriptEditUndoActions.setCurrentDocument(newIndex == -1 ? null : textAreas.get(newIndex).getDocument()); 284 } 285 } 286 287 /** 288 * @return the currently active TextArea (in front) 289 */ 290 @Nullable 291 public JEditTextArea getActiveTextArea() { 292 if (getTabCount() > 0) { 293 return textAreas.get(tabPane.getSelectedIndex()); 294 } 295 296 return null; // no window is open 297 } 298 299 /** 300 * @return the index of the selected tab pane 301 */ 302 public int getSelectedIndex() { 303 return tabPane.getSelectedIndex(); 304 } 305 306 /** 307 * @return the number of open tabs 308 */ 309 public int getTabCount() { 310 return tabPane.getTabCount(); 311 } 312 313 /** 314 * Sets the title of the tab at specified index. 315 * @param index the index of the tab to change title 316 * @param title the new title string 317 */ 318 public void setTitleAt(final int index, @NotNull final String title) { 319 tabPane.setTitleAt(index, title); 320 } 321 322 /** 323 * Returns the title of the active tab. 324 * @return the title of the active tab, or <code>null</code> if no tab 325 * exists 326 */ 327 @Nullable 328 public String getActiveTitle() { 329 return getTabCount() > 0 ? tabPane.getTitleAt(tabPane.getSelectedIndex()) : null; 330 } 331 332 /** 333 * Shows the given confirmation message as popup frame. The message is a 334 * yes/no option. The parent frame is disabled until the user picks an 335 * answer. 336 * @param title the title of the message 337 * @param message the message to be shown 338 * @return true if the user agrees, false if user disagrees 339 */ 340 public boolean askConfirm(@NotNull final String title, @NotNull final String message) { 341 return JOptionPane.showConfirmDialog(this, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE) == JOptionPane.YES_OPTION; 342 } 343 344 /** 345 * Shows the given message in the UI. 346 * @param title the title of the message 347 * @param message the message to be shown 348 * @param messageType type of message (see JOptionPane constants), defines 349 * icon used 350 */ 351 public void showMessage(@NotNull final String title, @NotNull final String message, final int messageType) { 352 JOptionPane.showMessageDialog(this, message, title, messageType); 353 } 354 355 public void showMessage(@NotNull final String title, @NotNull final String message) { 356 JOptionPane.showMessageDialog(this, message, title, JOptionPane.INFORMATION_MESSAGE); 357 } 358 359 /** 360 * Inner class: Listener for ChangeEvents in the tabPane. 361 */ 362 private class EditTabListener implements ChangeListener { 363 364 @NotNull 365 private final ScriptEditView view; // view 366 367 private int index; // index of selected tab 368 369 private EditTabListener(@NotNull final ScriptEditView view) { 370 this.view = view; 371 index = view.getSelectedIndex(); 372 } 373 374 @Override 375 public void stateChanged(@NotNull final ChangeEvent e) { 376 if (index != view.getSelectedIndex()) { 377 index = view.getSelectedIndex(); 378 // active selected tab has changed 379 actions.refresh(); // refresh state of menus 380 scriptEditUndoActions.setCurrentDocument(index == -1 ? null : textAreas.get(index).getDocument()); 381 } 382 } 383 384 } 385 386}