1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.oracle.javafx.scenebuilder.app.template; 33 34 import com.oracle.javafx.scenebuilder.app.DocumentWindowController; 35 import com.oracle.javafx.scenebuilder.app.SceneBuilderApp; 36 import com.oracle.javafx.scenebuilder.app.SceneBuilderApp.ApplicationControlAction; 37 import com.oracle.javafx.scenebuilder.app.i18n.I18N; 38 import com.oracle.javafx.scenebuilder.kit.editor.EditorController; 39 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.AbstractModalDialog; 40 import com.oracle.javafx.scenebuilder.kit.editor.panel.util.dialog.ErrorDialog; 41 42 import java.io.File; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.nio.file.CopyOption; 46 import java.nio.file.Files; 47 import java.nio.file.Path; 48 import java.nio.file.Paths; 49 import java.nio.file.StandardCopyOption; 50 import java.nio.file.attribute.FileAttribute; 51 import java.text.MessageFormat; 52 53 import javafx.beans.InvalidationListener; 54 import javafx.event.ActionEvent; 55 import javafx.fxml.FXML; 56 import javafx.scene.control.Button; 57 import javafx.scene.control.Label; 58 import javafx.scene.control.TextField; 59 import javafx.stage.DirectoryChooser; 60 61 public class TemplateDialogController extends AbstractModalDialog { 62 63 @FXML // fx:id="chooseButton" 64 private Button chooseButton; // Value injected by FXMLLoader 65 @FXML // fx:id="detailsLabel" 66 private Label detailsLabel; // Value injected by FXMLLoader 67 @FXML // fx:id="messageLabel" 68 private Label messageLabel; // Value injected by FXMLLoader 69 @FXML // fx:id="locationTextField" 70 private TextField locationTextField; // Value injected by FXMLLoader 71 @FXML // fx:id="nameTextField" 72 private TextField nameTextField; // Value injected by FXMLLoader 73 // private final Template template; 74 private final ApplicationControlAction template; 75 76 public TemplateDialogController(ApplicationControlAction template) { 77 super(TemplateDialogController.class.getResource("TemplateDialog.fxml"), //NOI18N 78 I18N.getBundle(), null); 79 this.template = template; 80 } 81 82 /* 83 * AbstractModalDialog 84 */ 85 @Override 86 protected void controllerDidLoadFxml() { 87 super.controllerDidLoadFxml(); 88 setActionButtonVisible(false); 89 setDefaultButtonID(AbstractModalDialog.ButtonID.OK); 90 setShowDefaultButton(true); 91 // Update title 92 final String title = MessageFormat.format( 93 I18N.getString("template.title.new.project"), 94 FxmlTemplates.getTemplateName(template)); 95 getStage().setTitle(title); 96 } 97 98 @Override 99 protected void controllerDidLoadContentFxml() { 100 101 nameTextField.textProperty().addListener((InvalidationListener) observable -> { 102 // Update details section 103 updateDetails(); 104 // Update OK button 105 updateOkButtonState(); 106 107 }); 108 locationTextField.textProperty().addListener((InvalidationListener) observable -> { 109 // Update details section 110 updateDetails(); 111 // Update OK button 112 updateOkButtonState(); 113 }); 114 115 // Update name text field 116 nameTextField.setText(FxmlTemplates.getTemplateName(template)); 117 118 // Update location text field 119 final File initialDirectory = EditorController.getNextInitialDirectory(); 120 if (initialDirectory != null) { 121 locationTextField.setText(initialDirectory.getAbsolutePath()); 122 } else { 123 locationTextField.setText(System.getProperty("user.home")); //NOI18N 124 } 125 } 126 127 public File getNewProjectDirectory() { 128 final String location = locationTextField.getText().trim(); 129 final String name = nameTextField.getText().trim(); 130 return new File(location, name); 131 } 132 133 @FXML 134 public void chooseButtonPressed(ActionEvent e) { 135 final DirectoryChooser directoryChooser = new DirectoryChooser(); 136 final File selectedDir = directoryChooser.showDialog(getStage().getOwner()); 137 // Directory is null when pressing cancel button 138 if (selectedDir != null) { 139 locationTextField.setText(selectedDir.getAbsolutePath()); 140 } 141 } 142 143 public void locationTextFieldOnAction(ActionEvent e) { 144 locationTextField.selectAll(); 145 } 146 147 public void nameTextFieldOnAction(ActionEvent e) { 148 nameTextField.selectAll(); 149 } 150 151 @Override 152 protected void okButtonPressed(ActionEvent e) { 153 final File newProjectDirectory = getNewProjectDirectory(); 154 // OK button is enabled => directory creation should succeed 155 assert newProjectDirectory.mkdir(); 156 157 try { 158 // Create template directory 159 Files.createDirectories(newProjectDirectory.toPath(), 160 new FileAttribute<?>[]{}); 161 // Create FXML and resource files 162 if (createTemplateFiles(newProjectDirectory)) { 163 final String fxmlFileName = FxmlTemplates.getTemplateFileName(template); 164 final File fxmlFile = new File(newProjectDirectory, fxmlFileName); 165 166 final DocumentWindowController newTemplateWindow 167 = SceneBuilderApp.getSingleton().makeNewWindow(); 168 newTemplateWindow.loadFromFile(fxmlFile); 169 newTemplateWindow.openWindow(); 170 } 171 } catch (IOException ex) { 172 final ErrorDialog errorDialog = new ErrorDialog(null); 173 errorDialog.setMessage(I18N.getString("alert.open.failure1.message", getStage().getTitle())); 174 errorDialog.setDetails(I18N.getString("alert.open.failure1.details")); 175 errorDialog.setDebugInfoWithThrowable(ex); 176 errorDialog.setTitle(I18N.getString("alert.title.open")); 177 errorDialog.showAndWait(); 178 } 179 180 EditorController.updateNextInitialDirectory(newProjectDirectory); 181 closeWindow(); 182 } 183 184 @Override 185 protected void cancelButtonPressed(ActionEvent e) { 186 closeWindow(); 187 } 188 189 @Override 190 protected void actionButtonPressed(ActionEvent e) { 191 // Should not be called because button is hidden 192 throw new IllegalStateException(); 193 } 194 195 private boolean createTemplateFiles(final File newProjectDirectory) { 196 197 assert newProjectDirectory.exists(); 198 final String fxmlFileName = FxmlTemplates.getTemplateFileName(template); 199 200 // Copy FXML file 201 final InputStream fromFxmlFile = TemplateDialogController.class.getResourceAsStream(fxmlFileName); 202 final File toFxmlFile = new File(newProjectDirectory, fxmlFileName); 203 if (!copyFile(fromFxmlFile, toFxmlFile, StandardCopyOption.REPLACE_EXISTING)) { 204 return false; 205 } 206 207 // Copy resource files 208 for (String resourceFileName : FxmlTemplates.getResourceFileNames(template)) { 209 final InputStream fromResourceFile = TemplateDialogController.class.getResourceAsStream(resourceFileName); 210 final File toResourceFile = new File(newProjectDirectory, resourceFileName); 211 if (!copyFile(fromResourceFile, toResourceFile, StandardCopyOption.REPLACE_EXISTING)) { 212 return false; 213 } 214 } 215 return true; 216 } 217 218 /** 219 * The OK button should be enabled according the validity of the user 220 * inputs. 221 */ 222 private void updateOkButtonState() { 223 final String location = locationTextField.getText().trim(); 224 final File newProjectDirectory = getNewProjectDirectory(); 225 boolean disabled = false; 226 227 // User inputs are declared *valid* if: 228 // 1) "Location" matches an existing folder file 229 // 2) "Location" / "Name" does not match any existing file 230 // 3) "Name" is a valid file name (i.e. it does not contain any '/' e.g.) 231 if (!new File(location).exists()) { 232 messageLabel.setText(MessageFormat.format( 233 I18N.getString("template.location.does.not.exist"), 234 location)); 235 disabled = true; 236 } else if (newProjectDirectory.exists()) { 237 messageLabel.setText(MessageFormat.format( 238 I18N.getString("template.name.already.exists"), 239 newProjectDirectory.getName())); 240 disabled = true; 241 } else if (!isValidFileName(newProjectDirectory)) { 242 messageLabel.setText(MessageFormat.format( 243 I18N.getString("template.cannot.create"), 244 newProjectDirectory.getAbsolutePath())); 245 disabled = true; 246 } 247 if (disabled) { 248 messageLabel.setVisible(true); 249 } else { 250 messageLabel.setVisible(false); 251 } 252 setOKButtonDisable(disabled); 253 } 254 255 private boolean isValidFileName(final File file) { 256 boolean isValid = true; 257 try { 258 if (file.mkdir()) { 259 // Code below to please findbugs 260 if (file.delete() == false) { 261 isValid = false; 262 } 263 } else { 264 isValid = false; 265 } 266 } catch (RuntimeException e) { 267 isValid = false; 268 } 269 return isValid; 270 } 271 272 private void updateDetails() { 273 final String location = locationTextField.getText().trim(); 274 final String name = nameTextField.getText().trim(); 275 final String path = location + File.separator + name + File.separator; 276 final String fxmlFileName = FxmlTemplates.getTemplateFileName(template); 277 final StringBuilder sb = new StringBuilder(); 278 // List fxml file 279 sb.append(path); 280 sb.append(fxmlFileName); 281 // List resource files 282 for (String resourceFileName : FxmlTemplates.getResourceFileNames(template)) { 283 sb.append("\n"); //NOI18N 284 sb.append(path); 285 sb.append(resourceFileName); 286 } 287 detailsLabel.setText(sb.toString()); 288 } 289 290 private boolean copyFile(final InputStream in, final File toFile, final CopyOption... options) { 291 try { 292 final Path target = Paths.get(toFile.toURI()); 293 Files.copy(in, target, options); 294 } catch (IOException ioe) { 295 final ErrorDialog errorDialog = new ErrorDialog(null); 296 errorDialog.setMessage(I18N.getString("alert.copy.failure.message", getStage().getTitle())); 297 errorDialog.setDebugInfoWithThrowable(ioe); 298 errorDialog.setTitle(I18N.getString("alert.title.copy")); 299 errorDialog.showAndWait(); 300 return false; 301 } 302 return true; 303 } 304 }