/*
* JFLAP - Formal Languages and Automata Package
*
*
* Susan H. Rodger
* Computer Science Department
* Duke University
* August 27, 2009
* Copyright (c) 2002-2009
* All rights reserved.
* JFLAP is open source software. Please see the LICENSE for terms.
*
*/
package gui.pumping;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import pumping.*;
/**
* A CasePanel
manages the graphical representation of
* the various {@link pumping.Case}s in a pumping lemma. It is
* associated with a {@link gui.pumping.PumpingLemmaInputPane}and a
* {@link pumping.PumpingLemma}.
*
* @author Jinghui Lim
*
*/
public class CasePanel extends JPanel
{
/**
* The preferred size of the CasePanel
. The width is set
* to the maximum length of the longest description of any case.
*/
private static Dimension PREFERRED_SIZE = new Dimension(350, 480);
/**
* An ArrayList
of String
s each of which
* describes a done case.
*/
private ArrayList myCases;
/**
* Each row of this JTable
displays a case.
*/
private JTable myTable;
/**
* The pumping lemma we are exploring.
*/
private PumpingLemma myLemma;
/**
* The last decomposition chosen. This is used when {@link #addCase()}
* is called.
*/
private int[] tempDecomposition;
/**
* The last i used.
*/
private int tempI;
/**
* A text area that displays any relevant information.
*/
private JTextArea myMessage;
/**
* The PumpingLemmaInputPane
that is the "parent" of this
* CasePanel
. This is used when
* {@link #setDecomposition(int[])} is called.
*/
private PumpingLemmaInputPane myPane;
/**
* The button that shows all cases.
*/
private JButton myShowAll;
/**
* The button that clears all cases.
*/
private JButton myClearAll;
/**
* The button that clears an individual case.
*/
private JButton myClearCase;
/**
* The button that shows a case in the myPane
.
*/
private JButton myShowCase;
/**
* The button that adds a case.
*/
private JButton myAddCase;
/**
* The button that checks if the user has explored all the cases.
*/
private JButton myDone;
/**
* The button that allows the user to replace a case.
*/
private JButton myReplace;
/**
* Constructs a CasePanel
that is linked to a
* PumpingLemma
and a PumpingLemmaInputPane
.
*
* @param l the pumping lemma we are demonstrating
* @param p the input pane that deals with the user input
*/
public CasePanel(PumpingLemma l, PumpingLemmaInputPane p)
{
myPane = p;
myLemma = l;
myCases = new ArrayList();
setLayout(new BorderLayout());
add(new JLabel("Cases:"), BorderLayout.NORTH);
add(initTable(), BorderLayout.CENTER);
add(initButtons(), BorderLayout.SOUTH);
setPreferredSize(PREFERRED_SIZE);
refresh();
}
/**
* Initializes the JTable
to display the different cases
* and returns the JScrollPane
it is in. Using a
* JScrollPane
allows us to scroll through the cases if
* there are too many.
*
* @return the JComponent
that contains the table
*/
protected JComponent initTable()
{
myTable = new JTable(new AbstractTableModel()
{
public final String[] COLUMN_NAMES = new String[]{"#", "Description"};
public Object getValueAt(int r, int c)
{
if(c == 0)
return Integer.toString(r + 1);
else
return myCases.get(r);
}
public String getColumnName(int c) {return COLUMN_NAMES[c];}
public int getRowCount() {return myCases.size();}
public int getColumnCount() {return COLUMN_NAMES.length;}
public boolean isCellEditable(int r, int c) {return false;}
public void setValueAt(Object value, int r, int c) {}
});
/*
* Only allow selecting one row at the time.
*/
ListSelectionModel m = myTable.getSelectionModel();
m.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
/*
* First column with numbers should not be too wide.
*/
TableColumn c = myTable.getColumnModel().getColumn(0);
c.setMaxWidth(25);
JScrollPane p = new JScrollPane(myTable);
return p;
}
/**
* Initializes all the buttons and returns a JScrollPane
that
* contains the buttons. The JScrollPane
allows the user to
* reach all the buttons even if the window becomes very narrow. It thus
* allows the user to make this part of the JSplitPane
smaller.
*
* @return the JComponent
that contains all the buttons.
*/
protected JComponent initButtons()
{
JPanel q = new JPanel();
q.setLayout(new BoxLayout(q, BoxLayout.Y_AXIS));
myMessage = new JTextArea();
myMessage.setEditable(false);
q.add(myMessage);
JPanel topRow = new JPanel();
JPanel bottomRow = new JPanel();
myAddCase = new JButton("Add");
myAddCase.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
addCase();
}
});
myAddCase.setToolTipText("Add the current case to the list");
myAddCase.setEnabled(false);
topRow.add(myAddCase);
myReplace = new JButton("Replace");
myReplace.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
replaceCase(myTable.getSelectedRow());
}
});
myReplace.setToolTipText("Replace the selected case with the current case");
myReplace.setEnabled(false);
topRow.add(myReplace);
myShowAll = new JButton("List");
myShowAll.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
listAll();
}
});
myShowAll.setEnabled(false);
myShowAll.setToolTipText("List all possible cases");
bottomRow.add(myShowAll);
myShowCase = new JButton("Show");
myShowCase.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
showCase(myTable.getSelectedRow());
}
});
myShowCase.setEnabled(false);
myShowCase.setToolTipText("Display the selected case");
topRow.add(myShowCase);
myClearCase = new JButton("Delete");
myClearCase.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
clearCase(myTable.getSelectedRow());
}
});
myClearCase.setEnabled(false);
myClearCase.setToolTipText("Delete the selected case");
topRow.add(myClearCase);
myClearAll = new JButton("Clear");
myClearAll.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
clearAll();
}
});
myClearAll.setToolTipText("Clear all cases");
bottomRow.add(myClearAll);
myDone = new JButton("Done?");
myDone.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
int numLeft = myLemma.numCasesTotal() - myCases.size();
if(numLeft == 1)
myMessage.setText("1 case left.");
else if(numLeft > 1)
myMessage.setText(numLeft + " cases left.");
else
myMessage.setText("All cases done.");
}
});
myDone.setToolTipText("Check if all cases are done");
bottomRow.add(myDone);
/*
* Enables and disables myShowCase and myClearCase depending on
* whether there is a selection.
*/
myTable.getSelectionModel().addListSelectionListener(new ListSelectionListener()
{
public void valueChanged(ListSelectionEvent e)
{
if(e.getValueIsAdjusting())
return;
ListSelectionModel lsm = (ListSelectionModel)e.getSource();
if(lsm.isSelectionEmpty())
{
myShowCase.setEnabled(false);
myClearCase.setEnabled(false);
myReplace.setEnabled(false);
}
else
{
myShowCase.setEnabled(true);
myClearCase.setEnabled(true);
myReplace.setEnabled(myAddCase.isEnabled());
}
}
});
q.add(topRow);
q.add(bottomRow);
JScrollPane r = new JScrollPane(q);
return r;
}
/**
* Shows all the cases.
*
*/
protected void listAll()
{
if(myCases.size() == myLemma.numCasesTotal())
myMessage.setText("All cases for m = " + myLemma.getM() + " are already shown.");
else
{
myLemma.doAll();
myMessage.setText("All cases for m = " + myLemma.getM() + " shown.");
refresh();
}
}
/**
* Clears all the cases that are done, restarting the whole process.
*
*/
public void clearAll()
{
myLemma.clearDoneCases();
myMessage.setText("All cases cleared.");
refresh();
}
/**
* Clears an individual case whose location is index i
.
*
* @param i the position of the case we wish to clear
*/
protected void clearCase(int i)
{
try
{
myLemma.clearCase(i);
myCases.remove(i);
myMessage.setText("Case #" + (i + 1) + " deleted.");
refresh();
}
catch(ArrayIndexOutOfBoundsException e)
{
/*
* This should not happen because the button should be disabled
* if there is no selection.
*/
myMessage.setText("Please select a case to clear.");
}
}
/**
* Displays the decomposition of the case selected in the
* PumpingLemmaInputPane
.
*
* @param i the case we wish to display
* @see gui.pumping.PumpingLemmaInputPane#setDecomposition(int[])
*/
protected void showCase(int i)
{
try
{
Case c = myLemma.getCase(i);
if(c.getI() != -1)
{
myPane.setDecomposition(c.getInput(), c.getI());
myPane.displayIEnd();
myPane.setVisibilityStages(4, true);
myPane.setCanvas();
setAddReplaceButtonsEnabled(true);
}
else
{
myPane.setDecomposition(c.getInput());
myPane.setVisibilityStages(4, false);
setAddReplaceButtonsEnabled(false);
}
refresh();
/*
* Highlight (select) the case shown.
*/
myTable.getSelectionModel().setSelectionInterval(i, i);
myMessage.setText("Showing case #" + (i + 1) + ".");
}
catch(ArrayIndexOutOfBoundsException e)
{
/*
* This should not happen because the button should be disabled
* if there is no selection.
*/
myMessage.setText("Please select a case to show.");
}
}
/**
* "Remembers" the most recent decomposition so that we can add it
* at a later time.
*
* @param decomposition the decomposition we wish to remember
* @see #addCase()
*/
protected void setDecomposition(int[] decomposition)
{
tempDecomposition = decomposition;
}
/**
* "Remembers" the most recent i so that we can use it at
* a later time.
*
* @param i the i we wish to remember
* @see #addCase()
*/
protected void setI(int i)
{
tempI = i;
}
/**
* Adds the most recent decomposition and i.
*
* @see #setDecomposition(int[])
* @see #setI(int)
* @see #addCase(int[], int)
*/
protected void addCase()
{
addCase(tempDecomposition, tempI);
}
/**
* Replaces a case.
*
* @param index the position of the case to replace
*/
protected void replaceCase(int index)
{
if(myLemma.replaceCase(tempDecomposition, tempI, index))
myMessage.setText("Case #" + (index + 1) + " replaced.");
else
myMessage.setText("Wrong case selected.");
}
/**
* Adds the decomposition to the "done" cases of the pumping lemma
* we are working with.
*
* @param decomposition the decomposition we wish to add
* @param i the i corresponding to the decomposition
* @see pumping.ContextFreePumpingLemma#addCase(int[], int)
*/
protected void addCase(int[] decomposition, int i)
{
int ret = myLemma.addCase(decomposition, i);
/*
* refresh() must be called before the rest of the code so we will
* update myCases and myCases.size() etc.
*/
refresh();
if(ret == -1)
{
/*
* "Illegal" decomposition. This should not happen as the input pane
* checks if the decomposition is legal.
*/
myMessage.setText("Illegal decomposition!");
return;
}
else if(ret >= myCases.size())
{
/*
* New case.
*/
ret = myCases.size() - 1;
myMessage.setText("Case added.");
}
else
{
/*
* Case that has already been done.
*/
myMessage.setText("This case is similar to #" + (ret + 1) + ".");
}
/*
* Highlight (select) the last case added or the case that it is
* similar to.
*/
myTable.getSelectionModel().setSelectionInterval(ret, ret);
}
/**
* Refreshes the panel.
*
*/
public void refresh()
{
myCases = myLemma.getDoneDescriptions();
/*
* Let the table know that data has changed.
*/
((AbstractTableModel)myTable.getModel()).fireTableDataChanged();
if(myCases.size() == 0)
{
myDone.setEnabled(false);
myClearAll.setEnabled(false);
}
else
{
myDone.setEnabled(true);
myClearAll.setEnabled(true);
}
repaint();
}
/**
* Set the ability to show all cases. Not enabled for ComputerFirstPane
* instances because preset list values could conflict with the user input w value.
*
* @param b the value we wish to set it to
*/
public void setListButtonEnabled(boolean b)
{
myShowAll.setEnabled(b);
}
/**
* Set the ability to add a case or replace an old one.
*
* @param b the value we wish to set it to
*/
public void setAddReplaceButtonsEnabled(boolean b)
{
myAddCase.setEnabled(b);
if (myShowCase.isEnabled())
myReplace.setEnabled(b);
}
/**
* Set the message displayed.
*
* @param message the message we wish to display
*/
protected void setMessage(String message)
{
myMessage.setText(message);
}
}