diff --git a/java/3-4-Undo/Main.java b/java/3-4-Undo/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..57e0370161dd225e77f002174cbca83283ee53b6 --- /dev/null +++ b/java/3-4-Undo/Main.java @@ -0,0 +1,45 @@ +// CS 349 Undo Demo + +import javax.swing.*; +import javax.swing.event.UndoableEditEvent; +import javax.swing.undo.UndoManager; + +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.*; + +public class Main { + + Model model; + + public static void main(String[] args) { + new Main(); + } + + public Main() { + + JFrame frame = new JFrame("UndoDemo"); + + // create Model and initialize it + // (value, min, max in this model) + model = new Model(22, 0, 100); + + // create View + View view = new View(model); + + // create Menu View + MainMenuView menuView = new MainMenuView(model); + + // add views to the window + frame.getContentPane().add(view); + frame.setJMenuBar(menuView); + + frame.setPreferredSize(new Dimension(500, 120)); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + + // let all the views know that they're connected to the model + model.updateViews(); + } +} diff --git a/java/3-4-Undo/MainMenuView.java b/java/3-4-Undo/MainMenuView.java new file mode 100644 index 0000000000000000000000000000000000000000..a856be44f57eedd0e94f4e98c2f59610d6ee0ae2 --- /dev/null +++ b/java/3-4-Undo/MainMenuView.java @@ -0,0 +1,78 @@ +// CS 349 Undo Demo + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.Observable; +import java.util.Observer; + +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; +import javax.swing.KeyStroke; + + +// a main menu view +public class MainMenuView extends JMenuBar implements Observer { + + // Undo menu items + private JMenuItem undoMenuItem; + private JMenuItem redoMenuItem; + + // the model that this view is showing + private Model model; + + public MainMenuView(Model model_) { + + // set the model + model = model_; + model.addObserver(this); + + // create a menu UI with undo/redo + JMenu fileMenu = new JMenu("File"); + JMenu editMenu = new JMenu("Edit"); + this.add(fileMenu); + this.add(editMenu); + + // Create a "quit" menu item and add it to the file menu + JMenuItem quitMenuItem = new JMenuItem("Quit"); + fileMenu.add(quitMenuItem); + + // quit menu controller + quitMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + System.exit(0); + } + }); + + // create undo and redo menu items + undoMenuItem = new JMenuItem("Undo"); + undoMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, + ActionEvent.CTRL_MASK)); + redoMenuItem = new JMenuItem("Redo"); + redoMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, + ActionEvent.CTRL_MASK)); + editMenu.add(undoMenuItem); + editMenu.add(redoMenuItem); + + // controllers for undo menu item + undoMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + model.undo(); + } + }); + // controller for redo menu item + redoMenuItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + model.redo(); + } + }); + } + + @Override + public void update(Observable arg0, Object arg1) { + undoMenuItem.setEnabled(model.canUndo()); + redoMenuItem.setEnabled(model.canRedo()); + } + +} diff --git a/java/3-4-Undo/Model.java b/java/3-4-Undo/Model.java new file mode 100644 index 0000000000000000000000000000000000000000..4f3d60d6e9bbc692cb7f5dfad37ac93d9512e676 --- /dev/null +++ b/java/3-4-Undo/Model.java @@ -0,0 +1,170 @@ +// CS 349 Undo Demo + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.lang.reflect.Field; +import java.util.Observable; + +import javax.swing.undo.*; + +// A simple model that is undoable +public class Model extends Observable { + + // Undo manager + private UndoManager undoManager; + + // Model data + private int value; + private int min; + private int max; + + Model(int value, int min, int max) { + undoManager = new UndoManager(); + this.value = value; + this.min = min; + this.max = max; + } + + public void updateViews() { + setChanged(); + notifyObservers(); + } + + public int getValue() { + return value; + } + + public void setValue(int v) { + System.out.println("Model: set value to " + v); + + // create undoable edit + UndoableEdit undoableEdit = new AbstractUndoableEdit() { + + // capture variables for closure + final int oldValue = value; + final int newValue = v; + + // Method that is called when we must redo the undone action + public void redo() throws CannotRedoException { + super.redo(); + value = newValue; + System.out.println("Model: redo value to " + value); + setChanged(); + notifyObservers(); + } + + public void undo() throws CannotUndoException { + super.undo(); + value = oldValue; + System.out.println("Model: undo value to " + value); + setChanged(); + notifyObservers(); + } + }; + + // Add this undoable edit to the undo manager + undoManager.addEdit(undoableEdit); + + // finally, set the value and notify views + value = v; + setChanged(); + notifyObservers(); + } + + public void incrementValue() { + System.out.println("Model: increment value "); + + // constrain value to valid range + if (value + 1 > max) return; + + // create undoable edit + UndoableEdit undoableEdit = new AbstractUndoableEdit() { + + // Method that is called when we must redo the undone action + public void redo() throws CannotRedoException { + super.redo(); + value = value + 1; + System.out.println("Model: redo value to " + value); + setChanged(); + notifyObservers(); + } + + public void undo() throws CannotUndoException { + super.undo(); + value = value - 1; + System.out.println("Model: undo value to " + value); + setChanged(); + notifyObservers(); + } + }; + + // Add this undoable edit to the undo manager + undoManager.addEdit(undoableEdit); + + value = value + 1; + setChanged(); + notifyObservers(); + } + + public void decrementValue() { + System.out.println("Model: decrement value "); + + // constrain value to valid range + if (value - 1 < min) return; + + // create undoable edit + UndoableEdit undoableEdit = new AbstractUndoableEdit() { + + // Method that is called when we must redo the undone action + public void redo() throws CannotRedoException { + super.redo(); + value = value - 1; + System.out.println("Model: redo value to " + value); + setChanged(); + notifyObservers(); + } + + public void undo() throws CannotUndoException { + super.undo(); + value = value + 1; + System.out.println("Model: undo value to " + value); + setChanged(); + notifyObservers(); + } + }; + + // Add this undoable edit to the undo manager + undoManager.addEdit(undoableEdit); + + value = value - 1; + setChanged(); + notifyObservers(); + } + + // could make these settable and undoable too + public int getMin() { return min; } + public int getMax() { return max; } + + + // undo and redo methods + // - - - - - - - - - - - - - - + + public void undo() { + if (canUndo()) + undoManager.undo(); + } + + public void redo() { + if (canRedo()) + undoManager.redo(); + } + + public boolean canUndo() { + return undoManager.canUndo(); + } + + public boolean canRedo() { + return undoManager.canRedo(); + } + +} diff --git a/java/3-4-Undo/View.java b/java/3-4-Undo/View.java new file mode 100644 index 0000000000000000000000000000000000000000..c7b38262c21f7f1ad20982b55696c03c49177dc2 --- /dev/null +++ b/java/3-4-Undo/View.java @@ -0,0 +1,114 @@ +// CS 349 Undo Demo + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.UndoableEdit; + +import java.awt.*; +import java.awt.event.*; +import java.util.Observable; +import java.util.Observer; + +class View extends JPanel implements Observer { + + // the view's main user interface + private JSlider slider; + private JTextField text; + private JButton increment; + private JButton decrement; + + // the model that this view is showing + private Model model; + + View(Model model_) { + + // set the model + model = model_; + model.addObserver(this); + + // create the view UI + this.setLayout(new FlowLayout()); + + // slider to choose exact value + slider = new JSlider(); + this.add(slider); + + // text boc to change value + text = new JTextField("X", 4); + this.add(text); + + // increment/decrement buttons + decrement = new JButton("-"); + increment = new JButton("+"); + this.add(decrement); + this.add(increment); + + this.add(new JLabel("shortcut keys: CTRL-Z to undo, CTRL-Y to redo")); + + // change the flag and see what happens + final boolean noChunking = false; + + if (noChunking) { + slider.addChangeListener(new ChangeListener() { + + @Override + public void stateChanged(ChangeEvent arg0) { + model.setValue(slider.getValue()); + } + }); + } else { + + // controller for when a drag event finishes + // this is the right undo "chunk" + slider.addMouseListener(new MouseAdapter() { + @Override + public void mouseReleased(MouseEvent e) { + model.setValue(slider.getValue()); + } + }); + } + + // add a controller for text edits too + // (will only fire when enter is pressed after editing ...) + text.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + try { + model.setValue(Integer.parseInt(text.getText())); + } catch (NumberFormatException ex) { + // not a number, just update views to reset it + // (need to be careful here not to insert an undo) + model.updateViews(); + } + } + }); + + increment.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + model.incrementValue(); + } + }); + + decrement.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + model.decrementValue(); + } + }); + } + + + // Observer interface + @Override + public void update(Observable arg0, Object arg1) { + System.out.println("View: update"); + slider.setValue(model.getValue()); + slider.setMinimum(model.getMin()); + slider.setMaximum(model.getMax()); + text.setText(Integer.toString(model.getValue())); + } +} diff --git a/java/3-4-Undo/makefile b/java/3-4-Undo/makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f843f1fb83fb74868ca6b0aa023df50fa80faad --- /dev/null +++ b/java/3-4-Undo/makefile @@ -0,0 +1,15 @@ +# super simple makefile +# call it using 'make NAME=name_of_code_file_without_extension' +# (assumes a .java extension) +NAME = "Main" + +all: + @echo "Compiling..." + javac *.java + +run: all + @echo "Running..." + java $(NAME) + +clean: + rm -rf *.class