001/* 002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games. 003 * Copyright (C) 2000-2011 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.gui.map.maptilepane; 021 022import java.awt.Component; 023import java.awt.GridBagConstraints; 024import java.awt.GridBagLayout; 025import java.awt.Insets; 026import java.awt.event.ActionEvent; 027import java.awt.event.ActionListener; 028import java.io.File; 029import java.io.IOException; 030import javax.swing.AbstractButton; 031import javax.swing.Action; 032import javax.swing.JButton; 033import javax.swing.JFileChooser; 034import javax.swing.JPanel; 035import javax.swing.JTextField; 036import javax.swing.filechooser.FileFilter; 037import net.sf.gridarta.model.io.PathManager; 038import net.sf.gridarta.utils.ActionBuilderUtils; 039import net.sf.gridarta.utils.FileChooserUtils; 040import net.sf.japi.swing.action.ActionBuilder; 041import net.sf.japi.swing.action.ActionBuilderFactory; 042import net.sf.japi.swing.action.ActionMethod; 043import org.jetbrains.annotations.NotNull; 044import org.jetbrains.annotations.Nullable; 045 046/** 047 * A tile panel displays exactly one direction for map tiling. It's basically an 048 * extended text field with some additional buttons to make usage easier for 049 * users. 050 * @author unknown 051 */ 052public class TilePanel extends JPanel { 053 054 /** 055 * The serial version UID. 056 */ 057 private static final long serialVersionUID = 1L; 058 059 /** 060 * The {@link ActionBuilder}. 061 */ 062 @NotNull 063 private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta"); 064 065 /** 066 * The {@link FileFilter} to use. 067 */ 068 @NotNull 069 private final FileFilter fileFilter; 070 071 /** 072 * The original value. 073 */ 074 @NotNull 075 private final String original; 076 077 /** 078 * The context reference for relative paths. 079 */ 080 @Nullable 081 private final File relativeReference; 082 083 /** 084 * The context reference for absolute paths. 085 */ 086 @NotNull 087 private final File absoluteReference; 088 089 /** 090 * The {@link JTextField} with the text. 091 */ 092 @NotNull 093 private final JTextField textField; 094 095 /** 096 * The {@link TilePanel.RASwitch}. 097 */ 098 @NotNull 099 private final RASwitch raSwitch; 100 101 /** 102 * Creates a new instance. 103 * @param fileFilter the file filter to use 104 * @param original the original value, used as initial value and for 105 * restoring it 106 * @param relativeReference file to reference for relative paths (context 107 * file or directory, not parent directory) 108 * @param absoluteReference file to reference for absolute paths (context 109 * file or directory, not parent directory) 110 */ 111 public TilePanel(@NotNull final FileFilter fileFilter, @NotNull final String original, @Nullable final File relativeReference, @NotNull final File absoluteReference) { 112 super(new GridBagLayout()); 113 this.fileFilter = fileFilter; 114 this.original = original; 115 this.relativeReference = relativeReference == null ? null : getAbsolutePath(relativeReference); 116 this.absoluteReference = getAbsolutePath(absoluteReference); 117 final GridBagConstraints gbc = new GridBagConstraints(); 118 textField = new JTextField(); 119 textField.setColumns(16); 120 121 gbc.fill = GridBagConstraints.NONE; 122 add(iconButton(ACTION_BUILDER.createAction(false, "mapTileRevert", this)), gbc); 123 add(iconButton(ACTION_BUILDER.createAction(false, "mapTileClear", this)), gbc); 124 gbc.fill = GridBagConstraints.HORIZONTAL; 125 gbc.weightx = 1.0; 126 add(textField, gbc); 127 gbc.weightx = 0.0; 128 gbc.fill = GridBagConstraints.NONE; 129 raSwitch = new RASwitch(); 130 add(raSwitch, gbc); 131 add(iconButton(ACTION_BUILDER.createAction(false, "mapTileChoose", this)), gbc); 132 133 textField.setText(original); 134 raSwitch.updateRAState(); 135 } 136 137 /** 138 * Returns the absolute path of a {@link File}. 139 * @param file the file 140 * @return the absolute path 141 */ 142 @NotNull 143 private static File getAbsolutePath(@NotNull final File file) { 144 try { 145 return file.getCanonicalFile(); 146 } catch (final IOException ignored) { 147 return file.getAbsoluteFile(); 148 } 149 } 150 151 /** 152 * Creates a new button for reverting a path entry. 153 * @param action the action for the button 154 * @return the button 155 */ 156 @NotNull 157 private static Component iconButton(@NotNull final Action action) { 158 final AbstractButton button = new JButton(action); 159 button.setMargin(new Insets(0, 0, 0, 0)); 160 return button; 161 } 162 163 /** 164 * Action method for reverting to stored path. 165 */ 166 @ActionMethod 167 public void mapTileRevert() { 168 setText(original, false); 169 } 170 171 /** 172 * Action method for deleting the path. 173 */ 174 @ActionMethod 175 public void mapTileClear() { 176 setText("", true); 177 } 178 179 /** 180 * Action method for choosing the path. 181 */ 182 @ActionMethod 183 public void mapTileChoose() { 184 final File tmpRelativeReference = relativeReference; 185 final JFileChooser chooser = tmpRelativeReference == null ? new JFileChooser() : new JFileChooser(tmpRelativeReference.getParentFile()); 186 final String oldFilename = getText(); 187 if (!oldFilename.isEmpty()) { 188 // Point the chooser on the current path tile file 189 final File oldFile; 190 if (tmpRelativeReference == null || oldFilename.startsWith("/")) { 191 oldFile = new File(absoluteReference, oldFilename.substring(1)); 192 } else { 193 oldFile = new File(tmpRelativeReference.getParentFile(), oldFilename); 194 } 195 FileChooserUtils.setCurrentDirectory(chooser, oldFile.getParentFile()); 196 chooser.setSelectedFile(oldFile); 197 } 198 chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); 199 chooser.setMultiSelectionEnabled(false); 200 chooser.setFileFilter(fileFilter); 201 FileChooserUtils.sanitizeCurrentDirectory(chooser); 202 final int returnVal = chooser.showOpenDialog(this); 203 if (returnVal == JFileChooser.APPROVE_OPTION) { 204 try { 205 final File selected = chooser.getSelectedFile(); 206 final String relPath = selected.getCanonicalPath().substring(absoluteReference.getCanonicalPath().length()).replace('\\', '/'); 207 setText(relPath, true); 208 } catch (final IOException ex) { 209 setText("Error: " + ex, true); 210 } 211 } 212 } 213 214 /** 215 * Sets the text. 216 * @param text the ext to set 217 * @param keepRA if set, modify <code>text</code> to match the current RA 218 * state 219 */ 220 public void setText(@NotNull final String text, final boolean keepRA) { 221 textField.setText(text); 222 if (keepRA) { 223 raSwitch.actionPerformed(false); 224 } else { 225 raSwitch.updateRAState(); 226 } 227 } 228 229 /** 230 * Returns the text. 231 * @return the text 232 */ 233 @NotNull 234 public String getText() { 235 final String text = textField.getText(); 236 assert text != null; 237 return text; 238 } 239 240 /** 241 * Activates the text input field. 242 */ 243 public void activateTextField() { 244 textField.requestFocusInWindow(); 245 } 246 247 /** 248 * Adds an {@link ActionListener} to the text input field. 249 * @param actionListener the action listener 250 */ 251 public void addTextFieldActionListener(@NotNull final ActionListener actionListener) { 252 textField.addActionListener(actionListener); 253 } 254 255 /** 256 * Updates the state of the {@link #raSwitch}. 257 */ 258 public void updateRAState() { 259 raSwitch.updateRAState(); 260 } 261 262 /** 263 * A JButton that converts relative to absolute paths and vice versa. 264 */ 265 private class RASwitch extends JButton implements ActionListener { 266 267 /** 268 * The serial version UID. 269 */ 270 private static final long serialVersionUID = 1L; 271 272 /** 273 * Whether the current mode is relative. 274 */ 275 private boolean isRelative; 276 277 /** 278 * Creates a new instance. 279 */ 280 private RASwitch() { 281 setMargin(new Insets(0, 0, 0, 0)); 282 setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "mapTilePathMode.shortdescription")); 283 updateText(); 284 addActionListener(this); 285 setPreferredSize(getMinimumSize()); 286 } 287 288 @Override 289 public void actionPerformed(@NotNull final ActionEvent e) { 290 actionPerformed(true); 291 } 292 293 public void actionPerformed(final boolean toggleRelative) { 294 final String path = TilePanel.this.getText(); 295 final File tmpRelativeReference = relativeReference; 296 if (path.isEmpty() || tmpRelativeReference == null) { 297 return; 298 } 299 if (toggleRelative) { 300 isRelative = !isRelative; 301 updateText(); 302 } 303 final String relRef = new StringBuilder().append('/').append(PathManager.absoluteToRelative(absoluteReference.getPath() + '/', tmpRelativeReference.getPath())).toString(); 304 if (isRelative) { 305 textField.setText(PathManager.absoluteToRelative(relRef, path)); 306 } else { 307 textField.setText(PathManager.relativeToAbsolute(relRef, path)); 308 } 309 } 310 311 /** 312 * Updates the state of the button. 313 */ 314 public void updateRAState() { 315 final String path = getText(); 316 isRelative = PathManager.isRelative(path); 317 updateText(); 318 } 319 320 /** 321 * Updates the button text to reflect the current state. 322 */ 323 private void updateText() { 324 setText(isRelative ? "R" : "A"); 325 } 326 327 } 328 329}