package de.unisiegen.tpml.graphics.components; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.prefs.Preferences; import javax.swing.JComponent; import javax.swing.JMenu; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import de.unisiegen.tpml.core.ProofRule; import de.unisiegen.tpml.core.languages.Language; import de.unisiegen.tpml.core.smallstep.SmallStepProofRule; import de.unisiegen.tpml.graphics.bigstep.BigStepNodeComponent; import de.unisiegen.tpml.graphics.smallstep.SmallStepNodeComponent; /** * this Class manages the Rules-Popup-Menu of the different ProofModels * The user gets the rules he can commit to an expression in different ways. * Ihe behavior is set by the int tomany and MAX. * If there are more than tomany rules, the rules will be grouped by the * language they are coming from. To let the user acces directly to the rules * he used last there will be MAX rules he can click directly. They will appear * at the top of the menu. * * Till now the method is only called by the Bigstepper and the Smallstepper * because of the count of rules. * * @author Michael * */ public class RulesMenu { /** * the menu wich will be returned by the getMEnu-methode */ private JPopupMenu menu = new JPopupMenu(); /** * saves the last used elements MAX elements will be saved */ private ArrayList<MenuRuleItem> lastUsedElements = new ArrayList<MenuRuleItem>(); /** * saves the state of the menu in the preferences to restor at the next start */ private Preferences preferences; /** * saves the state of the menu to be able to revert it. It will be used if the selected * rule throws an exception. By this rules will not be set at the top of the menu if * they are wrong */ private ArrayList <MenuRuleItem> revertMenu = new ArrayList <MenuRuleItem> (); /** * look at revertMenu */ private ArrayList<MenuRuleItem> revertLastUsedElements = new ArrayList <MenuRuleItem>(); /** * show the submenus */ boolean submenus; /** * the constructor inizailisizes the main elements * */ public RulesMenu () { this.revertLastUsedElements = new ArrayList <MenuRuleItem>(); this.revertMenu = new ArrayList <MenuRuleItem> (); this.lastUsedElements = new ArrayList<MenuRuleItem>(); this.menu = new JPopupMenu(); } /** * the default tomany value */ private final static int TOMANY = 15; /** * tomany organisizes the number of elements wicht must be in teh rulesmenu * to let the getMenu methode divide the rules into submenus by setting the * value to Integer.MAXVALUE * until tomany rules are in the menu, no submenus will be created */ private int tomany = TOMANY; /** * MAX is the count of elemnts the menu will show as the last used */ private static final int MAX = 10; /** * this methode crates the popupmenus containing the rules. If there are more than tomany elemnts, submenus for * every language will be crated. * * @param rules an array containing the rules * @param allRules an array containing all rules * @param lang the language: with this information, the menu crator will find out wich submenus are needed * @param tnc the jcomponent from wich the method is called * @param callBy a string "samllstep" or "bigstep" * @param advanced needed to decide wich rules must be included in the menu * @return the menu */ public JPopupMenu getMenu (ProofRule[] rules, ProofRule[] allRules, Language lang, final JComponent tnc, final String callBy, boolean advanced ) { // first of all load the correct preferences if (callBy.equalsIgnoreCase("bigstep")) { this.preferences = Preferences.userNodeForPackage(BigStepNodeComponent.class); } else if (callBy.equalsIgnoreCase("smallstep")) { this.preferences = Preferences.userNodeForPackage(SmallStepNodeComponent.class); } // clear everything... this.revertLastUsedElements = new ArrayList <MenuRuleItem>(); this.revertMenu = new ArrayList <MenuRuleItem> (); this.lastUsedElements = new ArrayList<MenuRuleItem>(); this.menu = new JPopupMenu(); // now we want to have the ability to enable or dissable the subgrouping // till now it is not implemented by prefferenc final JRadioButtonMenuItem test = new JRadioButtonMenuItem ("Submenus"); this.submenus = this.preferences.getBoolean("submenu", false); //TODO till now the submenus are always enabled if (false) //if (!submenus) { test.setSelected(false); // disable the submenus by setting the tomany to Integer.MAx_VALUE this.tomany = Integer.MAX_VALUE; } else { test.setSelected(true); this.tomany = TOMANY; } // TODO the actionlistener ist only for testing enable and disable the subgrouping.. ActionListener al1 = new ActionListener() { public void actionPerformed(ActionEvent e) { //if the Radiobutton is pressed, we will find out, if the Option was set or not if (test.isSelected()) { RulesMenu.this.submenus=true; setTomany(Integer.MAX_VALUE); //RulesMenu.this.getPreferences().put("submenu", "true"); RulesMenu.this.getPreferences().putBoolean("submenu", RulesMenu.this.submenus); } else { RulesMenu.this.submenus=false; setTomany(15); RulesMenu.this.getPreferences().putBoolean("submenu", RulesMenu.this.submenus); //RulesMenu.this.getPreferences().put("submenu", "false"); } } }; test.addActionListener(al1); // TODO test enabling and disabling the submenus... // this.menu.add(test); // if to many rules we will devide in menu and submenus, otherwise there will be only seperators // between the rules coming from the different languages if (rules.length > this.tomany) { if (rules.length > 0) { // first get the lastUsedRules of the preferences (last state of the programm) // get the names from the preferences, compare each with the list of all usable rules, add them // backwards to save the ordering for (int i = MAX - 1; i >= 0; i--) { String name = this.preferences.get("rule" + i, ""); if (name.equalsIgnoreCase("")) { // do nothing if the rule has no name, the rule dose not exist } else { for (ProofRule a : allRules) { if (new MenuRuleItem(a).getText().equalsIgnoreCase(name)) { //add at the beginning of the list to save the order boolean isIn=isIn(name, this.lastUsedElements); if(!isIn) { this.lastUsedElements.add(0, new MenuRuleItem(a)); MenuRuleItem tmp = new MenuRuleItem(a); //the actionlistener ist needed to be able to set the position of a selected //rule ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { //to be able to revert the changes in the menu if the rule throws an exception saveToRevert(); //if the rule is selected it will be moved to the top of the menu moveToTop(((MenuRuleItem) e.getSource()).getText(), MAX); //save this state of the menu to the preferences save(); } }; tmp.addActionListener(al); //inset at the top of the meun (the preferences are walked throu if ( (callBy.equalsIgnoreCase("bigstep")) || (callBy.equalsIgnoreCase("smallstep") && (((SmallStepProofRule)a).isAxiom() || !advanced))) { this.menu.insert(tmp, 0); } } } } } } save(); //saveToRevert(); //build the submenu int group = rules[0].getGroup(); //a seperator ist needed if there are last used elements if (this.lastUsedElements.size() > 0) { this.menu.addSeparator(); } JMenu subMenu; //the hasmap contains the names of the languages connected to the group-number HashMap <Number,String>names = getLanguageNames(lang); subMenu = new JMenu(names.get(new Integer(rules[0].getGroup()))); // evry rule for (final ProofRule r : rules) { if ( (callBy.equalsIgnoreCase("bigstep")) || (callBy.equalsIgnoreCase("smallstep") && (((SmallStepProofRule)r).isAxiom() || !advanced))) { if (r.getGroup() != group) { if (subMenu != null) { this.menu.add(subMenu); } subMenu = new JMenu(names.get(new Integer(r.getGroup()))); } MenuRuleItem tmp = new MenuRuleItem(r); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent e) { //look if the list is full if (RulesMenu.this.getLastUsedElements().size() < MAX) { MenuRuleItem lastUsed = new MenuRuleItem(r); //check if the element is in the list // boolean isIn = false; // for (int i = 0; i < MAX; i++) // { // int schleife = Math.min(MAX, lastUsedElements.size()); // for (int j = 0; j < schleife; j++) // { // if (lastUsedElements.get(j).getText().equals(lastUsed.getText())) // { // isIn = true; // } // } // } boolean isIn = isIn (lastUsed.getText(), RulesMenu.this.getLastUsedElements()); ActionListener al2 = new ActionListener() { public void actionPerformed(ActionEvent ae) { //move to to moveToTop(((MenuRuleItem) ae.getSource()).getText(), MAX); //the action must be called manualy if the element is in a submenu //menuItemActivated((JMenuItem) e.getSource()); if (callBy.equals("bigstep")) { ((BigStepNodeComponent) tnc).handleMenuActivated((JMenuItem) ae.getSource()); } else if (callBy.equals("smallstep")) { ((SmallStepNodeComponent) tnc).handleMenuActivated((JMenuItem) ae.getSource()); } } }; lastUsed.addActionListener(al2); if (!isIn) { saveToRevert(); RulesMenu.this.getMenu().insert(lastUsed, 0); RulesMenu.this.getLastUsedElements().add(0, lastUsed); } //may be we want to move it to top // else // { // menu.remove(lastUsed); // last10Elements.remove(lastUsed); // menu.insert(lastUsed,0); // last10Elements.add(0, lastUsed); // } //save the preferences to be able to reorganize save(); } //if the list is allrady full the last element must be removed else { MenuRuleItem lastUsed = new MenuRuleItem(r); ActionListener alMove = new ActionListener() { public void actionPerformed(ActionEvent evt) { moveToTop(((MenuRuleItem) evt.getSource()).getText(), MAX); //menuItemActivated((JMenuItem) e.getSource()); } }; lastUsed.addActionListener(alMove); boolean isIn = false; for (int i = 0; i < MAX; i++) { //check if it is already in the list int schleife = Math.min(MAX, RulesMenu.this.getLastUsedElements().size()); for (int j = 0; j < schleife; j++) { if (RulesMenu.this.getLastUsedElements().get(j).getText().equals(lastUsed.getText())) { isIn = true; } } } //if it is not in the list it will be added at the top and the last element will be deleted if (!isIn) { saveToRevert(); RulesMenu.this.getLastUsedElements().add(0, lastUsed); RulesMenu.this.getMenu().insert(lastUsed, 0); RulesMenu.this.getLastUsedElements().remove(MAX); RulesMenu.this.getMenu().remove(MAX); } //maybe we want to set it to the top position if it is allrady in the list // else // { // last10Elements.remove(lastUsed); // menu.remove(lastUsed); // last10Elements.add(0, lastUsed ); // menu.insert(lastUsed,0); // } //just save it in the preferences to be able to reorganize save(); } //the action must be called manualy if the element is in a submenu if (callBy.equals("bigstep")) { ((BigStepNodeComponent) tnc).handleMenuActivated((JMenuItem) e.getSource()); } else if (callBy.equals("smallstep")) { ((SmallStepNodeComponent) tnc).handleMenuActivated((JMenuItem) e.getSource()); } } }; tmp.addActionListener(al); subMenu.add(tmp); group = r.getGroup(); } this.menu.add(subMenu); } } saveToRevert(); } //if ther are less than tomany rules ther will be no submenus, only seperators //with this variable you would also be able to disable the submenufunction else { if (rules.length > 0) { int group = rules[0].getGroup(); for (ProofRule r : rules) { if ( (callBy.equalsIgnoreCase("bigstep")) || (callBy.equalsIgnoreCase("smallstep") && (((SmallStepProofRule)r).isAxiom() || !advanced))) { if (r.getGroup() != group) { this.menu.addSeparator(); } this.menu.add(new MenuRuleItem(r)); group = r.getGroup(); } } } } //JPopupMenu menu = new JPopupMenu (); //ProofRule[] rules = this.proofModel.getRules(); //if (rules.length > 0) { // int group = rules[0].getGroup(); // for (ProofRule r : rules) { // if (r.getGroup() != group) { // menu.addSeparator(); // } // menu.add(new MenuRuleItem(r)); // group = r.getGroup(); // } //} this.menu.addSeparator(); this.menu.add (new MenuGuessItem ()); this.menu.add (new MenuGuessTreeItem ()); //menu.add (this.menuTranslateItem = new MenuTranslateItem ()); return this.menu; } /** * @return preferences */ protected Preferences getPreferences() { return this.preferences; } /** * gets the names of the languages connected to the group of all languages including the given * language and every extended one * * @param language * the language of wich the group should start * @return the HashMap containing the LanguageName and the group */ private HashMap<Number, String> getLanguageNames(Language pLanguage) { Language language = pLanguage; HashMap <Number,String> result = new HashMap<Number,String>(); while ( language.getId ( ) > 0 ) { //System.out.println ( language.getId ( ) + " " + language.getName ( ) ) ; result.put(new Integer(language.getId ( )), language.getName ( )); try { language = ( Language ) language.getClass ( ).getSuperclass ( ) .newInstance ( ) ; } catch ( InstantiationException e ) { // Do nothing } catch ( IllegalAccessException e ) { // Do nothing } } try { language = ( Language ) language.getClass ( ).getSuperclass ( ) .newInstance ( ) ; } catch ( InstantiationException e ) { // Do nothing } catch ( IllegalAccessException e ) { // Do nothing } //System.out.println ( language.getId ( ) + " " + language.getName ( ) ) ; result.put(new Integer(language.getId ( )), language.getName ( )); return result; } /** * Moves the first element of the menu to top, corresponding to the label. * only elements from 0 to max will be recognized. * * @param label * the label of the element should be moved * @param max * the index of the last element should be moved. */ void moveToTop(String label, int max) { for (int i = 0; i < max; i++) { try { MenuRuleItem toCompare = (MenuRuleItem) this.menu.getComponent(i); MenuRuleItem tmp2 = this.lastUsedElements.get(i); // compair their names if (toCompare.getText().equals(label)) { // insert at the top this.menu.add(this.menu.getComponent(i), 0); } if (tmp2.getText().equals(label)) { this.lastUsedElements.remove(i); this.lastUsedElements.add(0, tmp2); } } catch (ClassCastException ex) { // should never trough } save(); } } /** * saves the state of the menu (last 10 elements) to the windows regestry * */ public void save() { for (int i = 0; i < this.lastUsedElements.size(); i++) { this.preferences.put("rule" + i, this.lastUsedElements.get(i).getText()); } } /** * saves the state of the menu to be able to revert changes * */ void saveToRevert() { // get the menu into a list // clear revertMenu if (this.revertMenu.size()>0) { this.revertMenu.clear(); } boolean isRuleItem = false; int i = 0; if (this.menu.getComponent(i) instanceof MenuRuleItem) { isRuleItem = true; } while (isRuleItem) { this.revertMenu.add(i, (MenuRuleItem)this.menu.getComponent(i)); i++; if (this.menu.getComponent(i) instanceof MenuRuleItem) { isRuleItem = true; } else { isRuleItem = false; } } //save the lastUsedElements this.revertLastUsedElements.clear(); this.revertLastUsedElements.addAll(this.lastUsedElements); } /** * reverts the changes in the menu * */ public void revertMenu() { //firt remove all entries from menu boolean isRuleItem = false; int i = 0; if (this.menu.getComponent(i) instanceof MenuRuleItem) { isRuleItem = true; } while (isRuleItem) { this.menu.remove(i); if (this.menu.getComponent(i) instanceof MenuRuleItem) { isRuleItem = true; } else { isRuleItem = false; } } //add the entries for (i=0; i<this.revertMenu.size(); i++) { this.menu.insert(this.revertMenu.get(i),i); } //reset the lastUsedElements this.lastUsedElements.clear(); this.lastUsedElements.addAll(this.revertLastUsedElements); } /** * checks if the elment given by its name is in the given arraylist * the elements will be compared by its names * * @param label the name of the item * @param list the list contaning the item or not * @return a boolean */ boolean isIn ( String label, ArrayList list ) { boolean isIn = false; for (int l = 0; l<list.size(); l++) { if (label.equalsIgnoreCase(this.lastUsedElements.get(l).getText())) { isIn=true; } } return isIn; } /** * @return the tomany */ public int getTomany() { return this.tomany; } /** * @param pTomany the tomany to set */ public void setTomany(int pTomany) { this.tomany = pTomany; } /** * @return the lastUsedElements */ public ArrayList<MenuRuleItem> getLastUsedElements() { return this.lastUsedElements; } /** * @return the menu */ public JPopupMenu getMenu() { return this.menu; } }