Integrating a large number bug fixes, all of which (or most of which) are based on bug reports submitted through SourceForge:

Feature: The window's last-observed location is saved as a preference, and that location is used when the application starts later.

Bug fix: The Probe's Radix attribute displayed the values as they are saved in the file, rather than the display strings based on the current translation being used.

Appearance change: Rectangular XOR and XNOR gates are drawn with "=1" to conform to the ISO standard (previously it was simply "1"). However, if the "Multiple-Input Behavior" attribute is "When an odd number are on" and there are more than two inputs, the label is "2k+1" to reflect that it is actually doing an odd parity. (Previously it showed this label for odd parity even when there were just two inputs.)

Bug fix: Edits to the Bit Extender's Extension Type attribute or the Comparator's did not have an immediate effect in the circuit.

Bug fix: Under MacOS X, the title bar's close-window button would indicate that the file was changed after the first change, but the indicator would not disappear when the file was saved.

Bug fix: When a rectangle, oval, or rounded rectangle in a circuit's appearance had a paint type of "Border & Fill" and it was filled with solid black, that information was not properly stored in the saved file.

Bug fix: Libraries could not be unloaded.

Bug fix: The XNOR gate incorrectly did even parity by default, rather than being off only when exactly one input is one.

Bug fix: Most options were not loaded when a file was later reopened (all except the toolbar and mouse mappings).

Bug fix: The Logisim version saved in the project file was not interpreted correctly when opening the project. One consequence: If you edited the toolbar to include the Select and Wiring tools, they would be replaced by the Edit tool when reloaded.

Bug fix: When moving a selection so that one wire in the selection is moved exactly into the location of a wire being moved, the wire would sometimes disappear entirely rather than be moved. 

Bug fix: With the Edit Tool, pressing the backspace key deletes the current selection (just as it does when the delete key is pressed).

Bug fix: Edits to the Bit Extender's Extension Type attribute or the Comparator's did not have an immediate effect in the circuit.

Bug fix: Under MacOS X, the title bar's close-window button would indicate that the file was changed after the first change, but the indicator would not disappear when the file was saved.



git-svn-id: https://circuit.svn.sourceforge.net/svnroot/circuit/trunk@164 70edf91d-0b9e-4248-82e7-2488d7716404
This commit is contained in:
Carl Burch 2010-12-09 21:02:07 +00:00
parent dcda046abb
commit d4499774e5
24 changed files with 232 additions and 39 deletions

View File

