/** * */ package cz.cuni.mff.peckam.java.origamist.gui.editor; import java.applet.AppletContext; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.MissingResourceException; import java.util.ResourceBundle; import java.util.concurrent.Callable; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListCellRenderer; import javax.swing.FocusManager; import javax.swing.ImageIcon; import javax.swing.JApplet; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JSpinner; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.SpinnerNumberModel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.origamist.BackgroundImageSupport; import javax.swing.origamist.BackgroundImageSupport.BackgroundRepeat; import javax.swing.origamist.BoundButtonGroup; import javax.swing.origamist.JDropDownButton; import javax.swing.origamist.JLocalizedLabel; import javax.swing.origamist.JStatusBar; import javax.swing.origamist.JToggleMenuItem; import javax.swing.origamist.JToolBarWithBgImage; import javax.swing.origamist.MessageBar; import javax.swing.origamist.OrigamistToolBar; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeModel; import org.apache.log4j.Level; import org.apache.log4j.Logger; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.CellConstraints; import com.jgoodies.forms.layout.FormLayout; import cz.cuni.mff.peckam.java.origamist.common.LangString; import cz.cuni.mff.peckam.java.origamist.configuration.Configuration; import cz.cuni.mff.peckam.java.origamist.exceptions.InvalidOperationException; import cz.cuni.mff.peckam.java.origamist.exceptions.PaperStructureException; import cz.cuni.mff.peckam.java.origamist.exceptions.UnsupportedDataFormatException; import cz.cuni.mff.peckam.java.origamist.gui.common.CommonGui; import cz.cuni.mff.peckam.java.origamist.gui.common.JEditableSlider; import cz.cuni.mff.peckam.java.origamist.gui.common.JLangStringListTextField; import cz.cuni.mff.peckam.java.origamist.gui.common.JZoomSlider; import cz.cuni.mff.peckam.java.origamist.gui.common.Java3DBootstrappingApplet; import cz.cuni.mff.peckam.java.origamist.model.ObjectFactory; import cz.cuni.mff.peckam.java.origamist.model.Operation; import cz.cuni.mff.peckam.java.origamist.model.OperationContainer; import cz.cuni.mff.peckam.java.origamist.model.OperationsHelper; import cz.cuni.mff.peckam.java.origamist.model.Origami; import cz.cuni.mff.peckam.java.origamist.model.RepeatOperation; import cz.cuni.mff.peckam.java.origamist.model.Step; import cz.cuni.mff.peckam.java.origamist.model.jaxb.Model; import cz.cuni.mff.peckam.java.origamist.model.jaxb.Operations; import cz.cuni.mff.peckam.java.origamist.model.jaxb.Steps; import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.EditorDataReceiver; import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.OperationArgument; import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.OperationsTreeDataReceiver; import cz.cuni.mff.peckam.java.origamist.modelstate.arguments.UserInputDataReceiver; import cz.cuni.mff.peckam.java.origamist.services.ServiceLocator; import cz.cuni.mff.peckam.java.origamist.services.StepThumbnailGenerator; import cz.cuni.mff.peckam.java.origamist.services.TooltipFactory; import cz.cuni.mff.peckam.java.origamist.services.interfaces.ConfigurationManager; import cz.cuni.mff.peckam.java.origamist.services.interfaces.OrigamiHandler; import cz.cuni.mff.peckam.java.origamist.utils.ExportFormat; import cz.cuni.mff.peckam.java.origamist.utils.ObservableList.ChangeTypes; import cz.cuni.mff.peckam.java.origamist.utils.ObservablePropertyEvent; import cz.cuni.mff.peckam.java.origamist.utils.ObservablePropertyListener; import cz.cuni.mff.peckam.java.origamist.utils.ParametrizedCallable; import cz.cuni.mff.peckam.java.origamist.utils.ParametrizedLocalizedString; /** * The editor of the origami model. <br /> * <br /> * Properties handled by this class (events are fired when they change - but not at startup): * <ul> * </ul> * * This applet is intended to be started using a bootstrapper that will add support for Java3D without the need to * install it on the client computer. * * @author Martin Pecka */ public class OrigamiEditor extends CommonGui { private static final long serialVersionUID = -6853141518719373854L; /** The resource bundle with editor strings. */ protected ResourceBundle editorMessages; /** The bootstrapper that has started this applet, or <code>null</code>, if it has not been bootstrapped. */ protected JApplet bootstrap = null; /** The currently displayed origami. May be <code>null</code>. */ protected Origami origami = null; /** The currently displayed step. */ protected Step step = null; /** The currently selected operation. */ protected Operation currentOperation = null; /** The currently processed argument of operation. */ protected OperationArgument currentOperationArgument = null; /** Reflects whether alternative action buttons are shown. */ protected boolean alternativeActionsShown = false; /** The main application toolbar. */ protected JToolBarWithBgImage toolbar = null; /** The dropdown button for saving the model. */ protected JDropDownButton saveButton = null; /** The button for displaying model properties. */ protected JButton propertiesButton = null; /** The button group holding all operation buttons. */ protected BoundButtonGroup operationGroup = null; /** Toolbar buttons for model operations. */ protected JToggleButton operationMountainFold, operationValleyFold, operationMountainFoldUnfold, operationValleyFoldUnfold, operationThunderboltFoldMountainFirst, operationThunderboltFoldValleyFirst, operationTurnOver, operationRotate, operationPull, operationCrimpFoldInside, operationCrimpFoldOutside, operationOpen, operationReverseFoldInside, operationReverseFoldOutside, operationRepeatAction, operationSymmetry, operationMark, operationImage; /** Toolbar buttons for model operations. */ protected JToggleMenuItem operationRabbitFold, operationSquashFold; /** The table of action alternatives. The key is the primary action and the value is its alternative. */ protected Hashtable<JComponent, JComponent> alternativeActions = new Hashtable<JComponent, JComponent>(); /** The panel with step tools. */ protected JPanel leftPanel; /** The component used to render and edit the step. */ protected StepEditor stepEditor; /** The status bar. */ protected JStatusBar statusBar = null; /** The slider for zoom. */ protected JEditableSlider zoomSlider; /** Toolbar button. */ protected JButton addStep, nextStep, prevStep, removeStep, undoRemoveStep, cancelLastOperation, undoCancelOperation; /** List of steps that can be undone. */ protected List<Step> stepsToUndo = new LinkedList<Step>(); /** List of operations that can be undone. */ protected List<Operation> operationsToUndo = new LinkedList<Operation>(); /** The steps the removed operations belong to. */ protected Map<Operation, Step> undoneOperationsSteps = new HashMap<Operation, Step>(); /** The string displaying the current position in the list of steps. */ protected ParametrizedLocalizedString stepXofY; /** Observer for the number of steps. */ protected ObservablePropertyListener<Step> stepsObserver; /** Observer for the number of steps. */ protected ObservablePropertyListener<Operation> operationsObserver; /** The list of operations defined for the current step. */ protected OperationsTree operationsTree; /** The description of the step. */ protected JLangStringListTextField<JTextField> description; /** Span of the step. */ protected JSpinner colSpan, rowSpan; /** The combobox for selecting the current step. */ protected JComboBox steps; /** Key for step editor's message bar. */ protected static final String OPERATION_HINT_KEY = "operation.hint"; /** * Instantiate the origami viewer without a bootstrapper. */ public OrigamiEditor() { this(null); } /** * Instanitate the origami viewer with the given bootstrapper. * * @param bootstrap The bootstrapper that starts this applet. */ public OrigamiEditor(JApplet bootstrap) { super(); this.bootstrap = bootstrap; UIManager.getDefaults().addResourceBundle("editor"); addGlobalKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ALT) { showAlternativeActions(!alternativeActionsShown); e.consume(); } else if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_SPACE) { if (currentOperation != null) { proceedToNextOperationArgument(); e.consume(); } } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { if (currentOperation != null) { setCurrentOperation(null); e.consume(); } } } }); } @Override public void init() { stepsObserver = new ObservablePropertyListener<Step>() { @Override public void changePerformed(ObservablePropertyEvent<? extends Step> evt) { stepXofY.setParameter(1, origami != null ? origami.getModel().getSteps().getStep().size() : 0); updateOperationsModel(); getContentPane().repaint(); } }; operationsObserver = new ObservablePropertyListener<Operation>() { @Override public void changePerformed(final ObservablePropertyEvent<? extends Operation> evt) { if (step == null || origami == null) return; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { addStep.setEnabled(step.getOperations().size() > 0); cancelLastOperation.setEnabled(step.getOperations().size() > 0); if (evt.getEvent().getChangeType() != ChangeTypes.ADD) { operationsTree.removeOperation(evt.getEvent().getItem()); } if (evt.getEvent().getChangeType() != ChangeTypes.REMOVE) { operationsTree.addTopLevelOperation(evt.getEvent().getItem()); operationsTree.scrollToOperation(evt.getEvent().getItem()); } operationsTree.repaint(); } }); } }; super.init(); ServiceLocator.get(ConfigurationManager.class).get() .addAndRunResourceBundleListener(new Configuration.ResourceBundleLocaleListener("editor") { @Override public void bundleChanged() { editorMessages = bundle; } }); processParams(); } /** * Process the application parameters. */ protected void processParams() { setOrigami(null); if (getParameter("file") != null) { try { Origami o = ServiceLocator.get(OrigamiHandler.class).loadModel( new URL(getDocumentBase(), getParameter("file")).toURI(), false); setOrigami(o); } catch (UnsupportedDataFormatException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("exception.UnsupportedDataFormatException.loadModel"), appMessages.getString("exception.UnsupportedDataFormatException.loadModel.title"), JOptionPane.ERROR_MESSAGE); Logger.getLogger("application").error(e1); } catch (IOException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("exception.IOException.loadModel"), appMessages.getString("exception.IOException.loadModel.title"), JOptionPane.ERROR_MESSAGE); Logger.getLogger("application").error(e1); } catch (URISyntaxException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("exception.IOException.loadModel"), appMessages.getString("exception.IOException.loadModel.title"), JOptionPane.ERROR_MESSAGE); Logger.getLogger("application").error(e1); } } } /** * Create and setup all the form components. */ @Override protected void createComponents() { toolbar = createToolbar(); leftPanel = createLeftPanel(); stepEditor = new StepEditor(); stepEditor.addPropertyChangeListener("zoom", new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { zoomSlider.setValue((int) (double) ((Double) evt.getNewValue())); } }); description = new JLangStringListTextField<JTextField>(new LinkedList<LangString>(), new JTextField()) { /** */ private static final long serialVersionUID = 3780617567174115127L; @Override protected LinkedHashSet<Locale> getSuggestedLocales() { LinkedHashSet<Locale> result = super.getSuggestedLocales(); // TODO may be slow for large and well-localized models; if the tests confirm this, create a Locale // cache somewhere in Origami or so if (origami != null) { for (Step s : origami.getModel().getSteps().getStep()) { for (LangString ls : s.getDescription()) { result.add(ls.getLang()); } } } return result; } }; statusBar = new JStatusBar(); statusBar.showMessage(" "); getTopmostComponent().setMinimumSize(new Dimension(750, 500)); } /** * Setup the form layout. */ @Override protected void buildLayout() { setLayout(new FormLayout("left:pref,$ugap,pref:grow", "pref,fill:pref:grow,pref,bottom:pref")); CellConstraints cc = new CellConstraints(); add(toolbar, cc.xyw(1, 1, 3)); add(leftPanel, cc.xywh(1, 2, 1, 2)); add(stepEditor, cc.xy(3, 2)); JPanel descPanel = new JPanel(new FormLayout("pref,$lcgap,pref:grow", "pref")); descPanel.add(new JLocalizedLabel("editor", "description.label"), cc.xy(1, 1)); descPanel.add(description, cc.xy(3, 1)); add(descPanel, cc.xy(3, 3)); add(statusBar, cc.xyw(1, 4, 3)); } /** * Show or hide (depending on the parameter) alternative diagram actions - eg. display a valley fold button instead * of mountain fold button and so on. * * @param show If <code>true</code>, show alternatives, otherwise show the default buttons. */ protected void showAlternativeActions(final boolean show) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { alternativeActionsShown = show; Component focusOwner = FocusManager.getCurrentManager().getFocusOwner(); for (Entry<JComponent, JComponent> e : alternativeActions.entrySet()) { e.getKey().setVisible(!show); e.getValue().setVisible(show); if (e.getKey().equals(focusOwner)) e.getValue().requestFocusInWindow(); if (e.getValue().equals(focusOwner)) e.getKey().requestFocusInWindow(); } } }); } /** * @return The main application toolbar. */ protected JToolBarWithBgImage createToolbar() { JToolBarWithBgImage toolbar = new JToolBarWithBgImage("editor"); toolbar.setFloatable(false); toolbar.setBackground(new Color(231, 231, 184, 230)); toolbar.setBackgroundImage(new BackgroundImageSupport(getClass() .getResource("/resources/images/tooltip-bg.png"), toolbar, 0, 0, BackgroundRepeat.REPEAT_X)); toolbar.add(toolbar.createToolbarButton(new NewFileAction(), "menu.new", "new-32.png")); JDropDownButton open = toolbar.createToolbarDropdownButton(null, "menu.open", "open-32.png"); toolbar.add(open); open.addComponent(toolbar.createToolbarDropdownItem(new OpenFileAction(), "menu.open.file", "open-file-32.png")); open.addComponent(toolbar.createToolbarDropdownItem(new OpenURLAction(), "menu.open.url", "open-url-32.png")); // TODO add the possibility to load a model from a listing toolbar.add(new JToolBar.Separator()); JDropDownButton dropDown = toolbar.createToolbarDropdownButton(null, "menu.save", "save-32.png"); toolbar.add(dropDown); dropDown.addComponent(toolbar.createToolbarDropdownSeparator("menu.separator.editable")); dropDown.addComponent(toolbar.createToolbarDropdownItem(new ExportAction(ExportFormat.XML), "menu.save.asXML", "xml-32.png")); dropDown.addComponent(toolbar.createToolbarDropdownItem(new ExportAction(ExportFormat.SVG), "menu.save.asSVG", "svg-32.png")); dropDown.addComponent(toolbar.createToolbarDropdownSeparator("menu.separator.non-editable")); dropDown.addComponent(toolbar.createToolbarDropdownItem(new ExportAction(ExportFormat.PDF), "menu.save.asPDF", "pdf-32.png")); dropDown.addComponent(toolbar.createToolbarDropdownItem(new ExportAction(ExportFormat.PNG), "menu.save.asPNG", "png-32.png")); dropDown.addComponent(toolbar.createToolbarDropdownSeparator("menu.separator.animation")); dropDown.addComponent(toolbar.createToolbarDropdownItem(new ExportAction(ExportFormat.MOV), "menu.save.asMOV", "mov-32.png")); saveButton = dropDown; toolbar.add(new JToolBar.Separator()); toolbar.add(propertiesButton = toolbar.createToolbarButton(new PropertiesAction(), "menu.properties", "properties-32.png")); toolbar.add(new JToolBar.Separator()); toolbar.add(toolbar.createToolbarButton(new SettingsAction(), "menu.settings", "settings-32.png")); toolbar.add(new JToolBar.Separator()); operationGroup = new BoundButtonGroup(); toolbar.add(operationMountainFold = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.mountain", "folds/mountain-32.png")); operationMountainFold.addActionListener(new OperationActionListener(Operations.MOUNTAIN_FOLD, "editor", "menu.operation.mountain")); toolbar.add(operationValleyFold = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.valley", "folds/valley-32.png")); operationValleyFold.addActionListener(new OperationActionListener(Operations.VALLEY_FOLD, "editor", "menu.operation.valley")); alternativeActions.put(operationMountainFold, operationValleyFold); toolbar.add(operationMountainFoldUnfold = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.mountainFoldUnfold", "folds/mountain-fold-unfold-32.png")); operationMountainFoldUnfold.addActionListener(new OperationActionListener( Operations.MOUNTAIN_VALLEY_FOLD_UNFOLD, "editor", "menu.operation.mountainFoldUnfold")); toolbar.add(operationValleyFoldUnfold = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.valleyFoldUnfold", "folds/valley-fold-unfold-32.png")); operationValleyFoldUnfold.addActionListener(new OperationActionListener(Operations.VALLEY_MOUNTAIN_FOLD_UNFOLD, "editor", "menu.operation.valleyFoldUnfold")); alternativeActions.put(operationMountainFoldUnfold, operationValleyFoldUnfold); toolbar.add(operationThunderboltFoldMountainFirst = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.thunderboltMountainFirst", "folds/thunderbolt-mountain-first-32.png")); operationThunderboltFoldMountainFirst.addActionListener(new OperationActionListener( Operations.THUNDERBOLT_FOLD, "editor", "menu.operation.thunderboltMountainFirst")); // TODO make a difference between this and the following operation toolbar.add(operationThunderboltFoldValleyFirst = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.thunderboltValleyFirst", "folds/thunderbolt-valley-first-32.png")); operationThunderboltFoldValleyFirst.addActionListener(new OperationActionListener(Operations.THUNDERBOLT_FOLD, "editor", "menu.operation.thunderboltValleyFirst")); alternativeActions.put(operationThunderboltFoldMountainFirst, operationThunderboltFoldValleyFirst); toolbar.add(operationTurnOver = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.turnOver", "folds/turn-over-32.png")); operationTurnOver.addActionListener(new OperationActionListener(Operations.TURN_OVER, "editor", "menu.operation.turnOver")); toolbar.add(operationRotate = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.rotate", "folds/rotate-32.png")); operationRotate.addActionListener(new OperationActionListener(Operations.ROTATE, "editor", "menu.operation.rotate")); alternativeActions.put(operationTurnOver, operationRotate); toolbar.add(operationPull = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.pull", "folds/pull-32.png")); operationPull.addActionListener(new OperationActionListener(Operations.PULL, "editor", "menu.operation.pull")); toolbar.add(operationCrimpFoldInside = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.crimpInside", "folds/crimp-inside-32.png")); operationCrimpFoldInside.addActionListener(new OperationActionListener(Operations.INSIDE_CRIMP_FOLD, "editor", "menu.operation.crimpInside")); toolbar.add(operationCrimpFoldOutside = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.crimpOutside", "folds/crimp-outside-32.png")); operationCrimpFoldOutside.addActionListener(new OperationActionListener(Operations.OUTSIDE_CRIMP_FOLD, "editor", "menu.operation.crimpOutside")); alternativeActions.put(operationCrimpFoldInside, operationCrimpFoldOutside); // toolbar.add(operationOpen = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.open", // "folds/open-32.png")); // operationOpen.addActionListener(new OperationActionListener(Operations.OPEN, "editor", // "menu.operation.open")); toolbar.add(operationReverseFoldInside = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.reverseInside", "folds/reverse-inside-32.png")); operationReverseFoldInside.addActionListener(new OperationActionListener(Operations.INSIDE_REVERSE_FOLD, "editor", "menu.operation.reverseInside")); toolbar.add(operationReverseFoldOutside = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.reverseOutside", "folds/reverse-outside-32.png")); operationReverseFoldOutside.addActionListener(new OperationActionListener(Operations.OUTSIDE_REVERSE_FOLD, "editor", "menu.operation.reverseOutside")); alternativeActions.put(operationReverseFoldInside, operationReverseFoldOutside); toolbar.add(operationRepeatAction = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.repeat", "folds/repeat-32.png")); operationRepeatAction.addActionListener(new OperationActionListener(Operations.REPEAT_ACTION, "editor", "menu.operation.repeat")); // operationRabbitFold = toolbar.createToolbarDropdownItem(new JToggleMenuItem(), null, "menu.operation.rabbit", // "folds/rabbit-32.png"); // operationRabbitFold.addActionListener(new OperationActionListener(Operations.RABBIT_FOLD, "editor", // "menu.operation.rabbit")); // // operationSquashFold = toolbar.createToolbarDropdownItem(new JToggleMenuItem(), null, "menu.operation.squash", // "folds/squash-32.png"); // operationSquashFold.addActionListener(new OperationActionListener(Operations.SQUASH_FOLD, "editor", // "menu.operation.squash")); toolbar.add(operationSymmetry = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.symmetry", "folds/symmetry-32.png")); operationSymmetry.addActionListener(new OperationActionListener(Operations.SYMMETRY, "editor", "menu.operation.symmetry")); toolbar.add(operationMark = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.mark", "folds/mark-32.png")); operationMark .addActionListener(new OperationActionListener(Operations.MARKER, "editor", "menu.operation.mark")); toolbar.add(operationImage = toolbar.createToolbarItem(new JToggleButton(), null, "menu.operation.image", "folds/image_op-32.png")); operationImage .addActionListener(new OperationActionListener(Operations.IMAGE, "editor", "menu.operation.image")); operationGroup.add(operationMountainFold); operationGroup.add(operationValleyFold); operationGroup.add(operationMountainFoldUnfold); operationGroup.add(operationValleyFoldUnfold); operationGroup.add(operationThunderboltFoldMountainFirst); operationGroup.add(operationThunderboltFoldValleyFirst); operationGroup.add(operationTurnOver); operationGroup.add(operationRotate); operationGroup.add(operationPull); operationGroup.add(operationCrimpFoldInside); operationGroup.add(operationCrimpFoldOutside); // operationGroup.add(operationOpen); operationGroup.add(operationReverseFoldInside); operationGroup.add(operationReverseFoldOutside); operationGroup.add(operationRepeatAction); operationGroup.add(operationSymmetry); // operationGroup.add(operationRabbitFold); // operationGroup.add(operationSquashFold); operationGroup.add(operationMark); operationGroup.add(operationImage); // JDropDownButton advancedButton = toolbar.createToolbarDropdownButton( // new JDropDownButtonReflectingSelectionGroup(new JButton(), operationGroup), null, // "menu.operation.advanced", "empty-32.png"); // toolbar.add(advancedButton); // advancedButton.addComponent(operationRabbitFold); // advancedButton.addComponent(operationSquashFold); showAlternativeActions(false); return toolbar; } /** * @return The panel with step tools. */ protected JPanel createLeftPanel() { JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.GRAY)); DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("fill:min(pref;100dlu)", ""), panel); stepXofY = new ParametrizedLocalizedString("editor", "stepXofY", 0, 0); steps = new JComboBox(); steps.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setStep((Step) steps.getSelectedItem()); } }); final Font stepsPopupFont = steps.getFont(); Font stepsFont = stepsPopupFont.deriveFont(Font.BOLD, 16f); steps.setFont(stepsFont); steps.setRenderer(new DefaultListCellRenderer() { /** */ private static final long serialVersionUID = -3935099215382824545L; @Override public Component getListCellRendererComponent(javax.swing.JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { JLabel result = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (origami == null) return result; if (index != -1) { result.setText(new Integer(origami.getModel().getSteps().getStep().indexOf(value) + 1).toString()); result.setFont(stepsPopupFont); } else { result.setText(stepXofY.toString()); } return result; } }); builder.append(steps); OrigamistToolBar toolbar = new OrigamistToolBar("editor"); toolbar.setLayout(new FormLayout("$ugap,pref,$rgap,pref,pref,$rgap,pref,$rgap,pref,$ugap", "$ugap,pref,$lgap,pref,$ugap")); toolbar.setFloatable(false); CellConstraints cc = new CellConstraints(); toolbar.add(addStep = toolbar.createToolbarButton(new AddStepAction(), "leftPanel.addStep", "step-add-24.png"), cc.xy(4, 2)); toolbar.add( nextStep = toolbar.createToolbarButton(new NextStepAction(), "leftPanel.nextStep", "step-next-24.png"), cc.xy(5, 2)); toolbar.add( prevStep = toolbar.createToolbarButton(new PrevStepAction(), "leftPanel.prevStep", "step-prev-24.png"), cc.xy(2, 2)); toolbar.add( removeStep = toolbar.createToolbarButton(new RemoveStepAction(), "leftPanel.removeStep", "step-remove-24.png"), cc.xy(7, 2)); toolbar.add( undoRemoveStep = toolbar.createToolbarButton(new RemoveStepUndoAction(), "leftPanel.undoRemoveStep", "step-remove-undo-24.png"), cc.xy(9, 2)); undoRemoveStep.setEnabled(false); toolbar.add( cancelLastOperation = toolbar.createToolbarButton(new CancelOperationAction(), "leftPanel.cancelLastOperation", "lastOperation-cancel-24.png"), cc.xy(2, 4)); toolbar.add( undoCancelOperation = toolbar.createToolbarButton(new CancelOperationUndoAction(), "leftPanel.cancelLastOperationUndo", "lastOperation-cancel-undo-24.png"), cc.xy(9, 4)); undoCancelOperation.setEnabled(false); builder.append(toolbar); builder.append(new JLocalizedLabel("editor", "leftPanel.zoomSlider.label")); builder.appendRelatedComponentsGapRow(); builder.nextLine(2); zoomSlider = new JZoomSlider(); zoomSlider.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (stepEditor != null && !zoomSlider.getSlider().getModel().getValueIsAdjusting()) { stepEditor.setZoom((double) zoomSlider.getValue()); } } }); builder.append(zoomSlider); builder.append(new JLocalizedLabel("editor", "leftPanel.operations.label")); builder.appendRelatedComponentsGapRow(); builder.nextLine(2); operationsTree = new OperationsTree(); operationsTree.setCellRenderer(new OperationListCellRenderer()); operationsTree.setBorder(BorderFactory.createEmptyBorder()); updateOperationsModel(); JScrollPane operationsListScroll = new JScrollPane(operationsTree, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); builder.appendRow("fill:min:grow"); builder.append(operationsListScroll); colSpan = new JSpinner(new SpinnerNumberModel(1, 1, null, 1)); JPanel colSpanPanel = new JPanel(new FormLayout("pref:grow,$lcgap,pref", "pref")); final JLabel colSpanLabel; colSpanPanel.add(colSpanLabel = new JLocalizedLabel("editor", "leftPanel.colSpan.label"), cc.xy(1, 1)); colSpanPanel.add(colSpan, cc.xy(3, 1)); builder.append(colSpanPanel); rowSpan = new JSpinner(new SpinnerNumberModel(1, 1, null, 1)); JPanel rowSpanPanel = new JPanel(new FormLayout("pref:grow,$lcgap,pref", "pref")); final JLabel rowSpanLabel; rowSpanPanel.add(rowSpanLabel = new JLocalizedLabel("editor", "leftPanel.rowSpan.label"), cc.xy(1, 1)); rowSpanPanel.add(rowSpan, cc.xy(3, 1)); builder.append(rowSpanPanel); ServiceLocator .get(ConfigurationManager.class) .get() .addAndRunResourceBundleListener( new Configuration.LocaleListener("editor", "leftPanel.colSpan.tooltip") { @Override protected void updateText(String text) { colSpanLabel.setToolTipText(text); colSpan.setToolTipText(colSpanLabel.getToolTipText()); rowSpanLabel.setToolTipText(bundle.getString("leftPanel.rowSpan.tooltip")); rowSpan.setToolTipText(rowSpanLabel.getToolTipText()); } }); colSpan.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (step != null) { step.setColspan((Integer) colSpan.getValue()); } } }); rowSpan.addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent e) { if (step != null) { step.setRowspan((Integer) rowSpan.getValue()); } } }); return panel; } /** * @return the origami */ public Origami getOrigami() { return origami; } /** * @param origami the origami to set */ public void setOrigami(final Origami origami) { if (this.origami != null) { this.origami.removeObservablePropertyListener(stepsObserver, Origami.MODEL_PROPERTY, Model.STEPS_PROPERTY, Steps.STEP_PROPERTY); } Origami old = this.origami; this.origami = origami; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { saveButton.setEnabled(origami != null); propertiesButton.setEnabled(origami != null); } }); stepEditor.setOrigami(origami); stepXofY.setParameter(1, origami != null ? origami.getModel().getSteps().getStep().size() : 0); setStep(null); if (origami != null) { this.origami.addObservablePropertyListener(stepsObserver, Origami.MODEL_PROPERTY, Model.STEPS_PROPERTY, Steps.STEP_PROPERTY); if (origami.getModel().getSteps().getStep().size() == 0) { // we have just created the origami, so initialize the first step new AddStepAction().actionPerformed(new ActionEvent(this, 0, "")); } setStep(origami.getModel().getSteps().getStep().get(0)); } getContentPane().repaint(); if (old != null) old.unloadModelStates(); } /** * @return the step */ public Step getStep() { return step; } /** * @param step the step to set */ public void setStep(final Step step) { if (step != null && step.getAttachedTo() == null) { return; } if (this.step != null) this.origami.removeObservablePropertyListener(operationsObserver, Origami.MODEL_PROPERTY, Model.STEPS_PROPERTY, Steps.STEP_PROPERTY, Step.OPERATIONS_PROPERTY); ParametrizedCallable<Void, Exception> errorHandler = new ParametrizedCallable<Void, Exception>() { @Override public Void call(final Exception arg) { if (arg != null) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (arg instanceof InvalidOperationException) { JOptionPane.showMessageDialog(OrigamiEditor.this, ((InvalidOperationException) arg).getUserFriendlyMessage(), appMessages.getString("invalid.operation.title"), JOptionPane.ERROR_MESSAGE); } else { JOptionPane.showMessageDialog(OrigamiEditor.this, arg.getMessage(), appMessages.getString("invalid.operation.title"), JOptionPane.ERROR_MESSAGE); } } }); } return null; } }; this.step = step; stepEditor.setStep(step, null, errorHandler); if (stepEditor.getMessageBar() != null) stepEditor.getMessageBar().removeMessage(OPERATION_HINT_KEY); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int index = 0, numSteps = 0, numOperations = 0; if (origami != null) { index = origami.getModel().getSteps().getStep().indexOf(step); index++; numSteps = origami.getModel().getSteps().getStep().size(); } if (step != null) { numOperations = step.getOperations().size(); origami.addObservablePropertyListener(operationsObserver, Origami.MODEL_PROPERTY, Model.STEPS_PROPERTY, Steps.STEP_PROPERTY, Step.OPERATIONS_PROPERTY); description.setStrings(step.getDescription()); colSpan.setValue(step.getColspan() != null ? step.getColspan() : 1); rowSpan.setValue(step.getRowspan() != null ? step.getRowspan() : 1); steps.setEnabled(true); steps.setModel(new DefaultComboBoxModel(origami.getModel().getSteps().getStep().toArray())); } else { description.setStrings(new LinkedList<LangString>()); colSpan.setValue(1); rowSpan.setValue(1); steps.setEnabled(false); } stepXofY.setParameter(0, index); updateOperationsModel(); boolean operationsEnabled = step != null && step.isEditable(); for (Enumeration<AbstractButton> en = operationGroup.getElements(); en.hasMoreElements();) { en.nextElement().setEnabled(operationsEnabled); } if (index == numSteps && index != 0) { addStep.setVisible(true); addStep.setEnabled(numOperations > 0); nextStep.setVisible(false); nextStep.setEnabled(false); removeStep.setEnabled(true); cancelLastOperation.setEnabled(numOperations > 0); } else { addStep.setVisible(false); nextStep.setVisible(true); nextStep.setEnabled(numSteps > 0); removeStep.setEnabled(false); cancelLastOperation.setEnabled(false); } prevStep.setEnabled(index > 1); stepEditor.setEnabled(step != null); zoomSlider.setEnabled(step != null); description.setEnabled(step != null); colSpan.setEnabled(step != null); rowSpan.setEnabled(step != null); getContentPane().repaint(); } }); } /** * Update the operations list to reflect the operations of the current step. */ protected void updateOperationsModel() { final TreeModel model = operationsTree.getModelForOperations(step == null ? null : step.getOperations()); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { operationsTree.setModel(model); } }); } /** * Set the current operation. If the active step isn't editable, nothing happens. * * @param operation The operation to set. */ public void setCurrentOperation(final Operation operation) { if (step == null || !step.isEditable()) return; if (currentOperation != null) { final Operation operationToRemove = currentOperation; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { operationsTree.removeOperation(operationToRemove); operationsTree.repaint(); } }); setCurrentOperationArgument(null); } currentOperation = operation; if (operation != null) { String userTip = currentOperation.getL7dUserTip(null); if (userTip != null) stepEditor.getMessageBar().showMessage(userTip, OPERATION_HINT_KEY); else stepEditor.getMessageBar().removeMessage(OPERATION_HINT_KEY); if (operation.getArguments().size() > 0) setCurrentOperationArgument(operation.getArguments().get(0)); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { operationsTree.addTopLevelOperation(operation); operationsTree.scrollToOperation(operation); operationsTree.repaint(); } }); } stepEditor.clearChosenItems(); } /** * Set the current operation argument. If current operation is null, nothing happens. * * @param argument The argument to set. */ public void setCurrentOperationArgument(OperationArgument argument) { this.currentOperationArgument = argument; stepEditor.setCurrentOperationArgument(argument); if (currentOperation != null && currentOperationArgument != null) { String userTip = currentOperation.getL7dUserTip(currentOperationArgument); if (userTip != null) stepEditor.getMessageBar().showMessage(userTip, OPERATION_HINT_KEY); else stepEditor.getMessageBar().removeMessage(OPERATION_HINT_KEY); } else { stepEditor.getMessageBar().removeMessage(OPERATION_HINT_KEY); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { operationsTree.recomputeHeights(); operationsTree.repaint(); operationsTree.scrollToOperation(currentOperation); } }); operationsTree.repaint(); new Thread(new Runnable() { @Override public void run() { // arguments that just fetch text input from the user can ask for the input right now // trying to proceed to next argument will cause the text input to be shown, and when it is completed, // next // argument is automatically selected if (currentOperationArgument != null && currentOperationArgument instanceof UserInputDataReceiver) proceedToNextOperationArgument(); } }).start(); } /** * If the current operation argument is complete, proceed to next operation argument (or finish the operation if no * more operation arguments are to be finished). If the current argument isn't completed and is required, show a * warning in the statusbar. */ protected void proceedToNextOperationArgument() { if (currentOperationArgument != null) { if (currentOperationArgument instanceof EditorDataReceiver) { ((EditorDataReceiver) currentOperationArgument).readDataFromObject(stepEditor); } else if (currentOperationArgument instanceof OperationsTreeDataReceiver) { ((OperationsTreeDataReceiver) currentOperationArgument).readDataFromObject(operationsTree); } else if (!currentOperationArgument.isComplete()) { if (currentOperationArgument instanceof UserInputDataReceiver) { ((UserInputDataReceiver) currentOperationArgument).askForData(); } } if (currentOperationArgument.isComplete() || !currentOperationArgument.isRequired()) { setCurrentOperationArgument(currentOperationArgument.getNext()); if (currentOperationArgument == null) tryCompleteOperation(); } else { stepEditor.getMessageBar().showMessage( "<html><body><span style=\"font-weight: bold; color: red;\">" + editorMessages.getString("operation.argument.please.complete") + "</span></body></html>", 4000, StepEditingCanvasController.INCOMPLMETE_ARGUMENT_KEY); } } else if (currentOperation != null) { // the operation has no arguments tryCompleteOperation(); } } /** * If the required arguments of the current operation are finished, finish the current operation and set current * operation to <code>null</code>. If the required arguments aren't finished, nothing happens. */ protected void tryCompleteOperation() { if (currentOperation.areRequiredAgrumentsComplete()) { final Operation operation = currentOperation; new Thread(new Runnable() { @Override public void run() { setCurrentOperation(null); operation.fillFromArguments(); if (operation instanceof RepeatOperation) { List<Operation> operations = ((RepeatOperation) operation).getOperations(); step.getOperations().removeAll(operations); } step.getOperations().add(operation); if (!step.isModelStateValid(true)) { try { step.getModelState(true); undoRemoveStep.setEnabled(false); stepsToUndo.clear(); undoCancelOperation.setEnabled(false); operationsToUndo.clear(); undoneOperationsSteps.clear(); } catch (RuntimeException e) { step.getOperations().remove(operation); if (e instanceof InvalidOperationException) { final InvalidOperationException ioe = (InvalidOperationException) e; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { JOptionPane.showMessageDialog(OrigamiEditor.this, ioe.getUserFriendlyMessage(), appMessages.getString("invalid.operation.title"), JOptionPane.ERROR_MESSAGE); } }); } else if (e instanceof PaperStructureException) { JOptionPane.showMessageDialog(OrigamiEditor.this, e.getMessage(), appMessages.getString("invalid.operation.title"), JOptionPane.ERROR_MESSAGE); } else { stepEditor.getMessageBar().showMessage( "<html><span style=\"color:red;font-weight:bold\">" + editorMessages.getString("OrigamiEditor.invalidOperation") .replaceAll("\\<", "<").replaceAll(">", ">") + "</span></html>", 7000, StepEditingCanvasController.INCOMPLMETE_ARGUMENT_KEY); Logger.getLogger("application").warn("Invalid operation", e); } } setStep(step); } operationsTree.repaint(); } }).start(); } } @Override protected void setupLoggers() { super.setupLoggers(); Logger l = Logger.getLogger("editor"); l.setResourceBundle(ResourceBundle.getBundle("editor", ServiceLocator.get(ConfigurationManager.class).get() .getLocale())); l.setLevel(Level.ALL); } @Override protected void registerServices() { super.registerServices(); ServiceLocator.add(OrigamiEditor.class, this); ServiceLocator.add(StepThumbnailGenerator.class, new Callable<StepThumbnailGenerator>() { @Override public StepThumbnailGenerator call() throws Exception { return new StepThumbnailGenerator(); } }); } @Override protected void registerServicesAfterComponentsAreCreated() { super.registerServicesAfterComponentsAreCreated(); ServiceLocator.add(MessageBar.class, statusBar); } @Override protected Component getTopmostComponent() { if (bootstrap != null && bootstrap instanceof Java3DBootstrappingApplet && ((Java3DBootstrappingApplet) bootstrap).getWindow() != null) return ((Java3DBootstrappingApplet) bootstrap).getWindow(); Component parent = (bootstrap != null ? bootstrap : this); while (parent.getParent() != null) parent = parent.getParent(); return parent; } // bootstrapping support @Override public JRootPane getRootPane() { if (bootstrap != null) return bootstrap.getRootPane(); return super.getRootPane(); } @Override public Container getContentPane() { if (bootstrap != null) return bootstrap.getContentPane(); return super.getContentPane(); } @Override public URL getDocumentBase() { if (bootstrap != null) return bootstrap.getDocumentBase(); return super.getDocumentBase(); } @Override public URL getCodeBase() { if (bootstrap != null) return bootstrap.getCodeBase(); return super.getCodeBase(); } @Override public String getParameter(String name) { if (bootstrap != null) return bootstrap.getParameter(name); return super.getParameter(name); } @Override public AppletContext getAppletContext() { if (bootstrap != null) return bootstrap.getAppletContext(); return super.getAppletContext(); } @Override public String getAppletInfo() { if (bootstrap != null) return bootstrap.getAppletInfo(); return super.getAppletInfo(); } /** * Shows the settings dialog. * * @author Martin Pecka */ protected class SettingsAction extends AbstractAction { /** */ private static final long serialVersionUID = -583073126579360879L; @Override public void actionPerformed(final ActionEvent e) { statusBar.showL7dMessage("editor", "settingsDialog.opening", null); if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); new Thread(new Runnable() { @Override public void run() { final SettingsFrame frame = new SettingsFrame(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getDefaultCursor()); frame.setVisible(true); } }); } }).start(); } } /** * Creates a new origami and sets it to be edited. * * @author Martin Pecka */ protected class NewFileAction extends AbstractAction { /** */ private static final long serialVersionUID = -4947778589092614575L; @Override public void actionPerformed(final ActionEvent e) { statusBar.showL7dMessage("editor", "newFileDialog.opening", null); if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); new Thread(new Runnable() { @Override public void run() { // TODO check if the currently edited origami is saved, if not, show a warning final OrigamiPropertiesFrame propertiesFrame = new OrigamiPropertiesFrame(null); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getDefaultCursor()); propertiesFrame.setVisible(true); new Thread(new Runnable() { @Override public void run() { if (propertiesFrame.getOrigami() != null) { setOrigami(propertiesFrame.getOrigami()); } } }).start(); } }); } }).start(); } } /** * Shows the open file from disk dialog. * * @author Martin Pecka */ protected class OpenFileAction extends AbstractAction { /** */ private static final long serialVersionUID = -4079648565735159553L; @Override public void actionPerformed(ActionEvent e) { statusBar.showL7dMessage("editor", "openDialog.opening", null); if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); JFileChooser chooser = new JFileChooser(); File currentDir = ServiceLocator.get(ConfigurationManager.class).get().getLastOpenPath().getParentFile(); chooser.setCurrentDirectory(currentDir); chooser.setFileFilter(new FileNameExtensionFilter("*.xml", "XML")); chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); chooser.setDialogType(JFileChooser.OPEN_DIALOG); chooser.setApproveButtonText(appMessages.getString("openDialog.approve")); chooser.setApproveButtonMnemonic(KeyStroke.getKeyStroke( appMessages.getString("openDialog.approve.mnemonic")).getKeyCode()); chooser.setApproveButtonToolTipText(ServiceLocator.get(TooltipFactory.class).getDecorated( appMessages.getString("openDialog.approve.tooltip.message"), appMessages.getString("openDialog.approve.tooltip.title"), "open-file-32.png", KeyStroke.getKeyStroke("alt " + appMessages.getString("openDialog.approve.mnemonic")))); if (chooser.showDialog(OrigamiEditor.this, null) == JFileChooser.APPROVE_OPTION) { if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getDefaultCursor()); final File selected = chooser.getSelectedFile(); ServiceLocator.get(ConfigurationManager.class).get().setLastOpenPath(selected); new Thread(new Runnable() { @Override public void run() { try { Origami o = ServiceLocator.get(OrigamiHandler.class).loadModel(selected.toURI(), false); setOrigami(o); } catch (UnsupportedDataFormatException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("exception.UnsupportedDataFormatException.loadModel"), appMessages.getString("exception.UnsupportedDataFormatException.loadModel.title"), JOptionPane.ERROR_MESSAGE); Logger.getLogger("application").error(e1); } catch (IOException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("exception.IOException.loadModel"), appMessages.getString("exception.IOException.loadModel.title"), JOptionPane.ERROR_MESSAGE); Logger.getLogger("application").error(e1); } } }).start(); } else { if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getDefaultCursor()); } } } /** * Shows the open file from URL dialog. * * @author Martin Pecka */ protected class OpenURLAction extends AbstractAction { /** */ private static final long serialVersionUID = -4948201244271857952L; @Override public void actionPerformed(ActionEvent e) { Object selected = null; URL selectedUrl = null; Object defaultURL = ServiceLocator.get(ConfigurationManager.class).get().getLastOpenURL(); if (defaultURL == null) defaultURL = "http://"; while ((selected = JOptionPane.showInputDialog(rootPane, appMessages.getString("openUrl.message"), appMessages.getString("openUrl.title"), JOptionPane.QUESTION_MESSAGE, new ImageIcon( OrigamiEditor.this.getClass().getResource("/resources/images/open-url.png")), null, selected == null ? defaultURL : selected)) != null) { try { selectedUrl = new URL(selected.toString()); if (!selectedUrl.toURI().isAbsolute()) { throw new MalformedURLException("The URL to open must be absolute!"); } break; } catch (MalformedURLException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("openUrl.invalidUrl"), appMessages.getString("openUrl.invalidUrl.title"), JOptionPane.ERROR_MESSAGE); } catch (URISyntaxException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("openUrl.invalidUrl"), appMessages.getString("openUrl.invalidUrl.title"), JOptionPane.ERROR_MESSAGE); } } // the user has cancelled the dialog if (selectedUrl == null) return; ServiceLocator.get(ConfigurationManager.class).get().setLastOpenURL(selectedUrl); final URL url = selectedUrl; new Thread(new Runnable() { @Override public void run() { try { Origami o = ServiceLocator.get(OrigamiHandler.class).loadModel(url, false); setOrigami(o); } catch (UnsupportedDataFormatException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("exception.UnsupportedDataFormatException.loadModel"), appMessages.getString("exception.UnsupportedDataFormatException.loadModel.title"), JOptionPane.ERROR_MESSAGE); Logger.getLogger("application").error(e1); } catch (IOException e1) { JOptionPane.showMessageDialog(rootPane, appMessages.getString("exception.IOException.loadModel"), appMessages.getString("exception.IOException.loadModel.title"), JOptionPane.ERROR_MESSAGE); Logger.getLogger("application").error(e1); } } }).start(); } } /** * Display the origami properties dialog. * * @author Martin Pecka */ protected class PropertiesAction extends AbstractAction { /** */ private static final long serialVersionUID = -3808672661069071582L; @Override public void actionPerformed(final ActionEvent e) { statusBar.showL7dMessage("editor", "propertiesDialog.opening", null); if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); new Thread(new Runnable() { @Override public void run() { final OrigamiPropertiesFrame propertiesFrame = new OrigamiPropertiesFrame(origami); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (e.getSource() instanceof JComponent) ((JComponent) e.getSource()).setCursor(Cursor.getDefaultCursor()); propertiesFrame.setVisible(true); Origami result = propertiesFrame.getOrigami(); if (origami == null) { setOrigami(result); } if (step != null && !step.isModelStateValid(true)) setStep(step); } }); } }).start(); } } /** * Exports the currently displayed origami to the desired format. * * @author Martin Pecka */ protected class ExportAction extends cz.cuni.mff.peckam.java.origamist.gui.common.ExportAction { /** */ private static final long serialVersionUID = -4751828351174341121L; public ExportAction(ExportFormat format) { super(null, format); } @Override protected void beforeAction(ActionEvent evt) { this.origami = OrigamiEditor.this.origami; if (evt.getSource() instanceof Component) ((Component) evt.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); } @Override protected void done(ActionEvent evt) { if (evt.getSource() instanceof Component) ((Component) evt.getSource()).setCursor(Cursor.getDefaultCursor()); } } /** * This listener selects the current operation and displays a tooltip in the statusbar. * * @author Martin Pecka */ protected class OperationActionListener implements ActionListener { /** The operation that belongs to the button. */ protected Operations operation; /** The name of the resource bundle where we get the strings from. */ protected String bundleName; /** The name of the base key for string getting from resource bundle. */ protected String key; /** * @param operation The operation that belongs to the button. * @param bundleName The name of the resource bundle where we get the strings from. * @param key The name of the base key for string getting from resource bundle. */ public OperationActionListener(Operations operation, String bundleName, String key) { this.operation = operation; this.bundleName = bundleName; this.key = key; } @Override public void actionPerformed(ActionEvent e) { setCurrentOperation(OperationsHelper.getOperation(operation, alternativeActionsShown)); final MessageBar statusBar = ServiceLocator.get(MessageBar.class); if (statusBar != null) { ResourceBundle b = ResourceBundle.getBundle(bundleName, ServiceLocator.get(ConfigurationManager.class) .get().getLocale()); String statusText = ""; try { statusText += b.getString(key); } catch (MissingResourceException ex) {} try { statusText += ": " + b.getString(key + ".description"); } catch (MissingResourceException ex) {} final String text = statusText; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { statusBar.showMessage(text); } }); } } } /** * Action for selecting the next step in the current model. * * @author Martin Pecka */ protected class NextStepAction extends AbstractAction { /** */ private static final long serialVersionUID = -6606989835699225730L; @Override public void actionPerformed(ActionEvent e) { if (step != null && step.getNext() != null) { setStep(step.getNext()); } } } /** * Action for selecting the previous step in the current model. * * @author Martin Pecka */ protected class PrevStepAction extends AbstractAction { /** */ private static final long serialVersionUID = -2065950879596721316L; @Override public void actionPerformed(ActionEvent e) { if (step != null && step.getPrevious() != null) { setStep(step.getPrevious()); } } } /** * Action for adding a new step in the current model. * * @author Martin Pecka */ protected class AddStepAction extends AbstractAction { /** */ private static final long serialVersionUID = 7388070768713032084L; @Override public void actionPerformed(ActionEvent e) { if (origami == null) return; List<Step> steps = origami.getModel().getSteps().getStep(); if (step == null && steps.size() > 0) setStep(steps.get(steps.size() - 1)); Step newStep = (Step) new ObjectFactory().createStep(); newStep.setPrevious(step); newStep.setNext(null); if (step != null) { step.setNext(newStep); newStep.setId(step.getId() + 1); newStep.setZoom(step.getZoom()); } steps.add(newStep); setStep(newStep); } } /** * Action for removing the last step in the current model. * * @author Martin Pecka */ protected class RemoveStepAction extends AbstractAction { /** */ private static final long serialVersionUID = -1758965284633881226L; @Override public void actionPerformed(ActionEvent e) { if (origami == null || step == null) return; List<Step> steps = origami.getModel().getSteps().getStep(); Step newStep = null; if (steps.size() == 0) { return; } else if (steps.size() == 1) { newStep = (Step) new ObjectFactory().createStep(); newStep.setId(1); stepsToUndo.add(steps.get(0)); steps.set(0, newStep); setStep(newStep); undoRemoveStep.setEnabled(true); } else { stepsToUndo.add(steps.get(steps.size() - 1)); steps.remove(steps.size() - 1); setStep(steps.get(steps.size() - 1)); undoRemoveStep.setEnabled(true); } operationsToUndo.clear(); undoneOperationsSteps.clear(); undoCancelOperation.setEnabled(false); } } /** * Action for undoing removal of the last step in the current model. * * @author Martin Pecka */ protected class RemoveStepUndoAction extends AbstractAction { /** */ private static final long serialVersionUID = -7332023681928821236L; @Override public void actionPerformed(ActionEvent e) { if (origami == null || step == null) return; List<Step> steps = origami.getModel().getSteps().getStep(); if (stepsToUndo.size() > 0) { Step step = stepsToUndo.get(stepsToUndo.size() - 1); stepsToUndo.remove(stepsToUndo.size() - 1); if (step.getId() == steps.get(steps.size() - 1).getId()) { // this can happen either when the user removed the very first step, or if he removed the last step, // and added a new one, which, however, must have no operations at this time, becuse performing an // operation clears the undoable steps. steps.remove(steps.size() - 1); } steps.add(step); setStep(step); } if (stepsToUndo.size() == 0) undoRemoveStep.setEnabled(false); } } /** * Action for removing the last operation in the current step. * * @author Martin Pecka */ protected class CancelOperationAction extends AbstractAction { /** */ private static final long serialVersionUID = 2326569539259850378L; @Override public void actionPerformed(ActionEvent e) { if (step == null) return; List<Operation> operations = step.getOperations(); if (operations.size() == 0) { return; } else { Operation last = operations.get(operations.size() - 1); if (last instanceof OperationContainer) { int dialogResult = JOptionPane.showConfirmDialog(OrigamiEditor.this, editorMessages.getString("OrigamiEditor.delete.repeat.operation.or.last.message"), editorMessages.getString("OrigamiEditor.delete.repeat.operation.or.last.title"), JOptionPane.YES_NO_CANCEL_OPTION); if (dialogResult == JOptionPane.YES_OPTION) { operations.remove(operations.size() - 1); operationsToUndo.add(last); undoneOperationsSteps.put(last, step); } else if (dialogResult == JOptionPane.NO_OPTION) { operations.remove(operations.size() - 1); operations.addAll(((OperationContainer) last).getOperations()); // TODO handle removal of container? maybe it isn't needed } } else { operations.remove(operations.size() - 1); operationsToUndo.add(last); undoneOperationsSteps.put(last, step); undoCancelOperation.setEnabled(true); } } if (!step.isModelStateValid(true)) { setStep(step); undoCancelOperation.setEnabled(true); } } } /** * Action for undoing removal of the last operation in the current step. * * @author Martin Pecka */ protected class CancelOperationUndoAction extends AbstractAction { /** */ private static final long serialVersionUID = 3874891966442246574L; @Override public void actionPerformed(ActionEvent e) { if (step == null) return; if (operationsToUndo.size() > 0) { Operation operation = operationsToUndo.get(operationsToUndo.size() - 1); operationsToUndo.remove(operationsToUndo.size() - 1); Step step = undoneOperationsSteps.get(operation); if (step != null) { undoneOperationsSteps.remove(operation); step.getOperations().add(operation); setStep(step); } if (operationsToUndo.size() == 0) undoCancelOperation.setEnabled(false); } } } /** * A renderer for operations in a step. * * @author Martin Pecka */ protected class OperationListCellRenderer extends DefaultTreeCellRenderer { /** */ private static final long serialVersionUID = -8983928724421088263L; { setLeafIcon(null); setOpenIcon(null); setClosedIcon(null); } @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { JLabel result = (JLabel) super .getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus); Object userObject = ((DefaultMutableTreeNode) value).getUserObject(); if (!(userObject instanceof Operation)) return result; result.setFont(result.getFont().deriveFont(Font.BOLD)); result.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0)); Operation operation = (Operation) userObject; if (operation != currentOperation) { result.setText(operation.getDefaultDescription()); } else { result.setText(operation.getConstructDescription(currentOperationArgument)); result.revalidate(); } return result; } } }