/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ package org.geogebra.desktop.gui.dialog; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Iterator; import java.util.TreeSet; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.ListCellRenderer; import javax.swing.border.BevelBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.ListDataListener; import org.geogebra.common.euclidian.EuclidianConstants; import org.geogebra.common.kernel.Kernel; import org.geogebra.common.kernel.Macro; import org.geogebra.common.kernel.NameDescriptionComparator; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.main.GeoElementSelectionListener; import org.geogebra.common.util.debug.Log; import org.geogebra.desktop.gui.GuiManagerD; import org.geogebra.desktop.gui.ToolNameIconPanelD; import org.geogebra.desktop.gui.view.algebra.MyComboBoxListener; import org.geogebra.desktop.main.AppD; import org.geogebra.desktop.main.LocalizationD; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Dialog to create a new user defined tool * * @author Markus Hohenwarter * @version 2010-06-14 Last change: Zbynek Konecny */ public class ToolCreationDialogD extends javax.swing.JDialog implements GeoElementSelectionListener { private static final long serialVersionUID = 1L; private final AppD app; private final LocalizationD loc; /** tabs */ protected JTabbedPane tabbedPane; private ToolNameIconPanelD namePanel; private OutputListModel outputList; private InputListModel inputList; private DefaultComboBoxModel cbInputAddList, cbOutputAddList; private Macro newTool; /** * Creates new tool creation dialog, if in macro-editing mode, * * @param app * Aplication to which this dialog belongs */ public ToolCreationDialogD(AppD app) { super(app.getFrame()); this.app = app; this.loc = app.getLocalization(); initLists(); initGUI(); Macro appMacro = app.getMacro(); if (appMacro != null) { this.setFromMacro(appMacro); } } @Override public void setVisible(boolean flag) { super.setVisible(flag); if (flag) { // add all currently selected geos to output list ArrayList<GeoElement> selGeos = app.getSelectionManager() .getSelectedGeos(); for (int i = 0; i < selGeos.size(); i++) { GeoElement geo = selGeos.get(i); outputList.addElement(geo); } app.setMoveMode(); app.setSelectionListenerMode(this); } else { app.setSelectionListenerMode(null); } } private static class OutputListModel extends DefaultListModel { private static final long serialVersionUID = 1L; private DefaultComboBoxModel cbOutputAddList; public OutputListModel(DefaultComboBoxModel cbOutputAddList) { this.cbOutputAddList = cbOutputAddList; } @Override public void addElement(Object ob) { if (!(ob instanceof GeoElement)) { return; } GeoElement geo = (GeoElement) ob; if (geo.isIndependent() || contains(geo)) { return; } // add geo to list super.addElement(geo); // remove listener from output add combobox before removing geo JComboBox cbListener = removeListeningJComboBox(cbOutputAddList); cbOutputAddList.removeElement(geo); cbOutputAddList.addListDataListener(cbListener); /* * // special case for polygon: add all visible points and segments * too if (geo.isGeoPolygon()) { GeoPolygon poly = (GeoPolygon) geo; * GeoPoint [] points = poly.getPoints(); for (int i=0; i < * points.length; i++) { if (points[i].isVisible()) * addElement(points[i]); } GeoSegment [] segments = * poly.getSegments(); for (int i=0; i < segments.length; i++) { if * (segments[i].isVisible()) addElement(segments[i]); } } */ } } private static class InputListModel extends DefaultListModel { private static final long serialVersionUID = 1L; private DefaultComboBoxModel cbInputAddList; public InputListModel(DefaultComboBoxModel cbInputAddList) { this.cbInputAddList = cbInputAddList; } @Override public void addElement(Object ob) { if (!(ob instanceof GeoElement)) { return; } GeoElement geo = (GeoElement) ob; if (!possibleInput(geo) || contains(geo)) { return; } // add geo to list super.addElement(geo); // remove listener from input add combobox before removing geo JComboBox cbListener = removeListeningJComboBox( this.cbInputAddList); this.cbInputAddList.removeElement(geo); this.cbInputAddList.addListDataListener(cbListener); } } private boolean createTool() { // get input and output objects GeoElement[] input = toGeoElements(inputList); GeoElement[] output = toGeoElements(outputList); // try to create macro Kernel kernel = app.getKernel(); try { newTool = new Macro(kernel, "newTool", input, output); return true; } catch (Exception e) { // go back to output tab tabbedPane.setSelectedIndex(1); // show error message app.showError(app.getLocalization().getError("Tool.CreationFailed") + "\n" + e.getMessage()); e.printStackTrace(); newTool = null; return false; } } /** * Last change Zbynek Konecny * * @version 2010-05-26 */ private void finish() { newTool.setCommandName(namePanel.getCommandName()); newTool.setToolName(namePanel.getToolName()); newTool.setToolHelp(namePanel.getToolHelp()); newTool.setShowInToolBar(namePanel.showInToolBar()); newTool.setIconFileName(namePanel.getIconFileName()); AppD appToSave = app; if (app.getMacro() != null) { appToSave = (AppD) app.getMacro().getKernel().getApplication(); } Kernel kernel = appToSave.getKernel(); String cmdName = namePanel.getCommandName(); // check if command name is not used already by another macro if (kernel.getMacro(cmdName) != null) { overwriteMacro(kernel.getMacro(cmdName)); return; } kernel.addMacro(newTool); // make sure new macro command gets into dictionary appToSave.updateCommandDictionary(); // set macro mode if (newTool.isShowInToolBar()) { int mode = kernel.getMacroID(newTool) + EuclidianConstants.MACRO_MODE_ID_OFFSET; ((GuiManagerD) appToSave.getGuiManager()) .addToToolbarDefinition(mode); appToSave.updateToolBar(); appToSave.setMode(mode); } if (app.getMacro() != null) { app.getFrame().setVisible(false); } app.showMessage(loc.getMenu("Tool.CreationSuccess")); // hide and dispose dialog setVisible(false); dispose(); } /** * Overwrites a macro with current value of newTool * * @param macro * macro to overwrite * @author Zbynek Konecny * @version 2010-06-04 */ private void overwriteMacro(Macro macro) { Object[] options = { loc.getMenu("Tool.Replace"), loc.getMenu("Tool.DontReplace") }; int returnVal = JOptionPane.showOptionDialog(this, app.getLocalization().getPlain("Tool.ReplaceQuestion", macro.getToolName()), loc.getMenu("Question"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[1]); if (returnVal == 1) { return; } Kernel kernel = macro.getKernel(); AppD appToSave = (AppD) kernel.getApplication(); boolean compatible = newTool.getNeededTypesString() .equals(macro.getNeededTypesString()); for (int i = 0; compatible && i < macro.getMacroOutput().length; i++) { compatible = compatible && macro.getMacroOutput()[i].getClass() .equals(newTool.getMacroOutput()[i].getClass()); } if (compatible) { StringBuilder sb = new StringBuilder(); newTool.getXML(sb); if (app.getMacro() != null) { kernel.removeMacro(app.getMacro()); } else { kernel.removeMacro(macro); } if (appToSave.addMacroXML(sb.toString())) { // successfully saved, quitting appToSave.setXML(appToSave.getXML(), true); if (app.getMacro() != null) { app.setSaved(); app.exit(); } else { setVisible(false); dispose(); } } } else { Log.debug("not compatible"); JOptionPane.showMessageDialog(this, app.getLocalization().getPlain("Tool.NotCompatible") + ":\n" + macro.toString()); } } /** * Updates the list of input objects by using the specified output objects. */ private void updateInputList() { // only change empty input list if (inputList.size() > 0) { return; } // get output objects GeoElement[] output = toGeoElements(outputList); // determine all free parents of output TreeSet<GeoElement> freeParents = new TreeSet<GeoElement>(); for (int i = 0; i < output.length; i++) { output[i].addPredecessorsToSet(freeParents, true); } // fill input list with labeled free parents Iterator<GeoElement> it = freeParents.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (geo.isVisibleInputForMacro()) { inputList.addElement(geo); } } } private static JComboBox removeListeningJComboBox( DefaultComboBoxModel cbModel) { // we need to remove the JComboBox as listener from the cbInputAddList // temporarily to avoid multiple additions to inputList ListDataListener[] listeners = cbModel.getListDataListeners(); JComboBox cbListener = null; for (int i = 0; i < listeners.length; i++) { if (listeners[i] instanceof JComboBox) { cbListener = (JComboBox) listeners[i]; break; } } cbModel.removeListDataListener(cbListener); return cbListener; } private static GeoElement[] toGeoElements(DefaultListModel listModel) { // get output objects int size = listModel.size(); GeoElement[] geos = new GeoElement[size]; for (int i = 0; i < size; i++) { geos[i] = (GeoElement) listModel.get(i); } return geos; } private void initLists() { // input and output objects combobox cbOutputAddList = new DefaultComboBoxModel(); cbInputAddList = new DefaultComboBoxModel() { private static final long serialVersionUID = 1L; @Override public void removeElement(Object geo) { super.removeElement(geo); // remove every input from outputList too outputList.removeElement(geo); } }; // lists for input and output objects inputList = new InputListModel(cbInputAddList); outputList = new OutputListModel(cbOutputAddList); TreeSet<GeoElement> sortedSet = app.getKernel().getConstruction() .getGeoSetNameDescriptionOrder(); // lists for combo boxes to select input and output objects // fill combobox models cbInputAddList.addElement(null); cbOutputAddList.addElement(null); Iterator<GeoElement> it = sortedSet.iterator(); while (it.hasNext()) { GeoElement geo = it.next(); if (possibleInput(geo)) { cbInputAddList.addElement(geo); } if (!geo.isIndependent()) { cbOutputAddList.addElement(geo); } } } /** * Returns whether geo can be used as an input object. * * @param geo * @return */ private static boolean possibleInput(GeoElement geo) { return geo.hasChildren(); } private void initGUI() { try { setTitle(loc.getMenu("Tool.CreateNew")); BorderLayout thisLayout = new BorderLayout(); getContentPane().setLayout(thisLayout); // Tabbed Pane tabbedPane = new JTabbedPane(); getContentPane().add(tabbedPane, BorderLayout.CENTER); tabbedPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // Button panel JPanel navPanel = createNavigationPanel(); getContentPane().add(navPanel, BorderLayout.SOUTH); // output and input panel JPanel outputPanel = createInputOutputPanel(loc, outputList, cbOutputAddList, true, false, null); JPanel inputPanel = createInputOutputPanel(loc, inputList, cbInputAddList, true, false, null); tabbedPane.addTab(loc.getMenu("OutputObjects"), null, outputPanel, null); tabbedPane.addTab(loc.getMenu("InputObjects"), null, inputPanel, null); // name & icon namePanel = new ToolNameIconPanelD(app, false); tabbedPane.addTab(loc.getMenu("NameIcon"), null, namePanel, null); app.setComponentOrientation(this); setResizable(true); pack(); // center setLocationRelativeTo(app.getFrame()); } catch (Exception e) { e.printStackTrace(); } } /** * Sets the tabs according to a macro. * * @param macro * whose parameters should be copied to the inputs. * @author Zbynek Konecny * @version 2010-06-14 */ public void setFromMacro(Macro macro) { namePanel.setFromMacro(macro); namePanel.setToolHelp(macro.getToolHelp()); namePanel.setToolName(macro.getToolName()); namePanel.setIconFileName(macro.getIconFileName()); for (int i = 0; i < macro.getMacroInput().length; i++) { GeoElement el = app.getKernel().lookupLabel(macro.getMacroInput()[i] .getLabel(StringTemplate.defaultTemplate)); if (el != null) { this.inputList.add(0, el); } } for (int i = 0; i < macro.getMacroOutput().length; i++) { GeoElement el = app.getKernel() .lookupLabel(macro.getMacroOutput()[i] .getLabel(StringTemplate.defaultTemplate)); if (el != null) { this.outputList.add(0, el); } } } private JPanel createNavigationPanel() { JPanel btPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); btPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 3, 5)); final JButton btBack = new JButton(); btPanel.add(btBack); btBack.setText("< " + loc.getMenu("Back")); final JButton btNext = new JButton(); btPanel.add(btNext); btNext.setText(loc.getMenu("Next") + " >"); final JButton btCancel = new JButton(); btPanel.add(Box.createRigidArea(new Dimension(10, 0))); btPanel.add(btCancel); btCancel.setText(loc.getMenu("Cancel")); ActionListener ac = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Object src = e.getSource(); if (src == btNext) { int index = tabbedPane.getSelectedIndex() + 1; if (index == tabbedPane.getTabCount()) { finish(); } else { tabbedPane.setSelectedIndex(index); } } else if (src == btBack) { int index = tabbedPane.getSelectedIndex() - 1; tabbedPane.setSelectedIndex(index); } else if (src == btCancel) { setVisible(false); } } }; btCancel.addActionListener(ac); btNext.addActionListener(ac); btBack.addActionListener(ac); ChangeListener cl = new ChangeListener() { @Override @SuppressFBWarnings({ "SF_SWITCH_FALLTHROUGH", "missing break is deliberate" }) public void stateChanged(ChangeEvent e) { int tab = tabbedPane.getSelectedIndex(); btBack.setEnabled(tab > 0); switch (tab) { case 1: // input objects updateInputList(); // fall through case 0: // output objects btNext.setText(loc.getMenu("Next") + " >"); btNext.setEnabled(true); break; case 2: // name panel (finish) if (createTool()) { btNext.setText(loc.getMenu("Finish")); btNext.setEnabled( inputList.size() > 0 && outputList.size() > 0); namePanel.requestFocus(); } break; default: } } }; tabbedPane.addChangeListener(cl); return btPanel; } /** * Creates a panel with a list to choose input/output objects of the new * tool. * * @param showUpDownButtons * true if up and down butons should appear on the right * @param cbModel * Combobox model with items than can be added to list (not * displayed if null) * @param loc * Application this dialog belongs to * @param listModel * list model containing the input/output GeoElements * @return Panel with the list, buttons and comboBox */ public static JPanel createInputOutputPanel(LocalizationD loc, final DefaultListModel listModel, final DefaultComboBoxModel cbModel, boolean showUpDownButtons, boolean allowMultiple, ActionListener listener) { JPanel panel = new JPanel(new BorderLayout(5, 5)); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // NORTH: text JLabel labelAddOutput = new JLabel(loc.getMenu("Tool.SelectObjects")); panel.add(labelAddOutput, BorderLayout.NORTH); // CENTER: combobox, list and some buttons on the right // combobox to add geos final JComboBox cbAdd = new JComboBox(cbModel); // listener for the combobox MyComboBoxListener ac = new MyComboBoxListener() { @Override public void doActionPerformed(Object source) { GeoElement geo = (GeoElement) cbAdd.getSelectedItem(); if (geo == null) { return; } listModel.addElement(geo); cbAdd.removeActionListener(this); cbAdd.setSelectedItem(null); cbAdd.addActionListener(this); } }; cbAdd.addActionListener(ac); if (listener != null) { cbAdd.addActionListener(listener); } cbAdd.addMouseListener(ac); // list to show selected geos JList list = new JList(listModel); panel.add( createListUpDownRemovePanel(loc, list, cbAdd, true, showUpDownButtons, allowMultiple, listener), BorderLayout.CENTER); // renderer to show long description of geos in list and combobox MyCellRenderer rend = new MyCellRenderer(); list.setCellRenderer(rend); cbAdd.setRenderer(rend); return panel; } /** * Creates a panel with a list on the left and buttons (up, down, remove) on * the right. If the combobox is not null it is added on top of the list. * * @param loc * Application this dialog belongs to * @param list * @param showRemoveButton * true if remove buton should appear on the right * @param showUpDownButtons * true if up and down butons should appear on the right * @param cbAdd * Combobox with items than can be added to list (not displayed * if null) * @return Panel with the list, buttons and comboBox */ public static JPanel createListUpDownRemovePanel(LocalizationD loc, final JList list, final JComboBox cbAdd, boolean showRemoveButton, boolean showUpDownButtons, final boolean allowMultiple, ActionListener listener) { JPanel centerPanel = new JPanel(new BorderLayout(5, 5)); JPanel listPanel = new JPanel(new BorderLayout(5, 3)); JScrollPane scrollPane = new JScrollPane(list); scrollPane.setBorder( BorderFactory.createBevelBorder(BevelBorder.LOWERED)); if (cbAdd != null) { listPanel.add(cbAdd, BorderLayout.NORTH); } listPanel.add(scrollPane, BorderLayout.CENTER); centerPanel.add(listPanel, BorderLayout.CENTER); // buttons on the right JPanel outputButtonPanel = new JPanel(); BoxLayout outputButtonPanelLayout = new BoxLayout(outputButtonPanel, BoxLayout.Y_AXIS); outputButtonPanel.setLayout(outputButtonPanelLayout); final JButton btUp = new JButton("\u25b2"); btUp.setVisible(showUpDownButtons); btUp.setToolTipText(loc.getPlainTooltip("Up")); final JButton btDown = new JButton("\u25bc"); btDown.setVisible(showUpDownButtons); btDown.setToolTipText(loc.getPlainTooltip("Down")); if (cbAdd != null) { outputButtonPanel.add(Box.createRigidArea(new Dimension(0, 30))); } outputButtonPanel.add(btUp); outputButtonPanel.add(Box.createRigidArea(new Dimension(0, 5))); outputButtonPanel.add(btDown); final JButton btRemove = new JButton("\u2718"); btRemove.setVisible(showRemoveButton); btRemove.setToolTipText(loc.getPlainTooltip("Remove")); outputButtonPanel.add(Box.createRigidArea(new Dimension(0, 15))); outputButtonPanel.add(btRemove); centerPanel.add(outputButtonPanel, loc.borderEast()); // listener for buttons ActionListener ac = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Object src = e.getSource(); DefaultListModel listModel = (DefaultListModel) list.getModel(); int[] selIndices = list.getSelectedIndices(); if (src == btUp && selIndices != null) { for (int i = 0; i < selIndices.length; i++) { int index = selIndices[i]; if (index > 0) { Object ob = listModel.get(index); listModel.remove(index); listModel.add(index - 1, ob); selIndices[i] = index - 1; } } list.setSelectedIndices(selIndices); } else if (src == btDown && selIndices != null) { for (int i = selIndices.length - 1; i >= 0; i--) { int index = selIndices[i]; if (index < listModel.size() - 1) { Object ob = listModel.get(index); listModel.remove(index); listModel.add(index + 1, ob); selIndices[i] = index + 1; } } list.setSelectedIndices(selIndices); } else if (src == btRemove && selIndices != null) { NameDescriptionComparator comparator = new NameDescriptionComparator(); for (int i = selIndices.length - 1; i >= 0; i--) { if (cbAdd != null) { DefaultComboBoxModel cbModel = (DefaultComboBoxModel) cbAdd .getModel(); if (!allowMultiple) { // take from list and insert sorted into // add-combobox GeoElement geo = (GeoElement) listModel .elementAt(selIndices[i]); int k = 0; for (; k < cbModel.getSize(); k++) { GeoElement cbGeo = (GeoElement) cbModel .getElementAt(k); if (comparator.compare(geo, cbGeo) <= 0) { break; } } cbModel.insertElementAt(geo, k); } } // remove from list listModel.remove(selIndices[i]); } } } }; btUp.addActionListener(ac); btDown.addActionListener(ac); btRemove.addActionListener(ac); if (listener != null) { btRemove.addActionListener(listener); btUp.addActionListener(listener); btDown.addActionListener(listener); DefaultListModel listModel = (DefaultListModel) list.getModel(); listModel.addListDataListener((ListDataListener) listener); } return centerPanel; } /** * Adds selected geo to input/output list of dialog. */ @Override public void geoElementSelected(GeoElement geo, boolean addToSelection) { int tab = tabbedPane.getSelectedIndex(); switch (tab) { case 0: // output objects outputList.addElement(geo); break; case 1: // input objects inputList.addElement(geo); break; default: } } public static boolean isMyCellRenderer(ListCellRenderer renderer) { return (renderer instanceof MyCellRenderer); } } class MyCellRenderer extends DefaultListCellRenderer { private static final long serialVersionUID = 1L; /* * This is the only method defined by ListCellRenderer. We just reconfigure * the Jlabel each time we're called. */ @Override public Component getListCellRendererComponent(JList list, Object value, // value // to // display int index, // cell index boolean iss, // is the cell selected boolean chf) // the list and the cell have the focus { /* * The DefaultListCellRenderer class will take care of the JLabels text * property, it's foreground and background colors, and so on. */ super.getListCellRendererComponent(list, value, index, iss, chf); if (value != null) { if (value instanceof String) { setText((String) value); } else if (value instanceof Integer) { setText(value + ""); } else { GeoElement geo = (GeoElement) value; String text = geo.getLongDescriptionHTML(true, true); if (text.length() < 100) { setText(text); } else { setText(geo.getNameDescriptionHTML(true, true)); } } } else { setText(" "); } return this; } }