Cleanup and polish
Build / Do we need to build the application? (push) Failing after 3s
Code style / Is there any Java source code to lint? (push) Failing after 3s
Build / Gradle builder (push) Skipped
Code style / Code style linter (push) Skipped
CodeQL / Is there any code to analyze? (push) Successful in 6s
MD Lint / Markdown linter (push) Failing after 9s
Translations / Translations linter (push) Failing after 8s
CodeQL / Analyze (java) (push) Failing after 1m21s

This commit is contained in:
2026-09-24 22:14:18 +02:00
parent 826fb1e05c
commit a4b9e4f770
86 changed files with 1276 additions and 2113 deletions
+5 -1
View File
@@ -291,4 +291,8 @@ gradle-app.setting
.vscode
# python代码
*.py
# Sources for the Revolution brand and its Snap launch command.
!/snap/local/usr/bin/
*.py
!/artwork/generate_revolution_assets.py
+5 -1
View File
@@ -1,8 +1,12 @@
[![Logisim-evolution](docs/img/logisim-evolution-logo.png)](https://github.com/logisim-evolution/logisim-evolution)
[![Logisim Revolution](artwork/logisim-revolution-wordmark.svg)](https://git.briggen.dev/NilsBriggen/Logisim-Revolution)
# Changes #
* @dev (????-??-??)
* Gave Revolution its own application identity, loading splash, vector icon family, readable
component captions and isolated settings and recovery paths. Refreshed Help and Preferences,
and rewrote the README around the new interface and verified source-build path
(@NilsBriggen).
* Preserve the last valid project and recovery file when serialization fails; Save As dismissal
never chooses an extension implicitly. Timing and vector drawers safely suspend for HDL
editors, and constrained layouts retain usable editor space without losing saved pane sizes
+38 -199
View File
@@ -1,223 +1,62 @@
[![Logisim-evolution](docs/img/logisim-evolution-logo.png)](https://github.com/logisim-evolution/logisim-evolution)
![Logisim Revolution wordmark](artwork/logisim-revolution-wordmark.svg)
---
# Logisim Revolution
# Logisim-evolution #
Design and simulate digital circuits in a local desktop app. Logisim Revolution combines a searchable parts picker, a tabbed circuit editor, an inspector for component settings, and built-in tools for testing and analyzing logic.
* **Table of contents**
* [Features](#features)
* [Requirements](#requirements)
* [Troubleshooting](#troubleshooting)
* **[Downloads](#download)**
* [Package Manager](#package-manager)
* [Nightly builds (unstable)](#nightly-builds)
* [Pictures of Logisim-evolution](docs/pics.md)
* [More Information](docs/docs.md)
* [Bug reports & feature requests](https://github.com/logisim-evolution/logisim-evolution/issues)
* [For developers](docs/developers.md)
* [How to contribute](docs/developers.md#how-to-contribute)
* [Credits](docs/credits.md)
It is a fork of [Logisim-evolution](https://github.com/logisim-evolution/logisim-evolution). It keeps the familiar circuit file format and component IDs while developing its own interface and application identity.
---
![Dark-theme editor showing the circuit canvas, parts picker, and inspector](docs/img/logisim-revolution-main-dark.png)
## Features ##
![Light-theme editor showing a selected gate and its settings beside the parts picker](docs/img/logisim-revolution-main-light.png)
`Logisim-evolution` is educational software for designing and simulating digital logic circuits.
`Logisim-evolution` is [free](#license), [open-source](https://github.com/logisim-evolution), and [cross-platform](#requirements).
## Try it from source
Project highlights:
* easy to use circuit designer,
* logic circuit simulations,
* chronogram (to see the evolution of signals in your circuit),
* electronic board integration (schematics can be simulated on real hardware),
* 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!
Getting started (no experience needed):
Step-by-step first circuit:
| Step | What you do | Why it matters |
| --- | --- | --- |
| 1 | `File → New` | Start a clean project |
| 2 | Place two `Input` components and one `AND` gate | Create a simple logic function |
| 3 | Add a `Probe` or `Lamp` as the output | Make the result visible |
| 4 | Use the wiring tool to connect inputs → gate → output | Build the circuit path |
| 5 | Toggle the inputs | Observe how logic affects the output |
| 6 | Save the project | Keep your work for later |
Key ideas in plain language:
| Concept | What it means in practice |
| --- | --- |
| Component | A building block you place on the canvas |
| Wire | The path that carries a 0 or 1 between components |
| Signal | The 0/1 value traveling on a wire |
| Subcircuit | A reusable group of components you define |
| Library | A collection of components you can add to a project |
If you get stuck:
* Click a component and read its properties panel.
* Use `Edit → Undo` to step back safely.
* Check the screenshots and docs links below for examples.
[![Logisim-evolution](docs/img/logisim-evolution-01-small.png)](docs/pics.md)
[![Logisim-evolution](docs/img/logisim-evolution-02-small.png)](docs/pics.md)
[![Logisim-evolution](docs/img/logisim-evolution-03-small.png)](docs/pics.md)
Image gallery: the three thumbnails above link to a larger set of screenshots in `docs/pics.md`.
---
## Requirements ##
`Logisim-evolution` is a Java application; therefore, it can run on any operating system supporting the Java runtime enviroment.
It requires [Java 21 (or newer)](https://adoptium.net/temurin/releases/).
Accessibility notes:
* If you use a screen reader, the `docs/` pages are plain Markdown and generally read well in linear order.
* If you prefer larger text or higher contrast, use your OS display settings or Java accessibility options.
* Keyboard navigation is supported throughout the app; consult the in-app help for shortcuts.
* The image gallery uses thumbnails that link to full-size images for better visibility.
Accessibility quick setup:
| Need | Tip |
| --- | --- |
| Larger text | Increase OS scaling or Java UI scaling |
| High contrast | Enable high-contrast mode in your OS |
| Keyboard-only use | Rely on menus and in-app shortcuts |
---
## Troubleshooting ##
### Blank window on Linux ###
With some non-reparenting window managers, the Logisim-evolution window may appear blank even
though its controls still respond. This is caused by an interaction between Java AWT and the window
manager. Try launching the JAR once with the following environment variable:
You need **JDK 21 or newer**. The Gradle wrapper is included; a separate Gradle installation is not needed.
```bash
env _JAVA_AWT_WM_NONREPARENTING=1 java -jar logisim-evolution-<version>-all.jar
git clone https://git.briggen.dev/NilsBriggen/Logisim-Revolution.git
cd Logisim-Revolution
./gradlew run
```
If this resolves the problem, configure the same environment variable in the session or launcher
used to start Logisim-evolution. See [issue #1235](https://github.com/logisim-evolution/logisim-evolution/issues/1235)
for background and confirmed configurations.
On Windows, run `gradlew.bat run`. To build a portable JAR instead:
---
```bash
./gradlew shadowJar
java -jar build/libs/logisim-revolution-5.1.0dev-all.jar
```
## Download ###
The JAR filename follows the version in [gradle.properties](gradle.properties); adjust that command when the version changes. Platform installers can be built on their respective operating systems with `./gradlew createAll`. **No Revolution installers or package-store releases are advertised here until they are published and verified.**
`Logisim-evolution` is available for
[download in compiled form](https://github.com/logisim-evolution/logisim-evolution/releases)
with ready to use installable packages for Windows, macOS, and Linux
or in [source code form](https://github.com/logisim-evolution), which you can [build yourself](docs/developers.md).
## Make a first circuit
The following [platform specific packages](https://github.com/logisim-evolution/logisim-evolution/releases)
include the Java runtime and do not require it to be installed separately:
1. Choose **New** on the welcome screen. Use the parts picker search to find **Pin**, place two input pins, then add an **AND** gate and an **LED**.
2. Select the wiring tool and connect both pins to the gate inputs, then connect the gate output to the LED.
3. Use the poke tool to switch the inputs between 0 and 1. The LED lights when both inputs are 1.
4. Save your work as a `.circ` file. Select a component to change its settings in the inspector.
* `logisim-evolution_<version>-1_amd64.deb`: Debian package (also suitable for Ubuntu and derivatives),
* `logisim-evolution-<version>-1.x86_64.rpm`: Package for Fedora/Redhat/CentOS/SuSE Linux distributions,
* `logisim-evolution-<version>_amd64.snap`: The [Snap](https://snapcraft.io/docs) archive for all
supported Linux distributions (also available in [Snapcraft store](https://snapcraft.io/logisim-evolution)),
* `logisim-evolution-<version>-aarch64.msi`: Installer package for Microsoft Windows for Arm processors,
* `logisim-evolution-<version>-amd64.msi`: Installer package for Microsoft Windows for Intel processors,
* `logisim-evolution-<version>-aarch64.dmg`: macOS package for Apple processors,
* `logisim-evolution-<version>-x86_64.dmg`: macOS package for Intel processors (also runs on Apple processors in simulation).
The [built-in help](src/main/resources/doc) and [project background](docs/docs.md) cover more concepts and examples.
The Java JAR [`logisim-evolution-<version>-all.jar`](https://github.com/logisim-evolution/logisim-evolution/releases)
is also available and can be run on any system with a supported Java runtime installed.
## What you can build
[![Get it from the Snap Store](https://snapcraft.io/static/images/badges/en/snap-store-black.svg)](https://snapcraft.io/logisim-evolution)
- Combinational and sequential circuits, from small gates to reusable subcircuits.
- Signals and displays, memory, arithmetic, TTL components, and SoC designs.
- Truth tables, expression analysis, test vectors, timing diagrams, and signal logs.
- VHDL components and HDL export; board and FPGA workflows where supported by your hardware and toolchain.
Which download should I pick?
The editor follows the system light or dark theme by default. UI zoom can be changed independently of circuit zoom.
| You are on | Pick this | Notes |
| --- | --- | --- |
| Windows (Intel/AMD) | `logisim-evolution-<version>-amd64.msi` | Standard installer |
| Windows (ARM) | `logisim-evolution-<version>-aarch64.msi` | For ARM devices |
| macOS (Apple Silicon) | `logisim-evolution-<version>-aarch64.dmg` | For M-series Macs |
| macOS (Intel) | `logisim-evolution-<version>-x86_64.dmg` | Also runs via Rosetta |
| Linux (Debian/Ubuntu) | `logisim-evolution_<version>-1_amd64.deb` | Debian package |
| Linux (Fedora/Redhat) | `logisim-evolution-<version>-1.x86_64.rpm` | RPM package |
| Linux (Snap) | `logisim-evolution-<version>_amd64.snap` | Snap store build |
| Any OS with Java | `logisim-evolution-<version>-all.jar` | Portable option |
## Files and settings
---
Revolution opens and saves the existing Logisim-evolution `.circ` format. Saved component and library identifiers are unchanged. On the first interactive launch, Revolution offers to copy compatible Evolution settings. Declining leaves Revolution on its own defaults. The two apps keep separate settings and recovery files; importing does not delete or modify Evolution's settings.
**Note for macOS users**:
The Logisim-evolution.app is not signed with an Apple approved certificate.
## Project and contributions
When launching the application for the first time, you will have to start it via the "Open" entry in the
application icon's context menu in the macOS Finder. This is either done by clicking the application
icon with the right mouse button or holding down <kbd>CTRL</kbd> while clicking the icon with the
left mouse button. This will open a panel asking you to verify that you wish to launch the application.
On more recent versions of macOS, the panel will only give you a choice of moving the app to the trash or Cancel.
On those systems, click Cancel, open `System Preferences`, and select `Security & Privacy`.
There you may need to click the lock to make changes and authenticate with an administrative acccount.
It should show an option to open the app.
See [Safely open apps on your Mac](https://support.apple.com/en-us/HT202491) for more information.
- [Source repository and bug reporting](https://git.briggen.dev/NilsBriggen/Logisim-Revolution)
- [Developer build and contribution notes](docs/developers.md)
- [Localization guide](docs/localization.md)
- [Full project credits](docs/credits.md)
- [License: GNU GPL version 3](LICENSE.md)
Depending on your security settings, you may also get a panel asking if you wish to allow it to accept
network connections. You can click "Deny" as we do not need network access currently nor we do request any.
### Package Manager ###
`Logisim-evolution` is available from a bunch of [package managers](https://repology.org/project/logisim-evolution/versions).
Note that these (except for Snap) are not maintained by the core developers.
If you should observe a bug in Logisim-evolution while using one of these packages,
first make sure that it can be reproduced with the most recent official packages
[provided through this repository](https://github.com/logisim-evolution/logisim-evolution/releases)
and ideally the HEAD of our [main branch](https://github.com/logisim-evolution/logisim-evolution/tree/main)
before [creating an issue](https://github.com/logisim-evolution/logisim-evolution/issues) on
the official [Logisim-evolution repository](https://github.com/logisim-evolution/logisim-evolution).
Otherwise, report the issue to the package maintainer!
| Package manager | Command | Available version |
| --------------- | ------- | ----------------- |
| [Snap](https://snapcraft.io/logisim-evolution) | `snap install logisim-evolution` | ![Snapcraft Version](https://img.shields.io/snapcraft/v/logisim-evolution/latest/stable) |
| [Flathub](https://flathub.org/apps/details/com.github.reds.LogisimEvolution) | `flatpak install flathub com.github.reds.LogisimEvolution` | ![Flathub Version](https://img.shields.io/flathub/v/com.github.reds.LogisimEvolution) |
| [Homebrew](https://formulae.brew.sh/cask/logisim-evolution) | `brew install --cask logisim-evolution` | ![Homebrew Cask Version](https://img.shields.io/homebrew/cask/v/logisim-evolution) |
| [MacPorts](https://ports.macports.org/port/logisim-evolution/details/) | `port install logisim-evolution` | ![MacPorts Port Version](https://img.shields.io/macports/v/logisim-evolution) |
| [Chocolatey](https://community.chocolatey.org/packages/logisim-evolution) | `choco install logisim-evolution` | ![Chocolatey Version](https://img.shields.io/chocolatey/v/Logisim-evolution) |
| [winget](https://winget.run/pkg/logisim-evolution/logisim-evolution) | `winget install -e --id logisim-evolution.logisim-evolution` | ![WinGet Package Version](https://img.shields.io/winget/v/logisim-evolution.logisim-evolution) |
| [Arch User Repository](https://aur.archlinux.org/packages/logisim-evolution) | | ![AUR Version](https://img.shields.io/aur/version/logisim-evolution) |
| [nixpkgs](https://search.nixos.org/packages?channel=26.05&show=logisim-evolution) | `nix-shell -p logisim-evolution` | [![nixpkgs stable 26.05 package](https://repology.org/badge/version-for-repo/nix_stable_26_05/logisim-evolution.svg)](https://repology.org/project/logisim-evolution/versions) |
### Nightly builds ###
We also offer builds based on the current state of the
[main](https://github.com/logisim-evolution/logisim-evolution/tree/main) branch.
If the main branch has been changed,
a new `Nightly build` is created at midnight [UTC](https://en.wikipedia.org/wiki/Coordinated_Universal_Time).
Note that these builds may be unstable since the main branch is a work in progress.
To get nightly downloads, please go to the
[nightly workflow page](https://github.com/logisim-evolution/logisim-evolution/actions/workflows/nightly.yml)
and browse to the last successful build from the `Nightly` worker.
Nightly runs each night, but terminates quickly and does not produce new packages if nothing has changed since the last run.
Find the most recent run that contains the packages.
The time Nightly took to run, given near the clock icon on the right, helps your search.
Note that due to GitHub internals, all files are provided as ZIP archives.
You must unzip the downloaded file to get the installation package.
Please share your experience in [Discussions](https://github.com/logisim-evolution/logisim-evolution/discussions)
or [open a ticket](https://github.com/logisim-evolution/logisim-evolution/issues)
if you found a bug or have suggestions for improvement.
---
## License ##
* `Logisim-evolution` is copyrighted ©2001-2026 by Logisim-evolution [developers](docs/credits.md).
* This is free software licensed under [GNU General Public License v3](https://www.gnu.org/licenses/gpl-3.0.en.html).
Logisim was created by Carl Burch. Logisim-evolution and its contributors developed the project further; Revolution builds on that work. See the [credits](docs/credits.md) and source headers for attribution. The new Revolution emblem and wordmark are included under the project's GPLv3 license.
+5 -84
View File
@@ -1,86 +1,7 @@
#!/bin/bash
# Generates PNG assets from SVG source files.
# Dependencies:
# - apt install -y inkscape icoutils icnsutils
#!/usr/bin/env bash
# Regenerate all Logisim Revolution runtime and package artwork from the SVG masters.
# Requires Python 3, rsvg-convert, and ImageMagick.
set -euo pipefail
# Sizes as supported by png2cns: https://icns.sourceforge.io/apidocs.html (Icon Type Constants).
declare -r SIZES=(16 32 48 128 256)
# Project root
declare -r ROOT="../"
# SVG folder
declare -r SRC_DIR="./"
# Target directory for generated PNG files.
declare -r DEST_DIR="${ROOT}/build/resources/icons/"
# local -r dest="${ROOT}build/resources/main/resources/logisim/img/"
declare -r SUPPORT_DIR="${ROOT}/support/jpackage/"
declare -r IMG_DIR="${ROOT}/src/main/resources/resources/logisim/img/"
# Generates PNG file from SVG source.
function svg_to_png {
local -r src="${1:-}"
local -r dest="${2:-}"
local -r width="${3:-}"
local -r opts=(
"--export-overwrite"
"--export-background-opacity=0"
"--export-type=png"
"--export-png-color-mode=RGBA_8"
)
inkscape ${opts[*]} --export-width="${width}" --export-filename="${dest}" "${src}"
}
# Generates all required size PNG icons.
function generate_png_icons {
local -r svg="${SRC_DIR}/logisim-evolution-icon.svg"
mkdir -p "${DEST_DIR}"
for width in ${SIZES[*]}; do
svg_to_png "${svg}" "${DEST_DIR}/logisim-icon-${width}.png" "${width}"
done
}
# ##############################################################################
# Update logo PNG
declare -r logo_src="${SRC_DIR}/logisim-evolution-logo.svg"
declare -r logo_width=550
declare -r logo_dest="${IMG_DIR}/logisim-evolution-logo.png"
svg_to_png "${logo_src}" "${logo_dest}" "${logo_width}"
# Update icon PNGs
generate_png_icons
src_pngs=()
for width in ${SIZES[*]}; do src_pngs+=("${DEST_DIR}/logisim-icon-${width}.png"); done
# Copy icons into sources
for icon in ${src_pngs[*]}; do
cp -f "${icon}" "${IMG_DIR}/$(basename "${icon}")"
done
# Copy icon for Linux installation packages.
cp -f "${DEST_DIR}/logisim-icon-128.png" "${SUPPORT_DIR}/linux/"
# build macOS icns package using:
# Linux: icnsutils package
# macOS: ???
# Windows: ???
for suffix in "" "-circ"; do
png2icns "${SUPPORT_DIR}/macos/Logisim-evolution-${suffix}.icns" ${src_pngs[*]}
done
# build Windows ico file using:
# Linux: icoutils package
# macOS: ???
# Windows: http://www.telegraphics.com.au/sw/product/ICOBundle (???)
icotool -c -o "${SUPPORT_DIR}/windows/Logisim-evolution.ico" ${src_pngs[*]}
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
exec python3 "$project_root/artwork/generate_revolution_assets.py"
+22 -10
View File
@@ -31,8 +31,7 @@ repositories {
sonar {
properties {
property("sonar.projectKey", "logisim-evolution_logisim-evolution")
property("sonar.organization", "logisim-evolution")
property("sonar.projectKey", "dev_briggen_logisim_revolution")
}
}
@@ -141,7 +140,7 @@ extra.apply {
set(SUPPORT_DIR, supportDir)
// Project name with uppercase first letter
val uppercaseProjectName = project.name.replaceFirstChar { it.uppercase() }.trim()
val uppercaseProjectName = "Logisim Revolution"
set(UPPERCASE_PROJECT_NAME, uppercaseProjectName)
// Base name of produced artifacts. Suffixes will be added later by relevant tasks.
@@ -168,7 +167,7 @@ extra.apply {
set(JDEPS_FILE, jdepsFile)
// Copyrights note.
val copyrights = "Copyright ©2001–${SimpleDateFormat("yyyy").format(Date())} ${project.name} developers"
val copyrights = "Copyright ©2001–${SimpleDateFormat("yyyy").format(Date())} Logisim-evolution and Logisim Revolution contributors"
// Platform-agnostic jpackage parameters shared across all the builds.
var params = listOf(
@@ -182,7 +181,7 @@ extra.apply {
"--java-options", "--enable-native-access=ALL-UNNAMED",
"--copyright", copyrights,
"--description", "Digital logic design tool and simulator",
"--vendor", "${project.name} developers",
"--vendor", "Logisim Revolution contributors",
)
if (logger.isDebugEnabled()) {
params += listOf("--verbose")
@@ -195,7 +194,7 @@ extra.apply {
"--dest", targetDir,
"--app-version", appVersion,
"--file-associations", "${supportDir}/linux/file.jpackage",
"--icon", "${supportDir}/linux/logisim-icon-128.png",
"--icon", "${supportDir}/linux/logisim-revolution-icon-128.png",
"--install-dir", "/opt",
"--linux-shortcut"
)
@@ -662,7 +661,7 @@ tasks.register("createMsi") {
"--name", projectName,
"--dest", targetDir,
"--file-associations", "${supportDir}/windows/file.jpackage",
"--icon", "${supportDir}/windows/Logisim-evolution.ico",
"--icon", "${supportDir}/windows/Logisim-Revolution.ico",
"--win-menu-group", projectName,
"--win-shortcut",
"--win-dir-chooser",
@@ -719,7 +718,7 @@ tasks.register("createExe") {
val params = sharedParams + func.getNeededModules(jdepsFile) + listOf(
"--name", projectName,
"--dest", dest,
"--icon", "${supportDir}/windows/Logisim-evolution.ico",
"--icon", "${supportDir}/windows/Logisim-Revolution.ico",
"--type", "app-image",
// we MUST use short version form (without any suffix like "-dev", as it is not allowed in MSI package:
// https://docs.microsoft.com/en-us/windows/win32/msi/productversion?redirectedfrom=MSDN
@@ -791,7 +790,7 @@ tasks.register("createApp") {
"--dest", dest,
"--name", projectName,
"--file-associations", "${supportDir}/macos/file.jpackage",
"--icon", "${supportDir}/macos/Logisim-evolution.icns",
"--icon", "${supportDir}/macos/Logisim-Revolution.icns",
// app versioning is strictly checked for macOS. No suffix allowed for `app-image` type.
"--app-version", appVersion,
"--type", "app-image",
@@ -1115,6 +1114,8 @@ tasks.register<JavaExec>("uiSnapshot") {
systemProperty("snapshot.page", project.findProperty("page") ?: "-1")
systemProperty("snapshot.file", project.findProperty("file") ?: "")
systemProperty("snapshot.print", project.findProperty("print") ?: "false")
systemProperty("snapshot.select", project.findProperty("select") ?: "")
systemProperty("snapshot.canvasZoom", project.findProperty("canvasZoom") ?: "0")
systemProperty("snapshot.side", project.findProperty("side") ?: "")
}
@@ -1138,9 +1139,20 @@ tasks.register<JavaExec>("themeProbe") {
args(project.findProperty("theme")?.toString() ?: "light")
}
tasks.register<JavaExec>("splashSnapshot") {
group = "verification"
description = "Renders the loading splash to a PNG."
classpath = sourceSets["test"].runtimeClasspath
mainClass.set("com.cburch.logisim.gui.start.SplashSnapshot")
systemProperty("snapshot.theme", project.findProperty("theme") ?: "dark")
systemProperty("snapshot.scale", project.findProperty("scale") ?: "1.0")
systemProperty("snapshot.stage", project.findProperty("stage") ?: "progressLibraries")
systemProperty("snapshot.out", project.findProperty("out") ?: "build/splash-snapshot.png")
}
// Development probes must never read/write the developer's real settings or recovery files.
tasks.withType<JavaExec>().configureEach {
if (name in setOf("uiSnapshot", "optionsProbe", "scaleProbe", "themeProbe")) {
if (name in setOf("uiSnapshot", "splashSnapshot", "optionsProbe", "scaleProbe", "themeProbe")) {
doFirst {
val isolated = Files.createTempDirectory("logisim-${name}-").toFile()
systemProperty("user.home", File(isolated, "home").apply { mkdirs() }.path)
+1 -1
View File
@@ -1,4 +1,4 @@
[![Logisim-evolution](img/logisim-evolution-logo.png)](https://github.com/logisim-evolution/logisim-evolution)
[![Logisim Revolution wordmark](../artwork/logisim-revolution-wordmark.svg)](../README.md)
---
+34 -136
View File
@@ -1,154 +1,52 @@
[![Logisim-evolution](img/logisim-evolution-logo.png)](https://github.com/logisim-evolution/logisim-evolution)
![Logisim Revolution wordmark](../artwork/logisim-revolution-wordmark.svg)
---
# Developing Logisim Revolution
# Developers #
[Back to the project overview](../README.md).
* [« Go back](../README.md)
* **Developer's corner**
* [Requirements](#requirements)
* [Gradle](#gradle)
* [Import project into IDE](#import-project-into-ide)
* [Eclipse](#import-project-into-ide)
* [InteliJ IDEA](#import-project-into-ide)
* [Building from sources](#building-from-sources)
* [Testing development code](#testing-development-code)
* [Code style](style.md)
* [Checking code style with InteliJ IDEA](style.md#checking-code-style-with-intelij-idea)
* [Using Gradle plugin](style.md#using-gradle-plugin)
* [Using `pre-commit`](style.md#using-pre-commit-hooks)
* [Translations](localization.md)
* [Updating existing translation](localization.md#updating-existing-translation)
* [Adding new translation](localization.md#adding-new-translation)
* [Using trans-tool](localization.md#using-trans-tool)
* [Implementing TTL components](implementing_ttl_components.md)
* [How to contribute](#how-to-contribute)
## Prerequisites
---
Use **JDK 21 or newer**. The repository includes Gradle wrapper scripts, so no separate Gradle installation is required. Set `JAVA_HOME` to your JDK when your environment does not pick it automatically.
## Requirements ##
## Build and run
`Logisim-evolution` is written in Java 21. To build it from sources you need JDK
(or equivalent, e.g. [OpenJDK](https://adoptopenjdk.net/)) version 21 or newer.
**NOTE:** Ensure your `$JAVA_HOME` environment variable points to the proper JDK version.
## Gradle ##
`Logisim-evolution` uses the [Gradle](https://gradle.org) build system, which means it can be easily imported into modern IDEs
that support it, including [Eclipse](https://www.eclipse.org) and [IntelliJ IDEA](https://www.jetbrains.com/idea/).
## Import project into IDE ##
How to import a Gradle project:
* [How to import Gradle project into Eclipse](https://www.eclipse.org/community/eclipse_newsletter/2018/february/buildship.php),
* [How to import Gradle project into IntelliJ IDEA](https://www.jetbrains.com/help/idea/gradle.html) (section "Importing a project
from a Gradle model").
**Note for Eclipse users:**
To successfully import the project in Eclipse, the complete project structure must be present,
including Java source files built by Gradle.
You can do this by running Gradle task `genFiles` before importing the project.
See [Building from sources](#building-from-sources) for how to run Gradle.
To run the task within Eclipse after importing the Logisim-evolution project:
* Bring up the `Gradle Tasks` view, if it is not already showing, by selecting the menu `Window/Show View/Other...`
and selecting `Gradle Tasks` under Gradle.
* In the `Gradle Tasks` view, double-click on `logisim-evolution/build/genFiles`.
Check the `Console View` to see when it finishes.
* Right-click on the Logisim-evolution project in the Project Explorer and select `Gradle/Refresh Gradle Project`.
* You may then need to right-click on the Logisim-evolution project and select `Refresh`.
## Building from sources ##
To build and run the `Logisim-evolution` application, invoke the `Gradle` build system and pass a task name as an argument.
`Logisim-evolution` comes with a Gradle wrapper script, which can be invoked as `./gradlew <ARGS>` on Linux or macOS, and
`gradlew <ARGS>` on Windows.
To build and run on Linux and macOS:
From the repository root on Linux or macOS:
```bash
./gradlew run
./gradlew build
```
or on Windows:
On Windows, use `gradlew.bat` in place of `./gradlew`. Useful focused tasks:
| Command | Result |
| --- | --- |
| `./gradlew test` | Run the JUnit suite. |
| `./gradlew checkstyleMain checkstyleTest` | Check Java source style. |
| `./gradlew shadowJar` | Build `build/libs/logisim-revolution-<version>-all.jar`. |
| `./gradlew genFiles` | Generate build metadata before an Eclipse import. |
| `./gradlew createAll` | Build installers for the current host platform. |
The application mark, document icon, and wordmark have editable SVG masters in [artwork](../artwork). To regenerate their runtime and installer assets, run `./artwork/update_assets.sh` with Python 3, `rsvg-convert`, and ImageMagick installed.
The current development JAR is `build/libs/logisim-revolution-5.1.0dev-all.jar`. Run it with `java -jar`; the filename changes when [gradle.properties](../gradle.properties) changes.
Gradle generates `com.cburch.logisim.generated.BuildInfo` under `build/generated/logisim/java`. Edit the version and project URL in `gradle.properties`, not the generated class. Platform packages use the JDK's `jpackage` and may require host-specific packaging tools.
## Working on the project
The simulator and circuit format still use the original `com.cburch.logisim` Java packages and serialized component/library IDs. Keep those IDs stable so existing `.circ` projects continue to open. Application UI code lives under `gui/`; the component libraries and simulation engine remain separate.
Tests are JUnit 5 under `src/test/java`. Run a focused class with:
```bash
gradlew run
./gradlew test --tests com.cburch.logisim.std.ttl.Ttl7493Test
```
If you wish to create a Java JAR package, which can then be run without [Gradle](https://gradle.org), execute:
Use the existing style and localization mechanisms. Start with the [style guide](style.md), [localization guide](localization.md), and [TTL component checklist](implementing_ttl_components.md) where relevant. User-visible strings belong in the English base bundle and should be localized through the package `Strings.S`.
```bash
./gradlew shadowJar
```
Before proposing a change, run tests and style checks relevant to it and inspect rendered UI changes at more than one scale. Record user-facing changes in the top `@dev` section of [CHANGES.md](../CHANGES.md), crediting the author. The repository's CI workflows are the source of truth for contribution gates.
which will create `logisim-evolution-<version>-all.jar` in `build/libs/`.
To run it with JRE/JDK 21 or higher, type:
## Reporting and attribution
```bash
java -jar logisim-evolution-<version>-all.jar
```
for example:
```bash
java -jar logisim-evolution-3.6.0-all.jar
```
You can also generate a platform-specific installer, which gets saved in `build/dist`.
Packages can be built by running the `createAll` task:
```bash
./gradlew createAll
```
> **NOTE:** `jpackage` creates the installer for the platform that builds it. Building cross-platform installers is not supported
> by Java's `jpackage` utility. You may also need to install additional developer tools for the platform in order to build the
> installer. See Java's [jpackage documentation](https://docs.oracle.com/en/java/javase/21/jpackage/packaging-overview.html)
> for more details of tool requirements.
To see all available tasks run: `./gradlew tasks --all`
## Testing development code ##
`Logisim-evolution` is often updated.
The [branch `main`](https://github.com/logisim-evolution/logisim-evolution/tree/main)
is the place where all the work on next release happens.
Once the code reaches the point it is ready, the next public release will be created from `main`.
If you want to contribute, or even just see what we are currently working on, checkout the `main` branch
and build `Logisim-evolution` from source as described above.
**If you see any issues or have any ideas for improvement, please
[create a ticket](https://github.com/logisim-evolution/logisim-evolution/issues) to make `Logisim-evolution` better!**
---
## How to contribute ##
If you want to contribute to Logisim-evolution, this is how to do it:
* Make a local *fork* of `Logisim-evolution` by clicking the *Fork* button on the
[project GitHub page](https://github.com/logisim-evolution/logisim-evolution). This will create
a copy of the `Logisim-evolution` repository on your own GitHub account.
* As all the development happens on [`main` branch](https://github.com/logisim-evolution/logisim-evolution/tree/main),
ensure you checkout [`main` branch](https://github.com/logisim-evolution/logisim-evolution/tree/main) before you
create your own branch.
* Fix the bugs you want to fix on your local fork of the
[`main` branch](https://github.com/logisim-evolution/logisim-evolution/tree/main).
* Add the features you want to add on your local fork.
* Add/modify the documentation/language support on your local fork.
Once it is running without bugs on your local fork, request a *Pull request* by:
* Go to the *Pull request*-tab and click the button *New pull request*.
* Click on *compare across forks*.
* On the right-hand side select your fork, for example: *head repository: BFH-ktt1/logisim-evolution*
* On the right-hand side select your branch, for example: *base: bugfixes*
* On the left-hand side select the main branch *base: main* (**Important:** All pull requests **MUST**
be made against [that branch](https://github.com/logisim-evolution/logisim-evolution/tree/main).
* Make sure that there are no conflicts reported.
Use the [Logisim Revolution repository](https://git.briggen.dev/NilsBriggen/Logisim-Revolution) for this fork's source and issue reporting. For the history and people behind the original Logisim and Logisim-evolution projects, see the [credits](credits.md).
+3 -3
View File
@@ -1,7 +1,7 @@
# DO NOT QUOTE STRINGS!
#
group = com.cburch
name = logisim-evolution
group = dev.briggen
name = logisim-revolution
org.gradle.configuration-cache = true
@@ -16,7 +16,7 @@ org.gradle.configuration-cache = true
version = 5.1.0-dev
# Project website URL (the shorter the better)
url = https://github.com/logisim-evolution/
url = https://git.briggen.dev/NilsBriggen/Logisim-Revolution
# Fix sonar warning
systemProp.sonar.gradle.skipCompile = true
+1 -1
View File
@@ -7,4 +7,4 @@
* in the user guide at https://docs.gradle.org/5.0/userguide/multi_project_builds.html
*/
rootProject.name = "logisim-evolution"
rootProject.name = "logisim-revolution"
-12
View File
@@ -1,12 +0,0 @@
[Desktop Entry]
Name=Logisim-evolution
Comment=Logisim-evolution is educational software for designing and simulating digital logic circuits.
GenericName=Digital logic design tool and simulator
Exec=logisim-evolution %f
Icon=${SNAP}/meta/gui/logisim-icon-128.png
Type=Application
StartupNotify=false
Categories=Electronics
MimeType=application/x-logisim-circuit;
Actions=new-empty-window;
Keywords=circuits;simulation;logic;HDL;chronogram;
+18 -18
View File
@@ -1,17 +1,17 @@
#
# Control file to build SNAP package of LSe
# https://github.com/logisim-evolution/logisim-evolution
# Control file to build the Logisim Revolution Snap package
# https://git.briggen.dev/NilsBriggen/Logisim-Revolution
# https://snapcraft.io/
#
# Marcin Orlowski
#
name: logisim-evolution
title: Logisim Evolution
name: logisim-revolution
title: Logisim Revolution
summary: Digital logic design tool and simulator
description: |
Logisim-evolution is educational software for designing and simulating digital logic circuits.
Logisim-evolution is free, open-source, and cross-platform.
Logisim Revolution is educational software for designing and simulating digital logic circuits.
Logisim Revolution is free, open-source, and cross-platform.
Project highlights:
* easy to use circuit designer,
@@ -23,13 +23,13 @@ description: |
* huge library of components (LEDs, TTLs, switches, SoCs),
* supports multiple languages,
* and more!
website: https://github.com/logisim-evolution/logisim-evolution
contact: https://github.com/logisim-evolution/logisim-evolution/issues
issues: https://github.com/logisim-evolution/logisim-evolution/issues
source-code: https://github.com/logisim-evolution/logisim-evolution
website: https://git.briggen.dev/NilsBriggen/Logisim-Revolution
contact: https://git.briggen.dev/NilsBriggen/Logisim-Revolution/issues
issues: https://git.briggen.dev/NilsBriggen/Logisim-Revolution/issues
source-code: https://git.briggen.dev/NilsBriggen/Logisim-Revolution
license: GPL-3.0
adopt-info: logisim-evolution
icon: snap/gui/logisim-icon-128.png
adopt-info: logisim-revolution
icon: snap/gui/logisim-revolution-icon-128.png
base: core24
grade: stable
@@ -41,9 +41,9 @@ platforms:
arm64:
apps:
logisim-evolution:
logisim-revolution:
extensions: [gnome]
command: usr/bin/logisim-evolution-wrapper
command: usr/bin/logisim-revolution-wrapper
plugs:
- desktop
- desktop-legacy
@@ -56,7 +56,7 @@ apps:
- removable-media
parts:
logisim-evolution:
logisim-revolution:
plugin: gradle
source: .
gradle-task: shadowJar
@@ -66,15 +66,15 @@ parts:
- --console=plain
override-pull: |
craftctl default
LSE_VERSION="$(cat gradle.properties | grep '^version\s*=.*' | awk '{print $3}')"
craftctl set version="${LSE_VERSION}"
REVOLUTION_VERSION="$(cat gradle.properties | grep '^version\s*=.*' | awk '{print $3}')"
craftctl set version="${REVOLUTION_VERSION}"
build-packages:
- gradle
- openjdk-21-jdk
stage-packages:
- openjdk-21-jre
organize:
jar/logisim-evolution-*-all.jar: jar/logisim-evolution.jar
jar/logisim-revolution-*-all.jar: jar/logisim-revolution.jar
local-parts:
plugin: dump
@@ -9,33 +9,12 @@
package com.cburch.draw.icons;
import com.cburch.logisim.gui.icons.BaseIcon;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
public class DrawCurveIcon extends BaseIcon {
import com.cburch.logisim.gui.icons.ActionIcon;
import com.cburch.logisim.gui.theme.AppIcons;
public class DrawCurveIcon extends ActionIcon {
@Override
protected void paintIcon(Graphics2D gfx) {
final var wh = scale(3);
gfx.setStroke(new BasicStroke(scale(1)));
gfx.setColor(Color.GRAY);
gfx.drawRect(scale(9), scale(0), wh, wh);
gfx.setStroke(new BasicStroke(scale(2)));
gfx.setColor(Color.BLUE.darker());
final var p = new GeneralPath();
p.moveTo(scale(1), scale(5));
p.quadTo(scale(10), scale(1), scale(14), scale(14));
gfx.draw(p);
gfx.setColor(Color.GRAY);
gfx.setStroke(new BasicStroke(scale(1)));
gfx.drawRect(scale(13), scale(13), wh, wh);
gfx.drawRect(scale(0), scale(5), wh, wh);
protected AppIcons.Id iconId() {
return AppIcons.Id.DRAW_CURVE;
}
}
@@ -9,28 +9,12 @@
package com.cburch.draw.icons;
import com.cburch.logisim.gui.icons.BaseIcon;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
public class DrawLineIcon extends BaseIcon {
import com.cburch.logisim.gui.icons.ActionIcon;
import com.cburch.logisim.gui.theme.AppIcons;
public class DrawLineIcon extends ActionIcon {
@Override
protected void paintIcon(Graphics2D gfx) {
gfx.setColor(Color.GRAY);
gfx.setStroke(new BasicStroke(AppPreferences.getScaled(1)));
final var wh = AppPreferences.getScaled(3);
gfx.setStroke(new BasicStroke(scale(2)));
gfx.setColor(Color.BLUE.darker());
gfx.drawLine(scale(1), scale(14), scale(14), scale(1));
gfx.setColor(Color.GRAY);
gfx.setStroke(new BasicStroke(AppPreferences.getScaled(1)));
gfx.drawRect(scale(13), 0, wh, wh);
gfx.drawRect(0, scale(13), wh, wh);
protected AppIcons.Id iconId() {
return AppIcons.Id.DRAW_LINE;
}
}
@@ -9,42 +9,18 @@
package com.cburch.draw.icons;
import com.cburch.logisim.gui.icons.BaseIcon;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import com.cburch.logisim.gui.icons.ActionIcon;
import com.cburch.logisim.gui.theme.AppIcons;
public class DrawPolylineIcon extends BaseIcon {
private static final int[] points = {1, 14, 1, 1, 7, 8, 13, 4, 10, 13};
private boolean isPolylineClosed = false;
public class DrawPolylineIcon extends ActionIcon {
private final boolean closed;
public DrawPolylineIcon(boolean closed) {
this.isPolylineClosed = closed;
this.closed = closed;
}
@Override
protected void paintIcon(Graphics2D gfx) {
gfx.setStroke(new BasicStroke(AppPreferences.getScaled(2)));
gfx.setColor(Color.BLUE.darker());
final var p = new GeneralPath();
var i = 0;
p.moveTo(AppPreferences.getScaled(points[i++]), AppPreferences.getScaled(points[i++]));
for (; i < points.length - 1; i += 2) {
p.lineTo(AppPreferences.getScaled(points[i]), AppPreferences.getScaled(points[i + 1]));
}
if (isPolylineClosed) p.closePath();
gfx.draw(p);
gfx.setStroke(new BasicStroke(AppPreferences.getScaled(1)));
gfx.setColor(Color.GRAY);
final var wh = AppPreferences.getScaled(3);
for (i = 0; i <= points.length - 1; i += 2)
gfx.drawRect(
AppPreferences.getScaled(points[i] - 1),
AppPreferences.getScaled(points[i + 1] - 1),
wh,
wh);
protected AppIcons.Id iconId() {
return closed ? AppIcons.Id.DRAW_POLYGON : AppIcons.Id.DRAW_POLYLINE;
}
}
@@ -9,55 +9,25 @@
package com.cburch.draw.icons;
import com.cburch.logisim.gui.icons.BaseIcon;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
public class DrawShapeIcon extends BaseIcon {
import com.cburch.logisim.gui.icons.ActionIcon;
import com.cburch.logisim.gui.theme.AppIcons;
public class DrawShapeIcon extends ActionIcon {
public static final int RECTANGLE = 0;
public static final int ROUNDED_RECTANGLE = 1;
public static final int ELIPSE = 2;
private static final int[] points = {3, 2, 8, 4, 14, 8};
private final int shapeType;
public DrawShapeIcon(int type) {
this.shapeType = type;
shapeType = type;
}
@Override
protected void paintIcon(Graphics2D gfx) {
final var state = 3;
gfx.setStroke(new BasicStroke(AppPreferences.getScaled(2)));
final var x = scale(1);
final var y = scale(3);
final var width = scale(points[(state - 1) * 2]);
final var height = scale(points[(state - 1) * 2 + 1]);
switch (shapeType) {
case RECTANGLE -> {
gfx.setColor(Color.BLUE.darker());
gfx.drawRect(x, y, width, height);
}
case ROUNDED_RECTANGLE -> {
gfx.setColor(Color.RED.darker());
// FIXME: rounded rect shape looks almost as regular rectangle on smaller zoom factor
gfx.drawRoundRect(x, y, width, height, y, y);
}
default -> {
gfx.setColor(Color.BLUE);
gfx.drawOval(x, y, width, height);
}
}
gfx.setColor(Color.GRAY);
gfx.setStroke(new BasicStroke(AppPreferences.getScaled(1)));
gfx.drawRect(0, y - x, y, y);
gfx.drawRect(width - x, y + height - x, y, y);
protected AppIcons.Id iconId() {
return switch (shapeType) {
case RECTANGLE -> AppIcons.Id.DRAW_RECTANGLE;
case ROUNDED_RECTANGLE -> AppIcons.Id.DRAW_ROUNDED_RECTANGLE;
default -> AppIcons.Id.DRAW_ELLIPSE;
};
}
}
+4 -2
View File
@@ -13,6 +13,7 @@ import com.cburch.logisim.generated.BuildInfo;
import com.cburch.logisim.gui.generic.OptionPane;
import com.cburch.logisim.gui.start.Startup;
import com.cburch.logisim.gui.theme.Theme;
import com.cburch.logisim.prefs.PreferenceBootstrap;
import java.awt.GraphicsEnvironment;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -31,6 +32,7 @@ public class Main {
// System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "debug");
System.setProperty("apple.awt.application.name", BuildInfo.name);
PreferenceBootstrap.offerImportIfInteractive(args);
try {
if (!GraphicsEnvironment.isHeadless()) {
SwingUtilities.invokeAndWait(Theme::install);
@@ -56,8 +58,8 @@ public class Main {
public static boolean headless = false;
// FloppyDisk unicode character: https://charbase.com/1f4be-unicode-floppy-disk
public static final String DIRTY_MARKER = "\ud83d\udcbe";
/** Uncommitted document marker in the window title. */
public static final String DIRTY_MARKER = "●";
public static boolean hasGui() {
return !headless;
@@ -15,17 +15,20 @@ import com.cburch.logisim.analyze.data.CsvInterpretor;
import com.cburch.logisim.analyze.data.CsvParameter;
import com.cburch.logisim.gui.generic.Dialogs;
import com.cburch.logisim.gui.generic.OptionPane;
import com.cburch.logisim.gui.theme.Tokens;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.Color;
import com.cburch.logisim.gui.generic.ScrollableForm;
import com.cburch.logisim.util.UiScale;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.List;
import java.util.Arrays;
import java.util.Scanner;
import javax.swing.BorderFactory;
import javax.swing.JButton;
@@ -34,140 +37,183 @@ import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
public class CsvReadParameterDialog extends JDialog implements ActionListener {
private static final long serialVersionUID = 1L;
private final JComboBox<String> quotes;
private final JComboBox<String> seperators;
private final JButton okButton;
private final JLabel[] labels;
private final PreviewTable preview = new PreviewTable();
private final File file;
private static final long serialVersionUID = 1L;
private final CsvParameter param;
private final String sepSpace;
private boolean setVisible;
private final ImportSettings draft;
public CsvReadParameterDialog(CsvParameter sel, File file, JFrame parentFrame) {
super(parentFrame);
setVisible = true;
super(parentFrame, S.get("csvImportTitle"), true);
this.file = file;
sepSpace = S.get("seperatorSpace");
final String[] possibleSeperators = {",", ";", ":", sepSpace, S.get("SeperatorTab")};
final String[] possibleQuotes = {"\"", "'"};
draft = new ImportSettings(sel);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
Dialogs.installEscapeToClose(this);
final var gap = UiScale.scaled(12);
final var content = new JPanel(new BorderLayout(gap, gap));
content.setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap));
setContentPane(content);
quotes = new JComboBox<>(new String[] {"\"", "'"});
quotes.setSelectedItem(String.valueOf(draft.quote()));
seperators = new JComboBox<>(new String[] {",", ";", ":", S.get("seperatorSpace"),
S.get("SeperatorTab")});
final var separatorIndex = ",;: \t".indexOf(draft.seperator());
seperators.setSelectedIndex(Math.max(0, separatorIndex));
quotes.addActionListener(this);
seperators.addActionListener(this);
final var form = new JPanel(new GridBagLayout());
final var gbc = new GridBagConstraints();
gbc.insets = new Insets(0, 0, UiScale.scaled(8), gap);
gbc.anchor = GridBagConstraints.LINE_START;
gbc.gridx = 0;
gbc.gridy = 0;
final var quoteLabel = new JLabel(S.get("UsedQuotesInFile"));
quoteLabel.setLabelFor(quotes);
form.add(quoteLabel, gbc);
gbc.gridy++;
final var separatorLabel = new JLabel(S.get("UsedSeperatorInFile"));
separatorLabel.setLabelFor(seperators);
form.add(separatorLabel, gbc);
gbc.gridx = 1;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
form.add(quotes, gbc);
gbc.gridy++;
form.add(seperators, gbc);
content.add(form, BorderLayout.NORTH);
final var previewPanel = new JPanel(new BorderLayout(0, UiScale.scaled(8)));
final var previewLabel = new JLabel(S.get("cvsFilePreview"));
previewLabel.setLabelFor(preview);
preview.getAccessibleContext().setAccessibleName(S.get("cvsFilePreview"));
previewPanel.add(previewLabel, BorderLayout.NORTH);
previewPanel.add(new JScrollPane(preview), BorderLayout.CENTER);
content.add(previewPanel, BorderLayout.CENTER);
okButton = new JButton(S.get("ConfirmCsvParameters"));
okButton.addActionListener(this);
getRootPane().setDefaultButton(okButton);
Dialogs.installEscapeToClose(this);
quotes = new JComboBox<>(possibleQuotes);
quotes.addActionListener(this);
seperators = new JComboBox<>(possibleSeperators);
seperators.addActionListener(this);
final var cancel = new JButton(S.get("buildCancel"));
cancel.addActionListener(event -> dispose());
final var buttons = new JPanel(new FlowLayout(FlowLayout.TRAILING, gap, 0));
buttons.add(cancel);
buttons.add(okButton);
content.add(buttons, BorderLayout.SOUTH);
if (!updatePreview()) {
dispose();
return;
}
ScrollableForm.sizeWindow(this, new Dimension(620, 340), new Dimension(420, 280), true);
setLocationRelativeTo(parentFrame);
this.param = sel;
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
setLayout(gbl);
gbc.gridy = 0;
gbc.gridx = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridwidth = 3;
add(new JLabel(S.get("UsedQuotesInFile")), gbc);
gbc.gridx = 3;
gbc.gridwidth = 1;
add(quotes, gbc);
gbc.gridy = 1;
gbc.gridx = 0;
gbc.gridwidth = 3;
add(new JLabel(S.get("UsedSeperatorInFile")), gbc);
gbc.gridx = 3;
gbc.gridwidth = 1;
add(seperators, gbc);
gbc.gridx = 0;
gbc.gridy = 2;
gbc.gridwidth = 4;
gbc.fill = GridBagConstraints.CENTER;
add(new JLabel(S.get("cvsFilePreview")), gbc);
pack();
var celwidth = AppPreferences.getScaled(200);
var celHeight = AppPreferences.getScaled(25);
gbc.gridwidth = 1;
labels = new JLabel[16];
final var border = BorderFactory.createLineBorder(Tokens.divider(), AppPreferences.getScaled(1));
final var dim = new Dimension(celwidth, celHeight);
for (int x = 0; x < 4; x++)
for (int y = 0; y < 4; y++) {
final var j = new JPanel();
labels[y * 4 + x] = new JLabel(x + "," + y);
j.setBorder(border);
j.setPreferredSize(dim);
j.add(labels[y * 4 + x]);
gbc.gridx = x;
gbc.gridy = 3 + y;
add(j, gbc);
}
gbc.gridwidth = 4;
gbc.gridy = 7;
gbc.gridx = 0;
add(okButton, gbc);
pack();
updateLabels();
setModal(true);
setVisible(setVisible);
setVisible(true);
}
private void updateLabels() {
try {
final var scan = new Scanner(file);
for (int y = 0; y < 4; y++) {
List<String> line = null;
if (scan.hasNext())
line = CsvInterpretor.parseCsvLine(scan.next(), param.seperator(), param.quote());
for (int x = 0; x < 4; x++) {
if (line == null || x >= line.size()) {
labels[y * 4 + x].setText("");
} else {
labels[y * 4 + x].setText(line.get(x));
}
/** Match the import parser, but limit preview work to the advertised four rows/columns. */
static String[][] readPreview(File file, CsvParameter parameters) throws FileNotFoundException {
final var values = new String[4][4];
for (final var row : values) Arrays.fill(row, "");
try (final var scanner = new Scanner(file)) {
for (var y = 0; y < values.length && scanner.hasNextLine(); y++) {
final var row = CsvInterpretor.parseCsvLine(
scanner.nextLine(), parameters.seperator(), parameters.quote());
for (var x = 0; x < Math.min(row.size(), values[y].length); x++) {
values[y][x] = row.get(x) == null ? "" : row.get(x);
}
}
scan.close();
} catch (FileNotFoundException e) {
OptionPane.showMessageDialog(
this,
S.get("cantReadMessage", file.getName()),
S.get("openButton"),
OptionPane.ERROR_MESSAGE);
setVisible = false;
if (this.isVisible()) {
setVisible(false);
dispose();
}
}
return values;
}
private boolean updatePreview() {
try {
preview.setValues(readPreview(file, draft));
return true;
} catch (FileNotFoundException exception) {
OptionPane.showMessageDialog(this, S.get("cantReadMessage", file.getName()),
S.get("csvImportTitle"), OptionPane.ERROR_MESSAGE);
dispose();
return false;
}
}
@Override
public void actionPerformed(ActionEvent e) {
if (param == null) return;
if (e.getSource() == quotes) {
final var sel = (String) quotes.getSelectedItem();
param.setQuote(sel.charAt(0));
updateLabels();
}
if (e.getSource() == seperators) {
final var sel = (String) seperators.getSelectedItem();
if (sel.length() == 1) {
param.setSeperator(sel.charAt(0));
} else if (sel.equals(sepSpace)) {
param.setSeperator(' ');
} else param.setSeperator('\t');
updateLabels();
}
if (e.getSource() == okButton) {
param.setValid();
setVisible(false);
public void actionPerformed(ActionEvent event) {
if (event.getSource() == okButton) {
draft.approve();
dispose();
} else {
draft.setQuote(((String) quotes.getSelectedItem()).charAt(0));
draft.setSeperator(",;: \t".charAt(seperators.getSelectedIndex()));
updatePreview();
}
}
static class ImportSettings extends CsvParameter {
private final CsvParameter target;
ImportSettings(CsvParameter target) {
this.target = target;
setQuote(target.quote());
setSeperator(target.seperator());
}
void approve() {
target.setQuote(quote());
target.setSeperator(seperator());
target.setValid();
}
}
static class PreviewTable extends JTable {
private static final long serialVersionUID = 1L;
PreviewTable() {
setValues(new String[4][4]);
setAutoResizeMode(AUTO_RESIZE_OFF);
getTableHeader().setReorderingAllowed(false);
setFillsViewportHeight(true);
}
void setValues(String[][] values) {
setModel(new DefaultTableModel(values, new String[] {"1", "2", "3", "4"}) {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
});
updateMetrics();
}
@Override
public void updateUI() {
super.updateUI();
updateMetrics();
}
private void updateMetrics() {
if (getFont() == null || getColumnModel() == null) return;
setRowHeight(getFontMetrics(getFont()).getHeight() + UiScale.scaled(10));
for (var i = 0; i < getColumnCount(); i++) {
getColumnModel().getColumn(i).setPreferredWidth(UiScale.scaled(180));
}
setPreferredScrollableViewportSize(new Dimension(UiScale.scaled(560), 4 * getRowHeight()));
}
@Override
public String getToolTipText(MouseEvent event) {
final var row = rowAtPoint(event.getPoint());
final var column = columnAtPoint(event.getPoint());
final var value = row < 0 || column < 0 ? null : getValueAt(row, column);
return value == null ? null : value.toString();
}
}
}
@@ -19,15 +19,15 @@ import com.cburch.logisim.data.Value;
import com.cburch.logisim.fpga.designrulecheck.CorrectLabel;
import com.cburch.logisim.fpga.designrulecheck.netlistComponent;
import com.cburch.logisim.fpga.hdlgenerator.HdlGeneratorFactory;
import com.cburch.logisim.gui.icons.ComponentIcons;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.IconsUtil;
import com.cburch.logisim.util.StringGetter;
import com.cburch.logisim.util.StringUtil;
import java.awt.Color;
import javax.swing.Icon;
public abstract class AbstractComponentFactory implements ComponentFactory {
private static final Icon toolIcon = IconsUtil.getIcon("subcirc.gif");
private static final Icon toolIcon = ComponentIcons.module();
private AttributeSet defaultSet;
private final HdlGeneratorFactory myHDLGenerator;
@@ -10,6 +10,7 @@
package com.cburch.logisim.file;
import static com.cburch.logisim.file.Strings.S;
import com.cburch.logisim.AppIdentity;
import com.cburch.logisim.gui.generic.OptionPane;
import com.cburch.logisim.std.Builtin;
import com.cburch.logisim.tools.Library;
@@ -28,8 +29,7 @@ import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -137,8 +137,12 @@ public class Loader implements LibraryLoader {
public static final String LOGISIM_PROJECT_BUNDLE_INFO_FILE = "LogisimEvolutionBundle.info";
public static final String LOGISIM_LIBRARY_DIR = "library";
public static final String LOGISIM_CIRCUIT_DIR = "circuit";
public static final String LOGISIM_UNNAMED_AUTOSAVE_PREFIX = ".logisim-unnamed-autosave_";
public static final String LOGISIM_UNNAMED_AUTOSAVE_PREFIX = "unnamed-";
public static final String LOGISIM_UNNAMED_AUTOSAVE_SUFFIX = ".circ.autosave";
public static File getUnnamedAutosaveDirectory() {
return AppIdentity.unnamedRecoveryDirectory();
}
public static final FileFilter LOGISIM_FILTER = new LogisimFileFilter();
public static final FileFilter LOGISIM_BUNDLE_FILTER = new LogisimProjectBundleFilter();
public static final FileFilter LOGISIM_DIRECTORY = new LogisimDirectoryFilter();
@@ -175,21 +179,16 @@ public class Loader implements LibraryLoader {
return null;
}
// Determine the autosave file name for any .circ file as .<basename>.circ.autosave
// Revolution sidecars cannot be mistaken for Evolution recovery of the same .circ file.
private static File determineAutosaveName(File base) {
if (base == null) {
String timestamp =
DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
final var candidate = new File(System.getProperty("user.home") + File.separator
+ LOGISIM_UNNAMED_AUTOSAVE_PREFIX + timestamp + LOGISIM_UNNAMED_AUTOSAVE_SUFFIX);
if (!candidate.exists()) return candidate;
return null;
return new File(getUnnamedAutosaveDirectory(),
LOGISIM_UNNAMED_AUTOSAVE_PREFIX + UUID.randomUUID()
+ LOGISIM_UNNAMED_AUTOSAVE_SUFFIX);
}
var extension = ".autosave";
if (!base.getName().endsWith(LOGISIM_EXTENSION)) extension = ".circ.autosave";
final var dir = base.getParentFile();
final var name = "." + base.getName() + extension;
return new File(dir, name);
final var extension = base.getName().endsWith(LOGISIM_EXTENSION)
? ".revolution.autosave" : ".circ.revolution.autosave";
return new File(base.getParentFile(), "." + base.getName() + extension);
}
static Optional<File> findAutosaveFile(File base) {
@@ -533,6 +532,7 @@ public class Loader implements LibraryLoader {
}
Path staged = null;
try {
if (mainFile == null) Files.createDirectories(destination.toPath().getParent());
staged = stageWrite(file, destination);
publishWrite(staged, destination.toPath());
staged = null;
@@ -21,8 +21,7 @@ import javax.swing.JOptionPane;
import com.cburch.draw.canvas.Canvas;
import com.cburch.draw.toolbar.ToolbarClickableItem;
import com.cburch.logisim.gui.generic.OptionPane;
import com.cburch.logisim.gui.icons.ArithmeticIcon;
import com.cburch.logisim.gui.icons.CircuitIcon;
import com.cburch.logisim.gui.theme.AppIcons;
public class ResetAppearanceTool implements ToolbarClickableItem {
@@ -33,7 +32,7 @@ public class ResetAppearanceTool implements ToolbarClickableItem {
public ResetAppearanceTool(Canvas canvas, boolean isClear) {
this.canvas = (canvas instanceof AppearanceCanvas appearanceCanvas) ? appearanceCanvas : null;
this.isClear = isClear;
icon = isClear ? new ArithmeticIcon("RST", 3) : new CircuitIcon();
icon = AppIcons.get(isClear ? AppIcons.Id.RESET : AppIcons.Id.CIRCUIT);
}
@Override
@@ -25,8 +25,8 @@ import javax.swing.JFrame;
public class LFrame extends JFrame implements WindowClosable {
private static final long serialVersionUID = 1L;
private static final String pathBasePart = "resources/logisim/img/logisim-icon-";
private static final int[] sizes = {16, 32, 48, 64, 128};
private static final String pathBasePart = "resources/logisim/img/logisim-revolution-icon-";
private static final int[] sizes = {16, 32, 48, 64, 128, 256};
private static final int defaultSize = 48;
private static List<Image> allIcons = null;
private static Image defaultIcon = null;
@@ -9,56 +9,11 @@
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.circuit.SubcircuitFactory;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
public class AppearEditIcon extends BaseIcon {
private final int[] tip = {0, 14, 1, 15, 0, 15};
private final int[] body = {0, 13, 13, 0, 15, 2, 2, 15};
private final int[] extendedtip = {0, 13, 1, 12, 3, 15, 2, 15};
private final int[] cleantip = {12, 1, 13, 0, 15, 2, 14, 3};
import com.cburch.logisim.gui.theme.AppIcons;
public class AppearEditIcon extends ActionIcon {
@Override
protected void paintIcon(Graphics2D g2) {
final var g = (Graphics2D) g2.create();
SubcircuitFactory.paintClasicIcon(g);
g.dispose();
g2.setColor(Color.MAGENTA);
var path = new GeneralPath();
path.moveTo(AppPreferences.getScaled(tip[0]), AppPreferences.getScaled(tip[1]));
for (int i = 2; i < tip.length; i += 2) {
path.lineTo(AppPreferences.getScaled(tip[i]), AppPreferences.getScaled(tip[i + 1]));
}
path.closePath();
g2.fill(path);
g2.setColor(new Color(139, 69, 19));
path = new GeneralPath();
path.moveTo(AppPreferences.getScaled(body[0]), AppPreferences.getScaled(body[1]));
for (int i = 2; i < body.length; i += 2) {
path.lineTo(AppPreferences.getScaled(body[i]), AppPreferences.getScaled(body[i + 1]));
}
path.closePath();
g2.fill(path);
g2.setColor(new Color(210, 180, 140));
path = new GeneralPath();
path.moveTo(AppPreferences.getScaled(extendedtip[0]), AppPreferences.getScaled(extendedtip[1]));
for (int i = 2; i < extendedtip.length; i += 2) {
path.lineTo(
AppPreferences.getScaled(extendedtip[i]), AppPreferences.getScaled(extendedtip[i + 1]));
}
path.closePath();
g2.fill(path);
g2.setColor(Color.GRAY);
path = new GeneralPath();
path.moveTo(AppPreferences.getScaled(cleantip[0]), AppPreferences.getScaled(cleantip[1]));
for (int i = 2; i < cleantip.length; i += 2) {
path.lineTo(AppPreferences.getScaled(cleantip[i]), AppPreferences.getScaled(cleantip[i + 1]));
}
path.closePath();
g2.fill(path);
protected AppIcons.Id iconId() {
return AppIcons.Id.APPEARANCE;
}
}
@@ -1,13 +1,21 @@
/*
* 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.gui.icons;
import java.awt.Graphics2D;
import com.cburch.logisim.circuit.SubcircuitFactory;
/** Semantic module thumbnail, shared with the component picker. */
public class CircuitIcon extends BaseIcon {
@Override
protected void paintIcon(Graphics2D g2d) {
SubcircuitFactory.paintEvolutionIcon(g2d);
protected void paintIcon(Graphics2D graphics) {
ComponentIcons.module().paintIcon(null, graphics, 0, 0);
}
}
@@ -9,35 +9,11 @@
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.util.GraphicsUtil;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
public class CompileIcon extends BaseIcon {
import com.cburch.logisim.gui.theme.AppIcons;
public class CompileIcon extends ActionIcon {
@Override
protected void paintIcon(Graphics2D g2) {
final var page = new int[] {0, 0, 0, 15, 15, 15, 15, 5, 10, 5, 10, 0, 15, 5, 10, 0, 0, 0};
g2.setColor(Color.BLACK);
g2.setStroke(new BasicStroke(AppPreferences.getScaled(1F)));
final var xpos = new int[9];
final var ypos = new int[9];
for (var i = 0; i < 9; i++) {
xpos[i] = AppPreferences.getScaled(page[i * 2]);
ypos[i] = AppPreferences.getScaled(page[i * 2 + 1]);
}
g2.drawPolygon(xpos, ypos, 9);
final var f = g2.getFont();
g2.setFont(f.deriveFont(AppPreferences.getScaled(4F)));
g2.setColor(Color.BLUE);
GraphicsUtil.drawCenteredText(
g2, "j r9", AppPreferences.getScaled(7), AppPreferences.getScaled(3));
GraphicsUtil.drawCenteredText(
g2, "nop", AppPreferences.getScaled(7), AppPreferences.getScaled(7));
g2.setColor(Color.MAGENTA);
GraphicsUtil.drawCenteredText(
g2, "101..", AppPreferences.getScaled(7), AppPreferences.getScaled(11));
protected AppIcons.Id iconId() {
return AppIcons.Id.COMPILE;
}
}
@@ -9,14 +9,11 @@
package com.cburch.logisim.gui.icons;
import java.awt.Color;
import java.awt.Font;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.Graphics2D;
import java.awt.font.TextLayout;
import java.awt.geom.GeneralPath;
public class DrcIcon extends BaseIcon {
public final boolean drawEmpty;
public DrcIcon(boolean isDrcError) {
@@ -24,56 +21,10 @@ public class DrcIcon extends BaseIcon {
}
@Override
protected void paintIcon(Graphics2D g2) {
if (drawEmpty) return;
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, getIconWidth(), getIconHeight());
var p = new GeneralPath();
p.moveTo(scale(15), 0);
p.quadTo(scale(7), scale(7), 0, scale(6));
p.lineTo(0, scale(8));
p.quadTo(scale(7), scale(9), scale(15), scale(2));
p.closePath();
g2.setColor(Color.RED.brighter().brighter());
g2.fill(p);
paintText(g2);
p = new GeneralPath();
p.moveTo(0, scale(6));
p.quadTo(scale(3), scale(14), scale(12), scale(11));
p.lineTo(scale(12), scale(9));
p.lineTo(scale(15), scale(12));
p.lineTo(scale(12), scale(15));
p.lineTo(scale(12), scale(13));
p.quadTo(scale(3), scale(16), 0, scale(8));
p.closePath();
g2.setColor(Color.RED.darker());
g2.fill(p);
}
private void paintText(Graphics2D g2) {
final var f = g2.getFont().deriveFont(scale((float) getIconWidth() / 3)).deriveFont(Font.BOLD);
final var t = new TextLayout("DRC", f, g2.getFontRenderContext());
g2.setColor(Color.BLUE.darker().darker());
t.draw(
g2,
getIconWidth() / 2 - (float) t.getBounds().getCenterX(),
getIconHeight() / 2 - (float) t.getBounds().getCenterY());
}
private void paintText(Graphics2D g2, String s) {
final var f =
g2.getFont()
.deriveFont(scale((float) getIconWidth() / (float) s.length()))
.deriveFont(Font.BOLD);
final var t = new TextLayout(s, f, g2.getFontRenderContext());
g2.setColor(Color.RED.darker().darker());
t.draw(
g2,
getIconWidth() / 2 - (float) t.getBounds().getCenterX(),
getIconHeight() / 2 - (float) t.getBounds().getCenterY());
protected void paintIcon(Graphics2D graphics) {
// An absent DRC error is not proof that checks have run successfully.
if (!drawEmpty) {
AppIcons.get(AppIcons.Id.ERROR, AppPreferences.IconSize).paintIcon(null, graphics, 0, 0);
}
}
}
@@ -10,35 +10,20 @@
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
import com.cburch.logisim.gui.theme.AppIcons;
public class FatArrowIcon extends BaseIcon {
public class FatArrowIcon extends ActionIcon {
private final Direction direction;
private static final int[] points = {2, 7, 7, 2, 12, 7, 9, 7, 9, 12, 5, 12, 5, 7};
private final Direction dir;
public FatArrowIcon(Direction dir) {
this.dir = dir;
public FatArrowIcon(Direction direction) {
this.direction = direction;
}
@Override
protected void paintIcon(Graphics2D g2) {
g2.translate(AppPreferences.getScaled(7), AppPreferences.getScaled(7));
if (dir.equals(Direction.WEST)) g2.rotate((6 * Math.PI) / 4);
else if (dir.equals(Direction.SOUTH)) g2.rotate(Math.PI);
else if (dir.equals(Direction.EAST)) g2.rotate(Math.PI / 2);
g2.translate(-AppPreferences.getScaled(7), -AppPreferences.getScaled(7));
g2.setColor(Color.blue);
final var path = new GeneralPath();
path.moveTo(AppPreferences.getScaled(points[0]), AppPreferences.getScaled(points[1]));
for (var i = 2; i < points.length; i += 2)
path.lineTo(AppPreferences.getScaled(points[i]), AppPreferences.getScaled(points[i + 1]));
path.closePath();
g2.fill(path);
g2.setColor(Color.blue.darker());
g2.draw(path);
protected AppIcons.Id iconId() {
if (direction == Direction.WEST) return AppIcons.Id.ARROW_LEFT;
if (direction == Direction.SOUTH) return AppIcons.Id.ARROW_DOWN;
if (direction == Direction.EAST) return AppIcons.Id.ARROW_RIGHT;
return AppIcons.Id.ARROW_UP;
}
}
@@ -7,16 +7,14 @@
* This is free software released under GNU GPLv3 license
*/
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.vhdl.gui.HdlToolbarModel;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.font.TextLayout;
public class HdlIcon extends BaseIcon {
/** Compatibility wrapper for the three HDL editor actions. */
public class HdlIcon extends ActionIcon {
private final String type;
public HdlIcon(String type) {
@@ -24,45 +22,11 @@ public class HdlIcon extends BaseIcon {
}
@Override
protected void paintIcon(Graphics2D g2) {
g2.setColor(Color.WHITE);
g2.fillRect(0, scale(4), scale(16), scale(12));
g2.setColor(Color.BLACK);
g2.drawRect(0, scale(4), scale(16), scale(12));
g2.setColor(Color.LIGHT_GRAY);
final var font = g2.getFont().deriveFont((float) getIconWidth() / (float) 4.5);
var t = new TextLayout("LIBRARY", font, g2.getFontRenderContext());
t.draw(
g2,
(float) (getIconWidth() / 2 - t.getBounds().getCenterX()),
(float) ((3 * getIconHeight()) / 8 - t.getBounds().getCenterY()));
t = new TextLayout("USE iee", font, g2.getFontRenderContext());
t.draw(
g2,
(float) (getIconWidth() / 2 - t.getBounds().getCenterX()),
(float) ((5 * getIconHeight()) / 8 - t.getBounds().getCenterY()));
t = new TextLayout("ENTITY ", font, g2.getFontRenderContext());
t.draw(
g2,
(float) (getIconWidth() / 2 - t.getBounds().getCenterX()),
(float) ((7 * getIconHeight()) / 8 - t.getBounds().getCenterY()));
if (type.equals(HdlToolbarModel.HDL_VALIDATE)) {
g2.setColor(Color.RED);
g2.setStroke(new BasicStroke(scale(2)));
final int[] xpos = {scale(3), scale(6), scale(15)};
final int[] ypos = {scale(3), scale(11), 0};
g2.drawPolyline(xpos, ypos, 3);
} else {
if (type.equals(HdlToolbarModel.HDL_EXPORT)) {
g2.translate(scale(8), scale(4));
g2.rotate(Math.PI);
g2.translate(-scale(8), -scale(4));
}
g2.setColor(Color.MAGENTA.darker());
final int[] x = {scale(7), scale(7), scale(5), scale(8), scale(11), scale(9), scale(9)};
final int[] y = {0, scale(5), scale(5), scale(8), scale(5), scale(5), 0};
g2.fillPolygon(x, y, 7);
g2.drawPolygon(x, y, 7);
}
protected AppIcons.Id iconId() {
return switch (type) {
case HdlToolbarModel.HDL_VALIDATE -> AppIcons.Id.CHECK;
case HdlToolbarModel.HDL_EXPORT -> AppIcons.Id.EXPORT;
default -> AppIcons.Id.OPEN;
};
}
}
@@ -9,22 +9,11 @@
package com.cburch.logisim.gui.icons;
import java.awt.Color;
import java.awt.Graphics2D;
public class ImageIcon extends BaseIcon {
import com.cburch.logisim.gui.theme.AppIcons;
public class ImageIcon extends ActionIcon {
@Override
protected void paintIcon(Graphics2D g2) {
g2.drawRect(scale(1), scale(1), scale(13), scale(13));
// Enlarged yellow sun
g2.setColor(new Color(0xFA, 0xCC, 0x15));
g2.fillOval(scale(3), scale(3), scale(4), scale(4));
g2.setColor(new Color(0x22, 0xC5, 0x5E));
final int[] xPoints = {scale(2), scale(7), scale(10), scale(13), scale(13), scale(2)};
final int[] yPoints = {scale(13), scale(7), scale(10), scale(7), scale(13), scale(13)};
g2.fillPolygon(xPoints, yPoints, 6);
protected AppIcons.Id iconId() {
return AppIcons.Id.IMAGE;
}
}
@@ -9,72 +9,24 @@
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.Color;
import java.awt.Graphics2D;
public class OpenSaveIcon extends BaseIcon {
import com.cburch.logisim.gui.theme.AppIcons;
public class OpenSaveIcon extends ActionIcon {
public static final int FILE_OPEN = 0;
public static final int FILE_SAVE = 1;
public static final int FILE_SAVE_AS = 2;
private static final int[] arrowUp = new int[] {4, 3, 7, 0, 10, 3, 8, 3, 8, 6, 6, 6, 6, 3};
private static final int[] arrowDown = new int[] {6, 0, 8, 0, 8, 3, 10, 3, 7, 6, 4, 3, 6, 3};
private final int myType;
private final int type;
public OpenSaveIcon(int type) {
myType = type;
}
private Bounds getScaled(int x, int y, int width, int height) {
return Bounds.create(
AppPreferences.getScaled(x),
AppPreferences.getScaled(y),
AppPreferences.getScaled(width),
AppPreferences.getScaled(height));
this.type = type;
}
@Override
protected void paintIcon(Graphics2D g2) {
final var discCol = myType == FILE_SAVE_AS ? Color.GRAY : Color.BLUE;
var bds = getScaled(2, 2, 12, 12);
g2.setColor(discCol);
g2.fillRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight());
g2.setColor(Color.YELLOW);
bds = getScaled(4, 2, 8, 7);
g2.fillRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight());
g2.setColor(Color.LIGHT_GRAY);
bds = getScaled(6, 10, 4, 4);
g2.fillRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight());
g2.setColor(Color.BLACK);
bds = getScaled(8, 11, 1, 2);
g2.fillRect(bds.getX(), bds.getY(), bds.getWidth(), bds.getHeight());
g2.setColor(Color.MAGENTA);
final int[] xPoints;
final int[] yPoints;
switch (myType) {
case FILE_OPEN -> {
xPoints = new int[7];
yPoints = new int[7];
for (var i = 0; i < 7; i++) {
xPoints[i] = AppPreferences.getScaled(arrowUp[i * 2]);
yPoints[i] = AppPreferences.getScaled(arrowUp[i * 2 + 1]);
}
g2.fillPolygon(xPoints, yPoints, 7);
}
case FILE_SAVE_AS, FILE_SAVE -> {
xPoints = new int[7];
yPoints = new int[7];
for (var i = 0; i < 7; i++) {
xPoints[i] = AppPreferences.getScaled(arrowDown[i * 2]);
yPoints[i] = AppPreferences.getScaled(arrowDown[i * 2 + 1]);
}
g2.fillPolygon(xPoints, yPoints, 7);
}
default -> {
// do nothing. should not really happen.
}
}
protected AppIcons.Id iconId() {
return switch (type) {
case FILE_OPEN -> AppIcons.Id.OPEN;
case FILE_SAVE_AS -> AppIcons.Id.SAVE_AS;
default -> AppIcons.Id.SAVE;
};
}
}
@@ -9,70 +9,30 @@
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.TextLayout;
import java.awt.geom.GeneralPath;
public class ProjectAddIcon extends BaseIcon {
private static final int[] points = {
2, 6, 6, 6, 6, 2, 9, 2, 9, 6, 13, 6, 13, 9, 9, 9, 9, 13, 6, 13, 6, 9, 2, 9
};
private boolean removeIcon = false;
private boolean vhdl = false;
private boolean deselect = false;
private final AppIcons.Id id;
private boolean deselect;
public ProjectAddIcon() {
vhdl = true;
id = AppIcons.Id.HDL;
}
public ProjectAddIcon(boolean removeSymbol) {
removeIcon = removeSymbol;
id = removeSymbol ? AppIcons.Id.DELETE : AppIcons.Id.ADD;
}
public void setDeselect(boolean val) {
deselect = val;
public void setDeselect(boolean value) {
deselect = value;
}
@Override
protected void paintIcon(Graphics2D g2) {
if (deselect) {
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f));
}
if (vhdl) {
g2.setColor(Color.GREEN.darker().darker());
final var f =
g2.getFont()
.deriveFont((float) AppPreferences.getIconSize() / (float) 1.6)
.deriveFont(Font.BOLD);
var l1 = new TextLayout("VH", f, g2.getFontRenderContext());
var top = AppPreferences.getIconSize() / 4 - l1.getBounds().getCenterY();
var left = AppPreferences.getIconSize() / 2 - l1.getBounds().getCenterX();
l1.draw(g2, (float) left, (float) top);
l1 = new TextLayout("DL", f, g2.getFontRenderContext());
top = (3 * AppPreferences.getIconSize()) / 4 - l1.getBounds().getCenterY();
left = AppPreferences.getIconSize() / 2 - l1.getBounds().getCenterX();
l1.draw(g2, (float) left, (float) top);
g2.dispose();
return;
}
if (removeIcon) {
g2.setColor(Color.RED);
g2.translate(AppPreferences.getIconSize() / 2, AppPreferences.getIconSize() / 2);
g2.rotate(Math.PI / 4);
g2.translate(-AppPreferences.getIconSize() / 2, -AppPreferences.getIconSize() / 2);
} else g2.setColor(Color.GREEN.darker());
final var path = new GeneralPath();
path.moveTo(AppPreferences.getScaled(points[0]), AppPreferences.getScaled(points[1]));
for (var i = 2; i < points.length; i += 2)
path.lineTo(AppPreferences.getScaled(points[i]), AppPreferences.getScaled(points[i + 1]));
path.closePath();
g2.fill(path);
g2.setColor(removeIcon ? Color.RED.darker() : Color.GREEN.darker().darker());
g2.draw(path);
protected void paintIcon(Graphics2D graphics) {
final var icon = deselect ? AppIcons.disabled(id, AppPreferences.IconSize)
: AppIcons.get(id, AppPreferences.IconSize);
icon.paintIcon(null, graphics, 0, 0);
}
}
@@ -7,31 +7,14 @@
* This is free software released under GNU GPLv3 license
*/
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.Color;
import java.awt.Graphics2D;
public class RunIcon extends BaseIcon {
import com.cburch.logisim.gui.theme.AppIcons;
public class RunIcon extends ActionIcon {
@Override
protected void paintIcon(Graphics2D g2) {
g2.setColor(Color.GREEN.darker());
g2.fillOval(
AppPreferences.getScaled(2),
AppPreferences.getScaled(2),
AppPreferences.getScaled(13),
AppPreferences.getScaled(13));
final int[] posX =
new int[] {
AppPreferences.getScaled(6), AppPreferences.getScaled(11), AppPreferences.getScaled(6)
};
final int[] posY =
new int[] {
AppPreferences.getScaled(5), AppPreferences.getScaled(8), AppPreferences.getScaled(11)
};
g2.setColor(Color.WHITE);
g2.fillPolygon(posX, posY, 3);
protected AppIcons.Id iconId() {
return AppIcons.Id.RUN;
}
}
@@ -9,25 +9,17 @@
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.Graphics2D;
public class SelectIcon extends BaseIcon {
public static void paint(Graphics2D g2) {
final int[] xp = {3, 3, 7, 10, 11, 9, 14};
final int[] yp = {0, 17, 12, 16, 16, 12, 12};
final int[] sxp = new int[xp.length];
final int[] syp = new int[yp.length];
for (int i = 0; i < xp.length; i++) {
sxp[i] = AppPreferences.getScaled(xp[i]);
syp[i] = AppPreferences.getScaled(yp[i]);
}
g2.fillPolygon(sxp, syp, xp.length);
public class SelectIcon extends ActionIcon {
public static void paint(Graphics2D graphics) {
AppIcons.get(AppIcons.Id.POINTER, AppPreferences.IconSize).paintIcon(null, graphics, 0, 0);
}
@Override
protected void paintIcon(Graphics2D g2) {
paint(g2);
protected AppIcons.Id iconId() {
return AppIcons.Id.POINTER;
}
}
@@ -9,14 +9,11 @@
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.font.TextLayout;
public class ShowStateIcon extends BaseIcon {
private final boolean pressed;
public ShowStateIcon(boolean pressed) {
@@ -24,29 +21,10 @@ public class ShowStateIcon extends BaseIcon {
}
@Override
protected void paintIcon(Graphics2D gfx) {
gfx.setStroke(new BasicStroke(AppPreferences.getScaled(1)));
if (pressed) {
gfx.setColor(Color.MAGENTA.brighter().brighter().brighter());
gfx.fillRect(0, 0, getIconWidth(), getIconHeight());
}
gfx.setColor(Color.BLACK);
gfx.drawRect(0, 0, getIconWidth(), getIconHeight() / 2);
final var font = gfx.getFont().deriveFont((float) getIconWidth() / (float) 2);
final var textLayout = new TextLayout("101", font, gfx.getFontRenderContext());
textLayout.draw(
gfx,
(float) ((double) getIconWidth() / 2.0 - textLayout.getBounds().getCenterX()),
(float) ((double) getIconHeight() / 4.0 - textLayout.getBounds().getCenterY()));
final var iconBorder = AppPreferences.ICON_BORDER;
final var wh = AppPreferences.getScaled(AppPreferences.IconSize / 2 - iconBorder);
final var offset = AppPreferences.getScaled(iconBorder);
gfx.setColor(Color.RED);
gfx.fillOval(offset, offset + getIconHeight() / 2, wh, wh);
gfx.setColor(Color.GREEN);
gfx.fillOval(offset + getIconWidth() / 2, offset + getIconHeight() / 2, wh, wh);
gfx.setColor(Color.BLACK);
gfx.drawOval(offset, offset + getIconHeight() / 2, wh, wh);
gfx.drawOval(offset + getIconWidth() / 2, offset + getIconHeight() / 2, wh, wh);
protected void paintIcon(Graphics2D graphics) {
final var icon = pressed
? AppIcons.accented(AppIcons.Id.EYE, AppPreferences.IconSize)
: AppIcons.get(AppIcons.Id.EYE, AppPreferences.IconSize);
icon.paintIcon(null, graphics, 0, 0);
}
}
@@ -9,20 +9,11 @@
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.Graphics2D;
import java.awt.font.TextLayout;
public class TextIcon extends BaseIcon {
import com.cburch.logisim.gui.theme.AppIcons;
public class TextIcon extends ActionIcon {
@Override
protected void paintIcon(Graphics2D g2) {
final var f = StdAttr.DEFAULT_LABEL_FONT.deriveFont((float) AppPreferences.getIconSize());
final var l = new TextLayout("A", f, g2.getFontRenderContext());
l.draw(
g2,
(float) (AppPreferences.getIconSize() / 2 - l.getBounds().getCenterX()),
(float) (AppPreferences.getIconSize() / 2 - l.getBounds().getCenterY()));
protected AppIcons.Id iconId() {
return AppIcons.Id.TYPE;
}
}
@@ -7,16 +7,12 @@
* This is free software released under GNU GPLv3 license
*/
package com.cburch.logisim.gui.icons;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.GeneralPath;
public class ZoomIcon extends BaseIcon {
import com.cburch.logisim.gui.theme.AppIcons;
public class ZoomIcon extends ActionIcon {
public static final int ZOOMIN = 0;
public static final int ZOOMOUT = 1;
public static final int NOZOOM = 2;
@@ -24,7 +20,7 @@ public class ZoomIcon extends BaseIcon {
private final int zoomType;
public ZoomIcon() {
zoomType = NOZOOM;
this(NOZOOM);
}
public ZoomIcon(int type) {
@@ -32,36 +28,11 @@ public class ZoomIcon extends BaseIcon {
}
@Override
protected void paintIcon(Graphics2D g2) {
g2.setStroke(new BasicStroke((int) AppPreferences.getScaled(1.5)));
g2.setColor(g2.getBackground().darker());
final var scaledOne = AppPreferences.getScaled(1);
final var scaledEleven = AppPreferences.getScaled(10);
g2.fillOval(scaledOne, scaledOne, scaledEleven, scaledEleven);
g2.setColor(g2.getBackground().darker().darker().darker());
if (zoomType != NOZOOM) {
g2.drawLine(
AppPreferences.getScaled(4),
AppPreferences.getScaled(6),
AppPreferences.getScaled(8),
AppPreferences.getScaled(6));
if (zoomType == ZOOMIN)
g2.drawLine(
AppPreferences.getScaled(6),
AppPreferences.getScaled(4),
AppPreferences.getScaled(6),
AppPreferences.getScaled(8));
}
g2.setColor(Color.BLACK);
g2.drawOval(scaledOne, scaledOne, scaledEleven, scaledEleven);
final var xyPoint = AppPreferences.getScaled(6.0 + Math.sqrt(12.5));
GeneralPath path = new GeneralPath();
path.moveTo(xyPoint, xyPoint);
path.lineTo(AppPreferences.getScaled(15), AppPreferences.getScaled(13));
path.lineTo(AppPreferences.getScaled(13), AppPreferences.getScaled(15));
path.closePath();
g2.setStroke(new BasicStroke(AppPreferences.getScaled(1)));
g2.setColor(new Color(139, 69, 19));
g2.fill(path);
protected AppIcons.Id iconId() {
return switch (zoomType) {
case ZOOMIN -> AppIcons.Id.ZOOM_IN;
case ZOOMOUT -> AppIcons.Id.ZOOM_OUT;
default -> AppIcons.Id.SEARCH;
};
}
}
@@ -12,18 +12,22 @@ package com.cburch.logisim.gui.log;
import static com.cburch.logisim.gui.Strings.S;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.gui.generic.ScrollableForm;
import com.cburch.logisim.gui.icons.FatArrowIcon;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.gui.theme.Theme;
import com.cburch.logisim.gui.theme.Tokens;
import com.cburch.logisim.util.JDialogOk;
import com.cburch.logisim.util.UiScale;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneConstants;
@@ -34,6 +38,8 @@ public class SelectionPanel extends LogPanel {
private final JLabel selectDesc;
private final JLabel exploreLabel;
private final JLabel listLabel;
private final JLabel emptyLabel = new JLabel();
private final TransferControls controls;
public SelectionPanel(LogFrame window) {
super(window);
@@ -58,7 +64,7 @@ public class SelectionPanel extends LogPanel {
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = gbc.weighty = 0.0;
gbc.insets = new Insets(15, 10, 0, 10);
gbc.insets = new Insets(UiScale.scaled(12), UiScale.scaled(10), 0, UiScale.scaled(10));
gbc.gridx = 0;
gbc.gridy = 0;
@@ -71,62 +77,72 @@ public class SelectionPanel extends LogPanel {
gbc.gridx = 0;
gbc.gridy = 1;
exploreLabel = new JLabel();
exploreLabel.setLabelFor(selector);
gridbag.setConstraints(exploreLabel, gbc);
add(exploreLabel);
gbc.gridx = 2;
gbc.gridy = 1;
listLabel = new JLabel();
listLabel.setLabelFor(list);
gridbag.setConstraints(listLabel, gbc);
add(listLabel);
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = gbc.weighty = 1.0;
gbc.insets = new Insets(10, 10, 10, 10);
final var gap = UiScale.scaled(10);
gbc.insets = new Insets(gap, gap, gap, gap);
gbc.gridx = 0;
gbc.gridy = 2;
gridbag.setConstraints(explorerPane, gbc);
add(explorerPane);
explorerPane.setPreferredSize(new Dimension(120, 200));
explorerPane.setPreferredSize(new Dimension(UiScale.scaled(220), UiScale.scaled(240)));
final var addArrow = new JButton(new FatArrowIcon(Direction.EAST));
final var delArrow = new JButton(new FatArrowIcon(Direction.WEST));
addArrow.setBorder(BorderFactory.createEmptyBorder());
delArrow.setBorder(BorderFactory.createEmptyBorder());
addArrow.setContentAreaFilled(false);
delArrow.setContentAreaFilled(false);
addArrow.setEnabled(false);
delArrow.setEnabled(false);
controls = new TransferControls(
() -> list.add(selector.getSelectedItems()), list::removeSelected);
selector
.getSelectionModel()
.addListSelectionListener(
e -> addArrow.setEnabled(!selector.getSelectionModel().isSelectionEmpty()));
e -> controls.addButton.setEnabled(!selector.getSelectionModel().isSelectionEmpty()));
list.getSelectionModel()
.addListSelectionListener(
e -> delArrow.setEnabled(!list.getSelectionModel().isSelectionEmpty()));
addArrow.addActionListener(e -> list.add(selector.getSelectedItems()));
delArrow.addActionListener(e -> list.removeSelected());
e -> controls.removeButton.setEnabled(!list.getSelectionModel().isSelectionEmpty()));
list.getModel().addTableModelListener(event -> updateEmptyState());
final var arrowBox = new Box(BoxLayout.Y_AXIS);
arrowBox.add(addArrow);
arrowBox.add(delArrow);
gbc.fill = GridBagConstraints.NONE;
gbc.weightx = gbc.weighty = 0.0;
gbc.insets = new Insets(0, 0, 0, 0);
gbc.gridx = 1;
gbc.gridy = 2;
gridbag.setConstraints(arrowBox, gbc);
add(arrowBox);
gridbag.setConstraints(controls, gbc);
add(controls);
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = gbc.weighty = 1.0;
gbc.insets = new Insets(10, 10, 10, 10);
gbc.insets = new Insets(gap, gap, gap, gap);
gbc.gridx = 2;
gbc.gridy = 2;
gridbag.setConstraints(listPane, gbc);
add(listPane);
final var selectedPanel = new JPanel(new BorderLayout(0, UiScale.scaled(8)));
selectedPanel.add(listPane, BorderLayout.CENTER);
selectedPanel.add(emptyLabel, BorderLayout.SOUTH);
gridbag.setConstraints(selectedPanel, gbc);
add(selectedPanel);
listPane.setPreferredSize(
new Dimension(AppPreferences.getScaled(180), AppPreferences.getScaled(200)));
new Dimension(UiScale.scaled(240), UiScale.scaled(240)));
updateMetrics();
updateEmptyState();
Theme.addListener(this, this::updateMetrics);
localeChanged();
}
private void updateMetrics() {
list.setRowHeight(list.getFontMetrics(list.getFont()).getHeight() + UiScale.scaled(10));
emptyLabel.setForeground(Tokens.mutedForeground());
revalidate();
}
private void updateEmptyState() {
emptyLabel.setVisible(list.getRowCount() == 0);
}
@Override
@@ -144,6 +160,8 @@ public class SelectionPanel extends LogPanel {
selectDesc.setText(S.get("selectionDesc"));
exploreLabel.setText(S.get("exploreLabel"));
listLabel.setText(S.get("listLabel"));
emptyLabel.setText(S.get("selectionEmpty"));
controls.localeChanged();
selector.localeChanged();
list.localeChanged();
}
@@ -152,6 +170,32 @@ public class SelectionPanel extends LogPanel {
public void modelChanged(Model oldModel, Model newModel) {
selector.setRootCircuit(newModel.getCircuit());
list.setLogModel(newModel);
updateEmptyState();
}
static class TransferControls extends JPanel {
private static final long serialVersionUID = 1L;
final JButton addButton = new JButton(new FatArrowIcon(Direction.EAST));
final JButton removeButton = new JButton(new FatArrowIcon(Direction.WEST));
TransferControls(Runnable addSelection, Runnable removeSelection) {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
addButton.setEnabled(false);
removeButton.setEnabled(false);
addButton.addActionListener(event -> addSelection.run());
removeButton.addActionListener(event -> removeSelection.run());
add(addButton);
add(Box.createVerticalStrut(UiScale.scaled(8)));
add(removeButton);
localeChanged();
}
void localeChanged() {
addButton.setText(S.get("selectionAdd"));
removeButton.setText(S.get("selectionRemove"));
addButton.setToolTipText(S.get("selectionAddTip"));
removeButton.setToolTipText(S.get("selectionRemoveTip"));
}
}
static class SelectionDialog extends JDialogOk {
@@ -159,13 +203,12 @@ public class SelectionPanel extends LogPanel {
final SelectionPanel selPanel;
SelectionDialog(LogFrame logFrame) {
super("Signal Selection", false);
super(S.get("selectionDialogTitle"), false);
selPanel = new SelectionPanel(logFrame);
selPanel.localeChanged();
getContentPane().add(selPanel);
setMinimumSize(new Dimension(350, 300));
setSize(400, 400);
pack();
ok.setText(S.get("statsCloseButton"));
ScrollableForm.sizeWindow(this, new Dimension(680, 420), new Dimension(480, 300), true);
}
@Override
@@ -15,12 +15,17 @@ import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.file.FileStatistics;
import com.cburch.logisim.file.LogisimFile;
import com.cburch.logisim.gui.generic.Dialogs;
import com.cburch.logisim.gui.generic.ScrollableForm;
import com.cburch.logisim.tools.Library;
import com.cburch.logisim.util.TableSorter;
import com.cburch.logisim.util.UiScale;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Comparator;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
@@ -43,27 +48,24 @@ public class StatisticsDialog extends JDialog implements ActionListener {
new CompareString("", S.get("statsTotalWithout"), S.get("statsTotalWith"));
mySorter.setColumnComparator(String.class, comp);
table.setModel(mySorter);
table.updateMetrics();
final var tablePane = new JScrollPane(table);
var button = new JButton(S.get("statsCloseButton"));
button.addActionListener(this);
getRootPane().setDefaultButton(button);
Dialogs.installEscapeToClose(this);
var buttonPanel = new JPanel();
var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING));
buttonPanel.add(button);
var contents = this.getContentPane();
contents.setLayout(new BorderLayout());
contents.setLayout(new BorderLayout(0, UiScale.scaled(12)));
final var gap = UiScale.scaled(12);
((JPanel) contents).setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap));
contents.add(tablePane, BorderLayout.CENTER);
contents.add(buttonPanel, BorderLayout.PAGE_END);
this.pack();
var pref = contents.getPreferredSize();
if (pref.width > 750 || pref.height > 550) {
if (pref.width > 750) pref.width = 750;
if (pref.height > 550) pref.height = 550;
this.setSize(pref);
}
ScrollableForm.sizeWindow(this, new Dimension(750, 500), new Dimension(420, 280), true);
setLocationRelativeTo(parent);
}
public static void show(JFrame parent, LogisimFile file, Circuit circuit) {
@@ -96,27 +98,35 @@ public class StatisticsDialog extends JDialog implements ActionListener {
}
}
private static class StatisticsTable extends JTable {
static class StatisticsTable extends JTable {
private static final long serialVersionUID = 1L;
@Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
setPreferredColumnWidths(new double[] {0.45, 0.25, 0.1, 0.1, 0.1});
StatisticsTable() {
setAutoResizeMode(AUTO_RESIZE_OFF);
setFillsViewportHeight(true);
getTableHeader().setReorderingAllowed(false);
}
protected void setPreferredColumnWidths(double[] percentages) {
final var tableDim = getPreferredSize();
@Override
public void updateUI() {
super.updateUI();
updateMetrics();
}
double total = 0;
for (int i = 0; i < getColumnModel().getColumnCount(); i++) {
total += percentages[i];
}
for (int i = 0; i < getColumnModel().getColumnCount(); i++) {
void updateMetrics() {
if (getFont() == null || getTableHeader() == null) return;
final var padding = UiScale.scaled(24);
setRowHeight(getFontMetrics(getFont()).getHeight() + UiScale.scaled(10));
getTableHeader().setFont(getFont());
for (var i = 0; i < getColumnCount(); i++) {
final var column = getColumnModel().getColumn(i);
final var width = tableDim.width * (percentages[i] / total);
column.setPreferredWidth((int) width);
var width = getFontMetrics(getFont()).stringWidth(getColumnName(i)) + padding;
column.setMinWidth(width);
for (var row = 0; row < getRowCount(); row++) {
final var cell = prepareRenderer(getCellRenderer(row, i), row, i);
width = Math.max(width, cell.getPreferredSize().width + padding);
}
column.setPreferredWidth(width);
}
}
}
@@ -14,6 +14,8 @@ import static com.cburch.logisim.gui.Strings.S;
import com.cburch.logisim.circuit.Circuit;
import com.cburch.logisim.gui.generic.Dialogs;
import com.cburch.logisim.gui.generic.OptionPane;
import com.cburch.logisim.gui.generic.ScrollableForm;
import com.cburch.logisim.util.UiScale;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
@@ -25,8 +27,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
@@ -35,6 +35,7 @@ import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
public class ExportSubcircuitDialog extends JDialog implements ActionListener {
private static final long serialVersionUID = 1L;
@@ -58,84 +59,49 @@ public class ExportSubcircuitDialog extends JDialog implements ActionListener {
}
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLayout(new BorderLayout(8, 8));
final var gap = UiScale.scaled(12);
setLayout(new BorderLayout(gap, gap));
final var topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 12, 8));
final var topPanel = new JPanel(new BorderLayout());
final var headerLabel = new JLabel(S.get("exportSubcircuitDependenciesHeader"));
topPanel.add(headerLabel);
tableModel = new DependentsTableModel();
table = new JTable(tableModel);
table.setRowHeight(24);
final var tableHeader = table.getTableHeader();
tableHeader.setReorderingAllowed(false);
tableHeader.setFont(table.getFont());
final var baseRenderer = tableHeader.getDefaultRenderer();
tableHeader.setDefaultRenderer(
(tbl, val, isSel, hasFocus, row, col) -> {
final var comp = baseRenderer.getTableCellRendererComponent(tbl, val, isSel, hasFocus, row, col);
comp.setFont(tbl.getFont());
return comp;
});
final int rowCount = dependentCircuits.size();
final int visibleRows = Math.min(Math.max(rowCount, 2), 8);
final int headerH = table.getTableHeader().getPreferredSize().height;
final int tableH = (visibleRows * table.getRowHeight()) + (headerH > 0 ? headerH : 24);
table.setPreferredScrollableViewportSize(new Dimension(520, tableH));
table = new DependencyTable(tableModel);
headerLabel.setLabelFor(table);
table.getAccessibleContext().setAccessibleName(S.get("exportSubcircuitDependenciesHeader"));
final var scrollPane = new JScrollPane(table);
scrollPane.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 12));
autoPrefixCheckBox = new JCheckBox(S.get("exportSubcircuitAutoPrefix"), true);
autoPrefixCheckBox.addActionListener(this);
final var infoLabel =
new JLabel(
"<html><body style='width: 480px;'>"
+ S.get("exportSubcircuitExplanation")
+ "</body></html>");
infoLabel.setForeground(java.awt.Color.DARK_GRAY);
final var okButton = new JButton(S.get("exportSubcircuitFileSelect"));
okButton.setActionCommand("OK");
okButton.addActionListener(this);
okButton.setMargin(new java.awt.Insets(4, 16, 4, 16));
getRootPane().setDefaultButton(okButton);
Dialogs.installEscapeToClose(this);
final var cancelButton = new JButton(S.get("showStateDialogCancelButton"));
cancelButton.setActionCommand("CANCEL");
cancelButton.addActionListener(this);
cancelButton.setMargin(new java.awt.Insets(4, 16, 4, 16));
final var buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 15, 0));
buttonPanel.add(okButton);
final var buttonPanel = new JPanel(new FlowLayout(FlowLayout.TRAILING, gap, 0));
buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, gap, gap, gap));
buttonPanel.add(cancelButton);
buttonPanel.add(okButton);
final var bottomPanel = new JPanel();
bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.Y_AXIS));
bottomPanel.setBorder(BorderFactory.createEmptyBorder(6, 12, 12, 12));
final var form = new JPanel(new BorderLayout(gap, gap));
form.setBorder(BorderFactory.createEmptyBorder(0, gap, gap, gap));
form.add(topPanel, BorderLayout.NORTH);
form.add(scrollPane, BorderLayout.CENTER);
form.add(autoPrefixCheckBox, BorderLayout.SOUTH);
final var viewport = new ScrollableForm(form);
viewport.setHelpText(S.get("exportSubcircuitExplanation"));
add(viewport.createScrollPane(), BorderLayout.CENTER);
add(buttonPanel, BorderLayout.SOUTH);
final var cbPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
cbPanel.add(autoPrefixCheckBox);
bottomPanel.add(cbPanel);
bottomPanel.add(Box.createVerticalStrut(6));
final var infoPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
infoPanel.add(infoLabel);
bottomPanel.add(infoPanel);
bottomPanel.add(Box.createVerticalStrut(10));
bottomPanel.add(buttonPanel);
add(topPanel, BorderLayout.NORTH);
add(scrollPane, BorderLayout.CENTER);
add(bottomPanel, BorderLayout.SOUTH);
pack();
setMinimumSize(new Dimension(540, getPreferredSize().height));
ScrollableForm.sizeWindow(this, new Dimension(620, 440), new Dimension(420, 280), true);
setLocationRelativeTo(parent);
}
@@ -165,9 +131,7 @@ public class ExportSubcircuitDialog extends JDialog implements ActionListener {
}
tableModel.fireTableDataChanged();
} else if ("OK".equals(e.getActionCommand())) {
if (table.isEditing()) {
table.getCellEditor().stopCellEditing();
}
if (table.isEditing() && !table.getCellEditor().stopCellEditing()) return;
if (validateNames()) {
isApproved = true;
dispose();
@@ -202,6 +166,41 @@ public class ExportSubcircuitDialog extends JDialog implements ActionListener {
OptionPane.showMessageDialog(this, msg, S.get("exportSubcircuitTitle", mainCircuit.getName()), OptionPane.ERROR_MESSAGE);
}
static class DependencyTable extends JTable {
private static final long serialVersionUID = 1L;
DependencyTable(TableModel model) {
super(model);
getTableHeader().setReorderingAllowed(false);
setFillsViewportHeight(true);
updateMetrics();
}
@Override
public void updateUI() {
super.updateUI();
updateMetrics();
}
private void updateMetrics() {
if (getFont() == null || getTableHeader() == null) return;
setRowHeight(getFontMetrics(getFont()).getHeight() + UiScale.scaled(10));
getTableHeader().setFont(getFont());
for (var i = 0; i < getColumnCount(); i++) {
final var column = getColumnModel().getColumn(i);
final var labelWidth = getFontMetrics(getFont()).stringWidth(getColumnName(i));
column.setMinWidth(labelWidth + UiScale.scaled(24));
column.setPreferredWidth(Math.max(column.getMinWidth(), UiScale.scaled(250)));
}
}
@Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(UiScale.scaled(520), Math.min(Math.max(getRowCount(), 2), 8)
* getRowHeight());
}
}
private class DependentsTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private final String[] columnNames = {
@@ -14,6 +14,7 @@ import static com.cburch.logisim.gui.Strings.S;
import com.cburch.logisim.generated.BuildInfo;
import com.cburch.logisim.gui.generic.LFrame;
import com.cburch.logisim.gui.generic.OptionPane;
import com.cburch.logisim.gui.generic.ScrollableForm;
import com.cburch.logisim.gui.search.OmniSearchDialog;
import com.cburch.logisim.gui.search.SearchContext;
import com.cburch.logisim.gui.search.providers.MenuSearchProvider;
@@ -23,7 +24,6 @@ import com.cburch.logisim.prefs.PrefMonitorKeyStroke;
import com.cburch.logisim.util.MacCompatibility;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URI;
@@ -134,15 +134,15 @@ class MenuHelp extends Menu implements ActionListener {
helpSetUrl = resolved.path();
helpSet = new HelpSet(null, resolved.url());
helpComponent = new JHelp(helpSet);
HelpPresentation.install(helpComponent.getContentViewer());
HelpPresentation.installNavigation(helpComponent);
if (helpFrame == null) {
helpFrame = new LFrame.Dialog(null);
helpFrame.setTitle(S.get("helpWindowTitle"));
helpFrame.getContentPane().add(helpComponent);
helpFrame.setPreferredSize(
new Dimension(
(int) Toolkit.getDefaultToolkit().getScreenSize().getWidth() >> 1,
(int) Toolkit.getDefaultToolkit().getScreenSize().getHeight() >> 1));
helpFrame.pack();
ScrollableForm.sizeWindow(
helpFrame, new Dimension(900, 640), new Dimension(480, 320), true);
helpFrame.setLocationRelativeTo(menubar.getParentFrame());
} else {
helpFrame.getContentPane().removeAll();
helpFrame.getContentPane().add(helpComponent);
@@ -14,10 +14,8 @@ import static com.cburch.logisim.gui.Strings.S;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.prefs.PrefMonitor;
import com.cburch.logisim.prefs.PrefMonitorKeyStroke;
import com.cburch.logisim.util.JAdjustableScroll;
import com.cburch.logisim.util.JHotkeyInput;
import com.cburch.logisim.util.TableLayout;
import java.awt.Dimension;
import java.awt.event.InputEvent;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -49,7 +47,7 @@ class HotkeyOptions extends OptionsPanel {
* when it collides with one of them; see AppPreferences.hotkeyCheckConflict.
*
* */
protected static List<PrefMonitor<KeyStroke>> hotkeys = new ArrayList<>();
private final List<PrefMonitor<KeyStroke>> hotkeys = new ArrayList<>();
private final List<JHotkeyInput> keyInputList;
private final List<JLabel> keyLabels;
private final JLabel menuKeyHeaderLabel;
@@ -79,20 +77,16 @@ class HotkeyOptions extends OptionsPanel {
add(menuKeyHeaderLabel);
add(new JLabel(" "));
JPanel menuKeyPanel = new JPanel();
JAdjustableScroll menuKeyScrollPane = new JAdjustableScroll(menuKeyPanel);
add(menuKeyScrollPane);
add(menuKeyPanel);
add(new JLabel(" "));
normalKeyHeaderLabel = new JLabel();
add(normalKeyHeaderLabel);
add(new JLabel(" "));
JPanel normalKeyPanel = new JPanel();
JAdjustableScroll normalKeyScrollPane = new JAdjustableScroll(normalKeyPanel);
add(normalKeyScrollPane);
add(normalKeyPanel);
menuKeyPanel.setMaximumSize(new Dimension(400, 400));
menuKeyPanel.setLayout(new TableLayout(2));
normalKeyPanel.setMaximumSize(new Dimension(400, 400));
normalKeyPanel.setLayout(new TableLayout(2));
/* bind up the hotkeys */
@@ -164,29 +158,18 @@ class HotkeyOptions extends OptionsPanel {
orientDescLabel = new JLabel();
normalKeyPanel.add(orientDescLabel);
normalKeyPanel.add(new JLabel(" "));
JPanel panelLeft = new JPanel();
JPanel panelRight = new JPanel();
panelLeft.setLayout(new TableLayout(3));
panelRight.setLayout(new TableLayout(3));
orientEastLabel = new JLabel();
orientNorthLabel = new JLabel();
orientSouthLabel = new JLabel();
orientWestLabel = new JLabel();
panelLeft.add(new JLabel(" "));
panelLeft.add(orientNorthLabel);
panelLeft.add(new JLabel(" "));
panelLeft.add(orientWestLabel);
panelLeft.add(orientSouthLabel);
panelLeft.add(orientEastLabel);
normalKeyPanel.add(panelLeft);
panelRight.add(new JLabel(" "));
panelRight.add(northBtn);
panelRight.add(new JLabel(" "));
panelRight.add(westBtn);
panelRight.add(southBtn);
panelRight.add(eastBtn);
normalKeyPanel.add(panelRight);
normalKeyPanel.add(orientNorthLabel);
normalKeyPanel.add(northBtn);
normalKeyPanel.add(orientEastLabel);
normalKeyPanel.add(eastBtn);
normalKeyPanel.add(orientSouthLabel);
normalKeyPanel.add(southBtn);
normalKeyPanel.add(orientWestLabel);
normalKeyPanel.add(westBtn);
// A 200ms timer used to poll this panel's own width for ever, to set a preferred size the
// layout can work out for itself. It never stopped, in every open preferences window.
@@ -109,7 +109,7 @@ public class PreferencesFrame extends LFrame.Dialog {
}
/** Unscaled width of the page list. */
private static final int NAV_WIDTH = 190;
private static final int NAV_WIDTH = 220;
/** Unscaled size the window opens at. */
private static final int WINDOW_WIDTH = 820;
@@ -103,6 +103,7 @@ public class SettingsNav extends JPanel {
}
});
final var scroll = new JScrollPane(list);
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setBorder(null);
add(scroll, BorderLayout.CENTER);
@@ -44,7 +44,8 @@ public class About {
static final int LOGO_HEIGHT = 200;
static final int SCROLLER_HEIGHT = 200;
private static final String LOGO_IMG = "resources/logisim/img/logisim-evolution-logo.png";
private static final String LOGO_IMG = "resources/logisim/img/logisim-revolution-logo.png";
private static JDialog activeDialog;
private About() {}
@@ -56,7 +57,13 @@ public class About {
if (!Main.hasGui()) {
return;
}
createAboutDialog(owner).setVisible(true);
if (activeDialog != null && activeDialog.isDisplayable()) {
activeDialog.toFront();
activeDialog.requestFocus();
return;
}
activeDialog = createAboutDialog(owner);
activeDialog.setVisible(true);
}
static JDialog createAboutDialog(JFrame owner) {
@@ -64,7 +71,9 @@ public class About {
content.add(new AboutPanel(true));
content.setBorder(BorderFactory.createLineBorder(Tokens.divider(), 1));
final var dialog = new JDialog(owner, S.get("aboutDialogTitle"), true);
// A modal transient dialog crashes on some Xwayland/KWin combinations while blocking its
// owner. About has no transaction to protect, so a single modeless window is sufficient.
final var dialog = new JDialog(owner, S.get("aboutDialogTitle"), false);
dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
Dialogs.installEscapeToClose(dialog);
final var optionPane = new JOptionPane(content, JOptionPane.PLAIN_MESSAGE);
@@ -71,7 +71,8 @@ class AboutCredits extends JComponent {
lines = new Lines();
lines
.title(BuildInfo.displayName)
.h2(String.format("Copyright © 2001-%s %s developers", BuildInfo.year, BuildInfo.name))
.h2(S.get("creditsRevolutionCopyright"))
.tiny(S.get("creditsUpstreamCopyright", BuildInfo.year))
.url(BuildInfo.url)
.space()
.h1(S.get("creditsDevelopedBy"))
@@ -12,19 +12,18 @@ package com.cburch.logisim.gui.start;
import static com.cburch.logisim.gui.Strings.S;
import com.cburch.logisim.generated.BuildInfo;
import com.cburch.logisim.gui.theme.Tokens;
import java.awt.BorderLayout;
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.MouseInfo;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Lightweight loading window with honest startup stages and no historical timing weights. */
public class SplashScreen extends JWindow {
public static final int LIBRARIES = 0;
public static final int TEMPLATE_CREATE = 1;
public static final int TEMPLATE_OPEN = 2;
@@ -35,95 +34,82 @@ public class SplashScreen extends JWindow {
public static final int FILE_LOAD = 7;
public static final int PROJECT_CREATE = 8;
public static final int FRAME_CREATE = 9;
static final Logger logger = LoggerFactory.getLogger(SplashScreen.class);
private static final long serialVersionUID = 1L;
private static final int PROGRESS_MAX = 3568;
private static final boolean PRINT_TIMES = false;
Marker[] markers =
new Marker[] {
new Marker(377, S.get("progressLibraries")),
new Marker(990, S.get("progressTemplateCreate")),
new Marker(1002, S.get("progressTemplateOpen")),
new Marker(1002, S.get("progressTemplateLoad")),
new Marker(1470, S.get("progressTemplateClose")),
new Marker(1478, S.get("progressGuiInitialize")),
new Marker(2114, S.get("progressFileCreate")),
new Marker(2114, S.get("progressFileLoad")),
new Marker(2383, S.get("progressProjectCreate")),
new Marker(2519, S.get("progressFrameCreate")),
};
boolean inClose = false; // for avoiding mutual recursion
final JProgressBar progress = new JProgressBar(0, PROGRESS_MAX);
final long startTime = System.currentTimeMillis();
private static final String[] STAGES = {
"progressLibraries", "progressTemplateCreate", "progressTemplateOpen",
"progressTemplateLoad", "progressTemplateClose", "progressGuiInitialize",
"progressFileCreate", "progressFileLoad", "progressProjectCreate",
"progressFrameCreate"
};
private final AtomicBoolean closed = new AtomicBoolean();
private final SplashPanel panel = new SplashPanel();
public SplashScreen() {
setName(BuildInfo.displayName);
JPanel imagePanel = About.getImagePanel();
imagePanel.setBorder(null);
progress.setStringPainted(true);
JPanel contents = new JPanel(new BorderLayout());
contents.add(imagePanel, BorderLayout.NORTH);
contents.add(progress, BorderLayout.SOUTH);
// A thin line in the theme's own divider colour rather than a two-pixel black frame, which
// read as a border drawn around the window by the application itself.
contents.setBorder(BorderFactory.createLineBorder(Tokens.divider(), 1));
final var bg = imagePanel.getBackground();
contents.setBackground(bg);
setBackground(bg);
setContentPane(contents);
setContentPane(panel);
setBackground(panel.getBackground());
}
/** Ignores callbacks that were queued before the window finished closing. */
public void close() {
if (inClose) return;
inClose = true;
setVisible(false);
inClose = false;
if (PRINT_TIMES) {
logger.info("{} closed", System.currentTimeMillis() - startTime);
}
markers = null;
if (!closed.compareAndSet(false, true)) return;
onEdt(() -> {
super.setVisible(false);
dispose();
});
}
/** Existing startup callers report an actual stage, not a guessed percentage. */
public void setProgress(int markerId) {
final Marker marker = markers == null ? null : markers[markerId];
if (marker != null) {
SwingUtilities.invokeLater(
() -> {
progress.setString(marker.message);
progress.setValue(marker.count);
});
if (PRINT_TIMES) {
logger.info("{} {}", System.currentTimeMillis() - startTime, marker.message);
}
} else {
if (PRINT_TIMES) {
logger.info("{} ??", System.currentTimeMillis() - startTime);
}
}
if (closed.get() || markerId < 0 || markerId >= STAGES.length) return;
final var key = STAGES[markerId];
onEdt(() -> {
if (!closed.get()) panel.setStage(S.get(key));
});
}
@Override
public void setVisible(boolean value) {
if (value) {
if (!SwingUtilities.isEventDispatchThread()) {
try {
SwingUtilities.invokeAndWait(() -> setVisible(value));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Interrupted opening splash", e);
} catch (InvocationTargetException e) {
throw new IllegalStateException("Could not open splash", e.getCause());
}
return;
}
if (value && !closed.get()) {
pack();
final var dim = getToolkit().getScreenSize();
final var x = (int) (dim.getWidth() - getWidth()) / 2;
final var y = (int) (dim.getHeight() - getHeight()) / 2;
setLocation(x, y);
centerOnActiveMonitor();
super.setVisible(true);
} else if (!value) {
super.setVisible(false);
}
super.setVisible(value);
}
private static class Marker {
final int count;
final String message;
private void centerOnActiveMonitor() {
final var pointer = MouseInfo.getPointerInfo();
final GraphicsConfiguration config = pointer == null
? getGraphicsConfiguration() : pointer.getDevice().getDefaultConfiguration();
final Rectangle available = new Rectangle(config.getBounds());
final Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(config);
available.x += insets.left;
available.y += insets.top;
available.width -= insets.left + insets.right;
available.height -= insets.top + insets.bottom;
setSize(Math.min(getWidth(), available.width), Math.min(getHeight(), available.height));
setLocation(
available.x + (available.width - getWidth()) / 2,
available.y + (available.height - getHeight()) / 2);
}
Marker(int count, String message) {
this.count = count;
this.message = message;
}
private static void onEdt(Runnable action) {
if (SwingUtilities.isEventDispatchThread()) action.run();
else SwingUtilities.invokeLater(action);
}
}
@@ -906,13 +906,15 @@ public class Startup implements AWTEventListener {
System.exit(exitCode);
}
// kick off the progress monitor
// (The values used for progress values are based on a single run where
// I loaded a large file.)
// Create and show Swing windows on the EDT before loading libraries on this thread.
if (showSplash) {
try {
monitor = new SplashScreen();
monitor.setVisible(true);
final var openSplash = (Runnable) () -> {
monitor = new SplashScreen();
monitor.setVisible(true);
};
if (javax.swing.SwingUtilities.isEventDispatchThread()) openSplash.run();
else javax.swing.SwingUtilities.invokeAndWait(openSplash);
} catch (Exception t) {
monitor = null;
showSplash = false;
@@ -979,7 +981,7 @@ public class Startup implements AWTEventListener {
// Check for unnamed autosaves here and allow to save them and/or open them again
if (testVector == null && testCircPathInput == null) {
// get list of all unnamed autosave files
final var autosaves = new File(System.getProperty("user.home")).listFiles(
final var autosaves = Loader.getUnnamedAutosaveDirectory().listFiles(
new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
@@ -990,7 +992,7 @@ public class Startup implements AWTEventListener {
);
// Go over all autosaves to select what to do with each
for (final var autosave : autosaves) {
for (final var autosave : autosaves == null ? new File[0] : autosaves) {
boolean retry = false;
do {
retry = false;
@@ -20,11 +20,11 @@ import javax.swing.Icon;
* colour from the theme.
*
* <p>These are the icons of the interface itself: menus, toolbars, panels, the status bar. The
* icons that stand for circuit components are a different matter and keep being painted by
* {@code gui.icons.BaseIcon}, because they are miniature drawings of the components themselves.
* icons that stand for circuit components use semantic vectors in {@code gui.icons.ComponentIcons}
* or their own attribute-dependent painters.
*
* <p>Glyphs are from the Lucide icon set (ISC licence); see {@code LICENSE-lucide.txt} beside the
* files.
* <p>Glyphs use the Lucide icon set (ISC licence) and application-specific line drawings;
* see {@code LICENSE-lucide.txt} and the individual SVG notices beside the files.
*/
public final class AppIcons {
@@ -97,7 +97,26 @@ public final class AppIcons {
STAR("star"),
HISTORY("history"),
LIST_FILTER("list-filter"),
MORE("more");
MORE("more"),
POINTER("pointer"),
HAND("hand"),
TYPE("type"),
IMAGE("image"),
COMPILE("compile"),
HOME("home"),
ARROW_RIGHT("arrow-right"),
ARROW_LEFT("arrow-left"),
ARROW_UP("arrow-up"),
ARROW_DOWN("arrow-down"),
EYE("eye"),
DRAW_LINE("draw-line"),
DRAW_CURVE("draw-curve"),
DRAW_POLYLINE("draw-polyline"),
DRAW_POLYGON("draw-polygon"),
DRAW_RECTANGLE("draw-rectangle"),
DRAW_ROUNDED_RECTANGLE("draw-rounded-rectangle"),
DRAW_ELLIPSE("draw-ellipse"),
WIRE("wire");
private final String fileName;
@@ -31,6 +31,6 @@ public class LogisimDarkLaf extends FlatDarkLaf {
@Override
public String getDescription() {
return "Logisim-evolution dark theme";
return "Logisim Revolution dark theme";
}
}
@@ -35,6 +35,6 @@ public class LogisimLightLaf extends FlatLightLaf {
@Override
public String getDescription() {
return "Logisim-evolution light theme";
return "Logisim Revolution light theme";
}
}
@@ -19,6 +19,7 @@ import com.cburch.logisim.data.Location;
import com.cburch.logisim.util.GraphicsUtil;
import java.awt.Font;
import java.util.List;
import java.util.function.Supplier;
public final class Instance implements Location.At {
@@ -118,6 +119,22 @@ public final class Instance implements Location.At {
comp.setTextField(labelAttr, fontAttr, x, y, hAlign, vAlign, multiline);
}
/**
* Opts a factory-controlled label into measured placement, without adding document attributes.
* Explicit label-location attributes and custom appearance text must use {@link #setTextField}.
* The safe interior is in circuit coordinates; an empty interior always keeps text outside.
*/
public void setAutoLabelTextField(int x, int y, int hAlign, int vAlign, Bounds safeInterior) {
setAutoLabelTextField(x, y, hAlign, vAlign, () -> safeInterior);
}
/** Supplies a current safe interior when a factory's painting style can change independently. */
public void setAutoLabelTextField(
int x, int y, int hAlign, int vAlign, Supplier<Bounds> safeInterior) {
comp.setTextField(StdAttr.LABEL, StdAttr.LABEL_FONT, x, y, hAlign, vAlign);
comp.setLabelSafeInterior(safeInterior);
}
public static final int AVOID_TOP = 1;
public static final int AVOID_RIGHT = 2;
public static final int AVOID_BOTTOM = 4;
@@ -43,6 +43,7 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
public final class InstanceComponent implements Component, AttributeListener, ToolTipMaker {
private EventSourceWeakSupport<ComponentListener> listeners;
@@ -467,6 +468,10 @@ public final class InstanceComponent implements Component, AttributeListener, To
setTextField(labelAttr, fontAttr, x, y, halign, valign, false);
}
void setLabelSafeInterior(Supplier<Bounds> safeInterior) {
if (textField != null) textField.setSafeInterior(safeInterior);
}
void setTextField(
Attribute<String> labelAttr,
Attribute<Font> fontAttr,
@@ -23,6 +23,7 @@ import com.cburch.logisim.data.AttributeEvent;
import com.cburch.logisim.data.AttributeListener;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.gui.main.Canvas;
import com.cburch.logisim.proj.Action;
import com.cburch.logisim.tools.Caret;
@@ -31,7 +32,9 @@ import com.cburch.logisim.tools.TextEditable;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Objects;
import java.util.function.Supplier;
public class InstanceTextField implements AttributeListener, TextFieldListener, TextEditable {
private Canvas canvas;
@@ -46,6 +49,7 @@ public class InstanceTextField implements AttributeListener, TextFieldListener,
private int halign;
private int valign;
private boolean multiline;
private Supplier<Bounds> safeInterior;
InstanceTextField(InstanceComponent comp) {
this.comp = comp;
@@ -79,6 +83,7 @@ public class InstanceTextField implements AttributeListener, TextFieldListener,
void draw(Component comp, ComponentDrawContext context) {
if (field != null && isLabelVisible) {
final var gfx = context.getGraphics().create();
resolveLayout(gfx);
final var currentColor = gfx.getColor();
if (!context.isPrintView())
gfx.setColor(fontColor == null ? StdAttr.getDefaultLabelColor() : fontColor);
@@ -89,9 +94,48 @@ public class InstanceTextField implements AttributeListener, TextFieldListener,
}
Bounds getBounds(Graphics g) {
resolveLayout(g);
return field == null || !isLabelVisible ? Bounds.EMPTY_BOUNDS : field.getBounds(g);
}
void setSafeInterior(Supplier<Bounds> bounds) {
safeInterior = bounds;
}
private static Rectangle rectangle(Bounds bounds) {
return new Rectangle(bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight());
}
// Resolve the same TextField for paint, hit bounds, export bounds and caret acquisition.
// This never changes a label attribute, font, component bounds or electrical endpoint.
private void resolveLayout(Graphics g) {
if (safeInterior == null || field == null || g == null) return;
field.setLocation(fieldX, fieldY, halign, valign);
final var text = rectangle(field.getBounds(g));
final var body = rectangle(comp.getBounds());
final var envelope = new Rectangle(body);
var hitsPort = false;
for (final var end : comp.getEnds()) {
final var p = end.getLocation();
final var reserved = new Rectangle(p.getX() - 6, p.getY() - 6, 12, 12);
hitsPort |= reserved.intersects(text);
envelope.add(reserved);
}
if (!hitsPort && (rectangle(safeInterior.get()).contains(text) || !body.intersects(text))) return;
final var facing = comp.getAttributeSet().getValue(StdAttr.FACING);
final int targetX;
final int targetY;
if (facing == Direction.NORTH || facing == Direction.SOUTH) {
targetX = envelope.x + envelope.width + 4;
targetY = body.y + (body.height - text.height) / 2;
} else {
targetX = body.x + (body.width - text.width) / 2;
targetY = envelope.y - 4 - text.height;
}
field.setLocation(fieldX + targetX - text.x, fieldY + targetY - text.y, halign, valign);
}
@Override
public Action getCommitAction(Circuit circuit, String oldText, String newText) {
if (Objects.equals(oldText, newText)) return null;
@@ -109,6 +153,7 @@ public class InstanceTextField implements AttributeListener, TextFieldListener,
// if field is absent, create it empty
// and if it is empty, just return a caret at its beginning
if (field == null) createField(comp.getAttributeSet(), "");
resolveLayout(gfx);
final var text = field.getText();
if (text == null || text.equals("")) return field.getCaret(gfx, 0);
@@ -149,6 +194,7 @@ public class InstanceTextField implements AttributeListener, TextFieldListener,
int halign,
int valign,
boolean multiline) {
safeInterior = null;
final var wasReg = shouldRegister();
this.labelAttr = labelAttr;
this.fontAttr = fontAttr;
@@ -11,7 +11,7 @@ package com.cburch.logisim.prefs;
import static com.cburch.logisim.gui.Strings.S;
import com.cburch.logisim.Main;
import com.cburch.logisim.AppIdentity;
import com.cburch.logisim.circuit.RadixOption;
import com.cburch.logisim.data.AttributeOption;
import com.cburch.logisim.data.Direction;
@@ -287,7 +287,7 @@ public class AppPreferences {
if (prefs == null) {
synchronized (AppPreferences.class) {
if (prefs == null) {
final var p = Preferences.userNodeForPackage(Main.class);
final var p = Preferences.userRoot().node(AppIdentity.PREFERENCES_PATH);
if (shouldClear) {
try {
p.clear();
@@ -77,6 +77,10 @@ public final class ThemeColorPrefs {
*/
private static final int PALETTE_VERSION = 7;
static int shippedPaletteVersion() {
return PALETTE_VERSION;
}
private static final String PALETTE_VERSION_KEY = "canvasPaletteVersion";
/**
@@ -13,9 +13,9 @@ import static com.cburch.logisim.soc.Strings.S;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.instance.ComponentCaption;
import com.cburch.logisim.soc.gui.CpuStyle;
import com.cburch.logisim.util.ColorUtil;
import com.cburch.logisim.util.GraphicsUtil;
import java.awt.Graphics;
public class SocBusInfo {
@@ -49,6 +49,12 @@ public class SocBusInfo {
return myComp;
}
/** Full display text, also used by component tooltips when the painted preview is shortened. */
public String getDisplayName() {
final var ident = socManager == null ? null : socManager.getSocBusDisplayString(busId);
return ident == null ? S.get("SocBusNotConnected") : ident;
}
public void paint(Graphics g, Bounds b) {
final var ident = socManager == null ? null : socManager.getSocBusDisplayString(busId);
final var color = (ident == null) ? CpuStyle.programCounter() : CpuStyle.header();
@@ -56,7 +62,6 @@ public class SocBusInfo {
g.fillRect(b.getX(), b.getY(), b.getWidth(), b.getHeight());
g.setColor(ident == null
? ColorUtil.getComplementaryBlackWhite(color) : CpuStyle.headerText());
GraphicsUtil.drawCenteredText(
g, ident == null ? S.get("SocBusNotConnected") : ident, b.getCenterX(), b.getCenterY());
ComponentCaption.draw(g, getDisplayName(), b, ComponentCaption.FONT);
}
}
@@ -10,9 +10,11 @@
package com.cburch.logisim.soc.data;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.tools.ToolTipMaker;
import com.cburch.logisim.util.StringGetter;
public abstract class SocInstanceFactory extends InstanceFactory {
@@ -49,6 +51,24 @@ public abstract class SocInstanceFactory extends InstanceFactory {
return myType;
}
@Override
protected Object getInstanceFeature(Instance instance, Object key) {
if (key != ToolTipMaker.class) return super.getInstanceFeature(instance, key);
return (ToolTipMaker) event -> {
final var portTip = instance.getComponent().getToolTip(event);
if (portTip != null) return portTip;
final var text = new StringBuilder();
final var attrs = instance.getAttributeSet();
for (final var attr : attrs.getAttributes()) {
if (attrs.getValue(attr) instanceof SocBusInfo bus) {
if (!text.isEmpty()) text.append("; ");
text.append(attr.getDisplayName()).append(": ").append(bus.getDisplayName());
}
}
return text.isEmpty() ? null : text.toString();
};
}
public boolean isSocSlave() {
return (myType & SOC_SLAVE) != 0;
}
@@ -19,6 +19,7 @@ import com.cburch.logisim.data.Location;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.gui.canvas.CanvasStyle;
import com.cburch.logisim.gui.icons.ArithmeticIcon;
import com.cburch.logisim.instance.ComponentCaption;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
@@ -31,10 +32,10 @@ import com.cburch.logisim.soc.data.SocBusSnifferInterface;
import com.cburch.logisim.soc.data.SocInstanceFactory;
import com.cburch.logisim.soc.data.SocProcessorInterface;
import com.cburch.logisim.soc.data.SocSimulationManager;
import com.cburch.logisim.tools.ToolTipMaker;
import com.cburch.logisim.util.GraphicsUtil;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
/**
@@ -157,35 +158,24 @@ public class SocDma extends SocInstanceFactory {
int x = loc.getX();
int y = loc.getY();
Font f = g2.getFont();
g2.setFont(StdAttr.DEFAULT_LABEL_FONT);
GraphicsUtil.drawCenteredText(g2, "SOC DMA Engine", x + WIDTH / 2, y + 10);
g2.setFont(f);
ComponentCaption.draw(g2, "SOC DMA Engine", Bounds.create(x + 5, y + 2, WIDTH - 10, 18),
ComponentCaption.FONT.deriveFont(java.awt.Font.BOLD));
// Show base address and burst size
int baseAddr = painter.getAttributeValue(DmaAttributes.START_ADDRESS);
int burst = painter.getAttributeValue(DmaAttributes.BURST_SIZE);
GraphicsUtil.drawCenteredText(g2,
ComponentCaption.draw(g2,
S.get("SocDmaBaseLabel") + String.format("0x%08X", baseAddr),
x + WIDTH / 4, y + 30);
GraphicsUtil.drawCenteredText(g2,
Bounds.create(x + 5, y + 23, 150, 16), ComponentCaption.FONT);
ComponentCaption.draw(g2,
S.get("SocDmaBurstLabel") + burst + " words/tick",
x + 3 * WIDTH / 4, y + 30);
Bounds.create(x + 165, y + 23, 150, 16), ComponentCaption.FONT);
// Show runtime status if available
DmaState.DmaRegState data = (DmaState.DmaRegState) painter.getData();
if (data != null) {
String statusStr;
if (data.busy) {
int pct = data.length > 0 ? (data.bytesDone * 100 / data.length) : 0;
statusStr = S.get("SocDmaStatusBusy") + " " + pct + "%"
+ " (" + data.bytesDone + "/" + data.length + " bytes)";
} else if (data.done) {
statusStr = S.get("SocDmaStatusDone");
} else {
statusStr = S.get("SocDmaStatusIdle");
}
GraphicsUtil.drawCenteredText(g2, statusStr, x + WIDTH / 2, y + 50);
ComponentCaption.draw(g2, statusCaption(data),
Bounds.create(x + 5, y + 43, WIDTH - 10, 18), ComponentCaption.FONT);
}
if (painter.isPrintView()) return;
@@ -202,9 +192,38 @@ public class SocDma extends SocInstanceFactory {
// Bus connection labels
g2.setColor(CanvasStyle.componentColor());
GraphicsUtil.drawCenteredText(g2, "CTRL", x + 55, y + 85);
GraphicsUtil.drawCenteredText(g2, "SRC", x + 160, y + 85);
GraphicsUtil.drawCenteredText(g2, "DST", x + 265, y + 85);
ComponentCaption.draw(g2, "CTRL", Bounds.create(x + 5, y + 82, 100, 15), ComponentCaption.FONT);
ComponentCaption.draw(g2, "SRC", Bounds.create(x + 110, y + 82, 100, 15), ComponentCaption.FONT);
ComponentCaption.draw(g2, "DST", Bounds.create(x + 215, y + 82, 100, 15), ComponentCaption.FONT);
}
private static String statusCaption(DmaState.DmaRegState data) {
if (data.busy) {
final var pct = data.length > 0 ? (data.bytesDone * 100L / data.length) : 0;
return S.get("SocDmaStatusBusy") + " " + pct + "%"
+ " (" + data.bytesDone + "/" + data.length + " bytes)";
}
return S.get(data.done ? "SocDmaStatusDone" : "SocDmaStatusIdle");
}
@Override
protected Object getInstanceFeature(Instance instance, Object key) {
final var inherited = super.getInstanceFeature(instance, key);
if (key != ToolTipMaker.class) return inherited;
return (ToolTipMaker) event -> {
final var ports = instance.getComponent().getToolTip(event);
if (ports != null) return ports;
final var attrs = instance.getAttributeSet();
var text = S.get("SocDmaBaseLabel")
+ String.format("0x%08X", attrs.getValue(DmaAttributes.START_ADDRESS)) + "; "
+ S.get("SocDmaBurstLabel") + attrs.getValue(DmaAttributes.BURST_SIZE) + " words/tick";
final var state = event.getCircuitState();
if (state != null && instance.getData(state) instanceof DmaState.DmaRegState data) {
text += "; " + statusCaption(data);
}
final var buses = ((ToolTipMaker) inherited).getToolTip(event);
return buses == null ? text : text + "; " + buses;
};
}
@Override
@@ -110,8 +110,15 @@ abstract class AbstractGate extends InstanceFactory {
cx = loc.getX() - axis;
cy = loc.getY() + perp;
}
instance.setTextField(
StdAttr.LABEL, StdAttr.LABEL_FONT, cx, cy, TextField.H_CENTER, TextField.V_CENTER);
// The central quarter stays clear of the curved outline and input/output bubbles.
instance.setAutoLabelTextField(cx, cy, TextField.H_CENTER, TextField.V_CENTER, () -> {
if (AppPreferences.GATE_SHAPE.get().equals(AppPreferences.SHAPE_RECTANGULAR)) {
return Bounds.EMPTY_BOUNDS; // Do not cover the IEC function glyph.
}
final var body = instance.getBounds();
return Bounds.create(body.getX() + body.getWidth() / 4, body.getY() + body.getHeight() / 4,
body.getWidth() / 2, body.getHeight() / 2);
});
}
protected abstract Value computeOutput(Value[] inputs, int numInputs, InstanceState state);
@@ -354,6 +361,7 @@ abstract class AbstractGate extends InstanceFactory {
} else if (attr == GateAttributes.ATTR_INPUTS || attr instanceof NegateAttribute) {
instance.recomputeBounds();
computePorts(instance);
computeLabel(instance);
} else if (attr == GateAttributes.ATTR_XOR) {
instance.fireInvalidated();
}
@@ -66,7 +66,7 @@ class NotGate extends InstanceFactory {
halign = TextField.H_CENTER;
}
}
instance.setTextField(StdAttr.LABEL, StdAttr.LABEL_FONT, x, y, halign, TextField.V_BASELINE);
instance.setAutoLabelTextField(x, y, halign, TextField.V_BASELINE, Bounds.EMPTY_BOUNDS);
}
public static final AttributeOption SIZE_NARROW =
@@ -17,8 +17,8 @@ import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Attributes;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.ComponentCaption;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
@@ -26,12 +26,11 @@ import com.cburch.logisim.instance.InstanceState;
import com.cburch.logisim.instance.Port;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.tools.ToolTipMaker;
import com.cburch.logisim.tools.key.BitWidthConfigurator;
import com.cburch.logisim.tools.key.JoinedConfigurator;
import com.cburch.logisim.util.GraphicsUtil;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Font;
public class BitExtender extends InstanceFactory {
/**
@@ -58,6 +57,7 @@ public class BitExtender extends InstanceFactory {
});
public static final BitExtender FACTORY = new BitExtender();
private static final Font CAPTION_FONT = ComponentCaption.FONT.deriveFont(10f);
public BitExtender() {
super(_ID, S.getter("extenderComponent"), new BitExtenderHdlGeneratorFactory());
@@ -98,6 +98,30 @@ public class BitExtender extends InstanceFactory {
return (String) topt.getValue();
}
private String modeCaption(AttributeSet attrs) {
return switch (getType(attrs)) {
case "zero" -> S.get("extenderZeroLabel");
case "one" -> S.get("extenderOneLabel");
case "sign" -> S.get("extenderSignLabel");
case "input" -> S.get("extenderInputLabel");
default -> "???";
};
}
private String widthCaption(AttributeSet attrs) {
return attrs.getValue(ATTR_IN_WIDTH).getWidth() + "→"
+ attrs.getValue(ATTR_OUT_WIDTH).getWidth();
}
@Override
protected Object getInstanceFeature(Instance instance, Object key) {
if (key == ToolTipMaker.class) {
return (ToolTipMaker) event -> modeCaption(instance.getAttributeSet()) + " "
+ S.get("extenderMainLabel") + " " + widthCaption(instance.getAttributeSet());
}
return super.getInstanceFeature(instance, key);
}
@Override
protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
if (attr == ATTR_TYPE) {
@@ -111,35 +135,17 @@ public class BitExtender extends InstanceFactory {
//
@Override
public void paintInstance(InstancePainter painter) {
Graphics g = painter.getGraphics();
FontMetrics fm = g.getFontMetrics();
int asc = fm.getAscent();
final var g = painter.getGraphics();
g.setColor(new Color(AppPreferences.COMPONENT_COLOR.get()));
painter.drawBounds();
String s0;
String type = getType(painter.getAttributeSet());
s0 = switch (type) {
case "zero" -> S.get("extenderZeroLabel");
case "one" -> S.get("extenderOneLabel");
case "sign" -> S.get("extenderSignLabel");
case "input" -> S.get("extenderInputLabel");
default -> "???"; // should never happen
};
String s1 = S.get("extenderMainLabel");
Bounds bds = painter.getBounds();
int x = bds.getX() + bds.getWidth() / 2;
int y0 = bds.getY() + (bds.getHeight() / 2 + asc) / 2;
int y1 = bds.getY() + (3 * bds.getHeight() / 2 + asc) / 2;
GraphicsUtil.drawText(g, s0, x, y0, GraphicsUtil.H_CENTER, GraphicsUtil.V_BASELINE);
GraphicsUtil.drawText(g, s1, x, y1, GraphicsUtil.H_CENTER, GraphicsUtil.V_BASELINE);
BitWidth w0 = painter.getAttributeValue(ATTR_OUT_WIDTH);
BitWidth w1 = painter.getAttributeValue(ATTR_IN_WIDTH);
painter.drawPort(0, "" + w0.getWidth(), Direction.WEST);
painter.drawPort(1, "" + w1.getWidth(), Direction.EAST);
if (type.equals("input")) painter.drawPort(2);
final var b = painter.getBounds();
// Two rows leave room for the top control port and side pin markers. The arrow expresses
// input/output width without three competing text rows in the unchanged 40-unit body.
ComponentCaption.draw(g, modeCaption(painter.getAttributeSet()),
Bounds.create(b.getX() + 3, b.getY() + 6, 34, 13), CAPTION_FONT);
ComponentCaption.draw(g, widthCaption(painter.getAttributeSet()),
Bounds.create(b.getX() + 1, b.getY() + 24, 38, 13), CAPTION_FONT);
painter.drawPorts();
}
@Override
@@ -26,6 +26,7 @@ import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Direction;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.gui.generic.OptionPane;
import com.cburch.logisim.gui.icons.ComponentIcons;
import com.cburch.logisim.gui.main.Canvas;
import com.cburch.logisim.gui.main.SelectionActions;
import com.cburch.logisim.gui.main.ToolAttributeAction;
@@ -656,7 +657,7 @@ public class AddTool extends Tool implements Transferable, PropertyChangeListene
@Override
public void paintIcon(ComponentDrawContext c, int x, int y) {
final var desc = description;
if (desc != null && !desc.isFactoryLoaded()) {
if (desc != null && (!desc.isFactoryLoaded() || desc.hasPersistentIcon())) {
final var icon = desc.getIcon();
if (icon != null) {
icon.paintIcon(c.getDestination(), c.getGraphics(), x + 2, y + 2);
@@ -666,6 +667,11 @@ public class AddTool extends Tool implements Transferable, PropertyChangeListene
ComponentFactory source = getFactory();
if (source != null) {
final var icon = ComponentIcons.forFactory(source.getClass());
if (icon != null) {
icon.paintIcon(c.getDestination(), c.getGraphics(), x + 2, y + 2);
return;
}
final var base = getBaseAttributes();
source.paintIcon(c, x, y, base);
}
@@ -10,6 +10,7 @@
package com.cburch.logisim.tools;
import com.cburch.logisim.comp.ComponentFactory;
import com.cburch.logisim.gui.icons.ComponentIcons;
import com.cburch.logisim.util.IconsUtil;
import com.cburch.logisim.util.LibraryUtil;
import com.cburch.logisim.util.StringGetter;
@@ -33,6 +34,8 @@ public class FactoryDescription {
private String iconName;
private boolean iconLoadAttempted;
private Icon icon;
private final Icon builtinIcon;
private boolean explicitIcon;
private final Class<? extends ComponentFactory> factoryClass;
private boolean factoryLoadAttempted;
private ComponentFactory factory;
@@ -41,6 +44,11 @@ public class FactoryDescription {
public FactoryDescription(
Class<? extends ComponentFactory> factoryClass, StringGetter displayName, Icon icon) {
this(factoryClass, displayName);
if (icon != null) {
this.icon = icon;
this.iconLoadAttempted = true;
this.explicitIcon = true;
}
}
public FactoryDescription(
@@ -54,8 +62,9 @@ public class FactoryDescription {
public FactoryDescription(
Class<? extends ComponentFactory> factoryClass, StringGetter displayName) {
this.displayName = displayName;
this.builtinIcon = ComponentIcons.forFactory(factoryClass);
this.iconName = "???";
this.iconLoadAttempted = true;
this.iconLoadAttempted = builtinIcon == null;
this.icon = null;
this.factoryClass = factoryClass;
this.factoryLoadAttempted = false;
@@ -111,13 +120,18 @@ public class FactoryDescription {
public Icon getIcon() {
var ret = icon;
if (ret == null && !iconLoadAttempted) {
ret = IconsUtil.getIcon(iconName);
ret = builtinIcon != null ? builtinIcon : IconsUtil.getIcon(iconName);
icon = ret;
iconLoadAttempted = true;
}
return ret;
}
/** Explicit extension icons and built-in miniatures must not change after lazy factory load. */
boolean hasPersistentIcon() {
return explicitIcon || builtinIcon != null;
}
/**
* Returns unique library identifier.
*
@@ -18,9 +18,10 @@ import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.gui.main.Canvas;
import com.cburch.logisim.gui.main.SelectionActions;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.proj.Project;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -195,13 +196,7 @@ public class MenuTool extends Tool {
@Override
public void paintIcon(ComponentDrawContext c, int x, int y) {
final var g = c.getGraphics();
g.fillRect(x + 2, y + 1, 9, 2);
g.drawRect(x + 2, y + 3, 15, 12);
g.setColor(Color.lightGray);
g.drawLine(x + 4, y + 2, x + 8, y + 2);
for (int y_offs = y + 6; y_offs < y + 15; y_offs += 3) {
g.drawLine(x + 4, y_offs, x + 14, y_offs);
}
AppIcons.get(AppIcons.Id.MORE, AppPreferences.IconSize)
.paintIcon(c.getDestination(), c.getGraphics(), x, y);
}
}
@@ -23,16 +23,14 @@ import com.cburch.logisim.comp.ComponentUserEvent;
import com.cburch.logisim.data.AttributeSet;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.gui.main.Canvas;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.prefs.AppPreferences;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
public class PokeTool extends Tool {
/**
@@ -273,31 +271,8 @@ public class PokeTool extends Tool {
@Override
public void paintIcon(ComponentDrawContext c, int x, int y) {
final var g2 = (Graphics2D) c.getGraphics().create();
g2.translate(x, y);
g2.setStroke(new BasicStroke(AppPreferences.getScaled(1)));
final var p = new GeneralPath();
p.moveTo(scale(6), scale(15));
p.quadTo(scale(5), scale(10), scale(1), scale(7));
p.quadTo(scale(2.5), scale(4), scale(4), scale(7));
p.quadTo(scale(6), scale(6), scale(6), scale(10));
p.lineTo(scale(6), scale(1));
p.quadTo(scale(7), scale(-1), scale(8), scale(1));
p.lineTo(scale(8), scale(8));
p.quadTo(scale(9), scale(2), scale(10), scale(8));
p.quadTo(scale(11), scale(2), scale(12), scale(8));
p.lineTo(scale(12), scale(9));
p.quadTo(scale(13), scale(4), scale(14), scale(9));
p.quadTo(scale(12), scale(11), scale(13), scale(15));
g2.setColor(new Color(240, 184, 160));
g2.fill(p);
g2.setColor(Color.BLACK);
g2.draw(p);
g2.dispose();
}
private static double scale(double s) {
return AppPreferences.getScaled(s);
AppIcons.get(AppIcons.Id.HAND, AppPreferences.IconSize)
.paintIcon(c.getDestination(), c.getGraphics(), x, y);
}
private void removeCaret(boolean normal) {
@@ -16,18 +16,16 @@ import com.cburch.logisim.circuit.Wire;
import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.gui.canvas.CanvasStyle;
import com.cburch.logisim.gui.main.Canvas;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.proj.Action;
import com.cburch.logisim.util.GraphicsUtil;
import com.cburch.logisim.util.StringGetter;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
@@ -338,22 +336,8 @@ public class WiringTool extends Tool {
@Override
public void paintIcon(ComponentDrawContext c, int x, int y) {
final var g2 = (Graphics2D) c.getGraphics().create();
g2.translate(x, y);
final int[] points = {3, 13, 8, 13, 8, 3, 13, 3};
g2.setStroke(new BasicStroke(AppPreferences.getScaled(2)));
for (var i = 0; i < points.length - 2; i += 2)
g2.drawLine(
AppPreferences.getScaled(points[i]),
AppPreferences.getScaled(points[i + 1]),
AppPreferences.getScaled(points[i + 2]),
AppPreferences.getScaled(points[i + 3]));
g2.setColor(Value.trueColor());
final var wh = AppPreferences.getScaled(5);
g2.fillOval(AppPreferences.getScaled(1), AppPreferences.getScaled(11), wh, wh);
g2.setColor(Value.unknownColor());
g2.fillOval(AppPreferences.getScaled(11), AppPreferences.getScaled(1), wh, wh);
g2.dispose();
AppIcons.get(AppIcons.Id.WIRE, AppPreferences.IconSize)
.paintIcon(c.getDestination(), c.getGraphics(), x, y);
}
private boolean performShortening(Canvas canvas, Location drag0, Location drag1) {
@@ -1,242 +1,228 @@
/*
* 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.util;
import static com.cburch.logisim.gui.Strings.S;
import com.cburch.logisim.gui.generic.OptionPane;
import com.cburch.logisim.gui.theme.AppIcons;
import com.cburch.logisim.gui.theme.Tokens;
import com.cburch.logisim.gui.theme.Theme;
import com.cburch.logisim.prefs.AppPreferences;
import com.cburch.logisim.prefs.PrefMonitorKeyStroke;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.Insets;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.prefs.BackingStoreException;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.text.AbstractDocument;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
import javax.swing.SwingUtilities;
/** A shortcut draft is applied explicitly; leaving the editor never changes the binding. */
public class JHotkeyInput extends JPanel {
private static JFrame topFrame = null;
private static final long serialVersionUID = 1L;
private final JButton resetButton = new JButton();
private final JButton applyButton = new JButton();
private static final String os = System.getProperty("os.name").toLowerCase();
private static final String lookAndFeel = UIManager.getLookAndFeel().getName();
private int fieldVerticalBias;
private int fieldHorizonalBias;
private final JPanel actions = new JPanel(new GridLayout(1, 2));
public final JTextField hotkeyInputField;
private transient PrefMonitorKeyStroke boundKeyStroke = null;
private final transient HotkeyInputKeyListener hotkeyListener;
private boolean focusableEnabled = false;
private static int layoutOptimizedDelay = 4;
private static boolean globalLayoutOptimized = false;
private boolean needUpdate = false;
private static boolean activeHotkeyInputUpdated = false;
private static String activeHotkeyInputName = "";
private transient PrefMonitorKeyStroke boundKeyStroke;
private KeyStroke draft;
private boolean clearRequested;
private boolean editing;
private String previousData;
private static final List<JHotkeyInput> JHotkeyInputList = new ArrayList<>();
private static final Timer optimizeTimer = new Timer(80, e -> {
if (topFrame == null) {
return;
}
for (var com : JHotkeyInputList) {
int height = com.getHeight();
int width = com.getWidth();
if (!globalLayoutOptimized && width > 0 && layoutOptimizedDelay-- > 0) {
/* run only once */
com.setPreferredSize(new Dimension(width + 18 + 18, height));
globalLayoutOptimized = true;
com.repaint();
com.updateUI();
}
if (!com.focusableEnabled && globalLayoutOptimized) {
/* run on every component's load */
Dimension preferredSize = com.hotkeyInputField.getPreferredSize();
if (preferredSize.height + com.fieldVerticalBias >= com.getHeight()) {
com.setPreferredSize(new Dimension(width, preferredSize.height + com.fieldVerticalBias));
}
com.exitEditMode();
com.hotkeyInputField.setFocusable(true);
com.focusableEnabled = true;
}
if (com.needUpdate && com.boundKeyStroke != null
&& activeHotkeyInputUpdated
&& !activeHotkeyInputName.equals(com.boundKeyStroke.getName())) {
com.needUpdate = false;
com.exitEditMode();
}
}
});
public JHotkeyInput(JFrame frame, String text) {
if (topFrame == null) {
topFrame = frame;
}
hotkeyListener = new HotkeyInputKeyListener(this);
hotkeyInputField = new JTextField(text.toUpperCase());
previousData = text;
setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));
Insets insets = hotkeyInputField.getBorder().getBorderInsets(hotkeyInputField);
fieldVerticalBias = insets.top + insets.bottom;
fieldHorizonalBias = insets.left + insets.right;
/* adaption for different look and feels in different platforms by tests */
if (os.contains("linux") && !lookAndFeel.contains("Flat")) {
fieldVerticalBias += 2;
}
setBorder(BorderFactory.createCompoundBorder(
hotkeyInputField.getBorder(),
BorderFactory.createEmptyBorder(2, 4, 2, 4)
));
setPreferredSize(new Dimension(170, 28));
((AbstractDocument) hotkeyInputField.getDocument())
.setDocumentFilter(new KeyboardInputFilter());
super(new BorderLayout());
previousData = text.toUpperCase(Locale.ROOT);
hotkeyInputField = new JTextField(previousData, 12);
hotkeyInputField.setHorizontalAlignment(SwingConstants.CENTER);
/* Sometimes the look and feel will override our settings
* Then it will look strange
* Especially for the theme Nimbus
* So we have to do the belows to make Nimbus happy
* */
if (lookAndFeel.contains("Nimbus")) {
hotkeyInputField.setBackground(new Color(0, 0, 0, 0));
} else {
hotkeyInputField.setBackground(topFrame.getBackground());
}
hotkeyInputField.setBorder(BorderFactory.createEmptyBorder());
hotkeyInputField.addKeyListener(hotkeyListener);
hotkeyInputField.addFocusListener(new FocusListener() {
// The key listener owns the draft, not JTextField's ordinary text editing bindings.
hotkeyInputField.setEditable(false);
hotkeyInputField.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
public void focusGained(FocusEvent event) {
enterEditMode();
}
@Override
public void focusLost(FocusEvent e) {
needUpdate = true;
public void focusLost(FocusEvent event) {
final var next = event.getOppositeComponent();
if (!event.isTemporary() && next != null
&& !SwingUtilities.isDescendingFrom(next, JHotkeyInput.this)) {
exitEditModeWithoutRefresh();
}
}
});
hotkeyInputField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent event) {
// Consume before validation or window-level Escape bindings can handle this key.
event.consume();
if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
exitEditModeWithoutRefresh();
return;
}
enterEditMode();
capture(event);
}
applyButton.setIcon(AppIcons.colored(AppIcons.Id.CHECK, AppIcons.SIZE, Tokens.success()));
resetButton.setIcon(AppIcons.colored(AppIcons.Id.CLOSE, AppIcons.SIZE, Tokens.error()));
applyButton.setBorder(BorderFactory.createEmptyBorder());
applyButton.setVisible(false);
resetButton.setBorder(BorderFactory.createEmptyBorder());
resetButton.setVisible(false);
resetButton.addActionListener(e -> exitEditMode());
applyButton.addActionListener(e -> applyChanges());
Font font = resetButton.getFont();
applyButton.setFont(new Font(font.getFontName(), Font.PLAIN, 8));
applyButton.setPreferredSize(new Dimension(18, 18));
resetButton.setFont(new Font(font.getFontName(), Font.PLAIN, 8));
resetButton.setPreferredSize(new Dimension(18, 18));
@Override
public void keyReleased(KeyEvent event) {
// In particular, Escape's release must not erase the restored binding.
event.consume();
}
hotkeyInputField.setFocusable(false);
add(hotkeyInputField);
add(applyButton);
add(resetButton);
@Override
public void keyTyped(KeyEvent event) {
event.consume();
}
});
getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancelCapture");
getActionMap().put("cancelCapture", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent event) {
exitEditModeWithoutRefresh();
}
});
applyButton.addActionListener(event -> applyChanges());
resetButton.addActionListener(event -> exitEditMode());
actions.add(applyButton);
actions.add(resetButton);
actions.setVisible(false);
add(hotkeyInputField, BorderLayout.CENTER);
add(actions, BorderLayout.LINE_END);
refreshPresentation();
Theme.addListener(this, this::refreshPresentation);
}
if (!optimizeTimer.isRunning()) {
optimizeTimer.start();
}
private void refreshPresentation() {
applyButton.setIcon(AppIcons.get(AppIcons.Id.CHECK));
resetButton.setIcon(AppIcons.get(AppIcons.Id.CLOSE));
applyButton.setToolTipText(S.get("hotkeyApply"));
resetButton.setToolTipText(S.get("hotkeyCancel"));
applyButton.getAccessibleContext().setAccessibleName(S.get("hotkeyApply"));
resetButton.getAccessibleContext().setAccessibleName(S.get("hotkeyCancel"));
hotkeyInputField.setToolTipText(S.get("hotkeyCaptureHelp"));
revalidate();
repaint();
}
@Override
public Dimension getPreferredSize() {
final var size = super.getPreferredSize();
// Reserve action space even outside capture, so neighbouring controls never jump.
if (actions != null && !actions.isVisible()) size.width += actions.getPreferredSize().width;
return size;
}
private void enterEditMode() {
activeHotkeyInputName = boundKeyStroke.getName();
activeHotkeyInputUpdated = true;
if (hotkeyListener.rewritable()) {
previousData = hotkeyInputField.getText();
}
hotkeyListener.clearStatus();
if (editing || boundKeyStroke == null || !isEnabled()) return;
editing = true;
clearDraft();
hotkeyInputField.setText("");
resetButton.setVisible(true);
applyButton.setVisible(true);
int height = hotkeyInputField.getHeight();
int width = getWidth() - 18 - 18 - 8 - 8 - 6 - fieldHorizonalBias;
hotkeyInputField.setPreferredSize(new Dimension(width, height));
actions.setVisible(true);
revalidate();
}
private void clearDraft() {
draft = null;
clearRequested = false;
applyButton.setEnabled(false);
}
private void capture(KeyEvent event) {
if (!editing) return;
final var code = event.getKeyCode();
if (code == KeyEvent.VK_CONTROL || code == KeyEvent.VK_ALT
|| code == KeyEvent.VK_ALT_GRAPH || code == KeyEvent.VK_SHIFT
|| code == KeyEvent.VK_META || code == KeyEvent.VK_UNDEFINED) return;
clearDraft();
if (code == KeyEvent.VK_BACK_SPACE || code == KeyEvent.VK_DELETE) {
clearRequested = true;
hotkeyInputField.setText("");
applyButton.setEnabled(true);
return;
}
final var modifiers = event.getModifiersEx();
if (!boundKeyStroke.metaCheckPass(modifiers)) {
showError(S.get("hotkeyErrMeta",
InputEvent.getModifiersExText(AppPreferences.hotkeyMenuMask)));
return;
}
final var conflict =
AppPreferences.hotkeyCheckConflict(boundKeyStroke.getName(), code, modifiers);
if (!conflict.isEmpty()) {
showError(conflict);
return;
}
draft = KeyStroke.getKeyStroke(code, modifiers);
final var prefix = InputEvent.getModifiersExText(modifiers);
setText((prefix.isEmpty() ? "" : prefix + "+") + KeyEvent.getKeyText(code));
applyButton.setEnabled(true);
}
private void showError(String message) {
hotkeyInputField.setText("");
OptionPane.showMessageDialog(this, message, S.get("hotkeyOptTitle"), OptionPane.ERROR_MESSAGE);
}
public void exitEditModeWithoutRefresh() {
activeHotkeyInputUpdated = false;
applyButton.setVisible(false);
resetButton.setVisible(false);
editing = false;
clearDraft();
actions.setVisible(false);
hotkeyInputField.setText(previousData);
revalidate();
repaint();
}
public void exitEditMode() {
exitEditModeWithoutRefresh();
int height = hotkeyInputField.getHeight();
int width = getWidth() - 12;
hotkeyInputField.setPreferredSize(new Dimension(width, height));
repaint();
updateUI();
topFrame.requestFocus();
}
private void applyChanges() {
var changed = false;
if (hotkeyListener.clearRequested) {
boundKeyStroke.set((KeyStroke) null);
previousData = "";
changed = true;
} else if (hotkeyListener.code != 0) {
boundKeyStroke.set(KeyStroke.getKeyStroke(hotkeyListener.code, hotkeyListener.modifier));
previousData = hotkeyListener.getHotkeyString();
changed = true;
if (boundKeyStroke == null || (!clearRequested && draft == null)) return;
boundKeyStroke.set(clearRequested ? null : draft);
previousData = hotkeyInputField.getText();
try {
AppPreferences.getPrefs().flush();
AppPreferences.hotkeySync();
} catch (BackingStoreException exception) {
throw new RuntimeException(exception);
}
if (changed) {
try {
AppPreferences.getPrefs().flush();
AppPreferences.hotkeySync();
} catch (BackingStoreException ex) {
throw new RuntimeException(ex);
}
}
applyButton.setVisible(false);
resetButton.setVisible(false);
int height = hotkeyInputField.getHeight();
int width = getWidth() - 12;
hotkeyInputField.setPreferredSize(new Dimension(width, height));
repaint();
updateUI();
topFrame.requestFocus();
exitEditModeWithoutRefresh();
}
public void setText(String s) {
hotkeyInputField.setText(s.toUpperCase());
public void setText(String text) {
hotkeyInputField.setText(text.toUpperCase(Locale.ROOT));
}
public void resetText(String s) {
hotkeyInputField.setText(s.toUpperCase());
previousData = s.toUpperCase();
public void resetText(String text) {
previousData = text.toUpperCase(Locale.ROOT);
exitEditModeWithoutRefresh();
}
public void setBoundKeyStroke(PrefMonitorKeyStroke keyStroke) {
boundKeyStroke = keyStroke;
JHotkeyInputList.add(this);
hotkeyInputField.getAccessibleContext().setAccessibleName(S.get(keyStroke.getName()));
}
public PrefMonitorKeyStroke getBoundKeyStroke() {
@@ -245,114 +231,12 @@ public class JHotkeyInput extends JPanel {
@Override
public void setEnabled(boolean enabled) {
hotkeyInputField.setEnabled(enabled);
super.setEnabled(enabled);
if (hotkeyInputField != null) hotkeyInputField.setEnabled(enabled);
if (!enabled && actions != null) exitEditModeWithoutRefresh();
}
public void setApplyEnabled(boolean enabled) {
applyButton.setEnabled(enabled);
}
private class HotkeyInputKeyListener implements KeyListener {
private final JHotkeyInput hotkeyInput;
private int modifier = 0;
private int code = 0;
private boolean clearRequested = false;
private boolean rewriteFlag = true;
private String hotkeyString = "";
public HotkeyInputKeyListener(JHotkeyInput hotkeyInput) {
this.hotkeyInput = hotkeyInput;
}
public void clearStatus() {
code = 0;
modifier = 0;
hotkeyString = "";
clearRequested = false;
rewriteFlag = false;
}
public boolean rewritable() {
return rewriteFlag;
}
public String getHotkeyString() {
return hotkeyString;
}
@Override
public void keyTyped(KeyEvent e) {
/* not-used */
}
@Override
public void keyPressed(KeyEvent e) {
modifier = e.getModifiersEx();
code = e.getKeyCode();
if (code == KeyEvent.VK_BACK_SPACE || code == KeyEvent.VK_DELETE) {
clearStatus();
clearRequested = true;
hotkeyInput.setText("");
hotkeyInput.setApplyEnabled(true);
return;
}
clearRequested = false;
if (code == 0
|| code == KeyEvent.VK_CONTROL
|| code == KeyEvent.VK_ALT
|| code == KeyEvent.VK_SHIFT
|| code == KeyEvent.VK_META) {
clearStatus();
return;
}
String modifierString = InputEvent.getModifiersExText(modifier);
if (modifierString.isEmpty()) {
hotkeyString = KeyEvent.getKeyText(code);
} else {
hotkeyString = InputEvent.getModifiersExText(modifier) + "+" + KeyEvent.getKeyText(code);
}
if (!(hotkeyInput.getBoundKeyStroke().metaCheckPass(modifier))) {
OptionPane.showMessageDialog(null, S.get("hotkeyErrMeta",
InputEvent.getModifiersExText(AppPreferences.hotkeyMenuMask)),
S.get("hotkeyOptTitle"),
OptionPane.ERROR_MESSAGE);
clearStatus();
return;
}
String checkPass = AppPreferences.hotkeyCheckConflict(hotkeyInput.boundKeyStroke.getName(),
code, modifier);
if (!checkPass.isEmpty()) {
OptionPane.showMessageDialog(null,
checkPass,
S.get("hotkeyOptTitle"),
OptionPane.ERROR_MESSAGE);
clearStatus();
return;
}
hotkeyInput.setText("");
}
@Override
public void keyReleased(KeyEvent e) {
hotkeyInput.setText(hotkeyString);
hotkeyInput.setApplyEnabled(clearRequested || !hotkeyString.isEmpty());
if (!hotkeyString.isEmpty()) {
rewriteFlag = true;
}
}
}
private class KeyboardInputFilter extends DocumentFilter {
@Override
public void insertString(DocumentFilter.FilterBypass fb, int offset,
String text, AttributeSet attr) throws BadLocationException {
fb.insertString(offset, text.toUpperCase(), attr);
}
@Override
public void replace(DocumentFilter.FilterBypass fb, int offset, int length,
String text, AttributeSet attrs) throws BadLocationException {
fb.replace(offset, length, text.toUpperCase(), attrs);
}
}
}
+3 -3
View File
@@ -5,8 +5,8 @@
JavaHelp TOC Version 2.0//EN"
"http://java.sun.com/products/javahelp/toc_2_0.dtd">
<toc version="2.0" categoryclosedimage="chapter" topicimage="topic">
<tocitem image="toplevelfolder" target="top" text="Logisim References">
<tocitem target="guide" text="Guide to Being a Logisim User">
<tocitem image="toplevelfolder" target="top" text="Logisim Revolution Help">
<tocitem target="guide" text="User Guide">
<tocitem target="tutorial" text="Beginner's tutorial">
<tocitem target="tutorial_orient" text="Step 0: Orienting yourself" />
<tocitem target="tutorial_gates" text="Step 1: Adding gates" />
@@ -15,7 +15,7 @@
<tocitem target="tutorial_test" text="Step 4: Testing your circuit" />
<tocitem target="tutorial_step" text="Step 5: Step by step mode"/>
</tocitem>
<tocitem target="gui" text="The graphical interfaces">
<tocitem target="gui" text="The editor">
<tocitem target="gui_canvas" text="The canvas"/>
<tocitem target="gui_menu" text="The menus"/>
<tocitem target="gui_explore" text="The explorer pane" />
@@ -1,142 +1,26 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="created" content="2019-03-21T16:12:10.000000000">
<meta name="changed" content="2019-03-21T16:12:42.000000000">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Language" content="en">
<title>
About the program Logisim-evolution
</title>
<title>About Logisim Revolution</title>
<link rel="stylesheet" type="text/css" href="../../style.css">
</head>
<body>
<div class="maindiv">
<img src="../../../../img-guide/logisim.png" alt="#########" height=276 width=734>
<h1>
About the program Logisim-evolution
</h1>
<p>
<strong>Logisim</strong> Logisim is an educational tool for designing and simulating digital logic circuits. It has been originally created by <a href="https://www.cburch.com/logisim/" rel="nofollow">Dr. Carl Burch</a> and actively developed until 2011. After this date the author focused on other projects, and recently the development has been officially stopped <a href="https://www.cburch.com/logisim/retire-note.html" rel="nofollow">(see his message here)</a>.
</p>
<p>
In the meantime, people from a group of swiss institutes (<a href="https://www.bfh.ch" rel="nofollow">Haute École Spécialisée Bernoise</a>,<a href="https://hepia.hesge.ch/" rel="nofollow">Haute École du paysage, d'ingénierie et d'architecture de Genève</a>,<a href="https://heig-vd.ch/" rel="nofollow">Haute École d'Ingénierie et de Gestion du Canton de Vaud</a>) started developing a version of Logisim that fitted their courses, integrating several tools -- for instance a chronogram, the possibility to test the schematics directly on an electronic board, TCL/TK consoles, ...
</p>
<p>
We have decided to release this new Logisim version under the name <b><a href="https://github.com/logisim-evolution/logisim-evolution/blob/master/README.md"> Logisim-evolution</a></b>, to highlight the large number of changes that occurred in these years, and we actively seek the contribution of the community.
</p>
<p>
This program 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. This program 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. (see below license)
</p>
<h2>
Languages
</h2>
<p>
Logisim supports many languages. Many of them were automatically translated using DeepL. If you should find bizarre translations, please do not hesitate to correct them in the corresponding property files and to make a pull request!
</p>
<h2>
What's new in logisim-evolution
</h2>
<ul>
<li>Chronogram -- to see the evolution of signals in your circuit
</li>
<li>Electronic board integration -- schematics can now be simulated on real hardware!
</li>
<li>Board editor -- to add new electronic boards
</li>
<li>VHDL component -- a new component type whose behavior is specified in VHDL.
</li>
<li>TCL/TK console -- interfaces between the circuit and the user
</li>
<li>DIP switches
</li>
<li>RGB LEDs
</li>
<li>Large number of bug-fixes
</li>
<li>GUI improvements
</li>
<li>Automatic updates
</li>
<li>Code refactoring
</li>
<li>...
</li>
</ul>
<h2>
Retro-compatibility
</h2>We cannot assure retro-compatibility of logisim-evolution with files created with the original Logisim. We have incorporated a parser that alters the name of the components to satisfy VHDL requirements for variable names, but components evolved in shape since then (think, for instance, to RAM and counters). You might need to rework a bit your circuits when opening them with logisim-evolution -- but the changes will be stored in the new format, therefore you have to do your work only once.
<h2>
Wish-list
</h2>
<p>
Logisim-evolution is a continuously-growing software, and we have several ideas we would like to implement. In particular, we would like to have:
</p>unit tests for the code
<ul>
<li>extensive documentation
</li>
<li>test circuits
</li>
<li>... If you are willing to contribute with any of these, please feel free to contact us!
</li>
</ul>
<h2>
How to get support for logisim-evolution
</h2>
<p>
Unfortunately, we do not have enough resources to provide direct support for logisim-evolution. We will, however, try to deal with the raised issues in a best-effort way.
</p>
<p>
If you find a bug or have an idea for an interesting feature, please do not hesitate to open a ticket!
</p>
<p>
<a href="https://github.com/logisim-evolution/logisim-evolution/blob/master/README.md">Logisim-evolution</a>
</p>
<h2>
License
</h2>
<p>
The code is licensed under the <a href="https://www.gnu.org/licenses/gpl-3.0.en.html">GNU GENERAL PUBLIC LICENSE, version 3.</a><br>
local version <a href="../../../../gpl.html">GPL</a>
</p>
<p align="center">
<img class="notscal" src="../../../../img-guide/gplv3-127x51.png" alt="GPL">
</p>
<h2>
Credits
</h2>
The Logisim-evolution project is based on Logisim software by:
<ul>
<li>Carl Burch, <a href="https://www.hendrix.edu/">Hendrix College</a>, USA</li>
</ul>
The following people and institutions actively contributed to further development of `Logisim-evolution`:
<ul>
<li>Moshe Berman, <a href="https://www.brooklyn.cuny.edu/">Brooklyn College</a>, USA</li>
<li>Theldo Cruz Franqueira, <a href="https://www.pucminas.br/destaques/Paginas/default.aspx">Pontifícia Universidade Católica de Minas Gerais</a>, Brazil</li>
<li><a href="https://github.com/gtxzsxxk">Zhao Hanyuan</a>, <a href="https://www.tsinghua.edu.cn/">Tsinghua University</a>, China</li>
<li>David H. Hutchens, <a href="https://www.millersville.edu/">Millersville University</a>, Pennsylvania, USA</li>
<li><a href="https://www.bfh.ch/en/theo-kluter">Theo Kluter</a>, <a href="https://bfh.ch/electrical">Berner Fachhochschule | Haute école spécialisée bernoise</a>, Switzerland.</li>
<li><a href="https://www.bfh.ch/en/torsten-maehne">Torsten Maehne</a>, <a href="https://bfh.ch/electrical">Berner Fachhochschule | Haute école spécialisée bernoise</a>, Switzerland.</li>
<li><a href="https://github.com/zdimension/">Tom Niget</a>, <a href="https://leat.univ-cotedazur.fr/">LEAT</a>, <a href="https://polytech.univ-cotedazur.fr/">Polytech Nice-Sophia</a>, France</li>
<li><a href="http://www.marcinorlowski.com/">Marcin Orłowski</a>, Poland</li>
<li>Kevin Walsh, <a href="https://www.holycross.edu/">College of the Holy Cross</a>, USA</li>
<li><a href="https://github.com/smallg0at">Liu Yuchen</a>, <a href="https://www.bjut.edu.cn/">University of Technology</a>, China</li>
<li><a href="https://www.bfh.ch/">Berner Fachhochschule | Haute école spécialisée bernoise</a>, Switzerland</li>
<li><a href="https://www.holycross.edu/">College of the Holy Cross</a>, USA</li>
<li><a href="https://www.heig-vd.ch/">Haute École d'Ingénierie et de Gestion du Canton de Vaud</a>, Switzerland</li>
<li><a href="https://hepia.hesge.ch/">Haute école du paysage, d'ingénierie et d'architecture de Genève</a>, Switzerland</li>
</ul>
If you feel that your name should be in this list, please feel free
to <a href="https://github.com/logisim-evolution/logisim-evolution/issues">open a ticket</a>.
<p>
<strong>Next: </strong> <em><a href="../index.html">User Guide</a></em>.
</p>
<table cellspacing="0" cellpadding="8">
<tr>
<td><img src="../../../../img-guide/revolution-mark.png" width="72" height="72" alt="Logisim Revolution circuit-R emblem"></td>
<td><h1>About Logisim Revolution</h1><p>Digital logic design and simulation</p></td>
</tr>
</table>
<p>Logisim Revolution is a local desktop application for building and simulating digital circuits. Its editor provides a searchable parts picker, circuit tabs, an inspector for component settings, and tools for simulation, analysis, timing diagrams, HDL, and supported FPGA workflows.</p>
<h2>Origins and compatibility</h2>
<p>Revolution is derived from <a href="https://github.com/logisim-evolution/logisim-evolution">Logisim-evolution</a>, which continues the original <a href="https://www.cburch.com/logisim/">Logisim by Carl Burch</a>. The existing <code>.circ</code> format and component identifiers remain compatible. Revolution uses separate settings and recovery locations, and can offer to copy compatible Evolution settings on its first interactive launch.</p>
<h2>Authorship</h2>
<p>Carl Burch created Logisim. The Logisim-evolution contributors, including Theo Kluter, Torsten Maehne, and the participating schools and community, built and maintained the project from which Revolution derives. Revolution's contributors maintain this redesign. The full upstream and current credits are kept with the project source.</p>
<h2>Free software</h2>
<p>This program is licensed under the <a href="../../../../gpl.html">GNU General Public License, version 3</a>. It is distributed without warranty. Source, issue reporting, and full credits are available from the <a href="https://git.briggen.dev/NilsBriggen/Logisim-Revolution">Logisim Revolution project</a>.</p>
<p><a href="../index.html">Return to the User Guide</a></p>
</div>
</body>
</html>
@@ -1,41 +1,25 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="created" content="2018-10-23T06:18:10.521000000">
<meta name="changed" content="2018-10-23T06:18:42.262000000">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Language" content="en">
<title>
The graphical interface
</title>
<title>The Logisim Revolution editor</title>
<link rel="stylesheet" type="text/css" href="../../style.css">
</head>
<body>
<div class="maindiv">
<h1>
The graphical interfaces
</h1>
<p>
In this section, we'll examine how to use the parts of the <b class=refguide>the graphical interface</b> of logisim-evolution.
</p>
<p align="center">
<img src="../../../img-guide/tutorial-shot-labeled.png" alt="#########">
</p>
<p>
You find below the links to the different elements.
</p>
<blockquote>
<a href="gui-canvas.html">The canvas</a><br>
<a href="gui-menu.html">The menus</a><br>
<a href="gui-explore.html">The explorer pane</a><br>
<a href="gui-toolsbar.html">The tools bars</a><br>
<a href="gui-attribut.html">The attribute table</a><br>
<a href="gui-attrcomp.html">Tool and component attributes</a>
</blockquote>
<p>
<b>Next:</b> <a href="gui-canvas.html">The canvas</a>.
</p>
<h1>The editor</h1>
<p>The activity bar on the left opens the circuits list, parts picker, simulation, search, settings, and other tools. The circuits list lets you switch circuits; open circuits appear as tabs above the central canvas.</p>
<p><img src="../../../../img-guide/revolution-main.png" width="800" height="430" alt="Current editor showing the parts picker, canvas, and component inspector"></p>
<p>Select a component on the canvas to see its properties in the inspector on the right. Search the parts picker by name or category, then click a part to place it. Use the hand, selection, wiring, and text tools in the toolbar above the canvas.</p>
<p>The chapters below are retained as a reference to circuit behavior. Some screenshots and menu paths still describe the earlier Logisim-evolution interface.</p>
<ul>
<li><a href="gui-canvas.html">The canvas</a></li>
<li><a href="gui-menu.html">Menus</a></li>
<li><a href="gui-explore.html">Circuits and libraries</a></li>
<li><a href="gui-toolsbar.html">Tools and toolbar</a></li>
<li><a href="gui-attribut.html">Component settings</a></li>
<li><a href="gui-attrcomp.html">Tool and component attributes</a></li>
</ul>
</div>
</body>
</html>
+35 -64
View File
@@ -1,64 +1,35 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="created" content="2019-03-16T06:18:10.521000000">
<meta name="changed" content="2019-12-19T06:18:42.262000000">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Language" content="en">
<title>
The Logisim-evolution user's guide
</title>
<link rel="stylesheet" type="text/css" href="../style.css">
</head>
<body>
<div class="maindiv">
<img src="../../../img-guide/logisim.png" alt="#########">
<h1>
The Logisim-evolution user's Guide
</h1>
<p align="center">
<img src="../../../img-guide/screen-shot.png" alt="#########">
</p>
<p>
Logisim-evolution is an educational tool for designing and simulating digital logic circuits. With its simple toolbar interface and simulation of circuits as they are built, it is simple enough to facilitate learning the most basic concepts related to logic circuits. With the capacity to build larger circuits from smaller subcircuits, and to draw bundles of wires with a single mouse drag, Logisim can be used (and is used) to design and simulate entire CPUs for educational purposes.
</p>
<p>
Students at colleges and universities around the world use Logisim for a variety of purposes, including:
</p>
<ul>
<li>A module in general-education computer science surveys
</li>
<li>A unit in sophomore-level computer organization courses
</li>
<li>Over a full semester in upper-division computer architecture courses
</li>
</ul>
<p>
<b class=refguide>The Guide to Being a Logisim User</b>, which you are reading now, is the official reference for Logisim's features. Its first part is a sequence of sections introducing the major parts of Logisim. These sections are written so that they can be read "cover to cover" to learn about all of the most important features of Logisim.
</p>
<blockquote>
<a href="tutorial/index.html">Beginner's tutorial</a><br>
<a href="gui/index.html">Graphic interface</a><br>
<a href="subcirc/index.html">Hierarchical design</a><br>
<a href="feature/index.html">Additional features </a><br>
<a href="analyze/index.html">Combinational analysis</a>
</blockquote>
<p>
The remaining sections are a motley bunch of reference materials and explanations of some of the lesser corners of Logisim.
</p>
<blockquote>
<a href="mem/index.html">Memory components</a><br>
<a href="prop/index.html">Value propagation</a><br>
<a href="log/index.html">Chronogram and logging</a><br>
<a href="verify/index.html">Command-line verification and test vectors</a><br>
<a href="menu/index.html">Menu reference</a><br>
<a href="prefs/index.html">Application preferences</a><br>
<a href="opts/index.html">Project options</a><br>
<a href="hdl_ip/index.html">HDL IP</a><br>
<a href="jar/index.html">JAR libraries</a><br>
<a href="about/index.html">About the program</a>
</blockquote>
</div>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Logisim Revolution User Guide</title>
<link rel="stylesheet" type="text/css" href="../style.css">
</head>
<body>
<div class="maindiv">
<table cellspacing="0" cellpadding="8">
<tr>
<td><img src="../../../img-guide/revolution-mark.png" width="72" height="72" alt="Logisim Revolution circuit-R emblem"></td>
<td><h1>Logisim Revolution</h1><p>Digital logic design and simulation</p></td>
</tr>
</table>
<p>Build a circuit with the searchable parts picker, wire it on the canvas, and inspect or change a component's settings in the right panel. Open several circuits in editor tabs; use the activity bar for the library, simulation, logs, and analysis.</p>
<p><img src="../../../img-guide/revolution-main.png" width="800" height="430" alt="Current light-theme editor with circuit, parts picker, and inspector"></p>
<h2>Get started</h2>
<ol>
<li><a href="tutorial/index.html">Build a first circuit</a> with gates, pins, and wires.</li>
<li><a href="gui/index.html">Explore the editor</a> and its panels.</li>
<li><a href="subcirc/index.html">Reuse a circuit</a> as a subcircuit.</li>
</ol>
<h2>Reference</h2>
<p>
<a href="analyze/index.html">Circuit analysis</a> &middot;
<a href="mem/index.html">Memory</a> &middot;
<a href="log/index.html">Timing and logging</a> &middot;
<a href="verify/index.html">Test vectors</a> &middot;
<a href="../libs/index.html">Component library</a>
</p>
<p>Logisim Revolution is derived from Logisim-evolution, itself based on Carl Burch's Logisim. The existing reference chapters remain available; some examples and screenshots in those chapters describe the earlier interface.</p>
</div>
</body>
</html>
+21 -29
View File
@@ -1,29 +1,21 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="created" content="2018-10-23T06:18:10.521000000">
<meta name="changed" content="2018-10-23T06:18:42.262000000">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Language" content="en">
<title>
Welcome to Logisim-evolution!
</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="maindiv">
<h1>
Welcome to Logisim-evolution!
</h1>
<p>
Units in the Logisim help system include:
</p>
<blockquote>
<a href="guide/about/index.html">About the program</a><br>
<a href="guide/index.html">Guide to Being a Logisim User</a><br>
<a href="libs/index.html">Library Reference</a>
</blockquote>
</div>
</body>
</html>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Logisim Revolution Help</title>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<div class="maindiv">
<h1>Logisim Revolution Help</h1>
<p>Design and simulate digital circuits with a searchable parts picker, a tabbed editor, and an inspector for component settings.</p>
<p>Choose where to start:</p>
<ul>
<li><a href="guide/tutorial/index.html">Build your first circuit</a></li>
<li><a href="guide/index.html">User guide and editor overview</a></li>
<li><a href="libs/index.html">Component library reference</a></li>
<li><a href="guide/about/index.html">About Revolution and its Logisim origins</a></li>
</ul>
</div>
</body>
</html>
+1 -1
View File
@@ -3,7 +3,7 @@
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN"
"http://java.sun.com/products/javahelp/map_2_0.dtd">
<map version="2.0">
<mapID target="toplevelfolder" url="img-guide/helptree-top.gif" />
<mapID target="toplevelfolder" url="img-guide/revolution-mark-24.png" />
<mapID target="chapter" url="img-guide/helptree-section.gif" />
<mapID target="topic" url="img-guide/helptree-page.gif" />
<mapID target="top" url="de/html/guide/index.html" />
+1 -1
View File
@@ -3,7 +3,7 @@
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN"
"http://java.sun.com/products/javahelp/map_2_0.dtd">
<map version="2.0">
<mapID target="toplevelfolder" url="img-guide/helptree-top.gif" />
<mapID target="toplevelfolder" url="img-guide/revolution-mark-24.png" />
<mapID target="chapter" url="img-guide/helptree-section.gif" />
<mapID target="topic" url="img-guide/helptree-page.gif" />
<mapID target="top" url="en/html/guide/index.html" />
+1 -1
View File
@@ -3,7 +3,7 @@
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN"
"http://java.sun.com/products/javahelp/map_2_0.dtd">
<map version="2.0">
<mapID target="toplevelfolder" url="img-guide/helptree-top.gif" />
<mapID target="toplevelfolder" url="img-guide/revolution-mark-24.png" />
<mapID target="chapter" url="img-guide/helptree-section.gif" />
<mapID target="topic" url="img-guide/helptree-page.gif" />
<mapID target="top" url="fr/html/guide/index.html" />
+1 -1
View File
@@ -3,7 +3,7 @@
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN"
"http://java.sun.com/products/javahelp/map_2_0.dtd">
<map version="2.0">
<mapID target="toplevelfolder" url="img-guide/helptree-top.gif" />
<mapID target="toplevelfolder" url="img-guide/revolution-mark-24.png" />
<mapID target="chapter" url="img-guide/helptree-section.gif" />
<mapID target="topic" url="img-guide/helptree-page.gif" />
<mapID target="top" url="pt/html/guide/index.html" />
+1 -1
View File
@@ -3,7 +3,7 @@
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN"
"http://java.sun.com/products/javahelp/map_2_0.dtd">
<map version="2.0">
<mapID target="toplevelfolder" url="img-guide/helptree-top.gif" />
<mapID target="toplevelfolder" url="img-guide/revolution-mark-24.png" />
<mapID target="chapter" url="img-guide/helptree-section.gif" />
<mapID target="topic" url="img-guide/helptree-page.gif" />
<mapID target="top" url="ru/html/guide/index.html" />
+1 -1
View File
@@ -3,7 +3,7 @@
PUBLIC "-//Sun Microsystems Inc.//DTD JavaHelp Map Version 2.0//EN"
"http://java.sun.com/products/javahelp/map_2_0.dtd">
<map version="2.0">
<mapID target="toplevelfolder" url="img-guide/helptree-top.gif" />
<mapID target="toplevelfolder" url="img-guide/revolution-mark-24.png" />
<mapID target="chapter" url="img-guide/helptree-section.gif" />
<mapID target="topic" url="img-guide/helptree-page.gif" />
<mapID target="top" url="zh/html/guide/index.html" />
@@ -113,6 +113,7 @@ zeroOrNotOptimizedMessage = Logic function(s) is/are all don’t care or not opt
# gui/CsvReadParameterDialog.java
#
ConfirmCsvParameters = Continue
csvImportTitle = Import CSV Truth Table
cvsFilePreview = Preview of the first four rows of the read-in file:
seperatorSpace = {space}
SeperatorTab = {tab}
@@ -240,6 +240,12 @@ addRemoveSignals = Add or Remove Signals
selectionTab = Signals
selectionHelp = Select signals to view.
selectionDesc = Drag signals to select or rearrange.
selectionDialogTitle = Signal Selection
selectionAdd = Add
selectionRemove = Remove
selectionAddTip = Add the selected signals to the log
selectionRemoveTip = Remove the selected signals from the log
selectionEmpty = No signals selected.
exploreLabel = Signals available in circuit:
listLabel = Signals to be viewed:
timeSelectionClock = Choose your clock:
@@ -445,7 +451,7 @@ helpSearchItem = Find Action…
helpsetUrl = doc/doc_en.hs
helpTutorialItem = Tutorial
helpUnavailableError = Could not load help data.
helpWindowTitle = Logisim-evolution Documentation
helpWindowTitle = Logisim Revolution Help
helpProjectWebsite = Project Website
#
# menu/MenuProject.java
@@ -513,7 +519,7 @@ explorerTab = Circuits
appVersionJvm = Running on %s
addComponentAction = Add %s
libraryTab = Library
paletteFilterHint = Search components
paletteFilterHint = Search parts
paletteShowTreeTip = Show the library tree
paletteShowTilesTip = Show the component palette
paletteGroupFavourites = Pinned
@@ -548,7 +554,7 @@ circuitRenamePrompt = New name for this circuit:
projectLoadBuiltinItem = Built-in Library…
projectLoadJarItem = JAR Library…
projectLoadLibraryItem = Load Library
projectLoadLogisimItem = Logisim-evolution Library…
projectLoadLogisimItem = Logisim library…
projectReloadLibraryItem = Reload Library
projectRemoveCircuitItem = Delete Circuit
projectRemoveVhdlItem = Remove VHDL Entity
@@ -825,7 +831,10 @@ windowRestartWarning = Restart Logisim for changes to take effect.
hotkeyOptMenuKeyHeader = Menu shortcuts that require the %s key:
hotkeyOptNormalKeyHeader = Context dependent hotkeys that do not require the %s key:
hotkeyOptOrientDesc = Component’s orientation shortcuts
hotkeyOptHelp = To set the key bindings, click the bordered area right-side and press your expected keys in the keypad. Click the check mark to save, or discard by clicking the cross mark.
hotkeyOptHelp = Select a shortcut field and press the desired keys. Apply saves the shortcut. Cancel or Escape restores the previous shortcut. Delete or Backspace clears it when applied.
hotkeyApply = Apply shortcut
hotkeyCancel = Cancel shortcut change
hotkeyCaptureHelp = Press a shortcut, then Apply. Escape cancels; Delete or Backspace clears.
hotkeyOptTitle = Hotkey settings
hotkeyOptResetBtn = Reset to defaults
hotkeySimAutoPropagate = Auto-Propagate
@@ -933,13 +942,17 @@ searchProviderRecent = Recent Files
#
# start/About.java
#
aboutDialogTitle = About Logisim-evolution
aboutDialogTitle = About Logisim Revolution
revolutionImportTitle = Bring your settings to Logisim Revolution?
revolutionImportQuestion = Copy your Logisim-evolution settings into Logisim Revolution? Your original settings will stay unchanged.
aboutDialogClose = Close
aboutDialogCopyDetails = Copy details
#
# start/AboutCredits.java
#
creditsRoleFork = Fork from original project
creditsRevolutionCopyright = Copyright © 2026 Logisim Revolution contributors
creditsUpstreamCopyright = Based on Logisim-evolution, © 2001–%s its contributors
creditsRoleOriginal = Original Version
creditsBuildInfo = Build info
creditsCompiled = Compiled: %s
@@ -952,6 +965,9 @@ progressFileCreate = Creating file…
progressFileLoad = Loading file…
progressFrameCreate = Creating window…
progressGuiInitialize = Initializing interface…
splashSubtitle = Digital logic design and simulation
splashStarting = Starting…
splashLoadingStage = Loading stage
progressLibraries = Loading components…
progressProjectCreate = Creating project…
progressTemplateClose = Closing template…
@@ -991,7 +1007,7 @@ argSubOption = Substitutes library lib1 with lib2. Arguments: lib1 lib2.
argSubOptionArgName = args
argTemplateOption = Select template. Argument: “empty” for an empty template, “plain” for the default template, or the filename of the template to load.
argTemplateOptionArgName = arg
argTestCircGenOption = Open a .circ file of any version of Logisim and write a new one in the current format used in Logisim-evolution. Arguments: <circ_input> <circ_output>
argTestCircGenOption = Open a .circ file of any version of Logisim and write it in the current format used by Logisim Revolution. Arguments: <circ_input> <circ_output>
argTestCircGenOptionArgName = args
argTestCircuitOption = Open up a circ file and start the test bench within it. It returns success or fail. Arguments: <path_to_circ>
argTestCircuitOptionArgName = file
@@ -171,6 +171,21 @@ public final class UiSnapshot {
final var frame = project.getFrame();
if (frame == null) throw new IllegalStateException("the project window was never created");
final var selectionName = System.getProperty("snapshot.select", "");
if (!selectionName.isBlank()) {
final var component = project.getCurrentCircuit().getComponents().stream()
.filter(item -> selectionName.equals(item.getFactory().getName()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(
"No component named " + selectionName + " in the current circuit"));
SwingUtilities.invokeAndWait(() -> project.getSelection().add(component));
}
final var canvasZoom = Double.parseDouble(System.getProperty("snapshot.canvasZoom", "0"));
if (canvasZoom > 0) {
SwingUtilities.invokeAndWait(() -> frame.getZoomModel().setZoomFactor(canvasZoom));
}
if (Boolean.getBoolean("snapshot.print")) {
capturePrintView(project, output);
System.exit(0);
+1 -1
View File
@@ -1 +1 @@
This directory holds the files required to build the Flatpak package of logisim-evolution
This directory holds the files required to build the Flatpak package of Logisim Revolution
@@ -1,11 +0,0 @@
[Desktop Entry]
Name=Logisim Evolution
GenericName=Logisim Logic Circuit Simulator
Comment=Digital Logic Circuit Simulator
Exec=LogisimEvolution.sh
Icon=com.github.reds.LogisimEvolution
Terminal=false
Type=Application
Categories=Education;Electronics
MimeType=application/x-logisim-circuit;
X-Desktop-File-Install-Version=0.24
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-logisim-circuit">
<comment>Logisim Circuit</comment>
<glob pattern="*.circ"/>
</mime-type>
</mime-info>
+1 -1
View File
@@ -1,4 +1,4 @@
extension=circ
mime-type=application/x-logisim-circuit
icon=support/jpackage/linux/logisim-icon-128.png
icon=support/jpackage/linux/logisim-revolution-icon-128.png
description=Logisim circuit file
+1 -1
View File
@@ -1,4 +1,4 @@
extension=circ
mime-type=application/x-logisim-circuit
icon=support/jpackage/macos/Logisim-evolution-circ.icns
icon=support/jpackage/macos/Logisim-Revolution-circ.icns
description=Logisim circuit file
+1 -1
View File
@@ -1,4 +1,4 @@
extension=circ
mime-type=application/x-logisim-circuit
icon=support/jpackage/windows/Logisim-evolution.ico
icon=support/jpackage/windows/Logisim-Revolution-circ.ico
description=Logisim circuit file