/*******************************************************************************
* This file is part of logisim-evolution.
*
* logisim-evolution is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* logisim-evolution is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with logisim-evolution. If not, see <http://www.gnu.org/licenses/>.
*
* Original code by Carl Burch (http://www.cburch.com), 2011.
* Subsequent modifications by :
* + Haute École Spécialisée Bernoise
* http://www.bfh.ch
* + Haute École du paysage, d'ingénierie et d'architecture de Genève
* http://hepia.hesge.ch/
* + Haute École d'Ingénierie et de Gestion du Canton de Vaud
* http://www.heig-vd.ch/
* The project is currently maintained by :
* + REDS Institute - HEIG-VD
* Yverdon-les-Bains, Switzerland
* http://reds.heig-vd.ch
*******************************************************************************/
package com.cburch.logisim.proj;
import java.awt.Component;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.file.LoadFailedException;
import com.cburch.logisim.file.LoadedLibrary;
import com.cburch.logisim.file.Loader;
import com.cburch.logisim.file.LogisimFile;
import com.cburch.logisim.file.LogisimFileActions;
import com.cburch.logisim.gui.main.Frame;
import com.cburch.logisim.gui.start.SplashScreen;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.tools.Library;
import com.cburch.logisim.tools.LibraryTools;
import com.cburch.logisim.tools.Tool;
import com.cburch.logisim.util.JFileChoosers;
import com.cburch.logisim.util.StringUtil;
public class ProjectActions {
private static String FILE_NAME_FORMAT_ERROR = "FileNameError";
private static String FILE_NAME_KEYWORD_ERROR = "ExistingToolName";
private static class CreateFrame implements Runnable {
private Loader loader;
private Project proj;
private boolean isStartupScreen;
public CreateFrame(Loader loader, Project proj, boolean isStartup) {
this.loader = loader;
this.proj = proj;
this.isStartupScreen = isStartup;
}
public void run() {
try {
Frame frame = createFrame(null, proj);
frame.setVisible(true);
frame.toFront();
frame.getCanvas().requestFocus();
loader.setParent(frame);
if (isStartupScreen) {
proj.setStartupScreen(true);
}
} catch (Exception e) {
Writer result = new StringWriter();
PrintWriter printWriter = new PrintWriter(result);
e.printStackTrace(printWriter);
JOptionPane.showMessageDialog(null, result.toString());
System.exit(-1);
}
}
}
/**
* Returns true if the filename contains valid characters only, that is,
* alphanumeric characters and underscores.
*/
private static boolean checkValidFilename(String filename, Project proj, HashMap<String,String> Errors) {
boolean IsOk = true;
HashSet<String> TempSet = new HashSet<String>();
HashSet<String> ForbiddenNames = new HashSet<String>();
LibraryTools.BuildLibraryList(proj.getLogisimFile(), TempSet);
LibraryTools.BuildToolList(proj.getLogisimFile(), ForbiddenNames);
ForbiddenNames.addAll(TempSet);
Pattern p = Pattern.compile("[^a-z0-9_.]", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(filename);
if (m.find()) {
IsOk = false;
Errors.put(FILE_NAME_FORMAT_ERROR, "InvalidFileFormatError");
}
if (ForbiddenNames.contains(filename.toUpperCase())) {
IsOk = false;
Errors.put(FILE_NAME_KEYWORD_ERROR, "UsedLibraryToolnameError");
}
return IsOk;
}
private static Project completeProject(SplashScreen monitor, Loader loader,
LogisimFile file, boolean isStartup) {
if (monitor != null)
monitor.setProgress(SplashScreen.PROJECT_CREATE);
Project ret = new Project(file);
if (monitor != null)
monitor.setProgress(SplashScreen.FRAME_CREATE);
SwingUtilities.invokeLater(new CreateFrame(loader, ret, isStartup));
updatecircs(file,ret);
return ret;
}
private static LogisimFile createEmptyFile(Loader loader,Project proj) {
InputStream templReader = AppPreferences.getEmptyTemplate()
.createStream();
LogisimFile file;
try {
file = loader.openLogisimFile(templReader);
} catch (Exception t) {
file = LogisimFile.createNew(loader,proj);
file.addCircuit(new Circuit("main", file,proj));
} finally {
try {
templReader.close();
} catch (IOException e) {
}
}
return file;
}
private static Frame createFrame(Project sourceProject, Project newProject) {
if (sourceProject != null) {
Frame frame = sourceProject.getFrame();
if (frame != null) {
frame.savePreferences();
}
}
Frame newFrame = new Frame(newProject);
newProject.setFrame(newFrame);
return newFrame;
}
public static LogisimFile createNewFile(Project baseProject) {
Loader loader = new Loader(baseProject == null ? null
: baseProject.getFrame());
InputStream templReader = AppPreferences.getTemplate().createStream();
LogisimFile file;
try {
file = loader.openLogisimFile(templReader);
} catch (IOException ex) {
displayException(baseProject.getFrame(), ex);
file = createEmptyFile(loader,baseProject);
} catch (LoadFailedException ex) {
if (!ex.isShown()) {
displayException(baseProject.getFrame(), ex);
}
file = createEmptyFile(loader,baseProject);
} finally {
try {
templReader.close();
} catch (IOException e) {
}
}
return file;
}
private static void displayException(Component parent, Exception ex) {
String msg = StringUtil.format(Strings.get("templateOpenError"),
ex.toString());
String ttl = Strings.get("templateOpenErrorTitle");
JOptionPane.showMessageDialog(parent, msg, ttl,
JOptionPane.ERROR_MESSAGE);
}
public static Project doNew(Project baseProject) {
LogisimFile file = createNewFile(baseProject);
Project newProj = new Project(file);
Frame frame = createFrame(baseProject, newProj);
frame.setVisible(true);
frame.getCanvas().requestFocus();
newProj.getLogisimFile().getLoader().setParent(frame);
updatecircs(file,newProj);
return newProj;
}
public static Project doNew(SplashScreen monitor) {
return doNew(monitor, false);
}
public static Project doNew(SplashScreen monitor, boolean isStartupScreen) {
if (monitor != null)
monitor.setProgress(SplashScreen.FILE_CREATE);
Loader loader = new Loader(monitor);
InputStream templReader = AppPreferences.getTemplate().createStream();
LogisimFile file = null;
try {
file = loader.openLogisimFile(templReader);
} catch (IOException ex) {
displayException(monitor, ex);
} catch (LoadFailedException ex) {
displayException(monitor, ex);
} finally {
try {
templReader.close();
} catch (IOException e) {
}
}
if (file == null)
file = createEmptyFile(loader,null);
return completeProject(monitor, loader, file, isStartupScreen);
}
public static void doMerge(Component parent, Project baseProject) {
JFileChooser chooser;
LogisimFile mergelib;
Loader loader = null;
if (baseProject != null) {
Loader oldLoader = baseProject.getLogisimFile().getLoader();
chooser = oldLoader.createChooser();
if (oldLoader.getMainFile() != null) {
chooser.setSelectedFile(oldLoader.getMainFile());
}
} else {
chooser = JFileChoosers.create();
}
chooser.setFileFilter(Loader.LOGISIM_FILTER);
chooser.setDialogTitle(Strings.get("FileMergeItem"));
int returnVal = chooser.showOpenDialog(parent);
if (returnVal != JFileChooser.APPROVE_OPTION)
return;
File selected = chooser.getSelectedFile();
loader = new Loader(baseProject == null ? parent
: baseProject.getFrame());
try {
mergelib = loader.openLogisimFile(selected);
if (mergelib == null)
return;
} catch (LoadFailedException ex) {
if (!ex.isShown()) {
JOptionPane.showMessageDialog(
parent,
StringUtil.format(Strings.get("fileMergeError"),
ex.toString()),
Strings.get("FileMergeErrorItem"),
JOptionPane.ERROR_MESSAGE);
}
return;
}
updatecircs(mergelib,baseProject);
baseProject.doAction(LogisimFileActions.MergeFile(mergelib, baseProject.getLogisimFile()));
}
private static void updatecircs(LogisimFile lib, Project proj) {
for (Circuit circ:lib.getCircuits()) {
circ.SetProject(proj);
}
for (Library libs : lib.getLibraries()) {
if (libs instanceof LoadedLibrary) {
LoadedLibrary test = (LoadedLibrary) libs;
if (test.getBase() instanceof LogisimFile) {
updatecircs((LogisimFile)test.getBase(),proj);
}
}
}
}
public static boolean doOpen(Component parent, Project baseProject) {
JFileChooser chooser;
if (baseProject != null) {
Loader oldLoader = baseProject.getLogisimFile().getLoader();
chooser = oldLoader.createChooser();
if (oldLoader.getMainFile() != null) {
chooser.setSelectedFile(oldLoader.getMainFile());
}
} else {
chooser = JFileChoosers.create();
}
chooser.setFileFilter(Loader.LOGISIM_FILTER);
chooser.setDialogTitle(Strings.get("FileOpenItem"));
int returnVal = chooser.showOpenDialog(parent);
if (returnVal != JFileChooser.APPROVE_OPTION)
return false;
File selected = chooser.getSelectedFile();
if (selected != null) {
doOpen(parent, baseProject, selected);
}
return true;
}
public static Project doOpen(Component parent, Project baseProject, File f) {
Project proj = Projects.findProjectFor(f);
Loader loader = null;
if (proj != null) {
proj.getFrame().toFront();
loader = proj.getLogisimFile().getLoader();
if (proj.isFileDirty()) {
String message = StringUtil.format(Strings
.get("openAlreadyMessage"), proj.getLogisimFile()
.getName());
String[] options = {
Strings.get("openAlreadyLoseChangesOption"),
Strings.get("openAlreadyNewWindowOption"),
Strings.get("openAlreadyCancelOption"), };
int result = JOptionPane
.showOptionDialog(proj.getFrame(), message,
Strings.get("openAlreadyTitle"), 0,
JOptionPane.QUESTION_MESSAGE, null, options,
options[2]);
if (result == 0) {
; // keep proj as is, so that load happens into the window
} else if (result == 1) {
proj = null; // we'll create a new project
} else {
return proj;
}
}
}
if (proj == null && baseProject != null
&& baseProject.isStartupScreen()) {
proj = baseProject;
proj.setStartupScreen(false);
loader = baseProject.getLogisimFile().getLoader();
} else {
loader = new Loader(baseProject == null ? parent
: baseProject.getFrame());
}
try {
LogisimFile lib = loader.openLogisimFile(f);
AppPreferences.updateRecentFile(f);
if (lib == null)
return null;
LibraryTools.RemovePresentLibraries(lib,new HashSet<String>(),true);
if (proj == null) {
proj = new Project(lib);
updatecircs(lib,proj);
} else {
updatecircs(lib,proj);
proj.setLogisimFile(lib);
}
} catch (LoadFailedException ex) {
if (!ex.isShown()) {
JOptionPane.showMessageDialog(
parent,
StringUtil.format(Strings.get("fileOpenError"),
ex.toString()),
Strings.get("fileOpenErrorTitle"),
JOptionPane.ERROR_MESSAGE);
}
return null;
}
Frame frame = proj.getFrame();
if (frame == null) {
frame = createFrame(baseProject, proj);
}
frame.setVisible(true);
frame.toFront();
frame.getCanvas().requestFocus();
proj.getLogisimFile().getLoader().setParent(frame);
return proj;
}
public static Project doOpen(SplashScreen monitor, File source,
Map<File, File> substitutions) throws LoadFailedException {
if (monitor != null)
monitor.setProgress(SplashScreen.FILE_LOAD);
Loader loader = new Loader(monitor);
LogisimFile file = loader.openLogisimFile(source, substitutions);
AppPreferences.updateRecentFile(source);
return completeProject(monitor, loader, file, false);
}
public static Project doOpenNoWindow(SplashScreen monitor, File source)
throws LoadFailedException {
Loader loader = new Loader(monitor);
LogisimFile file = loader.openLogisimFile(source);
Project ret = new Project(file);
updatecircs(file,ret);
return ret;
}
public static void doQuit() {
Frame top = Projects.getTopFrame();
top.savePreferences();
for (Project proj : new ArrayList<Project>(Projects.getOpenProjects())) {
if (!proj.confirmClose(Strings.get("confirmQuitTitle")))
return;
}
System.exit(0);
}
public static boolean doSave(Project proj) {
Loader loader = proj.getLogisimFile().getLoader();
File f = loader.getMainFile();
if (f == null)
return doSaveAs(proj);
else
return doSave(proj, f);
}
private static boolean doSave(Project proj, File f) {
Loader loader = proj.getLogisimFile().getLoader();
Tool oldTool = proj.getTool();
proj.setTool(null);
boolean ret = loader.save(proj.getLogisimFile(), f);
if (ret) {
AppPreferences.updateRecentFile(f);
proj.setFileAsClean();
}
proj.setTool(oldTool);
return ret;
}
/**
* Saves a Logisim project in a .circ file.
*
* It is the action listener for the File->Save as... menu option.
*
* @param proj
* project to be saved
* @return true if success, false otherwise
*/
public static boolean doSaveAs(Project proj) {
Loader loader = proj.getLogisimFile().getLoader();
JFileChooser chooser = loader.createChooser();
chooser.setFileFilter(Loader.LOGISIM_FILTER);
if (loader.getMainFile() != null) {
chooser.setSelectedFile(loader.getMainFile());
}
int returnVal;
boolean validFilename = false;
HashMap<String,String> Error = new HashMap<String,String> ();
do {
Error.clear();
returnVal = chooser.showSaveDialog(proj.getFrame());
if (returnVal != JFileChooser.APPROVE_OPTION) {
return false;
}
validFilename = checkValidFilename(chooser.getSelectedFile() .getName(),proj,Error);
if (!validFilename) {
String Message = "\""+chooser.getSelectedFile()+"\":\n";
for (String key : Error.keySet())
Message = Message.concat("=> "+Strings.get(Error.get(key))+"\n");
JOptionPane.showMessageDialog(chooser,Message,Strings.get("FileSaveAsItem"),JOptionPane.ERROR_MESSAGE);
}
} while (!validFilename);
File f = chooser.getSelectedFile();
String circExt = Loader.LOGISIM_EXTENSION;
if (!f.getName().endsWith(circExt)) {
String old = f.getName();
int ext0 = old.lastIndexOf('.');
if (ext0 < 0
|| !Pattern.matches("\\.\\p{L}{2,}[0-9]?",
old.substring(ext0))) {
f = new File(f.getParentFile(), old + circExt);
} else {
String ext = old.substring(ext0);
String ttl = Strings.get("replaceExtensionTitle");
String msg = Strings.get("replaceExtensionMessage", ext);
Object[] options = {
Strings.get("replaceExtensionReplaceOpt", ext),
Strings.get("replaceExtensionAddOpt", circExt),
Strings.get("replaceExtensionKeepOpt") };
JOptionPane dlog = new JOptionPane(msg);
dlog.setMessageType(JOptionPane.QUESTION_MESSAGE);
dlog.setOptions(options);
dlog.createDialog(proj.getFrame(), ttl).setVisible(true);
Object result = dlog.getValue();
if (result == options[0]) {
String name = old.substring(0, ext0) + circExt;
f = new File(f.getParentFile(), name);
} else if (result == options[1]) {
f = new File(f.getParentFile(), old + circExt);
}
}
}
if (f.exists()) {
int confirm = JOptionPane.showConfirmDialog(proj.getFrame(),
Strings.get("confirmOverwriteMessage"),
Strings.get("confirmOverwriteTitle"),
JOptionPane.YES_NO_OPTION);
if (confirm != JOptionPane.YES_OPTION)
return false;
}
return doSave(proj, f);
}
private ProjectActions() {
}
}