diff --git a/java/3-0-MVC/hellomvc1/Controller.java b/java/3-0-MVC/hellomvc1/Controller.java new file mode 100644 index 0000000000000000000000000000000000000000..e50adaee423ff65f50fa470b5023e1a6dd05c597 --- /dev/null +++ b/java/3-0-MVC/hellomvc1/Controller.java @@ -0,0 +1,21 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ +// (C) Joseph Mack 2011, jmack (at) wm7d (dot) net, released under GPL v3 (or any later version) + +import java.awt.event.*; + +class Controller implements ActionListener { + + Model model; + + Controller(Model model) { + this.model = model; + } + + // event from the view's button + public void actionPerformed(java.awt.event.ActionEvent e){ + System.out.println("Controller: changing Model"); + model.incrementCounter(); + } +} diff --git a/java/3-0-MVC/hellomvc1/Main.java b/java/3-0-MVC/hellomvc1/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..2d27accf22ab51264dccbdba39409d0aa7af90c0 --- /dev/null +++ b/java/3-0-MVC/hellomvc1/Main.java @@ -0,0 +1,36 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +/** + * One view. Separate controller. + */ + +import javax.swing.*; +import java.awt.Dimension; +import java.awt.event.*; + +public class Main { + + public static void main(String[] args){ + JFrame frame = new JFrame("HelloMVC1"); + + // create Model and initialize it + Model model = new Model(); + // create Controller, tell it about model + Controller controller = new Controller(model); + // create View, tell it about model and controller + View view = new View(model, controller); + // tell Model about View. + model.setView(view); + + // add view (view is a JPanel) + frame.getContentPane().add(view); + + // setup window + frame.setPreferredSize(new Dimension(300,300)); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/hellomvc1/Model.java b/java/3-0-MVC/hellomvc1/Model.java new file mode 100644 index 0000000000000000000000000000000000000000..8781d0ff624b6ffcef2625b651c4fc4cf09bb9cc --- /dev/null +++ b/java/3-0-MVC/hellomvc1/Model.java @@ -0,0 +1,42 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +// View interface +interface IView { + public void updateView(); +} + +public class Model { + + // the data in the model, just a counter + private int counter; + + // the view (note, only supports 1 view!) + IView view; + + // set the view observer + public void setView(IView view) { + this.view = view; + // update the view to current state of the model + view.updateView(); + } + + public int getCounterValue() { + return counter; + } + + public void incrementCounter() { + if (counter < 5) { + counter++; + System.out.println("Model: increment counter to " + counter); + notifyObserver(); + } + } + + // notify the IView observer + private void notifyObserver() { + System.out.println("Model: notify View"); + view.updateView(); + } +} diff --git a/java/3-0-MVC/hellomvc1/View.java b/java/3-0-MVC/hellomvc1/View.java new file mode 100644 index 0000000000000000000000000000000000000000..7989d2c981750b9db0d7d70beba56a92f51ce3d5 --- /dev/null +++ b/java/3-0-MVC/hellomvc1/View.java @@ -0,0 +1,42 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.*; + +class View extends JPanel implements IView { + + // the view's main user interface + private JButton button; + + // the model that this view is showing + private Model model; + + View(Model model, Controller controller) { + + // create the view UI + button = new JButton("?"); + button.setMaximumSize(new Dimension(100, 50)); + button.setPreferredSize(new Dimension(100, 50)); + // a GridBagLayout with default constraints centres + // the widget in the window + this.setLayout(new GridBagLayout()); + this.add(button, new GridBagConstraints()); + + // set the model + this.model = model; + // setup the event to go to the controller + button.addActionListener(controller); + } + + // IView interface + public void updateView() { + System.out.println("View: updateView"); + button.setText(Integer.toString(model.getCounterValue())); + } +} diff --git a/java/3-0-MVC/hellomvc1/makefile b/java/3-0-MVC/hellomvc1/makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f843f1fb83fb74868ca6b0aa023df50fa80faad --- /dev/null +++ b/java/3-0-MVC/hellomvc1/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 diff --git a/java/3-0-MVC/hellomvc2/Controller.java b/java/3-0-MVC/hellomvc2/Controller.java new file mode 100644 index 0000000000000000000000000000000000000000..910dafdeae627d1c9a935e2b4a7b20e9b0440fe9 --- /dev/null +++ b/java/3-0-MVC/hellomvc2/Controller.java @@ -0,0 +1,32 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ +// (C) Joseph Mack 2011, jmack (at) wm7d (dot) net, released under GPL v3 (or any later version) + +import java.awt.event.*; + +class Controller implements ActionListener, MouseListener { + + Model model; + + Controller(Model model) { + this.model = model; + } + + // event from the view's button + public void actionPerformed(java.awt.event.ActionEvent e){ + System.out.println("Controller: changing Model (actionPerformed)"); + model.incrementCounter(); + } + + public void mouseClicked(MouseEvent e) { + System.out.println("Controller: changing Model (mouseClicked)"); + model.incrementCounter(); + } + + public void mouseEntered(MouseEvent e) { } + public void mouseExited(MouseEvent e) { } + public void mousePressed(MouseEvent e) { } + public void mouseReleased(MouseEvent e) { } + +} diff --git a/java/3-0-MVC/hellomvc2/Main.java b/java/3-0-MVC/hellomvc2/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..5dea7f2264301add9178dec48e879f6d5cb79b0e --- /dev/null +++ b/java/3-0-MVC/hellomvc2/Main.java @@ -0,0 +1,47 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +/** + * Two views coordinated with the observer pattern. Separate controller. + * The mechanics of a separate controller are starting to break down. + */ + +import javax.swing.*; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.*; + +public class Main { + + public static void main(String[] args){ + JFrame frame = new JFrame("HelloMVC2"); + + // create Model and initialize it + Model model = new Model(); + // create Controller, tell it about model + Controller controller = new Controller(model); + // create View, tell it about model and controller + View view = new View(model, controller); + // tell Model about View + model.addView(view); + + // create second view ... + View2 view2 = new View2(model, controller); + model.addView(view2); + + // create a layout panel to hold the two views + JPanel p = new JPanel(new GridLayout(2,1)); + frame.getContentPane().add(p); + + // add views (each view is a JPanel) + p.add(view); + p.add(view2); + + // setup the window + frame.setPreferredSize(new Dimension(300,300)); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/hellomvc2/Model.java b/java/3-0-MVC/hellomvc2/Model.java new file mode 100644 index 0000000000000000000000000000000000000000..7599422ddaf3fbe63b1612f0c43babc9079c2680 --- /dev/null +++ b/java/3-0-MVC/hellomvc2/Model.java @@ -0,0 +1,45 @@ +import java.util.ArrayList; + +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +// View interface +interface IView { + public void updateView(); +} + +public class Model { + + // the data in the model, just a counter + private int counter; + + // all views of this model + private ArrayList<IView> views = new ArrayList<IView>(); + + // set the view observer + public void addView(IView view) { + views.add(view); + view.updateView(); + } + + public int getCounterValue() { + return counter; + } + + public void incrementCounter() { + if (counter < 5) { + counter++; + System.out.println("Model: increment counter to " + counter); + notifyObservers(); + } + } + + // notify the IView observer + private void notifyObservers() { + for (IView view : this.views) { + System.out.println("Model: notify View"); + view.updateView(); + } + } +} diff --git a/java/3-0-MVC/hellomvc2/View.java b/java/3-0-MVC/hellomvc2/View.java new file mode 100644 index 0000000000000000000000000000000000000000..05bf27f89122391470da347239d3d15d00156d07 --- /dev/null +++ b/java/3-0-MVC/hellomvc2/View.java @@ -0,0 +1,42 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.*; + +class View extends JPanel implements IView { + + // the view's main user interface + private JButton button; + + // the model that this view is showing + private Model model; + + View(Model model, Controller controller) { + + // create the view UI + button = new JButton("?"); + button.setMaximumSize(new Dimension(100, 50)); + button.setPreferredSize(new Dimension(100, 50)); + // a GridBagLayout with default constraints centres + // the widget in the window + this.setLayout(new GridBagLayout()); + this.add(button, new GridBagConstraints()); + + // set the model + this.model = model; + // setup the event to go to the controller + button.addActionListener(controller); + } + + // IView interface + public void updateView() { + System.out.println("View: updateView"); + button.setText(Integer.toString(model.getCounterValue())); + } +} diff --git a/java/3-0-MVC/hellomvc2/View2.java b/java/3-0-MVC/hellomvc2/View2.java new file mode 100644 index 0000000000000000000000000000000000000000..218afa478032e77bba6e23853e24fd7b277e9fb9 --- /dev/null +++ b/java/3-0-MVC/hellomvc2/View2.java @@ -0,0 +1,39 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.FlowLayout; +import java.awt.Color; +import java.awt.event.*; +import java.util.*; + +class View2 extends JPanel implements IView { + + // the model that this view is showing + private Model model; + private JLabel label = new JLabel(); + + View2(Model model, Controller controller) { + + // create UI + setBackground(Color.WHITE); + setLayout(new FlowLayout(FlowLayout.LEFT)); + this.add(this.label); + + // set the model + this.model = model; + + // setup the event to go to the controller + addMouseListener(controller); + } + + // IView interface + public void updateView() { + System.out.println("View2: updateView"); + // just displays an 'X' for each counter value + String s = ""; + for (int i=0; i<this.model.getCounterValue(); i++) s = s + "X"; + this.label.setText(s); + } +} diff --git a/java/3-0-MVC/hellomvc2/makefile b/java/3-0-MVC/hellomvc2/makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f843f1fb83fb74868ca6b0aa023df50fa80faad --- /dev/null +++ b/java/3-0-MVC/hellomvc2/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 diff --git a/java/3-0-MVC/hellomvc3/Main.java b/java/3-0-MVC/hellomvc3/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..c276da56f3327bb95b9faced295c5cfdab48ba1e --- /dev/null +++ b/java/3-0-MVC/hellomvc3/Main.java @@ -0,0 +1,45 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +/** + * Two views with integrated controllers. + */ + +import javax.swing.*; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.*; + +public class Main { + + public static void main(String[] args){ + JFrame frame = new JFrame("HelloMVC3"); + + // create Model and initialize it + Model model = new Model(); + + // create View, tell it about model + View view = new View(model); + // tell Model about View. + model.addView(view); + + // create second view ... + View2 view2 = new View2(model); + model.addView(view2); + + // create a layout panel to hold the two views + JPanel p = new JPanel(new GridLayout(2,1)); + frame.getContentPane().add(p); + + // add views (each view is a JPanel) + p.add(view); + p.add(view2); + + // create the window + frame.setPreferredSize(new Dimension(300,300)); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/hellomvc3/Model.java b/java/3-0-MVC/hellomvc3/Model.java new file mode 100644 index 0000000000000000000000000000000000000000..cda4d73a662c591329bd6fbf02c1d985cb92701c --- /dev/null +++ b/java/3-0-MVC/hellomvc3/Model.java @@ -0,0 +1,45 @@ +import java.util.ArrayList; + +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +// View interface +interface IView { + public void updateView(); +} + +public class Model { + + // the data in the model, just a counter + private int counter; + // all views of this model + private ArrayList<IView> views = new ArrayList<IView>(); + + // set the view observer + public void addView(IView view) { + views.add(view); + // update the view to current state of the model + view.updateView(); + } + + public int getCounterValue() { + return counter; + } + + public void incrementCounter() { + if (counter < 5) { + counter++; + System.out.println("Model: increment counter to " + counter); + notifyObservers(); + } + } + + // notify the IView observer + private void notifyObservers() { + for (IView view : this.views) { + System.out.println("Model: notify View"); + view.updateView(); + } + } +} diff --git a/java/3-0-MVC/hellomvc3/View.java b/java/3-0-MVC/hellomvc3/View.java new file mode 100644 index 0000000000000000000000000000000000000000..ba8cf1474b65381a4cfe14474c9403688fd1c1a1 --- /dev/null +++ b/java/3-0-MVC/hellomvc3/View.java @@ -0,0 +1,48 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.*; + +class View extends JPanel implements IView { + + // the view's main user interface + private JButton button; + + // the model that this view is showing + private Model model; + + public View(Model model) { + + // create the view UI + button = new JButton("?"); + button.setMaximumSize(new Dimension(100, 50)); + button.setPreferredSize(new Dimension(100, 50)); + // a GridBagLayout with default constraints centres + // the widget in the window + this.setLayout(new GridBagLayout()); + this.add(button, new GridBagConstraints()); + + // set the model + this.model = model; + + // setup the event to go to the "controller" + // (this anonymous class is essentially the controller) + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + model.incrementCounter(); + } + }); + } + + // IView interface + public void updateView() { + System.out.println("View: updateView"); + button.setText(Integer.toString(model.getCounterValue())); + } +} diff --git a/java/3-0-MVC/hellomvc3/View2.java b/java/3-0-MVC/hellomvc3/View2.java new file mode 100644 index 0000000000000000000000000000000000000000..c628dd2c608b377f1feb604e7229af3df9748ac5 --- /dev/null +++ b/java/3-0-MVC/hellomvc3/View2.java @@ -0,0 +1,44 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.FlowLayout; +import java.awt.Color; +import java.awt.event.*; +import java.util.*; + +class View2 extends JPanel implements IView { + + // the model that this view is showing + private Model model; + private JLabel label = new JLabel(); + + View2(Model model) { + // create UI + setBackground(Color.WHITE); + setLayout(new FlowLayout(FlowLayout.LEFT)); + + // set the model + this.model = model; + + // setup the event to go to the "controller" + // (this anonymous class is essentially the controller) + addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + model.incrementCounter(); + } + }); + + this.add(this.label); + } + + // IView interface + public void updateView() { + System.out.println("View2: updateView"); + // just displays an 'X' for each counter value + String s = ""; + for (int i=0; i<this.model.getCounterValue(); i++) s = s + "X"; + this.label.setText(s); + } +} diff --git a/java/3-0-MVC/hellomvc3/makefile b/java/3-0-MVC/hellomvc3/makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f843f1fb83fb74868ca6b0aa023df50fa80faad --- /dev/null +++ b/java/3-0-MVC/hellomvc3/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 diff --git a/java/3-0-MVC/hellomvc3b/Main.java b/java/3-0-MVC/hellomvc3b/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..86f9ea4700618d6358357c54299e8f9c05cb6daa --- /dev/null +++ b/java/3-0-MVC/hellomvc3b/Main.java @@ -0,0 +1,42 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +/** + * Views using a listener anonymous class to handle Model update "events". + */ + +import javax.swing.*; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.*; + +public class Main { + + public static void main(String[] args){ + JFrame frame = new JFrame("HelloMVC3b"); + + // create Model and initialize it + Model model = new Model(); + + // create View, tell it about model + View view = new View(model); + + // create second view ... + View2 view2 = new View2(model); + + // create a layout panel to hold the two views + JPanel p = new JPanel(new GridLayout(2,1)); + frame.getContentPane().add(p); + + // add views (each view is a JPanel) + p.add(view); + p.add(view2); + + // create the window + frame.setPreferredSize(new Dimension(300,300)); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/hellomvc3b/Model.java b/java/3-0-MVC/hellomvc3b/Model.java new file mode 100644 index 0000000000000000000000000000000000000000..cda4d73a662c591329bd6fbf02c1d985cb92701c --- /dev/null +++ b/java/3-0-MVC/hellomvc3b/Model.java @@ -0,0 +1,45 @@ +import java.util.ArrayList; + +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +// View interface +interface IView { + public void updateView(); +} + +public class Model { + + // the data in the model, just a counter + private int counter; + // all views of this model + private ArrayList<IView> views = new ArrayList<IView>(); + + // set the view observer + public void addView(IView view) { + views.add(view); + // update the view to current state of the model + view.updateView(); + } + + public int getCounterValue() { + return counter; + } + + public void incrementCounter() { + if (counter < 5) { + counter++; + System.out.println("Model: increment counter to " + counter); + notifyObservers(); + } + } + + // notify the IView observer + private void notifyObservers() { + for (IView view : this.views) { + System.out.println("Model: notify View"); + view.updateView(); + } + } +} diff --git a/java/3-0-MVC/hellomvc3b/View.java b/java/3-0-MVC/hellomvc3b/View.java new file mode 100644 index 0000000000000000000000000000000000000000..113d8ad33404a074504d55c12716bfd6025e7638 --- /dev/null +++ b/java/3-0-MVC/hellomvc3b/View.java @@ -0,0 +1,54 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.event.*; + +class View extends JPanel { + + // the view's main user interface + private JButton button; + + // the model that this view is showing + private Model model; + + + public View(Model model) { + + // create the view UI + button = new JButton("?"); + button.setMaximumSize(new Dimension(100, 50)); + button.setPreferredSize(new Dimension(100, 50)); + // a GridBagLayout with default constraints centres + // the widget in the window + this.setLayout(new GridBagLayout()); + this.add(button, new GridBagConstraints()); + + // set the model + this.model = model; + + // anonymous class acts as model listener + this.model.addView(new IView() { + public void updateView() { + System.out.println("View: updateView"); + button.setText(Integer.toString(model.getCounterValue())); + } + }); + + + // setup the event to go to the "controller" + // (this anonymous class is essentially the controller) + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + model.incrementCounter(); + } + }); + } + + +} diff --git a/java/3-0-MVC/hellomvc3b/View2.java b/java/3-0-MVC/hellomvc3b/View2.java new file mode 100644 index 0000000000000000000000000000000000000000..7f2f761735326d9d54e40b641f44e1787b7c13f7 --- /dev/null +++ b/java/3-0-MVC/hellomvc3b/View2.java @@ -0,0 +1,47 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.FlowLayout; +import java.awt.Color; +import java.awt.event.*; +import java.util.*; + +class View2 extends JPanel { + + // the model that this view is showing + private Model model; + private JLabel label = new JLabel(); + + View2(Model model) { + // create UI + setBackground(Color.WHITE); + setLayout(new FlowLayout(FlowLayout.LEFT)); + + // set the model + this.model = model; + + // anonymous class acts as model listener + this.model.addView(new IView() { + public void updateView() { + System.out.println("View2: updateView"); + // just displays an 'X' for each counter value + String s = ""; + for (int i=0; i< model.getCounterValue(); i++) s = s + "X"; + label.setText(s); + } + }); + + // setup the event to go to the "controller" + // (this anonymous class is essentially the controller) + addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + model.incrementCounter(); + } + }); + + this.add(this.label); + } + +} diff --git a/java/3-0-MVC/hellomvc3b/makefile b/java/3-0-MVC/hellomvc3b/makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f843f1fb83fb74868ca6b0aa023df50fa80faad --- /dev/null +++ b/java/3-0-MVC/hellomvc3b/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 diff --git a/java/3-0-MVC/hellomvc4/Main.java b/java/3-0-MVC/hellomvc4/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..5a47279956493269254d9a0ef31086b5c95fc950 --- /dev/null +++ b/java/3-0-MVC/hellomvc4/Main.java @@ -0,0 +1,49 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +/** + * Two views with integrated controllers. Uses java.util.Observ{er, able} instead + * of custom IView. + */ + +import javax.swing.*; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.*; + +public class Main { + + public static void main(String[] args){ + JFrame frame = new JFrame("HelloMVC4"); + + // create Model and initialize it + Model model = new Model(); + + // create View, tell it about model (and controller) + View view = new View(model); + // tell Model about View. + model.addObserver(view); + + // create second view ... + View2 view2 = new View2(model); + model.addObserver(view2); + + // let all the views know that they're connected to the model + model.notifyObservers(); + + // create a layout panel to hold the two views + JPanel p = new JPanel(new GridLayout(2,1)); + frame.getContentPane().add(p); + + // add views (each view is a JPanel) + p.add(view); + p.add(view2); + + // setup window + frame.setPreferredSize(new Dimension(300,300)); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/hellomvc4/Model.java b/java/3-0-MVC/hellomvc4/Model.java new file mode 100644 index 0000000000000000000000000000000000000000..14b46c2b718a190bfd7bd5cdbc3e06638ef4185e --- /dev/null +++ b/java/3-0-MVC/hellomvc4/Model.java @@ -0,0 +1,27 @@ +import java.util.Observable; + +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +public class Model extends Observable { + // the data in the model, just a counter + private int counter; + + Model() { + setChanged(); + } + + public int getCounterValue() { + return counter; + } + + public void incrementCounter() { + if (counter < 5) { + counter++; + System.out.println("Model: increment counter to " + counter); + setChanged(); + notifyObservers(); + } + } +} diff --git a/java/3-0-MVC/hellomvc4/View.java b/java/3-0-MVC/hellomvc4/View.java new file mode 100644 index 0000000000000000000000000000000000000000..277fc55f6740f4a11d4f80c54578d367f179e51a --- /dev/null +++ b/java/3-0-MVC/hellomvc4/View.java @@ -0,0 +1,52 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +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 JButton button; + + // the model that this view is showing + private Model model; + + View(Model model) { + + // create the view UI + button = new JButton("?"); + button.setMaximumSize(new Dimension(100, 50)); + button.setPreferredSize(new Dimension(100, 50)); + + // a GridBagLayout with default constraints centres + // the widget in the window + this.setLayout(new GridBagLayout()); + this.add(button, new GridBagConstraints()); + + // set the model + this.model = model; + + // setup the event to go to the "controller" + // (this anonymous class is essentially the controller) + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + model.incrementCounter(); + } + }); + } + + // Observer interface + @Override + public void update(Observable arg0, Object arg1) { + System.out.println("View: update"); + button.setText(Integer.toString(model.getCounterValue())); + } +} diff --git a/java/3-0-MVC/hellomvc4/View2.java b/java/3-0-MVC/hellomvc4/View2.java new file mode 100644 index 0000000000000000000000000000000000000000..2a617ab28b41365d2c5dc3eb16cf78730987c7ff --- /dev/null +++ b/java/3-0-MVC/hellomvc4/View2.java @@ -0,0 +1,46 @@ +// HelloMVC: a simple MVC example +// the model is just a counter +// inspired by code by Joseph Mack, http://www.austintek.com/mvc/ + +import javax.swing.*; +import java.awt.FlowLayout; +import java.awt.Color; +import java.awt.event.*; +import java.util.*; + +class View2 extends JPanel implements Observer { + + // the model that this view is showing + private Model model; + private JLabel label = new JLabel(); + + View2(Model model) { + + // create UI + setBackground(Color.WHITE); + setLayout(new FlowLayout(FlowLayout.LEFT)); + this.add(this.label); + + // set the model + this.model = model; + + // setup the event to go to the "controller" + // (this anonymous class is essentially the controller) + addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + model.incrementCounter(); + } + }); + + } + + // Observer interface + @Override + public void update(Observable o, Object arg) { + System.out.println("View2: updateView"); + // just displays an 'X' for each counter value + String s = ""; + for (int i=0; i<this.model.getCounterValue(); i++) s = s + "X"; + this.label.setText(s); + } +} diff --git a/java/3-0-MVC/hellomvc4/makefile b/java/3-0-MVC/hellomvc4/makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f843f1fb83fb74868ca6b0aa023df50fa80faad --- /dev/null +++ b/java/3-0-MVC/hellomvc4/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 diff --git a/java/3-0-MVC/nomvc/Main.java b/java/3-0-MVC/nomvc/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..f3e834cb3e8867b112846d4c9173d994f3b7822b --- /dev/null +++ b/java/3-0-MVC/nomvc/Main.java @@ -0,0 +1,64 @@ +import javax.swing.*; +import java.awt.Dimension; +import java.awt.GridLayout; +import java.awt.event.*; +import java.awt.*; + +public class Main extends JPanel { + + public static void main(String[] args){ + JFrame frame = new JFrame("NoMvc"); + frame.getContentPane().add(new Main()); + frame.setPreferredSize(new Dimension(300,300)); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } + + int counter = 0; + + void increaseCounter() { + if (counter < 5) { + counter++; + String s = Integer.toString(counter); + button.setText(s); + // just displays an 'X' for each counter value + s = ""; + for (int i = 0; i< counter; i++) s = s + "X"; + label.setText(s); + } + } + + JButton button; + JLabel label; + + public Main() { + + this.setLayout(new GridLayout(2,1)); + + JPanel topPanel = new JPanel(); + button = new JButton("?"); + button.setMaximumSize(new Dimension(100, 50)); + button.setPreferredSize(new Dimension(100, 50)); + topPanel.setLayout(new GridBagLayout()); + topPanel.add(button, new GridBagConstraints()); + button.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + increaseCounter(); + } + }); + this.add(topPanel); + + + JPanel bottomPanel = new JPanel(); + bottomPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + label = new JLabel(""); + bottomPanel.add(label); + bottomPanel.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + increaseCounter(); + } + }); + this.add(bottomPanel); + } +} \ No newline at end of file diff --git a/java/3-0-MVC/nomvc/makefile b/java/3-0-MVC/nomvc/makefile new file mode 100644 index 0000000000000000000000000000000000000000..2f843f1fb83fb74868ca6b0aa023df50fa80faad --- /dev/null +++ b/java/3-0-MVC/nomvc/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 diff --git a/java/3-0-MVC/readme.md b/java/3-0-MVC/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..53226291d7cca61d8d24c23979f4eb9da42ef616 --- /dev/null +++ b/java/3-0-MVC/readme.md @@ -0,0 +1,42 @@ +# MVC Code Demos + +## Hello MVC Demos + +Simple MVC examples using an incremental counter model. Each directory has a make file with MVC classes. These demos were inspired by Joseph Mack: http://www.austintek.com/mvc/ + +* `nomvc/` same functionality as MVC demos, but not using MVC +* `hellomvc1/` separate view and controller classes, only 1 view +* `hellomvc2/` separate view and controller classes, multiple views +* `hellomvc3/` controller combined into view, multiple views +* `hellomvc3b/` controller combined into view, views use modelListeners +* `hellomvc4/` using Java's Observer interface and Observable base class + +## Triangle Demos + +These are more complex MVC examples using a right-angle triangle model + +All use the same "right-angle triangle" Model: + +* `model/TriangleModel.java` the model used in every example +* `model/IView.java` the view interface used in every example + +There are different Views and Controllers: + +* `SimpleTextView.java` a very limited but simple view +* `TextView.java` a text view with correct controller updates and protected field +* `SliderView` uses slider widgets to adjust side lengths +* `SpinnerView` uses spinner widgets to adjust side lengths +* `GraphicalView` adjust side length using direct manipulation of a picture of the triangle + +These views are combined in different ways. Set makefile NAME to one of the following startup classes to run: + +* `Main1.java` SimpleTextView only +* `Main2.java` TextView only +* `Main3.java` multiple views at once +* `Main4.java` GraphicalView demo +* `Main5.java` multiple views using a tab + +These demos also show how to use packages to organize code and they use the `FormLayout` custom layout manager. + + + \ No newline at end of file diff --git a/java/3-0-MVC/triangle/Main1.java b/java/3-0-MVC/triangle/Main1.java new file mode 100644 index 0000000000000000000000000000000000000000..a355dedf4057a1320b95a6003151a2e6b0875583 --- /dev/null +++ b/java/3-0-MVC/triangle/Main1.java @@ -0,0 +1,19 @@ +import javax.swing.JFrame; + +import model.TriangleModel; + +public class Main1 { + + public static void main(String[] args) { + + TriangleModel model = new TriangleModel(); + + view.SimpleTextView view = new view.SimpleTextView(model); + + JFrame frame = new JFrame("Triangle Main1"); + frame.getContentPane().add(view); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/triangle/Main2.java b/java/3-0-MVC/triangle/Main2.java new file mode 100644 index 0000000000000000000000000000000000000000..1bf37cfe874cd02f2a647197aa9e6110ad01a159 --- /dev/null +++ b/java/3-0-MVC/triangle/Main2.java @@ -0,0 +1,17 @@ +import javax.swing.JFrame; + +import model.TriangleModel; + +public class Main2 { + + public static void main(String[] args) { + model.TriangleModel model = new TriangleModel(); + view.TextView view = new view.TextView(model); + + JFrame frame = new JFrame("Triangle Main2"); + frame.getContentPane().add(view); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/triangle/Main3.java b/java/3-0-MVC/triangle/Main3.java new file mode 100644 index 0000000000000000000000000000000000000000..536639d2c5be9da91079f404aa5ef6aef2452a79 --- /dev/null +++ b/java/3-0-MVC/triangle/Main3.java @@ -0,0 +1,27 @@ +import javax.swing.JFrame; + +import model.TriangleModel; +import view.*; +import java.awt.GridLayout; + +public class Main3 { + + public static void main(String[] args) { + model.TriangleModel model = new TriangleModel(); + TextView vText = new TextView(model); + ButtonView vButton = new ButtonView(model); + SliderView vSlider = new SliderView(model); + SpinnerView vSpinner = new SpinnerView(model); + + JFrame frame = new JFrame("Triangle Main3"); + frame.getContentPane().setLayout(new GridLayout(2, 2)); + frame.getContentPane().add(vText); + frame.getContentPane().add(vButton); + frame.getContentPane().add(vSlider); + frame.getContentPane().add(vSpinner); + + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/triangle/Main4.java b/java/3-0-MVC/triangle/Main4.java new file mode 100644 index 0000000000000000000000000000000000000000..bb986e8594f5c80d302748616e30d6ef13d63b9b --- /dev/null +++ b/java/3-0-MVC/triangle/Main4.java @@ -0,0 +1,19 @@ +import javax.swing.JFrame; + +import model.TriangleModel; +import view.*; +import java.awt.GridLayout; + +public class Main4 { + + public static void main(String[] args) { + model.TriangleModel model = new TriangleModel(); + GraphicalView vGraphical = new GraphicalView(model); + + JFrame frame = new JFrame("Triangle Main4"); + frame.getContentPane().add(vGraphical); + frame.pack(); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/triangle/Main5.java b/java/3-0-MVC/triangle/Main5.java new file mode 100644 index 0000000000000000000000000000000000000000..3337199040aa97904645cd55f44e014d09cc0e80 --- /dev/null +++ b/java/3-0-MVC/triangle/Main5.java @@ -0,0 +1,24 @@ +import javax.swing.*; + +import model.TriangleModel; +import view.*; +import java.awt.GridLayout; + +public class Main5 { + + public static void main(String[] args) { + model.TriangleModel model = new TriangleModel(); + GraphicalView vGraphical = new GraphicalView(model); + TextView vText = new TextView(model); + + JFrame frame = new JFrame("Triangle Main5"); + JTabbedPane tabs = new JTabbedPane(); + tabs.add("Text View", vText); + tabs.add("Graphical View", vGraphical); + frame.getContentPane().add(tabs); + + frame.setSize(300, 300); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setVisible(true); + } +} diff --git a/java/3-0-MVC/triangle/makefile b/java/3-0-MVC/triangle/makefile new file mode 100644 index 0000000000000000000000000000000000000000..06bac5dc51632c642c904a38c76928bb49981fdf --- /dev/null +++ b/java/3-0-MVC/triangle/makefile @@ -0,0 +1,18 @@ +# super simple makefile +# call it using 'make NAME=name_of_code_file_without_extension' +# (assumes a .java extension) +NAME = "Main1" + +all: + # (a bit of a hack to compile everything each time ...) + @echo "Compiling..." + javac *.java model/*.java view/*.java + +run: all + @echo "Running..." + java $(NAME) + +clean: + rm -rf *.class + rm -rf view/*.class + rm -rf model/*.class diff --git a/java/3-0-MVC/triangle/model/IView.java b/java/3-0-MVC/triangle/model/IView.java new file mode 100644 index 0000000000000000000000000000000000000000..2d7f3f8106241a5855f51379be5bd0f03290374b --- /dev/null +++ b/java/3-0-MVC/triangle/model/IView.java @@ -0,0 +1,9 @@ +package model; + +public interface IView { + /** + * This method is called by the model whenever it changes state. + */ + public void updateView(); + +} diff --git a/java/3-0-MVC/triangle/model/TriangleModel.java b/java/3-0-MVC/triangle/model/TriangleModel.java new file mode 100644 index 0000000000000000000000000000000000000000..c5f2e1b8bc9b22a810a63682311df1bbb4f0b03b --- /dev/null +++ b/java/3-0-MVC/triangle/model/TriangleModel.java @@ -0,0 +1,79 @@ +package model; + +// Note! Nothing from the view package is imported here. +import java.util.ArrayList; +import javax.swing.undo.AbstractUndoableEdit; +import javax.swing.undo.UndoableEdit; + +public class TriangleModel { + + /* A list of the model's views. */ + private ArrayList<IView> views = new ArrayList<IView>(); + + // Limit the size of the triangle. + public static final double MAX_SIDE = 100.0; + public static final double MAX_HYPO = + Math.sqrt(MAX_SIDE * MAX_SIDE + MAX_SIDE * MAX_SIDE); + + private double base = 50.0; // length of the base + private double height = 50.0; // height of the triangle + + public TriangleModel() { } + + /** Set the base to a new value. Must be between 0 and MAX_BASE. */ + public void setBase(double theBase) { + double tmp = Math.max(0, theBase); + this.base = Math.min(tmp, MAX_SIDE); + + this.updateAllViews(); // update Views! + } + + /** Set the height to a new value. Must be between 0 and MAX_HEIGHT. */ + public void setHeight(double theHeight) { + this.height = Math.min(Math.max(0, theHeight), MAX_SIDE); + + this.updateAllViews(); // update Views! + } + + /** Set both the base and the height to new values. */ + public void setValues(double theBase, double theHeight) { + this.base = Math.min(Math.max(0, theBase), MAX_SIDE); + this.height = Math.min(Math.max(0, theHeight), MAX_SIDE); + + this.updateAllViews(); // update Views! + } + + /** Get the length of this triangle's base. */ + public double getBase() { + return this.base; + } + + /** Get this triangle's height. */ + public double getHeight() { + return this.height; + } + + /** Get the length of this triangle's hypotenuse. */ + public double getHypotenuse() { + return Math.sqrt(this.base * this.base + this.height * this.height); + } + + /** Add a new view of this triangle. */ + public void addView(IView view) { + this.views.add(view); + + view.updateView(); // update Views! + } + + /** Remove a view from this triangle. */ + public void removeView(IView view) { + this.views.remove(view); + } + + /** Update all the views that are viewing this triangle. */ + private void updateAllViews() { + for (IView view : this.views) { + view.updateView(); + } + } +} diff --git a/java/3-0-MVC/triangle/view/ButtonView.java b/java/3-0-MVC/triangle/view/ButtonView.java new file mode 100644 index 0000000000000000000000000000000000000000..ed73bad73b3937cc42b33e2d8bd13073dda8b37d --- /dev/null +++ b/java/3-0-MVC/triangle/view/ButtonView.java @@ -0,0 +1,109 @@ +package view; + +import model.*; + +import javax.swing.*; +import java.awt.Dimension; +import java.awt.event.*; +import java.text.NumberFormat; +import java.awt.GridLayout; + +public class ButtonView extends JPanel { + private TriangleModel model; + private JLabel base = new JLabel("0.0"); + private JLabel height = new JLabel("0.0"); + private JLabel hypo = new JLabel("0.0"); + private JButton baseUp = new JButton("+"); + private JButton baseDn = new JButton("-"); + private JButton heightUp = new JButton("+"); + private JButton heightDn = new JButton("-"); + + private NumberFormat formatter = NumberFormat.getNumberInstance(); + private static final int MAX_FRACTION_DIGITS = 5; + + public ButtonView(TriangleModel aModel) { + super(); + this.model = aModel; + + this.layoutView(); + this.registerControllers(); + + this.model.addView(new IView() { + public void updateView() { + base.setText(formatter.format(model.getBase())); + height.setText(formatter.format(model.getHeight())); + hypo.setText(formatter.format(model.getHypotenuse())); + + // Updating the view includes enabling/disabling components! + baseUp.setEnabled(model.getBase() < TriangleModel.MAX_SIDE); + baseDn.setEnabled(model.getBase() > 1); + heightUp.setEnabled(model.getHeight() < TriangleModel.MAX_SIDE); + heightDn.setEnabled(model.getHeight() > 1); + } + + }); + } + + private void layoutView() { + this.setLayout(new FormLayout()); + this.add(new JLabel("Base:")); + this.add(this.groupComponents(this.baseUp, this.baseDn, this.base)); + + this.add(new JLabel("Height:")); + this.add(this + .groupComponents(this.heightUp, this.heightDn, this.height)); + + this.add(new JLabel("Hypotenuse:")); + this.add(this.hypo); + + Dimension d = this.hypo.getPreferredSize(); + d.width = 80; + this.base.setPreferredSize(d); + this.height.setPreferredSize(d); + this.hypo.setPreferredSize(d); + + this.formatter.setMaximumFractionDigits(MAX_FRACTION_DIGITS); + } + + private Box groupComponents(JButton up, JButton dn, JLabel label) { + Box group = Box.createHorizontalBox(); + group.add(up); + group.add(dn); + group.add(label); + + Dimension d = up.getPreferredSize(); + d.width = Math.max(up.getPreferredSize().width, + dn.getPreferredSize().width); + up.setPreferredSize(d); + dn.setPreferredSize(d); + + return group; + } + + private void registerControllers() { + this.baseUp.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + model.setBase(model.getBase() + 1); + } + }); + + this.baseDn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + model.setBase(model.getBase() - 1); + } + }); + + this.heightUp.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + model.setHeight(model.getHeight() + 1); + } + }); + + this.heightDn.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + model.setHeight(model.getHeight() - 1); + } + }); + } + +} diff --git a/java/3-0-MVC/triangle/view/FormLayout.java b/java/3-0-MVC/triangle/view/FormLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..abb9f141fc9cf7f4be0c0d9426777710d48d4b5a --- /dev/null +++ b/java/3-0-MVC/triangle/view/FormLayout.java @@ -0,0 +1,216 @@ +package view; + +import java.awt.LayoutManager; +import java.awt.Insets; +import java.util.ArrayList; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; + +/** + * A layout manager that arranges components in a double column. In most cases + * the left column will hold a label and the right column will hold a component + * the user can manipulate. Preferred component sizes are respected as much as + * possible. Components in the left column are right justified; components in + * the right column are left justified. <img src="doc-files/FormLayout.gif"> + * + * @author Byron Weber Becker + */ +public class FormLayout implements LayoutManager { + private int hGap = 6; + private int vGap = 4; + + /** Construct a new FormLayout object. */ + public FormLayout() { + super(); + } + + /** + * Construct a new FormLayout object. + * + * @param hGap + * the number of hortizontal pixels between components + * @param vGap + * the number of vertical pixels between components + */ + public FormLayout(int hGap, int vGap) { + super(); + this.hGap = hGap; + this.vGap = vGap; + } + + /** + * Adds the specified component with the specified name to the layout. + * + * @param name + * the component name + * @param comp + * the component to be added + */ + public void addLayoutComponent(String name, Component comp) { + } + + /** + * Removes the specified component from the layout. + * + * @param comp + * the component to be removed + */ + public void removeLayoutComponent(Component comp) { + } + + /** + * Calculates the preferred size dimensions for the specified panel given + * the components in the specified parent container. + * + * @param parent + * the component to be laid out + * + * @see #minimumLayoutSize + */ + public Dimension preferredLayoutSize(Container parent) { + synchronized (parent.getTreeLock()) { + ColDims cd = colDims(parent, true); + + Insets insets = parent.getInsets(); + Dimension d = new Dimension(cd.left + cd.right + insets.left + + insets.right, cd.height + insets.top + insets.bottom); + return d; + } + } + + /* + * Precondition: the caller has gotten the treelock. preferred = true for + * preferred sizes; false for minimum sizes + */ + private ColDims colDims(Container parent, boolean preferred) { + ColDims cd = new ColDims(); + int nComponents = parent.getComponentCount(); + // left column + for (int i = 1; i < nComponents; i += 2) { + Component left = parent.getComponent(i - 1); + Component right = parent.getComponent(i); + + Dimension dLeft; + Dimension dRight; + if (preferred) { + dLeft = left.getPreferredSize(); + dRight = right.getPreferredSize(); + } else { + dLeft = left.getMinimumSize(); + dRight = right.getMinimumSize(); + } + cd.left = (int) Math.max(cd.left, dLeft.width); + cd.right = (int) Math.max(cd.right, dRight.width); + cd.height += (int) Math.max(dLeft.height, dRight.height); + cd.height += this.vGap; + } + if (nComponents % 2 == 1) { // odd number of components -- get the last + // one on the left + Component left = parent.getComponent(nComponents - 1); + + Dimension dLeft; + if (preferred) { + dLeft = left.getPreferredSize(); + } else { + dLeft = left.getMinimumSize(); + } + cd.left = (int) Math.max(cd.left, dLeft.width); + cd.height += dLeft.height + this.vGap; + } + cd.left += this.hGap / 2; + cd.right += this.hGap / 2; + return cd; + } + + /** + * Calculates the minimum size dimensions for the specified panel given the + * components in the specified parent container. + * + * @param parent + * the component to be laid out + * @see #preferredLayoutSize + */ + public Dimension minimumLayoutSize(Container parent) { + synchronized (parent.getTreeLock()) { + ColDims cd = colDims(parent, false); + + Insets insets = parent.getInsets(); + Dimension d = new Dimension(cd.left + cd.right + insets.left + + insets.right, cd.height + insets.top + insets.bottom); + return d; + } + } + + /** + * Lays out the container in the specified panel. + * + * @param parent + * the component which needs to be laid out + */ + public void layoutContainer(Container parent) { + Insets insets = parent.getInsets(); + + synchronized (parent.getTreeLock()) { + ColDims cd = this.colDims(parent, true); + + int desiredWidth = cd.left + cd.right + insets.left + insets.right; + double widthScale = Math.min(1.0, parent.getWidth() + / (double) desiredWidth); + double heightScale = Math.min(1.0, parent.getHeight() + / (double) cd.height); + + double scale = Math.min(widthScale, heightScale); + + // System.out.println("FormLayout.layoutContainer: widthScale = " + + // widthScale + "; heightScale = " + heightScale); + + int midPt = (int) (((cd.left + insets.left + this.hGap) / (double) (cd.left + + cd.right + insets.left + insets.right + hGap)) * parent + .getWidth()); + int top = insets.top + this.vGap; + + int nComponents = parent.getComponentCount(); + for (int i = 1; i < nComponents; i += 2) { + Component left = parent.getComponent(i - 1); + Component right = parent.getComponent(i); + Dimension lDim = left.getPreferredSize(); + Dimension rDim = right.getPreferredSize(); + + int rowHeight = (int) (Math.max(lDim.height, rDim.height) * heightScale); + + // scale left side, if necessary; then position + if (lDim.width > desiredWidth || lDim.height > rowHeight) { + lDim.width = (int) (lDim.width * scale); + lDim.height = (int) (lDim.height * scale); + } + left.setBounds((midPt - lDim.width - this.hGap / 2), top, + lDim.width, lDim.height); + + // scale left side, if necessary; then position + if (rDim.width > desiredWidth || rDim.height > rowHeight) { + rDim.width = (int) (rDim.width * scale); + rDim.height = (int) (rDim.height * scale); + } + right.setBounds(midPt + this.hGap / 2, top, rDim.width, + rDim.height); + top = top + rowHeight + this.vGap; + } + if (nComponents % 2 == 1) { // odd number of components -- get the + // last one on the left + Component left = parent.getComponent(nComponents - 1); + Dimension lDim = left.getPreferredSize(); + lDim.width = (int) (lDim.width * scale); + lDim.height = (int) (lDim.height * scale); + left.setBounds((midPt - lDim.width - this.hGap / 2), top, + lDim.width, lDim.height); + } + } + } + + private static class ColDims { + int left = 0; + int right = 0; + int height = 0; + } +} diff --git a/java/3-0-MVC/triangle/view/GraphicalView.java b/java/3-0-MVC/triangle/view/GraphicalView.java new file mode 100644 index 0000000000000000000000000000000000000000..74a2d4514dbb682d24af2533ee10fa4ca2e02c45 --- /dev/null +++ b/java/3-0-MVC/triangle/view/GraphicalView.java @@ -0,0 +1,144 @@ +package view; + +import model.*; + +import javax.swing.*; +import javax.swing.event.*; +import java.awt.*; +import java.awt.event.*; +import java.text.NumberFormat; + +/** + * A view of a right triangle that displays the triangle graphically and allows + * the user to change the size by dragging the image with a mouse. + */ +public class GraphicalView extends JComponent { + private TriangleModel model; + + private double scale = 1.0; // how much should the triangle be scaled? + + private int handleSize = 5; // size of selectable square for dragging + + // To format numbers consistently in the text fields. + private static final NumberFormat formatter = NumberFormat + .getNumberInstance(); + + public GraphicalView(TriangleModel aModel) { + super(); + this.model = aModel; + this.layoutView(); + this.registerControllers(); + this.model.addView(new IView() { + /** The model changed. Ask the system to repaint the triangle. */ + public void updateView() { + repaint(); + } + }); + } + + /** How should it look on the screen? */ + private void layoutView() { + this.setPreferredSize(new Dimension(200, 200)); + this.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + } + + /** Register event Controllers for mouse clicks and motion. */ + private void registerControllers() { + MouseInputListener mil = new MController(); + this.addMouseListener(mil); + this.addMouseMotionListener(mil); + } + + /** Paint the triangle with "handle" for resizing */ + public void paintComponent(Graphics g) { + super.paintComponent(g); + Insets insets = this.getInsets(); + + this.scale = Math.min((this.getWidth() - insets.left - insets.right) + / (TriangleModel.MAX_SIDE + 2), + (this.getHeight() - insets.top - insets.bottom) + / (TriangleModel.MAX_SIDE + 2)); + + double base = this.model.getBase(); + double height = this.model.getHeight(); + double hypo = this.model.getHypotenuse(); + + int left = this.toX(0); + int right = this.toX(base); + int bottom = this.toY(0); + int top = this.toY(height); + + // Draw the triangle + g.setColor(Color.black); + g.drawLine(left, bottom, right, bottom); + g.drawLine(right, bottom, right, top); + g.drawLine(left, bottom, right, top); + + // Label the edges + g.drawString(formatter.format(base), left + (right - left) / 2, bottom); + g.drawString(formatter.format(height), right, top + (bottom - top) / 2); + g.drawString(formatter.format(hypo), left + (right - left) / 2 - 15, + top + (bottom - top) / 2 - 15); + + /** Draw "handles" for resizing the triangle. */ + // g.fillRect(right - handleSize, bottom - handleSize, + // handleSize * 2, handleSize * 2); + g.fillRect(right - handleSize, top - handleSize, + handleSize * 2, handleSize * 2); + + } + + /** Convert from the model's X coordinate to the component's X coordinate. */ + protected int toX(double modelX) { + return (int) (modelX * this.scale) + this.getInsets().left; + } + + /** Convert from the model's Y coordinate to the component's Y coordinate. */ + protected int toY(double modelY) { + return this.getHeight() - (int) (modelY * this.scale) - 1 + - this.getInsets().bottom; + } + + /** Convert from the component's X coordinate to the model's X coordinate. */ + protected double fromX(int x) { + return (x - this.getInsets().left) / this.scale; + } + + /** Convert from the component's Y coordinate to the model's Y coordinate. */ + protected double fromY(int y) { + return (this.getHeight() - 1 - this.getInsets().bottom - y) + / this.scale; + } + + /** + * Does the given point (screen coordinates) lie on the upper right corner + * of the triangle? + */ + private boolean onTopCorner(int x, int y) { + return Math.abs(fromX(x) - this.model.getBase()) < handleSize + && Math.abs(fromY(y) - this.model.getHeight()) < handleSize; + } + + private class MController extends MouseInputAdapter { + + private boolean selected = false; // did the user select the triangle to + // resize it? + + public void mousePressed(MouseEvent e) { + selected = onTopCorner(e.getX(), e.getY()); + } + + // public void mouseReleased(MouseEvent e) { + // selected = onTopCorner(e.getX(), e.getY()); + // } + + /** The user is dragging the mouse. Resize appropriately. */ + public void mouseDragged(MouseEvent e) { + if (selected) { + model.setBase(fromX(e.getX())); + model.setHeight(fromY(e.getY())); + } + } + } // MController +} // GraphicalView + diff --git a/java/3-0-MVC/triangle/view/SimpleTextView.java b/java/3-0-MVC/triangle/view/SimpleTextView.java new file mode 100644 index 0000000000000000000000000000000000000000..ee68ab08224d2f7a5cb98baff170475cb514a706 --- /dev/null +++ b/java/3-0-MVC/triangle/view/SimpleTextView.java @@ -0,0 +1,73 @@ +package view; + +import model.IView; +import model.TriangleModel; + +import javax.swing.*; +import java.awt.event.*; +import java.text.NumberFormat; + +/* + * View a triangle as text: numbers for the base, height, and hypotenuse. + * This view leaves a lot to be desired in terms of "polish". + * -- inconsistent decimal precision displayed, esp. in hypotenuse + * -- tabbing or clicking out of a text field doesn't update + * -- can edit the hypotenuse field + * + * @author Byron Weber Becker + */ +public class SimpleTextView extends JPanel implements IView { + + private TriangleModel model; + + private JTextField baseTF = new JTextField(10); + private JTextField heightTF = new JTextField(10); + private JTextField hypoTF = new JTextField(10); + + public SimpleTextView(TriangleModel aModel) { + super(); + this.model = aModel; + this.layoutView(); + this.registerControllers(); + + // Add a this view as a listener to the model + this.model.addView(this); + } + + /** + * What to do when the model changes. + */ + public void updateView() { + baseTF.setText("" + model.getBase()); + heightTF.setText("" + model.getHeight()); + hypoTF.setText("" + model.getHypotenuse()); + } + + private void layoutView() { + this.setLayout(new FormLayout()); + this.add(new JLabel("Base:")); + this.add(this.baseTF); + this.add(new JLabel("Height:")); + this.add(this.heightTF); + this.add(new JLabel("Hypotenuse:")); + this.add(this.hypoTF); + } + + private void registerControllers() { + // Add a controller to interpret user actions in the base text field + this.baseTF.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + double base = Double.parseDouble(baseTF.getText()); + model.setBase(base); + } + }); + + // Add a controller to interpret user actions in the height text field + this.heightTF.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + double height = Double.parseDouble(heightTF.getText()); + model.setHeight(height); + } + }); + } +} \ No newline at end of file diff --git a/java/3-0-MVC/triangle/view/SliderView.java b/java/3-0-MVC/triangle/view/SliderView.java new file mode 100644 index 0000000000000000000000000000000000000000..0384158719f23b683aee56d2b944fb9f8877cfc1 --- /dev/null +++ b/java/3-0-MVC/triangle/view/SliderView.java @@ -0,0 +1,68 @@ +package view; + +import model.*; + +import javax.swing.*; +import javax.swing.event.*; + +public class SliderView extends JPanel { + private TriangleModel model; + private JSlider baseSlider = new JSlider(0, (int) TriangleModel.MAX_HYPO); + private JSlider heightSlider = new JSlider(0, (int) TriangleModel.MAX_HYPO); + private JSlider hypoSlider = new JSlider(0, (int) TriangleModel.MAX_HYPO); + + public SliderView(TriangleModel aModel) { + super(); + this.model = aModel; + this.layoutView(); + this.registerControllers(); + this.model.addView(new IView() { + public void updateView() { + baseSlider.setValue((int) model.getBase()); + heightSlider.setValue((int) model.getHeight()); + hypoSlider.setValue((int) model.getHypotenuse()); + } + }); + } + + private void layoutView() { + this.setLayout(new FormLayout()); + this.add(new JLabel("Base:")); + this.add(this.baseSlider); + this.add(new JLabel("Height:")); + this.add(this.heightSlider); + this.add(new JLabel("Hypotenuse:")); + this.add(this.hypoSlider); + + this.baseSlider.setMajorTickSpacing((int) TriangleModel.MAX_HYPO / 10); + this.baseSlider.setPaintTicks(true); + + this.heightSlider + .setMajorTickSpacing((int) TriangleModel.MAX_HYPO / 10); + this.heightSlider.setPaintTicks(true); + + this.hypoSlider.setMajorTickSpacing((int) TriangleModel.MAX_HYPO / 10); + this.hypoSlider.setPaintTicks(true); + + this.hypoSlider.setEnabled(false); + } + + private void registerControllers() { + this.baseSlider.addChangeListener(new BaseController()); + this.heightSlider.addChangeListener(new HeightController()); + } + + private class BaseController implements ChangeListener { + public void stateChanged(ChangeEvent e) { + double base = baseSlider.getValue(); + model.setBase(base); + } + } + + private class HeightController implements ChangeListener { + public void stateChanged(ChangeEvent e) { + double height = heightSlider.getValue(); + model.setHeight(height); + } + } +} diff --git a/java/3-0-MVC/triangle/view/SpinnerView.java b/java/3-0-MVC/triangle/view/SpinnerView.java new file mode 100644 index 0000000000000000000000000000000000000000..095eee1a50c99e2390c77566e769618522484a4b --- /dev/null +++ b/java/3-0-MVC/triangle/view/SpinnerView.java @@ -0,0 +1,72 @@ +package view; + +import java.text.NumberFormat; + +import model.*; + +import javax.swing.*; +import javax.swing.event.*; + +public class SpinnerView extends JPanel { + private TriangleModel model; + + // The spinner requires a number model that specifies the starting + // value, the minimum, maximum, and step size. + private JSpinner base = new JSpinner(new SpinnerNumberModel(1, 1, + TriangleModel.MAX_SIDE, 1)); + private JSpinner height = new JSpinner(new SpinnerNumberModel(1, 1, + TriangleModel.MAX_SIDE, 1)); + private JLabel hypo = new JLabel(); + private static final NumberFormat formatter = NumberFormat + .getNumberInstance(); + + public SpinnerView(TriangleModel aModel) { + super(); + this.model = aModel; + this.layoutView(); + this.registerControllers(); + + this.model.addView(new IView() { + public void updateView() { + base.setValue(new Double(model.getBase())); + height.setValue(new Double(model.getHeight())); + hypo.setText(formatter.format(model.getHypotenuse())); + } + }); + + } + + private void layoutView() { + this.setLayout(new FormLayout()); + this.add(new JLabel("Base:")); + this.add(this.base); + this.add(new JLabel("Height:")); + this.add(this.height); + this.add(new JLabel("Hypotenuse:")); + this.add(this.hypo); + + } + + private void registerControllers() { + // Instantiate an anonymous ChangeListener class. + // Then add it to both spinners. Having one class + // that handles both spinners is one style; I prefer + // having separate listeners, one for each widget. + ChangeListener sc = new ChangeListener() { + public void stateChanged(ChangeEvent e) { + JSpinner src = (JSpinner) e.getSource(); + double val = ((Double) src.getValue()).doubleValue(); + if (src == SpinnerView.this.base) { + SpinnerView.this.model.setBase(val); + } else if (src == SpinnerView.this.height) { + SpinnerView.this.model.setHeight(val); + } else { + assert false; + } + } + }; + + this.base.addChangeListener(sc); + this.height.addChangeListener(sc); + } +} \ No newline at end of file diff --git a/java/3-0-MVC/triangle/view/TextView.java b/java/3-0-MVC/triangle/view/TextView.java new file mode 100644 index 0000000000000000000000000000000000000000..e09ecc3f5f4ef31d5d1abeef57680980968bac2e --- /dev/null +++ b/java/3-0-MVC/triangle/view/TextView.java @@ -0,0 +1,112 @@ +package view; + +import model.IView; +import model.TriangleModel; + +import javax.swing.*; +import java.awt.event.*; +import java.text.NumberFormat; + +/* + * View a triangle as text: numbers for the base, height, and hypotenuse. + * + * @author Byron Weber Becker + */ +public class TextView extends JPanel { + + private TriangleModel model; + + private JTextField baseTF = new JTextField(10); + private JTextField heightTF = new JTextField(10); + private JTextField hypoTF = new JTextField(10); + + // To format numbers consistently in the text fields. + private static final NumberFormat formatter = NumberFormat + .getNumberInstance(); + + public TextView(TriangleModel aModel) { + super(); + this.model = aModel; + this.layoutView(); + this.registerControllers(); + + // There's no need to do this for such a simple view, but for a complex + // view you might have several ViewUpdate objects registered with + // the model. + this.model.addView(new IView() { + public void updateView() { + baseTF.setText(formatter.format(model.getBase())); + heightTF.setText(formatter.format(model.getHeight())); + } + }); + + this.model.addView(new IView() { + public void updateView() { + hypoTF.setText(formatter.format(model.getHypotenuse())); + } + }); + } + + private void layoutView() { + this.setLayout(new FormLayout()); + this.add(new JLabel("Base:")); + this.add(this.baseTF); + this.add(new JLabel("Height:")); + this.add(this.heightTF); + this.add(new JLabel("Hypotenuse:")); + this.add(this.hypoTF); + + // Don't allow the user to edit the hypotenuse + this.hypoTF.setEditable(false); // can still select and copy + //this.hypoTF.setEnabled(false); // greyed out, can't select + } + + private void registerControllers() { + this.baseTF.addActionListener(new BaseController()); + this.baseTF.addFocusListener(new BaseFocusController()); + + CombinedHeightController hc = new CombinedHeightController(); + this.heightTF.addActionListener(hc); + this.heightTF.addFocusListener(hc); + } + + private class BaseController implements ActionListener { + public void actionPerformed(ActionEvent evt) { + double base = Double.parseDouble(baseTF.getText()); + model.setBase(base); + } + } + + private class BaseFocusController implements FocusListener { + public void focusGained(FocusEvent evt) { + //baseTF.selectAll(); + } + + public void focusLost(FocusEvent evt) { + // Note duplicated code. + double value = Double.parseDouble(baseTF.getText()); + model.setBase(value); + } + } + + private class CombinedHeightController implements ActionListener, + FocusListener { + private void setHeight() { + double height = Double.parseDouble(heightTF.getText()); + model.setHeight(height); + } + + public void actionPerformed(ActionEvent evt) { + this.setHeight(); + } + + public void focusGained(FocusEvent evt) { + //heightTF.selectAll(); + } + + public void focusLost(FocusEvent evt) { + this.setHeight(); + baseTF.requestFocus(); // skip over hypotenuse + } + } +} \ No newline at end of file