@ -143,7 +143,9 @@ class SvgCreator {
elt.setAttribute("fill", "none");
} else {
Color fill = shape.getValue(DrawAttr.FILL_COLOR);
if (!colorMatches(fill, Color.BLACK)) {
if (colorMatches(fill, Color.BLACK)) {
elt.removeAttribute("fill");
} else {
elt.setAttribute("fill", getColorString(fill));
}
if (showOpacity(fill)) {

View File

@ -49,7 +49,7 @@ public class SvgReader {
String fill = elt.getAttribute("fill");
if (stroke.equals("") || stroke.equals("none")) {
ret.setValue(DrawAttr.PAINT_TYPE, DrawAttr.PAINT_FILL);
} else if (fill.equals("") || fill.equals("none")) {
} else if (fill.equals("none")) {
ret.setValue(DrawAttr.PAINT_TYPE, DrawAttr.PAINT_STROKE);
} else {
ret.setValue(DrawAttr.PAINT_TYPE, DrawAttr.PAINT_STROKE_FILL);
@ -69,6 +69,7 @@ public class SvgReader {
}
if (attrs.contains(DrawAttr.FILL_COLOR)) {
String color = elt.getAttribute("fill");
if (color.equals("")) color = "#000000";
String opacity = elt.getAttribute("fill-opacity");
if (!color.equals("none")) {
ret.setValue(DrawAttr.FILL_COLOR, getColor(color, opacity));

View File

@ -15,7 +15,7 @@ public class LogisimVersion {
}
public static LogisimVersion parse(String versionString) {
String[] parts = versionString.split(".");
String[] parts = versionString.split("\\.");
int major = 0;
int minor = 0;
int release = 0;

View File

@ -145,20 +145,28 @@ class CircuitChange {
}
}
void execute(CircuitMutator mutator) {
void execute(CircuitMutator mutator, ReplacementMap prevReplacements) {
switch (type) {
case CLEAR: mutator.clear(circuit); break;
case ADD: mutator.add(circuit, comp); break;
case CLEAR: mutator.clear(circuit); prevReplacements.reset(); break;
case ADD: prevReplacements.add(comp); break;
case ADD_ALL:
for (Component comp : comps) mutator.add(circuit, comp);
for (Component comp : comps) prevReplacements.add(comp);
break;
case REMOVE: mutator.remove(circuit, comp); break;
case REMOVE: prevReplacements.remove(comp); break;
case REMOVE_ALL:
for (Component comp : comps) mutator.remove(circuit, comp);
for (Component comp : comps) prevReplacements.remove(comp);
break;
case REPLACE: prevReplacements.append((ReplacementMap) newValue); break;
case SET:
mutator.replace(circuit, prevReplacements);
prevReplacements.reset();
mutator.set(circuit, comp, attr, newValue);
break;
case SET_FOR_CIRCUIT:
mutator.replace(circuit, prevReplacements);
prevReplacements.reset();
mutator.setForCircuit(circuit, attr, newValue);
break;
case REPLACE: mutator.replace(circuit, (ReplacementMap) newValue); break;
case SET: mutator.set(circuit, comp, attr, newValue); break;
case SET_FOR_CIRCUIT: mutator.setForCircuit(circuit, attr, newValue); break;
default: throw new IllegalArgumentException("unknown change type " + type);
}
}

View File

@ -103,8 +103,21 @@ public final class CircuitMutation extends CircuitTransaction {
@Override
protected void run(CircuitMutator mutator) {
Circuit curCircuit = null;
ReplacementMap curReplacements = null;
for (CircuitChange change : changes) {
change.execute(mutator);
Circuit circ = change.getCircuit();
if (circ != curCircuit) {
if (curCircuit != null) {
mutator.replace(curCircuit, curReplacements);
}
curCircuit = circ;
curReplacements = new ReplacementMap();
}
change.execute(mutator, curReplacements);
}
if (curCircuit != null) {
mutator.replace(curCircuit, curReplacements);
}
}
}

View File

@ -4,12 +4,13 @@
package com.cburch.logisim.circuit;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeOption;
import com.cburch.logisim.data.Attributes;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.util.StringGetter;
public abstract class RadixOption {
public abstract class RadixOption extends AttributeOption {
public static final RadixOption RADIX_2 = new Radix2();
public static final RadixOption RADIX_8 = new Radix8();
public static final RadixOption RADIX_10_UNSIGNED = new Radix10Unsigned();
@ -35,6 +36,7 @@ public abstract class RadixOption {
private StringGetter displayGetter;
private RadixOption(String saveName, StringGetter displayGetter) {
super(saveName, displayGetter);
this.saveName = saveName;
this.displayGetter = displayGetter;
}
@ -47,6 +49,7 @@ public abstract class RadixOption {
return saveName;
}
@Override
public String toDisplayString() {
return displayGetter.get();
}

View File

@ -39,6 +39,11 @@ public class ReplacementMap {
this.inverse = inverse;
}
public void reset() {
map.clear();
inverse.clear();
}
public boolean isEmpty() {
return map.isEmpty() && inverse.isEmpty();
}

View File

@ -28,6 +28,15 @@ class WireIterator implements Iterator<Location> {
if (curY < destY) deltaY = 10;
else if (curY > destY) deltaY = -10;
else deltaY = 0;
int offX = (destX - curX) % 10;
if (offX != 0) { // should not happen, but in case it does...
destX = curX + deltaX * ((destX - curX) / 10);
}
int offY = (destY - curY) % 10;
if (offY != 0) { // should not happen, but in case it does...
destY = curY + deltaY * ((destY - curY) / 10);
}
}
public boolean hasNext() {

View File

@ -270,8 +270,8 @@ public class LogisimFile extends Library implements LibraryEventSource {
factories.add(((AddTool) tool).getFactory());
}
}
for (AddTool tool : tools) {
Circuit circuit = (Circuit) tool.getFactory();
for (Circuit circuit : getCircuits()) {
for (Component comp : circuit.getNonWires()) {
if (factories.contains(comp.getFactory())) {
return StringUtil.format(Strings.get("unloadUsedError"),
@ -300,10 +300,6 @@ public class LogisimFile extends Library implements LibraryEventSource {
fireEvent(LibraryEvent.SET_MAIN, circuit);
}
public void setOptions(Options options) {
this.options = options;
}
//
// other methods
//

View File

@ -32,11 +32,15 @@ public class Options {
GATE_UNDEFINED_IGNORE, Integer.valueOf(1000), Integer.valueOf(0),
};
private AttributeSet attrs = AttributeSets.fixedSet(ATTRIBUTES, DEFAULTS);
private MouseMappings mmappings = new MouseMappings();
private ToolbarData toolbar = new ToolbarData();
private AttributeSet attrs;
private MouseMappings mmappings;
private ToolbarData toolbar;
public Options() { }
public Options() {
attrs = AttributeSets.fixedSet(ATTRIBUTES, DEFAULTS);
mmappings = new MouseMappings();
toolbar = new ToolbarData();
}
public AttributeSet getAttributeSet() {
return attrs;

View File

@ -94,7 +94,7 @@ class XmlReader {
if (name.equals("circuit") || name.equals("lib")) {
; // Nothing to do: Done earlier.
} else if (name.equals("options")) {
initAttributeSet(elt, file.getOptions().getAttributeSet(), null);
initAttributeSet(sub_elt, file.getOptions().getAttributeSet(), null);
} else if (name.equals("mappings")) {
initMouseMappings(sub_elt);
} else if (name.equals("toolbar")) {

View File

@ -1,3 +1,6 @@
/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
package com.cburch.logisim.gui.generic;
import java.awt.Frame;

View File

@ -1,3 +1,6 @@
/* Copyright (c) 2010, Carl Burch. License information is located in the
* com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
package com.cburch.logisim.gui.generic;
import java.awt.Image;

View File

@ -7,6 +7,12 @@ import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.IllegalComponentStateException;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
@ -45,6 +51,7 @@ import com.cburch.logisim.proj.Project;
import com.cburch.logisim.proj.ProjectActions;
import com.cburch.logisim.proj.ProjectEvent;
import com.cburch.logisim.proj.ProjectListener;
import com.cburch.logisim.proj.Projects;
import com.cburch.logisim.tools.SetAttributeAction;
import com.cburch.logisim.tools.Tool;
import com.cburch.logisim.util.HorizontalSplitPane;
@ -65,12 +72,6 @@ public class Frame extends LFrame implements LocaleListener {
public void projectChanged(ProjectEvent event) {
int action = event.getAction();
if (action == ProjectEvent.ACTION_COMPLETE
|| action == ProjectEvent.UNDO_COMPLETE
|| action == ProjectEvent.ACTION_SET_FILE) {
enableSave();
}
if (action == ProjectEvent.ACTION_SET_FILE) {
computeTitle();
proj.setTool(proj.getOptions().getToolbarData().getFirstTool());
@ -93,6 +94,8 @@ public class Frame extends LFrame implements LocaleListener {
public void libraryChanged(LibraryEvent e) {
if (e.getAction() == LibraryEvent.SET_NAME) {
computeTitle();
} else if (e.getAction() == LibraryEvent.DIRTY_STATE) {
enableSave();
}
}
@ -259,6 +262,10 @@ public class Frame extends LFrame implements LocaleListener {
this.setSize(AppPreferences.WINDOW_WIDTH.get().intValue(),
AppPreferences.WINDOW_HEIGHT.get().intValue());
Point prefPoint = getInitialLocation();
if (prefPoint != null) {
this.setLocation(prefPoint);
}
this.setExtendedState(AppPreferences.WINDOW_STATE.get().intValue());
menuListener.register(mainPanel);
@ -372,6 +379,7 @@ public class Frame extends LFrame implements LocaleListener {
s = StringUtil.format(Strings.get("titleFileKnown"), name);
}
this.setTitle(s);
myProjectListener.enableSave();
}
void viewAttributes(Tool newTool) {
@ -436,6 +444,15 @@ public class Frame extends LFrame implements LocaleListener {
Dimension dim = getSize();
AppPreferences.WINDOW_WIDTH.set(Integer.valueOf(dim.width));
AppPreferences.WINDOW_HEIGHT.set(Integer.valueOf(dim.height));
Point loc;
try {
loc = getLocationOnScreen();
} catch (IllegalComponentStateException e) {
loc = Projects.getLocation(this);
}
if (loc != null) {
AppPreferences.WINDOW_LOCATION.set(loc.x + "," + loc.y);
}
AppPreferences.WINDOW_LEFT_SPLIT.set(Double.valueOf(leftRegion.getFraction()));
AppPreferences.WINDOW_MAIN_SPLIT.set(Double.valueOf(mainRegion.getFraction()));
}
@ -468,4 +485,62 @@ public class Frame extends LFrame implements LocaleListener {
}
return ret;
}
private static Point getInitialLocation() {
String s = AppPreferences.WINDOW_LOCATION.get();
if (s == null) return null;
int comma = s.indexOf(',');
if (comma < 0) return null;
try {
int x = Integer.parseInt(s.substring(0, comma));
int y = Integer.parseInt(s.substring(comma + 1));
while (isProjectFrameAt(x, y)) {
x += 20;
y += 20;
}
Rectangle desired = new Rectangle(x, y, 50, 50);
int gcBestSize = 0;
Point gcBestPoint = null;
GraphicsEnvironment ge;
ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
for (GraphicsDevice gd : ge.getScreenDevices()) {
for (GraphicsConfiguration gc : gd.getConfigurations()) {
Rectangle gcBounds = gc.getBounds();
if (gcBounds.intersects(desired)) {
Rectangle inter = gcBounds.intersection(desired);
int size = inter.width * inter.height;
if (size > gcBestSize) {
gcBestSize = size;
int x2 = Math.max(gcBounds.x, Math.min(inter.x,
inter.x + inter.width - 50));
int y2 = Math.max(gcBounds.y, Math.min(inter.y,
inter.y + inter.height - 50));
gcBestPoint = new Point(x2, y2);
}
}
}
}
if (gcBestPoint != null) {
if (isProjectFrameAt(gcBestPoint.x, gcBestPoint.y)) {
gcBestPoint = null;
}
}
return gcBestPoint;
} catch (Throwable t) {
return null;
}
}
private static boolean isProjectFrameAt(int x, int y) {
for (Project current : Projects.getOpenProjects()) {
Frame frame = current.getFrame();
if (frame != null) {
Point loc = frame.getLocationOnScreen();
int d = Math.abs(loc.x - x) + Math.abs(loc.y - y);
if (d <= 3) return true;
}
}
return false;
}
}

View File

@ -26,7 +26,7 @@ class MenuHelp extends JMenu implements ActionListener {
private JMenuItem library = new JMenuItem();
private JMenuItem about = new JMenuItem();
private HelpSet helpSet;
private String helpSetUrl;
private String helpSetUrl = "";
private JHelp helpComponent;
private LFrame helpFrame;
@ -77,11 +77,11 @@ class MenuHelp extends JMenu implements ActionListener {
private void loadBroker() {
String helpUrl = Strings.get("helpsetUrl");
if (helpUrl == null) helpUrl = "doc/en/doc.hs";
if (helpUrl == null) helpUrl = "doc/doc_en.hs";
if (helpSet == null || helpFrame == null || !helpUrl.equals(helpSetUrl)) {
ClassLoader cl = MenuHelp.class.getClassLoader();
ClassLoader loader = MenuHelp.class.getClassLoader();
try {
URL hsURL = HelpSet.findHelpSet(cl, helpUrl);
URL hsURL = HelpSet.findHelpSet(loader, helpUrl);
if (hsURL == null) {
disableHelp();
JOptionPane.showMessageDialog(menubar.getParentWindow(),
@ -100,6 +100,7 @@ class MenuHelp extends JMenu implements ActionListener {
} else {
helpFrame.getContentPane().removeAll();
helpFrame.getContentPane().add(helpComponent);
helpComponent.revalidate();
}
} catch (Exception e) {
disableHelp();

View File

@ -400,7 +400,7 @@ public class Startup {
System.err.println(); //OK
System.err.println(Strings.get("argOptionHeader")); //OK
System.err.println(" " + Strings.get("argAccentsOption")); //OK
System.err.println(" " + Strings.get("argClearPropsOption")); //OK
System.err.println(" " + Strings.get("argClearOption")); //OK
System.err.println(" " + Strings.get("argEmptyOption")); //OK
System.err.println(" " + Strings.get("argGatesOption")); //OK
System.err.println(" " + Strings.get("argHelpOption")); //OK

View File

@ -144,6 +144,8 @@ public class AppPreferences {
= create(new PrefMonitorInt("windowWidth", 640));
public static final PrefMonitor<Integer> WINDOW_HEIGHT
= create(new PrefMonitorInt("windowHeight", 480));
public static final PrefMonitor<String> WINDOW_LOCATION
= create(new PrefMonitorString("windowLocation", "0,0"));
public static final PrefMonitor<Double> WINDOW_MAIN_SPLIT
= create(new PrefMonitorDouble("windowMainSplit", 0.25));
public static final PrefMonitor<Double> WINDOW_LEFT_SPLIT

View File

@ -5,6 +5,7 @@ package com.cburch.logisim.proj;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeListener;
@ -12,6 +13,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.WeakHashMap;
import com.cburch.logisim.file.Loader;
import com.cburch.logisim.gui.main.Frame;
@ -21,6 +23,9 @@ import com.cburch.logisim.util.PropertyChangeWeakSupport;
public class Projects {
public static final String projectListProperty = "projectList";
private static final WeakHashMap<Window, Point> frameLocations
= new WeakHashMap<Window, Point>();
private static void projectRemoved(Project proj, Frame frame,
MyListener listener) {
frame.removeWindowListener(listener);
@ -40,6 +45,9 @@ public class Projects {
Frame frame = (Frame) event.getSource();
if ((frame.getExtendedState() & Frame.ICONIFIED) == 0) {
mostRecentFrame = frame;
try {
frameLocations.put(frame, frame.getLocationOnScreen());
} catch (Throwable t) { }
}
}
@ -159,4 +167,9 @@ public class Projects {
public static void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
propertySupport.removePropertyChangeListener(propertyName, listener);
}
public static Point getLocation(Window win) {
Point ret = frameLocations.get(win);
return ret == null ? null : (Point) ret.clone();
}
}

View File

@ -10,6 +10,7 @@ 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.Instance;
import com.cburch.logisim.instance.InstanceFactory;
import com.cburch.logisim.instance.InstancePainter;
import com.cburch.logisim.instance.InstanceState;
@ -115,4 +116,18 @@ public class Comparator extends InstanceFactory {
painter.drawPort(EQ, "=", Direction.WEST);
painter.drawPort(LT, "<", Direction.WEST);
}
//
// methods for instances
//
@Override
protected void configureNewInstance(Instance instance) {
instance.addAttributeListener();
}
@Override
protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
instance.fireInvalidated();
}
}

View File

@ -103,6 +103,9 @@ public class BitExtender extends InstanceFactory {
protected void instanceAttributeChanged(Instance instance, Attribute<?> attr) {
if (attr == ATTR_TYPE) {
configurePorts(instance);
instance.fireInvalidated();
} else {
instance.fireInvalidated();
}
}

View File

@ -54,7 +54,12 @@ class XnorGate extends AbstractGate {
@Override
protected Value computeOutput(Value[] inputs, int numInputs, InstanceState state) {
return GateFunctions.computeOddParity(inputs, numInputs).not();
Object behavior = state.getAttributeValue(GateAttributes.ATTR_XOR);
if (behavior == GateAttributes.XOR_ODD) {
return GateFunctions.computeOddParity(inputs, numInputs).not();
} else {
return GateFunctions.computeExactlyOne(inputs, numInputs).not();
}
}
@Override

View File

@ -28,8 +28,15 @@ class XorGate extends AbstractGate {
@Override
public String getRectangularLabel(AttributeSet attrs) {
if (attrs == null) return "";
boolean isOdd = false;
Object behavior = attrs.getValue(GateAttributes.ATTR_XOR);
return behavior == GateAttributes.XOR_ODD ? "2k+1" : "1";
if (behavior == GateAttributes.XOR_ODD) {
Object inputs = attrs.getValue(GateAttributes.ATTR_INPUTS);
if (inputs == null || ((Integer) inputs).intValue() != 2) {
isOdd = true;
}
}
return isOdd ? "2k+1" : "=1";
}
@Override

View File

@ -380,7 +380,18 @@ public class SelectTool extends Tool {
if (state == MOVING && e.getKeyCode() == KeyEvent.VK_SHIFT) {
handleMoveDrag(canvas, curDx, curDy, e.getModifiersEx());
} else {
processKeyEvent(canvas, e, KeyConfigurationEvent.KEY_PRESSED);
switch (e.getKeyCode()) {
case KeyEvent.VK_BACK_SPACE:
case KeyEvent.VK_DELETE:
if (!canvas.getSelection().isEmpty()) {
Action act = SelectionActions.clear(canvas.getSelection());
canvas.getProject().doAction(act);
e.consume();
}
break;
default:
processKeyEvent(canvas, e, KeyConfigurationEvent.KEY_PRESSED);
}
}
}

View File

@ -68,11 +68,25 @@ public class MoveResult {
}
public void print(PrintStream out) {
boolean printed = false;
for (Component w : replacements.getAdditions()) {
printed = true;
out.println("add " + w);
}
for (Component w : replacements.getRemovals()) {
printed = true;
out.println("del " + w);
}
for (Component w : replacements.getReplacedComponents()) {
printed = true;
out.print("repl " + w + " by");
for (Component w2 : replacements.getComponentsReplacing(w)) {
out.print(" " + w2);
}
out.println();
}
if (!printed) {
out.println("no replacements");
}
}
}