/* ============================================================================ * File: StackJFrame.java * ---------------------------------------------------------------------------- * * This file is part of the Microsoft Supplemental UI Library for Visual J# .NET * Code Samples. * * Copyright (C) 2003 Microsoft Corporation. All rights reserved. * * THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, * WHETHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. * ============================================================================ */ package RecursionDemo; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.TitledBorder; /** * This class draws one call stack. */ class StackPanel extends JPanel { // Font, width and height to use private static final Font FONT = new Font ("Verdana", Font.PLAIN, 10); private static final int WIDTH = 150; private static final int HEIGHT = 50; private int step; // step# private int input; // input value to this step private int result; // result of this step private boolean resultSet; // true if result is known private boolean current; // is this step currently executing public StackPanel(int st, int in) { step = st; input = in; setToolTipText(""); } public void setResult(int ret) { result = ret; resultSet = true; } public void setCurrent(boolean b) { current = b; } // return appropriate tool tip public String getToolTipText() { StringBuffer tip = new StringBuffer(); if (current) tip.append("Currently executing. "); else { tip.append("Function call: "); tip.append(step); tip.append(", "); } tip.append("Input value: "); tip.append(input); if (resultSet) { tip.append(", Return value: "); tip.append(result); } return tip.toString(); } public Dimension getPreferredSize() { return new Dimension(WIDTH, HEIGHT); } public void paint(Graphics g) { super.paint(g); // Get panel's width and height. int width = getSize().width; int height = getSize().height; // set background color based on whether the func is executing or over if (current) g.setColor(Color.yellow); else if (resultSet) g.setColor(Color.lightGray); else g.setColor(Color.white); g.fillRoundRect(3, 3, width - 4, height - 4, 10, 10); g.setColor(Color.darkGray); g.drawRoundRect(3, 3, width - 4, height - 4, 10, 10); g.setFont(FONT); g.setColor(Color.red); g.drawString("" + step + ")", 25, 25); g.setColor(Color.blue); g.drawString("In value: " + input, 50, 15); if (resultSet) { g.drawString("Return value: " + result, 50, height - 10); } } } /** * This is the main UI class. */ public class StackJFrame extends JFrame implements RecursionStack, Runnable { private static final int MAX = 25; // maximum steps allowed in recursion private static final int HEIGHT = 500; // height (and width) of this frame private RecursiveFunction[] recursiveFuncs; // all recursive functions private int totalSteps; // total steps private StackPanel[] stackPanels; // call stack display panel private JPanel stacks; // container for the above private JScrollBar stacksScrollBar; // scrollbar for the above private JComboBox comboFunctions; // combo box for recursive functions private JTextField inputValue; // input value to RF private JButton startButton; // start simulation private JButton nextButton; // go to next step private JButton finishButton; // finish simulation at speed private JTextArea funcDescription; // display area for RF description private JTextArea funcCode; // display area for RF code private JTextField funcResult; // display area for RF result private JProgressBar funcProgress; // progress so far private boolean next; // go to next step private boolean finish; // finish simulation at speed private int step; // current step public StackJFrame(RecursiveFunction[] funcs) { super("Recursive Function Simulation"); recursiveFuncs = funcs; // Exit when window is closed setDefaultCloseOperation(EXIT_ON_CLOSE); // Two columns - right one for stacks & left one for rest getContentPane().setLayout(new GridLayout(1, 2)); // Left column JPanel info = new JPanel(new GridLayout(2, 1)); // choose panel - includes comboFunctions, funcDescription and funcCode JPanel choose = new JPanel(new BorderLayout()); choose.setBorder(new TitledBorder("Choose Function")); // init comboFunctions comboFunctions = new JComboBox(); comboFunctions.setToolTipText("Choose a recursive function to simulate"); for (int i = 0; i < recursiveFuncs.length; i++) { comboFunctions.addItem(recursiveFuncs[i].getName()); recursiveFuncs[i].setRecursionStack(this); } comboFunctions.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { selectFunction(); } }); choose.add(comboFunctions, BorderLayout.NORTH); // init funcDescription funcDescription = new JTextArea(); funcDescription.setEditable(false); funcDescription.setLineWrap(true); funcDescription.setToolTipText("Recursive function description"); choose.add(new JScrollPane(funcDescription), BorderLayout.CENTER); // init funcCode funcCode = new JTextArea(); funcCode.setEditable(false); funcCode.setLineWrap(true); funcCode.setToolTipText("Recursive function code"); choose.add(funcCode, BorderLayout.SOUTH); info.add(choose); // controls panel - include inputValue and buttons. JPanel controls = new JPanel(new GridLayout(2, 1)); controls.setBorder(new TitledBorder("Controls")); // init inputValue JPanel steps = new JPanel(); inputValue = new JTextField("5", 6); // 5 is default value inputValue.setToolTipText("Input value to recursive function"); JLabel lbl = new JLabel("Input Value (integer only)"); lbl.setLabelFor(inputValue); steps.add(lbl); steps.add(inputValue); controls.add(steps); // init buttons JPanel buttons = new JPanel(); startButton = new JButton("Start"); startButton.setToolTipText("Start simulation"); startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { startButton.setEnabled(false); inputValue.setEditable(false); comboFunctions.setEnabled(false); new Thread(StackJFrame.this).start(); } }); inputValue.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { startButton.setEnabled(false); inputValue.setEditable(false); comboFunctions.setEnabled(false); new Thread(StackJFrame.this).start(); } }); buttons.add(startButton); nextButton = new JButton("Next"); nextButton.setToolTipText("Go to next step"); nextButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { nextButton.setEnabled(false); finishButton.setEnabled(false); next = true; } }); buttons.add(nextButton); finishButton = new JButton("Finish"); finishButton.setToolTipText("Finish simulation"); finishButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { nextButton.setEnabled(false); finishButton.setEnabled(false); finish = true; } }); buttons.add(finishButton); startButton.setEnabled(true); inputValue.setEditable(true); comboFunctions.setEnabled(true); nextButton.setEnabled(false); finishButton.setEnabled(false); controls.add(buttons); // output panel - includes funcProgress and funcResult JPanel dummy = new JPanel(new BorderLayout()); dummy.setBorder(new TitledBorder("Output")); funcResult = new JTextField(); funcResult.setEditable(false); funcResult.setToolTipText("Result of function"); funcProgress = new JProgressBar(); funcProgress.setStringPainted(true); funcProgress.setToolTipText("Progress"); dummy.add(funcProgress, BorderLayout.NORTH); dummy.add(funcResult, BorderLayout.SOUTH); JPanel dummy1 = new JPanel(new BorderLayout()); dummy1.add(controls, BorderLayout.CENTER); dummy1.add(dummy, BorderLayout.SOUTH); info.add(dummy1); getContentPane().add(info); // right column - stacks stacks = new JPanel(new GridLayout(MAX, 1)); stacks.setBorder(new TitledBorder("Function Calls")); stacks.setToolTipText("Function call stack"); stackPanels = new StackPanel[MAX]; JScrollPane sp = new JScrollPane(stacks); stacksScrollBar = sp.getVerticalScrollBar(); stacksScrollBar.setBlockIncrement(25); // some default stacksScrollBar.setUnitIncrement(25); getContentPane().add(sp); // set initial values and show selectFunction(); setSize(HEIGHT, HEIGHT); setResizable(false); show(); } /** * Push this recursive function call into the call stack. The 'input' is the * input value passed to the RecursiveFunction. */ public void push(int input) { // create new stack, make it current and add it stackPanels[step] = new StackPanel(step + 1, input); stackPanels[step].setCurrent(true); if (step > 0) stackPanels[step - 1].setCurrent(false); stacks.add(stackPanels[step]); stacks.revalidate(); // scroll if needed int stackSize = step * stackPanels[0].getHeight(); stacksScrollBar.setValue(Math.max(stackSize - HEIGHT + 100, 0)); // set result and update progress funcResult.setText("In step: " + (step + 1) + ". Input value is: " + input); funcProgress.setValue((int)(50.0 * step / totalSteps)); // pushing is half done only // increase the step value and wait for user to press next or finish step++; waitForNext(); } /** * Pop this recursive function call out of the call stack. The 'result' is the * resultant value that will be returned by the RecursiveFunction. */ public void pop(int result) { // set the result and make the previous stack current stackPanels[step - 1].setResult(result); stackPanels[step - 1].setCurrent(false); if (step > 1) stackPanels[step - 2].setCurrent(true); // scroll if needed int stackSize = step * stackPanels[0].getHeight(); stacksScrollBar.setValue(Math.max(stackSize - HEIGHT + 100, 0)); stacks.repaint(); // set result and update progress funcResult.setText("Done step: " + step + ". Return value is: " + result); // pop means 50% is complete funcProgress.setValue((int)(50.0 + (50.0 * (totalSteps - step) / totalSteps))); // decrease the step value and wait for user to press next or finish step--; waitForNext(); } public void run() { // do simulation try { // get totalSteps from inputValue totalSteps = Integer.parseInt(inputValue.getText()); if (totalSteps > MAX) throw new IllegalArgumentException("Number of steps greater than maximum permissible value of " + MAX); // clear previous output funcResult.setText(""); funcProgress.setValue(0); clearStack(); repaint(); next = false; finish = false; // do recursion int result = recursiveFuncs[comboFunctions.getSelectedIndex()].doRecursion(totalSteps); // update result funcResult.setText("Result is: " + result); funcProgress.setValue(100); } catch (NumberFormatException ex) { String msg = "Invalid Input: " + inputValue.getText(); JOptionPane.showMessageDialog(null, msg, "Error", JOptionPane.ERROR_MESSAGE); } catch (Exception ex) { JOptionPane.showMessageDialog(null, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } finally { // ensure button states are correct even if some exception gets thrown startButton.setEnabled(true); inputValue.setEditable(true); comboFunctions.setEnabled(true); nextButton.setEnabled(false); finishButton.setEnabled(false); } } private void selectFunction() { RecursiveFunction func = recursiveFuncs[comboFunctions.getSelectedIndex()]; funcDescription.setText(func.getDescription()); funcCode.setText(func.getCode()); } private void clearStack() { stacks.removeAll(); step = 0; for (int i = 0; i < MAX; i++) { stackPanels[i] = null; } } private void waitForNext() { // if finish, simulate delay if (finish) { try { Thread.sleep(3000); // 3 sec } catch (InterruptedException ex) { // Some one wants to exit } } // wait for next if (!finish && !next) { nextButton.setEnabled(true); finishButton.setEnabled(true); while (!finish && !next) { try { Thread.sleep(1000); // 1 sec } catch (InterruptedException ex) { // Some one wants to exit } } next = false; } } }