Loading of Custom Logisim Libraries at Startup
Added a method to load any .circ libraries in a folder named `logisim-defaults` in the user's home directory. This is run in `Startup.run()`, allowing the libraries to be automatically loaded when Logisim is first run. Unit tests and documentation for the method have also been added.
This commit is contained in:
parent
8895e463ac
commit
b37ea4c429
|
@ -33,6 +33,7 @@ Project highlights:
|
|||
* VHDL components (components behavior can be specified in VHDL!),
|
||||
* TCL/TK console (interfaces between the circuit and the user),
|
||||
* huge library of components (LEDs, TTLs, switches, SoCs),
|
||||
* allows for custom libraries to be [loaded on startup](docs/automatic_library_import.md)
|
||||
* supports [multiple languages](docs/docs.md#translations),
|
||||
* and more!
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# Automatically Importing Logisim Libraries
|
||||
Logisim Evolution supports loading custom libraries at startup, contained in Logisim `.circ` files.
|
||||
To do this, create a directory in your **home directory** named `logisim-defaults` and insert any Logisim libraries you
|
||||
would like to load every time Logsim Evolution starts. **Every circuit must have a unique name, and must not be called
|
||||
"main"**. This is to avoid conflicts caused by loading libraries with the same name.
|
||||
|
||||

|
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
|
@ -10,7 +10,6 @@
|
|||
package com.cburch.logisim.file;
|
||||
|
||||
import static com.cburch.logisim.file.Strings.S;
|
||||
|
||||
import com.cburch.logisim.gui.generic.OptionPane;
|
||||
import com.cburch.logisim.std.Builtin;
|
||||
import com.cburch.logisim.tools.Library;
|
||||
|
@ -26,11 +25,7 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import java.util.*;
|
||||
import javax.swing.JFileChooser;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JScrollPane;
|
||||
|
@ -352,6 +347,39 @@ public class Loader implements LibraryLoader {
|
|||
return ret;
|
||||
}
|
||||
|
||||
// Loads all the custom logisim (.circ) libraries found in the default library folder
|
||||
public Library[] loadCustomStartupLibraries(String customLibraryDirectoryPath) {
|
||||
File directory = new File(customLibraryDirectoryPath);
|
||||
|
||||
if (!directory.exists() || !LOGISIM_DIRECTORY.accept(directory)) {
|
||||
return new Library[0];
|
||||
}
|
||||
|
||||
var files = directory.listFiles();
|
||||
|
||||
if (files == null) {
|
||||
return new Library[0];
|
||||
}
|
||||
|
||||
List<Library> loadedLibraries = new ArrayList<>();
|
||||
for (File file : files) {
|
||||
if(!LOGISIM_FILTER.accept(file)) continue;
|
||||
|
||||
try{
|
||||
var library = loadLogisimLibrary(file);
|
||||
|
||||
if(library != null && !library.getLibraries().isEmpty())
|
||||
loadedLibraries.add(library);
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return loadedLibraries.toArray(new Library[0]);
|
||||
}
|
||||
|
||||
public void reload(LoadedLibrary lib) {
|
||||
LibraryManager.instance.reload(this, lib);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import static com.cburch.logisim.gui.Strings.S;
|
|||
import com.cburch.logisim.Main;
|
||||
import com.cburch.logisim.file.LoadFailedException;
|
||||
import com.cburch.logisim.file.Loader;
|
||||
import com.cburch.logisim.file.LogisimFileActions;
|
||||
import com.cburch.logisim.fpga.download.Download;
|
||||
import com.cburch.logisim.fpga.file.BoardReaderClass;
|
||||
import com.cburch.logisim.generated.BuildInfo;
|
||||
|
@ -43,11 +44,7 @@ import java.awt.event.AWTEventListener;
|
|||
import java.awt.event.ContainerEvent;
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import javax.help.JHelp;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
|
@ -885,6 +882,10 @@ public class Startup implements AWTEventListener {
|
|||
System.exit(-1);
|
||||
}
|
||||
|
||||
// Load in any user-defined default circuit files
|
||||
var defaultLibraries = templLoader.loadCustomStartupLibraries(
|
||||
System.getProperty("user.home") + File.separator + "logisim-defaults");
|
||||
|
||||
// load in template
|
||||
loadTemplate(templLoader, templFile, templEmpty);
|
||||
|
||||
|
@ -994,8 +995,9 @@ public class Startup implements AWTEventListener {
|
|||
}
|
||||
|
||||
// load file
|
||||
Project proj = null;
|
||||
if (filesToOpen.isEmpty()) {
|
||||
final var proj = ProjectActions.doNew(monitor);
|
||||
proj = ProjectActions.doNew(monitor);
|
||||
proj.setStartupScreen(true);
|
||||
if (showSplash) {
|
||||
monitor.close();
|
||||
|
@ -1003,7 +1005,6 @@ public class Startup implements AWTEventListener {
|
|||
} else {
|
||||
var numOpened = 0;
|
||||
var first = true;
|
||||
Project proj;
|
||||
for (final var fileToOpen : filesToOpen) {
|
||||
try {
|
||||
if (testVector != null) {
|
||||
|
@ -1047,6 +1048,9 @@ public class Startup implements AWTEventListener {
|
|||
if (numOpened == 0) System.exit(-1);
|
||||
}
|
||||
|
||||
if(proj != null)
|
||||
proj.doAction(LogisimFileActions.loadLibraries(defaultLibraries, proj.getLogisimFile()));
|
||||
|
||||
for (final var fileToPrint : filesToPrint) {
|
||||
doPrintFile(fileToPrint);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Logisim-evolution - digital logic design tool and simulator
|
||||
* Copyright by the Logisim-evolution developers
|
||||
*
|
||||
* https://github.com/logisim-evolution/
|
||||
*
|
||||
* This is free software released under GNU GPLv3 license
|
||||
*/
|
||||
|
||||
package com.cburch.logisim.file;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.cburch.logisim.gui.start.SplashScreen;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
public class CustomLibraryImportTest {
|
||||
private static final String VALID_LOGISIM_LIBRARY_XML = """
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><project source="4.0.0dev" version="1.0">
|
||||
This file is intended to be loaded by Logisim-evolution v4.0.0dev(https://github.com/logisim-evolution/).
|
||||
|
||||
<lib desc="#Wiring" name="0"><tool name="Pin"><a name="appearance" val="classic"/></tool></lib><lib desc="#Gates" name="1"/><lib desc="#Plexers" name="2"/><lib desc="#Arithmetic" name="3"/><lib desc="#Memory" name="4"/><lib desc="#I/O" name="5"/><lib desc="#TTL" name="6"/><lib desc="#TCL" name="7"/><lib desc="#Base" name="8"/><lib desc="#BFH-Praktika" name="9"/><lib desc="#Input/Output-Extra" name="10"/><lib desc="#Soc" name="11"/><main name="FourAdder"/><options><a name="gateUndefined" val="ignore"/><a name="simlimit" val="1000"/><a name="simrand" val="0"/></options><mappings><tool lib="8" map="Button2" name="Poke Tool"/><tool lib="8" map="Button3" name="Menu Tool"/><tool lib="8" map="Ctrl Button1" name="Menu Tool"/></mappings><toolbar><tool lib="8" name="Poke Tool"/><tool lib="8" name="Edit Tool"/><tool lib="8" name="Wiring Tool"/><tool lib="8" name="Text Tool"/><sep/><tool lib="0" name="Pin"/><tool lib="0" name="Pin"><a name="facing" val="west"/><a name="output" val="true"/></tool><sep/><tool lib="1" name="NOT Gate"/><tool lib="1" name="AND Gate"/><tool lib="1" name="OR Gate"/><tool lib="1" name="XOR Gate"/><tool lib="1" name="NAND Gate"/><tool lib="1" name="NOR Gate"/><sep/><tool lib="4" name="D Flip-Flop"/><tool lib="4" name="Register"/></toolbar><circuit name="FourAdder"><a name="appearance" val="logisim_evolution"/><a name="circuit" val="FourAdder"/><a name="circuitnamedboxfixedsize" val="true"/><a name="simulationFrequency" val="1.0"/><comp lib="0" loc="(110,140)" name="Pin"><a name="appearance" val="NewPins"/></comp><comp lib="0" loc="(110,180)" name="Pin"><a name="appearance" val="NewPins"/></comp><comp lib="0" loc="(110,210)" name="Pin"><a name="appearance" val="NewPins"/></comp><comp lib="0" loc="(110,250)" name="Pin"><a name="appearance" val="NewPins"/></comp><comp lib="0" loc="(290,190)" name="Pin"><a name="appearance" val="NewPins"/><a name="facing" val="west"/><a name="output" val="true"/></comp><comp lib="1" loc="(190,160)" name="AND Gate"/><comp lib="1" loc="(190,230)" name="AND Gate"/><comp lib="1" loc="(260,190)" name="AND Gate"/><wire from="(110,140)" to="(140,140)"/><wire from="(110,180)" to="(140,180)"/><wire from="(110,210)" to="(140,210)"/><wire from="(110,250)" to="(140,250)"/><wire from="(190,160)" to="(210,160)"/><wire from="(190,230)" to="(210,230)"/><wire from="(210,160)" to="(210,170)"/><wire from="(210,210)" to="(210,230)"/><wire from="(260,190)" to="(290,190)"/></circuit></project>
|
||||
""";
|
||||
|
||||
private static final String RANDOM_FILE_DATA = "Lorem Ipsum";
|
||||
|
||||
private static class TestFile {
|
||||
public String contents;
|
||||
public String fileName;
|
||||
|
||||
public TestFile(String contents, String fileName) {
|
||||
this.contents = contents;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test method for {@link com.cburch.logisim.file.Loader#loadCustomStartupLibraries(String)}
|
||||
*/
|
||||
@Test
|
||||
public final void testLoadCustomStartupLibraries() {
|
||||
// Create a loader to test loading files with
|
||||
var testLoader = new Loader(new SplashScreen());
|
||||
|
||||
// Test for an invalid file type
|
||||
TestFile[] files = new TestFile[]{ new TestFile(RANDOM_FILE_DATA, "not_a_logisim_file.txt") };
|
||||
String testDirectoryPath = generateTestDefaultLibrary(files);
|
||||
var loadedLibraries = testLoader.loadCustomStartupLibraries(testDirectoryPath);
|
||||
assertEquals(0, loadedLibraries.length);
|
||||
|
||||
// Test for if the directory does not exist
|
||||
String fakeDirectory = findFakeDirectory();
|
||||
loadedLibraries = testLoader.loadCustomStartupLibraries(fakeDirectory);
|
||||
assertEquals(0, loadedLibraries.length);
|
||||
|
||||
// Test for if the directory contains no files
|
||||
files = new TestFile[0];
|
||||
testDirectoryPath = generateTestDefaultLibrary(files);
|
||||
loadedLibraries = testLoader.loadCustomStartupLibraries(testDirectoryPath);
|
||||
assertEquals(0, loadedLibraries.length);
|
||||
|
||||
// Test if the file given is invalid
|
||||
files = new TestFile[]{ new TestFile(RANDOM_FILE_DATA, "bad_file.circ") };
|
||||
testDirectoryPath = generateTestDefaultLibrary(files);
|
||||
loadedLibraries = testLoader.loadCustomStartupLibraries(testDirectoryPath);
|
||||
assertEquals(0, loadedLibraries.length);
|
||||
|
||||
// Test if the system works correctly under normal circumstances
|
||||
files = new TestFile[]{ new TestFile(VALID_LOGISIM_LIBRARY_XML, "good_file.circ") };
|
||||
testDirectoryPath = generateTestDefaultLibrary(files);
|
||||
loadedLibraries = testLoader.loadCustomStartupLibraries(testDirectoryPath);
|
||||
assertEquals(1, loadedLibraries.length);
|
||||
}
|
||||
|
||||
private String findFakeDirectory(){
|
||||
String directoryPath;
|
||||
while(true) {
|
||||
directoryPath = System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID();
|
||||
File directory = new File(directoryPath);
|
||||
if(!directory.exists()) break;
|
||||
}
|
||||
return directoryPath;
|
||||
}
|
||||
|
||||
private String generateTestDefaultLibrary(TestFile[] files){
|
||||
String directoryPath;
|
||||
while(true) {
|
||||
directoryPath = System.getProperty("java.io.tmpdir") + File.separator + UUID.randomUUID();
|
||||
File directory = new File(directoryPath);
|
||||
if(directory.mkdir()) break;
|
||||
}
|
||||
|
||||
for (TestFile file : files) {
|
||||
String fileName = directoryPath + File.separator + file.fileName;
|
||||
File newDirectoryFile = new File(fileName);
|
||||
try {
|
||||
boolean fileCreatedSuccess = newDirectoryFile.createNewFile();
|
||||
if (!fileCreatedSuccess) throw new IOException("Unable to create new directory");
|
||||
|
||||
FileWriter fileWriter = new FileWriter(fileName);
|
||||
fileWriter.write(file.contents);
|
||||
fileWriter.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return directoryPath;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue