public class EditArrangeAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.arrange";

    public EditArrangeAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.arrange.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.arrange.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.edit.arrange.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        ArrangeDialog copyform = new ArrangeDialog(localization, selection, model);
        copyform.setVisible(true);
        if (copyform.isOK()) {
            int copies = copyform.getCopies();
            int offset = copyform.getOffset(model.getWidth());
            model.arrangeSelection(selection, copies, offset);
        }
    }

} public class EditDeleteAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.delete";

    public EditDeleteAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.delete.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.delete.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        model.delete(selection);
    }

} public class EditDeleteRowAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.deleterow";

    public EditDeleteRowAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.deleterow.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.deleterow.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        model.deleteRow();
    }

} public class EditInsertRowAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.insertrow";

    public EditInsertRowAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.insertrow.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.insertrow.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        model.insertRow();
    }

} public class EditMirrorHorizontalAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.mirrorhorizontal";

    public EditMirrorHorizontalAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.mirrorhorizontal.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.mirrorhorizontal.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        if (selection.isActive()) {
            model.mirrorHorizontal(selection);
        } else {
            model.mirrorHorizontal(model.getUsedRect());
        }
    }

} public class EditMirrorVerticalAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.mirrorvertical";

    public EditMirrorVerticalAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.mirrorvertical.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.mirrorvertical.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        if (selection.isActive()) {
            model.mirrorVertical(selection);
        } else {
            model.mirrorVertical(model.getUsedRect());
        }
    }

} public class EditRedoAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.redo";

    public EditRedoAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.redo.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.redo.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.edit.redo.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        model.redo();
    }

} public class EditRotateAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.rotate";

    public EditRotateAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.rotate.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.rotate.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        model.rotate(selection);
    }

} public class EditUndoAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "edit.undo";

    public EditUndoAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.edit.undo.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.edit.undo.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.edit.undo.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        model.undo();
    }

} public class FileExitAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "file.exit";

    public FileExitAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.file.exit.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.file.exit.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.file.exit.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        if (model.isModified()) {
            int r = JOptionPane.showConfirmDialog(frame, localization.getString("savechanges"));
            if (r == JOptionPane.CANCEL_OPTION) return;
            if (r == JOptionPane.OK_OPTION) frame.getAction("file.save").actionPerformed(e);
        }
        System.exit(0);
    }

} public class FileMRUAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private int index;

    public FileMRUAction(JBeadFrame frame, int index) {
        super("file.mru" + index, frame);
        this.index = index;
    }

    public void actionPerformed(ActionEvent e) {
        File file = frame.getMRU(index);
        frame.updateFileFormat(null, file);
        frame.loadFile(file, true);
    }

} public class FileNewAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "file.new";

    public FileNewAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.file.new.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.file.new.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.file.new.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        if (model.isModified()) {
            int answer = JOptionPane.showConfirmDialog(frame, localization.getString("savechanges"));
            if (answer == JOptionPane.CANCEL_OPTION) return;
            if (answer == JOptionPane.YES_OPTION) {
                frame.getAction("file.save").actionPerformed(e);
            }
        }
        frame.selectDefaultColor();
        frame.initDefaultSymbols();
        selection.clear();
        model.clear();
        frame.updateScrollbar();
    }

} public class FileOpenAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "file.open";

    public FileOpenAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.file.open.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.file.open.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.file.open.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        JFileChooser dialog = new JFileChooser();
        dialog.setCurrentDirectory(model.getCurrentDirectory());
        dialog.setMultiSelectionEnabled(false);
        frame.setOpenFileFilters(dialog);
        if (dialog.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) {
            frame.updateFileFormat(dialog.getFileFilter(), dialog.getSelectedFile());
            frame.loadFile(dialog.getSelectedFile(), true);
        }
    }

} public class FilePageSetupAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "file.pagesetup";

    public FilePageSetupAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.file.pagesetup.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.file.pagesetup.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        PrinterJob job = PrinterJob.getPrinterJob();
        PrintSettings settings = frame.getPrintSettings();
        if (job.pageDialog(settings.getAttributes()) != null) {
            saveConfig(settings.getAttributes());
        }
    }

    private void saveConfig(PrintRequestAttributeSet attributes) {
        Settings settings = new Settings();
        settings.setCategory("print");
        Media media = (Media) attributes.get(Media.class);
        if (media == MediaSizeName.ISO_A0) {
            settings.saveString("paper", "A0");
        } else if (media == MediaSizeName.ISO_A1) {
            settings.saveString("paper", "A1");
        } else if (media == MediaSizeName.ISO_A2) {
            settings.saveString("paper", "A2");
        } else if (media == MediaSizeName.ISO_A3) {
            settings.saveString("paper", "A3");
        } else if (media == MediaSizeName.ISO_A4) {
            settings.saveString("paper", "A4");
        } else if (media == MediaSizeName.ISO_A5) {
            settings.saveString("paper", "A5");
        } else if (media == MediaSizeName.ISO_A6) {
            settings.saveString("paper", "A6");
        } else if (media == MediaSizeName.ISO_A7) {
            settings.saveString("paper", "A7");
        } else if (media == MediaSizeName.ISO_A8) {
            settings.saveString("paper", "A8");
        } else if (media == MediaSizeName.ISO_A9) {
            settings.saveString("paper", "A9");
        } else if (media == MediaSizeName.ISO_A10) {
            settings.saveString("paper", "A10");
        } else if (media == MediaSizeName.NA_LETTER) {
            settings.saveString("paper", "Letter");
        } else if (media == MediaSizeName.NA_LEGAL) {
            settings.saveString("paper", "Legal");
        } else if (media == MediaSizeName.EXECUTIVE) {
            settings.saveString("paper", "Executive");
        } else if (media == MediaSizeName.LEDGER) {
            settings.saveString("paper", "Ledger");
        } else if (media == MediaSizeName.TABLOID) {
            settings.saveString("paper", "Tabloid");
        } else if (media == MediaSizeName.INVOICE) {
            settings.saveString("paper", "Invoice");
        } else if (media == MediaSizeName.FOLIO) {
            settings.saveString("paper", "Folio");
        } else if (media == MediaSizeName.QUARTO) {
            settings.saveString("paper", "Quarto");
        }
        OrientationRequested orientation = (OrientationRequested) attributes.get(OrientationRequested.class);
        if (orientation == OrientationRequested.LANDSCAPE) {
            settings.saveString("orientation", "Landscape");
        } else if (orientation == OrientationRequested.PORTRAIT) {
            settings.saveString("orientation", "Portrait");
        } else if (orientation == OrientationRequested.REVERSE_LANDSCAPE) {
            settings.saveString("orientation", "Reverse_Landscape");
        } else if (orientation == OrientationRequested.REVERSE_PORTRAIT) {
            settings.saveString("orientation", "Reverse_Portrait");
        }
    }

} public class FilePrintAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "file.print";

    public FilePrintAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.file.print.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.file.print.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.file.print.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        if (model.getUsedHeight() == 0) return;
        boolean showDialog = e.getSource() instanceof JMenuItem;
        new DesignPrinter(model, frame, localization, frame.getPrintSettings())
                .print(showDialog);
    }

} public class FileSaveAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "file.save";

    public FileSaveAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.file.save.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.file.save.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.file.save.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        frame.fileSaveClick(model.isSaved(), model.getFile());
    }

} public class InfoAboutAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "info.about";

    public InfoAboutAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.info.about.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.info.about.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        new InfoAboutDialog(frame).setVisible(true);
    }

} public class InfoTechInfosAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "info.techinfos";

    public InfoTechInfosAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.info.techinfos.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.info.techinfos.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        new TechInfosDialog(frame).setVisible(true);
    }

} public class PatternHeightAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "pattern.height";

    public PatternHeightAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.pattern.height.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.pattern.height.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        PatternHeightDialog dialog = new PatternHeightDialog(frame);
        dialog.setPatternHeight(model.getHeight());
        dialog.setVisible(true);
        if (dialog.isOK()) {
            selection.clear();
            model.setHeight(dialog.getPatternHeight());
        }
    }

} public class PatternPreferencesAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "pattern.preferences";

    public PatternPreferencesAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.pattern.preferences.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.pattern.preferences.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        PreferencesDialog dialog = new PreferencesDialog(frame, model, frame);
        dialog.setVisible(true);
    }

} public class PatternWidthAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "pattern.width";

    public PatternWidthAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.pattern.width.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.pattern.width.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        PatternWidthDialog dialog = new PatternWidthDialog(frame);
        dialog.setPatternWidth(model.getWidth());
        dialog.setVisible(true);
        if (dialog.isOK()) {
            selection.clear();
            model.setWidth(dialog.getPatternWidth());
        }
    }

} public class ToolFillAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "tool.fill";

    public ToolFillAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.tool.fill.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.tool.fill.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.tool.fill.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        selection.clear();
        frame.selectTool("fill");
    }

} public class ToolPencilAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "tool.pencil";

    public ToolPencilAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.tool.pencil.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.tool.pencil.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.tool.pencil.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        selection.clear();
        frame.selectTool("pencil");
    }

} public class ToolPipetteAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "tool.pipette";

    public ToolPipetteAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.tool.pipette.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.tool.pipette.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.tool.pipette.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        selection.clear();
        frame.selectTool("pipette");
    }

} public class ToolSelectAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "tool.select";

    public ToolSelectAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.tool.select.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.tool.select.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.tool.select.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        selection.clear();
        frame.selectTool("select");
    }

} public class ViewCorrectedAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "view.corrected";

    public ViewCorrectedAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.view.corrected.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.view.corrected.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        frame.updateVisibility();
    }

} public class ViewDraftAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "view.draft";

    public ViewDraftAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.view.draft.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.view.draft.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        frame.updateVisibility();
    }

} public class ViewDrawColorsAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "view.drawcolors";

    public ViewDrawColorsAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.view.drawcolors.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.view.drawcolors.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        frame.fireDrawColorsChanged();
    }

} public class ViewDrawSymbolsAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "view.drawsymbols";

    public ViewDrawSymbolsAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.view.drawsymbols.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.view.drawsymbols.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        frame.fireDrawSymbolsChanged();
    }

} public class ViewSimulationAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "view.simulation";

    public ViewSimulationAction(JBeadFrame frame) {
        super(NAME, frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.view.simulation.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.view.simulation.mnemonic"));
    }

    public void actionPerformed(ActionEvent e) {
        frame.updateVisibility();
    }

} public class ViewZoomInAction extends BaseAction {

    private static final long serialVersionUID = 1L;

    private static final String NAME = "view.zoomin";

    public ViewZoomInAction(JBeadFrame frame) {
        super(NAME, ImageFactory.getIcon(NAME), frame);
        putValue(SHORT_DESCRIPTION, localization.getString("action.view.zoomin.description"));
        putValue(MNEMONIC_KEY, localization.getMnemonic("action.view.zoomin.mnemonic"));
        putValue(ACCELERATOR_KEY, localization.getKeyStroke("action.view.zoomin.keystroke"));
    }

    public void actionPerformed(ActionEvent e) {
        model.zoomIn();
        frame.updateScrollbar();
    }

} public class ArrangeDialog extends JDialog {
    private static final long serialVersionUID = 1L;

    private boolean isOK = false;

    private Selection selection;
    private Model model;

    private SpinnerModel horzModel = new SpinnerNumberModel(5, 0, 100, 1);
    private SpinnerModel vertModel = new SpinnerNumberModel(5, 0, 100, 1);
    private SpinnerModel copyModel = new SpinnerNumberModel(1, 0, 100, 1);

    private JSpinner horz = new JSpinner(horzModel);
    private JSpinner vert = new JSpinner(vertModel);
    private JSpinner Copies = new JSpinner(copyModel);
    private JButton bOK;
    private JButton bCancel;

    public ArrangeDialog(Localization localization, Selection selection, Model model) {
        this.selection = selection;
        this.model = model;
        setTitle(localization.getString("arrangedialog.title"));
        JPanel main = new JPanel();
        main.setBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7));
        setContentPane(main);
        setLayout(new BorderLayout());

        JPanel form = new JPanel();
        form.setLayout(new GridBagLayout());

        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = 0;
        constraints.gridy = 0;
        form.add(new JLabel(localization.getString("arrangedialog.horz")), constraints);

        constraints = new GridBagConstraints();
        constraints.gridx = 1;
        constraints.gridy = 0;
        form.add(horz, constraints);

        constraints = new GridBagConstraints();
        constraints.gridx = 0;
        constraints.gridy = 1;
        form.add(new JLabel(localization.getString("arrangedialog.vert")), constraints);

        constraints = new GridBagConstraints();
        constraints.gridx = 1;
        constraints.gridy = 1;
        form.add(vert, constraints);

        constraints = new GridBagConstraints();
        constraints.gridx = 0;
        constraints.gridy = 2;
        form.add(new JLabel(localization.getString("arrangedialog.copies")), constraints);

        constraints = new GridBagConstraints();
        constraints.gridx = 1;
        constraints.gridy = 2;
        form.add(Copies, constraints);

        add(form, BorderLayout.CENTER);

        JPanel buttons = new JPanel();
        buttons.setLayout(new FlowLayout());
        buttons.add(bOK = new JButton(localization.getString("ok")));
        buttons.add(bCancel  = new JButton(localization.getString("cancel")));
        add(buttons, BorderLayout.SOUTH);

        initDefaultValues();

        setModal(true);
        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        bOK.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                isOK = true;
                dispose();
            }
        });
        bCancel.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                isOK = false;
                dispose();
            }
        });
    }

    private void initDefaultValues() {
        horzModel.setValue(getDefaultHorzDisplacement());
        vertModel.setValue(getDefaultVertDisplacement());
    }

    private int getDefaultHorzDisplacement() {
        if (selection.width() == model.getWidth()) {
            return 0;
        } else {
            return selection.width();
        }
    }

    private int getDefaultVertDisplacement() {
        return selection.height();
    }

    public boolean isOK() {
        return isOK;
    }

    public int getCopies() {
        return (Integer) Copies.getValue();
    }

    public int getVertOffset() {
        return (Integer) vert.getValue();
    }

    public int getHorzOffset() {
        return (Integer) horz.getValue();
    }

    public int getOffset(int width) {
        return getVertOffset() * width + getHorzOffset();
    }

} public class DbbFileFilter extends FileFilter {

    @Override
    public boolean accept(File f) {
        return f.isDirectory() || f.getName().toLowerCase().endsWith(".dbb");
    }

    @Override
    public String getDescription() {
        return "dbbead files (*.dbb)";
    }

} public class DbbFileFormat implements FileFormat {

    private static final String MAGIC_FILE_HEADER = "DB-BEAD/01:\r\n";

    public static final String EXTENSION = ".dbb";

    public String getName() {
        return "DB-BEAD";
    }

    public void save(Model model, JBeadFrame form, File destfile) throws IOException {
        JBeadOutputStream out = new JBeadOutputStream(new FileOutputStream(destfile));
        try {
            out.write(MAGIC_FILE_HEADER);
            Memento memento = new DbbMemento();
            model.saveTo(memento);
            form.saveTo(memento);
            memento.save(out);
            model.setModified(false);
        } finally {
            out.close();
        }
    }

    public void load(Model model, JBeadFrame form, File srcfile) throws IOException {
        JBeadInputStream in = new JBeadInputStream(new FileInputStream(srcfile));
        try {
            String strid = in.read(13);
            if (!strid.equals(MAGIC_FILE_HEADER)) {
                throw new IOException(form.getString("invalidformat"));
            }
            form.clearSelection();
            model.clear();
            Memento memento = new DbbMemento();
            memento.load(in);
            model.loadFrom(memento);
            form.loadFrom(memento);
        } finally {
            in.close();
        }
    }

    public FileFilter getFileFilter() {
        return new DbbFileFilter();
    }

    public String getExtension() {
        return EXTENSION;
    }

} public interface FileFormat {

    String getName();
    void save(Model model, JBeadFrame form, File destfile) throws IOException;
    void load(Model model, JBeadFrame form, File srcfile) throws IOException;
    FileFilter getFileFilter();
    String getExtension();

} public class JBeadFileFilter extends FileFilter {

    @Override
    public boolean accept(File f) {
        if (f.isDirectory()) return true;
        String filename = f.getName().toLowerCase();
        return filename.endsWith(".jbb") || filename.endsWith(".dbb");
    }

    @Override
    public String getDescription() {
        return "jbead and db-bead files (*.jbb, *.dbb)";
    }

} public class JBeadFileFormat implements FileFormat {

    public static final String EXTENSION = ".jbb";

    public String getName() {
        return "jbead";
    }

    public void save(Model model, JBeadFrame form, File destfile) throws IOException {
        JBeadOutputStream out = new JBeadOutputStream(new FileOutputStream(destfile));
        try {
            Memento memento = new JBeadMemento();
            model.saveTo(memento);
            form.saveTo(memento);
            memento.save(out);
            model.setModified(false);
        } finally {
            out.close();
        }
    }

    public void load(Model model, JBeadFrame form, File srcfile) throws IOException {
        JBeadInputStream in = new JBeadInputStream(new FileInputStream(srcfile));
        try {
            Memento memento = new JBeadMemento();
            memento.load(in);
            form.clearSelection();
            model.clear();
            model.loadFrom(memento);
            form.loadFrom(memento);
        } finally {
            in.close();
        }
    }

    public FileFilter getFileFilter() {
        return new JbbFileFilter();
    }

    public String getExtension() {
        return EXTENSION;
    }

} public class JbbFileFilter extends FileFilter {

    @Override
    public boolean accept(File f) {
        return f.isDirectory() || f.getName().toLowerCase().endsWith(".jbb");
    }

    @Override
    public String getDescription() {
        return "jbead files (*.jbb)";
    }

} public class PrintSettings {

    private static Map<String, MediaSizeName> mediamap = new HashMap<String, MediaSizeName>();

    static {
        mediamap.put("a0", MediaSizeName.ISO_A0);
        mediamap.put("a1", MediaSizeName.ISO_A1);
        mediamap.put("a2", MediaSizeName.ISO_A2);
        mediamap.put("a3", MediaSizeName.ISO_A3);
        mediamap.put("a4", MediaSizeName.ISO_A4);
        mediamap.put("a5", MediaSizeName.ISO_A5);
        mediamap.put("a6", MediaSizeName.ISO_A6);
        mediamap.put("a7", MediaSizeName.ISO_A7);
        mediamap.put("a8", MediaSizeName.ISO_A8);
        mediamap.put("a9", MediaSizeName.ISO_A9);
        mediamap.put("a10", MediaSizeName.ISO_A10);
        mediamap.put("letter", MediaSizeName.NA_LETTER);
        mediamap.put("legal", MediaSizeName.NA_LEGAL);
        mediamap.put("executive", MediaSizeName.EXECUTIVE);
        mediamap.put("ledger", MediaSizeName.LEDGER);
        mediamap.put("tabloid", MediaSizeName.TABLOID);
        mediamap.put("invoice", MediaSizeName.INVOICE);
        mediamap.put("folio", MediaSizeName.FOLIO);
        mediamap.put("quarto", MediaSizeName.QUARTO);
    }

    private PrintService service;
    private PrintRequestAttributeSet attributes;

    public PrintSettings(Settings settings) {
        service = null;
        attributes = new HashPrintRequestAttributeSet();
        initDefaultFormat(settings);
    }

    public PrintService getService() {
        return service;
    }

    public PrintRequestAttributeSet getAttributes() {
        return attributes;
    }

    public void setService(PrintService service) {
        this.service = service;
    }

    public void setAttributes(PrintRequestAttributeSet attributes) {
        this.attributes = attributes;
    }

    private void initDefaultFormat(Settings settings) {
        addOrientation(settings);
        addCopies();
        addMedia(settings);
    }

    private void addOrientation(Settings settings) {
        settings.setCategory("print");
        String paperorient = settings.loadString("orientation");
        if (paperorient.equalsIgnoreCase("portrait")) {
            attributes.add(OrientationRequested.PORTRAIT);
        } else if (paperorient.equalsIgnoreCase("reverse_portrait")) {
            attributes.add(OrientationRequested.REVERSE_PORTRAIT);
        } else if (paperorient.equalsIgnoreCase("reverse_landscape")) {
            attributes.add(OrientationRequested.REVERSE_LANDSCAPE);
        } else {
            attributes.add(OrientationRequested.LANDSCAPE);
        }
    }

    private void addCopies() {
        attributes.add(new Copies(1));
    }

    private void addMedia(Settings settings) {
        settings.setCategory("print");
        String papername = settings.loadString("paper");
        if (papername.length() > 0) {
            attributes.add(getMedia(papername));
        } else {
            Paper paper = PrinterJob.getPrinterJob().defaultPage().getPaper();
            if (isLetter(paper)) {
                attributes.add(MediaSizeName.NA_LETTER);
            } else if (isLegal(paper)) {
                attributes.add(MediaSizeName.NA_LEGAL);
            } else {
                attributes.add(getMedia("a4"));
            }
        }
    }

    private MediaSizeName getMedia(String paper) {
        paper = paper.toLowerCase();
        if (!mediamap.containsKey(paper)) return MediaSizeName.ISO_A4;
        return mediamap.get(paper);
    }

    private boolean isLetter(Paper paper) {
        return paper.getWidth() == 612.0 && paper.getHeight() == 792.0;
    }

    private boolean isLegal(Paper paper) {
        return paper.getWidth() == 612.0 && paper.getHeight() == 1008.0;
    }

} public class ColorsToolbar extends JToolBar {

    private static final long serialVersionUID = 1L;

    private Model model;

    public ColorsToolbar(Model model, View view, Localization localization) {
        this.model = model;
        add(new ColorPalette(model, view, localization));
    }

    public void selectDefaultColor() {
        model.setSelectedColor((byte) 1);
        repaint();
    }

    public void selectColor(byte colorindex) {
        model.setSelectedColor(colorindex);
        repaint();
    }

    public void updateColorIcon(byte colorindex) {
        repaint();
    }

    public void updateAll() {
        repaint();
    }

} public class RotateLeftButton extends JButton {

    private static final long serialVersionUID = 1L;

    public RotateLeftButton(final JBeadFrame form) {
        super(ImageFactory.getIcon("view.rotateleft"));
        setFocusable(false);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                super.mousePressed(e);
                form.sbRotateleftMouseDown(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                super.mouseReleased(e);
                form.sbRotateleftMouseUp(e);
            }
        });
    }
} public class ToolsGroup {

    private ButtonGroup menugroup = new ButtonGroup();
    private ButtonGroup toolbargroup = new ButtonGroup();

    private Map<String, ToolButton> buttons = new HashMap<String, ToolButton>();
    private Map<String, ToolMenuItem> items = new HashMap<String, ToolMenuItem>();

    public ToolButton addTool(String tool, ToolButton button) {
        buttons.put(tool, button);
        toolbargroup.add(button);
        return button;
    }

    public ToolMenuItem addTool(String tool, ToolMenuItem item) {
        items.put(tool, item);
        menugroup.add(item);
        return item;
    }

    public void selectTool(String tool) {
        if (!buttons.containsKey(tool)) tool = "pencil";
        buttons.get(tool).setSelected(true);
        items.get(tool).setSelected(true);
    }

    public boolean isSelected(String tool) {
        return buttons.get(tool).isSelected();
    }

    public String getSelectedTool() {
        for (Map.Entry<String, ToolButton> entry : buttons.entrySet()) {
            if (entry.getValue().isSelected()) return entry.getKey();
        }
        return "pencil";
    }

} public class VersionChecker {

    private VersionListener listener;

    public VersionChecker(VersionListener listener) {
        this.listener = listener;
    }

    protected URL getLatestVersionURL() throws MalformedURLException {
        return new URL("http://www.jbead.ch/latestversion");
    }

    public void check() {
        try {
            VersionCheckerThread thread = new VersionCheckerThread(listener, getLatestVersionURL());
            thread.start();
        } catch (MalformedURLException e) {
            listener.failure(e.toString());
        }

    }
} public class DraftPanel extends BasePanel implements SelectionListener, CoordinateCalculator {

    private static final long serialVersionUID = 1L;

    private static final int GAP = 6;
    private static final int MARKER_WIDTH = 30;

    private int offsetx;
    private int maxj;
    private Font defaultfont;
    private Font symbolfont;

    public DraftPanel(Model model, Selection selection, final JBeadFrame frame) {
        super(model, frame, selection);
        setBackground(Color.LIGHT_GRAY);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                handleMouseDown(e);
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                handleMouseUp(e);
            }
        });
        addMouseMotionListener(new MouseMotionListener() {
            public void mouseDragged(MouseEvent e) {
                handleMouseMove(e);
            }
            public void mouseMoved(MouseEvent e) {
                // empty
            }
        });
    }

    private void draftLinePreview() {
        if (!view.getSelectedTool().equals("pencil")) return;
        if (!selection.isActive()) return;
        linePreview(selection.getOrigin(), selection.getLineDest());
    }

    private void drawPrepress() {
        if (view.getSelectedTool().equals("pencil")) {
            drawPrepress(selection.getOrigin());
        }
    }

    public void handleMouseDown(MouseEvent event) {
        if (view.isDragging()) return;
        Point pt = new Point(event.getX(), event.getY());
        if (event.getButton() == MouseEvent.BUTTON1) {
            pt = mouseToField(pt);
            if (pt == null) return;
            view.setDragging(true);
            selection.init(pt);
            drawPrepress();
            draftLinePreview();
        }
    }

    public void handleMouseMove(MouseEvent event) {
        Point pt = new Point(event.getX(), event.getY());
        if (view.isDragging()) {
            pt = mouseToField(pt);
            if (pt == null) return;
            draftLinePreview();
            selection.update(pt);
            draftLinePreview();
        }
    }

    private void handleMouseUp(MouseEvent event) {
        Point pt = new Point(event.getX(), event.getY());
        if (view.isDragging()) {
            pt = mouseToField(pt);
            if (pt == null) return;
            draftLinePreview();
            selection.update(pt);
            view.setDragging(false);
            String tool = view.getSelectedTool();
            if (tool.equals("pencil")) {
                if (!selection.isActive()) {
                    setPoint(selection.getOrigin());
                } else {
                    drawLine(selection.getOrigin(), selection.getLineDest());
                }
            } else if (tool.equals("fill")) {
                fillLine(selection.getOrigin());
            } else if (tool.equals("pipette")) {
                selectColorFrom(selection.getOrigin());
            } else if (tool.equals("select")) {
                if (!selection.isActive()) {
                    setPoint(selection.getOrigin());
                }
            }
        }
    }

    private void selectColorFrom(Point pt) {
        byte colorIndex = model.get(pt.scrolled(model.getScroll()));
        view.selectColor(colorIndex);
    }

    private void drawLine(Point begin, Point end) {
        model.drawLine(begin, end);
    }

    private void fillLine(Point pt) {
        model.fillLine(pt);
    }

    private void setPoint(Point pt) {
        model.setPoint(pt);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        setHints(g);
        defaultfont = g.getFont();
        symbolfont = SymbolFont.get(gridy);
        offsetx = getOffsetX();
        maxj = getMaxJ();
        paintBeads(g);
        paintMarkers(g);
        if (selection.isNormal()) {
            paintSelection(g, Color.RED, selection);
        }
    }

    private void setHints(Graphics g) {
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    }

    @Override
    public Dimension getMinimumSize() {
        return new Dimension(model.getWidth() * gridx + MARKER_WIDTH + GAP, 3 * gridy);
    }

    @Override
    public Dimension getPreferredSize() {
        return getMinimumSize();
    }

    @Override
    public Dimension getMaximumSize() {
        return getMinimumSize();
    }

    private int getOffsetX() {
        return Math.max(3 + MARKER_WIDTH + GAP, (getWidth() - model.getWidth() * gridx - 1) / 2);
    }

    private int getMaxJ() {
        return Math.min(model.getHeight() - scroll, getHeight() / gridy + 1);
    }

    public int x(Point pt) {
        return offsetx + pt.getX() * gridx;
    }

    public int y(Point pt) {
        return getHeight() - 1 - (pt.getY() + 1) * gridy;
    }

    private int y(int j) {
        return y(new Point(0, j));
    }

    private void paintBeads(Graphics g) {
        BeadPainter painter = new BeadPainter(this, model, view, symbolfont);
        for (int j = 0; j < maxj; j++) {
            for (int i = 0; i < model.getWidth(); i++) {
                byte c = model.get(new Point(i, j).scrolled(scroll));
                painter.paint(g, new Point(i, j), c);
            }
        }
    }

    private void paintMarkers(Graphics g) {
        g.setFont(defaultfont);
        g.setColor(Color.DARK_GRAY);
        setHints(g);
        int fontHeight = g.getFontMetrics().getAscent();
        for (int j = 0; j < maxj; j++) {
            if (((j + scroll) % 10) == 0) {
                g.drawLine(offsetx - GAP - MARKER_WIDTH, y(j) + gridy, offsetx - GAP, y(j) + gridy);
                String label = Integer.toString(j + scroll);
                int labelWidth = g.getFontMetrics().stringWidth(label);
                g.drawString(label, offsetx - GAP - MARKER_WIDTH + (MARKER_WIDTH - labelWidth) / 2, y(j) + gridy + fontHeight + 1);
            }
        }
    }

    public void drawSelection(Selection sel) {
        if (!sel.isNormal()) return;
        Graphics g = getGraphics();
        paintSelection(g, Color.RED, sel);
        g.dispose();
    }

    public void clearSelection(Selection sel) {
        if (!sel.isNormal()) return;
        Graphics g = getGraphics();
        paintSelection(g, Color.DARK_GRAY, sel);
        g.dispose();
    }

    private void paintSelection(Graphics g, Color color, Selection sel) {
        g.setColor(color);
        g.drawRect(x(sel.getBegin()), y(sel.getEnd()), sel.width() * gridx, sel.height() * gridy);
    }

    public void redraw(int i, int j) {
        if (!isVisible()) return;
        byte c = model.get(new Point(i, j).scrolled(model.getScroll()));
        BeadPainter painter = new BeadPainter(this, model, view, symbolfont);
        Graphics g = getGraphics();
        setHints(g);
        painter.paint(g, new Point(i, j), c);
        g.dispose();
    }

    @Override
    public void redraw(Point pt) {
        redraw(pt.getX(), pt.getY() - scroll);
    }

    public void selectPreview(boolean draw, Point pt1, Point pt2) {
        Graphics g = getGraphics();
        g.setColor(draw ? Color.RED : Color.DARK_GRAY);
        g.drawRect(x(pt1), y(pt2), (pt2.getX() - pt1.getX() + 1) * gridx, (pt2.getY() - pt1.getY() + 1) * gridy);
        g.dispose();
    }

    public void linePreview(Point pt1, Point pt2) {
        Graphics g = getGraphics();
        g.setColor(Color.WHITE);
        g.setXORMode(Color.BLACK);
        g.drawLine(x(pt1) + gridx / 2, y(pt1.getY()) + gridy / 2, x(pt2) + gridx / 2, y(pt2.getY()) + gridy / 2);
        g.dispose();
    }

    public void drawPrepress(Point pt) {
        Graphics g = getGraphics();
        int x0 = x(pt);
        int y0 = y(pt);
        g.setColor(Color.BLACK);
        g.drawLine(x0 + 1, y0 + gridy - 1, x0 + 1, y0 + 1);
        g.drawLine(x0 + 1, y0 + 1, x0 + gridx - 1, y0 + 1);
        g.setColor(Color.WHITE);
        g.drawLine(x0 + 1 + 1, y0 + gridy - 1, x0 + gridx - 1, y0 + gridy - 1);
        g.drawLine(x0 + gridx - 1, y0 + gridy - 1, x0 + gridx - 1, y0 + 1);
        g.dispose();
    }

    public Point mouseToField(Point pt) {
        int _i = pt.getX();
        int _j = pt.getY();
        int i, jj;
        if (_i < offsetx || _i > offsetx + model.getWidth() * gridx) return null;
        i = (_i - offsetx) / gridy;
        if (i >= model.getWidth()) return null;
        jj = (getHeight() - _j) / gridy;
        return new Point(i, jj);
    }

    public void selectionUpdated(Selection before, Selection current) {
        clearSelection(before);
        drawSelection(current);
    }

    public void selectionDeleted(Selection sel) {
        clearSelection(sel);
    }

} public class ReportPanel extends BasePanel {

    private static final long serialVersionUID = 1L;

    private Localization localization;
    private Font defaultfont;

    public ReportPanel(Model model, View view, Selection selection, Localization localization) {
        super(model, view, selection);
        this.localization = localization;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        defaultfont = g.getFont();
        setHints(g);
        ReportInfos infos = new ReportInfos(model, localization);
        int dy = dy(g);
        int y = drawInfos(g, infos, dy);
        if (model.getRepeat() > 0) {
            y += dy / 2;
            y = drawColorList(g, y);
            y += dy / 2;
            drawBeadList(g, y);
        }
    }

    private int drawInfos(Graphics g, ReportInfos infos, int y) {
        g.setFont(defaultfont);
        FontMetrics metrics = g.getFontMetrics();
        int x1 = x1();
        int x2 = x1 + infos.getMaxLabelWidth(metrics) + metrics.stringWidth(" ");
        int dy = dy(g);
        for (String label : infos) {
            drawText(g, x1, x2, y, label, infos.getInfo(label));
            y += dy;
        }
        return y;
    }

    private int drawColorList(Graphics g, int y) {
        FontMetrics metrics = g.getFontMetrics();
        BeadCounts counts = new BeadCounts(model);
        int x = x1();
        int bx = dx(g);
        int countw = metrics.stringWidth("9999 x");
        int w = countw + 4 + bx + 1 + bx;
        SimpleCoordinateCalculator coord = new SimpleCoordinateCalculator(bx, bx);
        BeadPainter painter = new BeadPainter(coord, model, view, SymbolFont.get(bx));
        for (byte color = 0; color < model.getColorCount(); color++) {
            if (!drawColorCount(g, x, y, bx, color, counts, metrics, painter, coord))
                continue;
            x += w;
            if (x + w > getWidth()) {
                x = x1();
                y += dy(g) + 3;
            }
        }
        if (x != x1()) {
            y += dy(g) + 3;
        }
        return y;
    }

    private boolean drawColorCount(Graphics g, int x, int y, int bx, byte color, BeadCounts counts,
            FontMetrics metrics, BeadPainter painter, SimpleCoordinateCalculator coord) {
        int count = counts.getCount(color);
        if (count == 0) return false;
        String s = String.format("%d x", count);
        g.setColor(Color.BLACK);
        g.setFont(defaultfont);
        int cw = metrics.stringWidth("9999 x");
        g.drawString(s, x + cw - metrics.stringWidth(s), y);
        coord.setOffsetX(x + cw + 4);
        coord.setOffsetY(y);
        painter.paint(g, new Point(0, 0), color);
        return true;
    }

    private void drawBeadList(Graphics g, int y) {
        FontMetrics metrics = g.getFontMetrics();
        BeadList beads = new BeadList(model);
        int height = metrics.getLeading() + metrics.getAscent();
        g.setFont(defaultfont);
        g.drawString(localization.getString("report.listofbeads"), x1(), y);
        y += 3;
        int ystart = y;
        int x1 = x1();
        int dx = dx(g);
        int dy = dy(g);
        int colwidth = colwidth(g);
        SimpleCoordinateCalculator coord = new SimpleCoordinateCalculator(dx, dy);
        BeadPainter painter = new BeadPainter(coord, model, view, SymbolFont.get(dx));
        for (BeadRun bead : beads) {
            drawBeadCount(g, x1, y, dx, dy, height, bead.getColor(), bead.getCount(), painter, coord);
            y += dy + 3;
            if (y >= getHeight() - dy) {
                x1 += colwidth;
                y = ystart;
            }
        }
    }

    private int colwidth(Graphics g) {
        return dx(g) + 2 + g.getFontMetrics().stringWidth("9999") + 3;
    }

    private void setHints(Graphics g) {
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    }

    private int x1() {
        return 12;
    }

    private int dx(Graphics g) {
        return g.getFontMetrics(defaultfont).getHeight();
    }

    private int dy(Graphics g) {
        return dx(g);
    }

    private void drawText(Graphics g, int x1, int x2, int y, String label, String value) {
        g.setColor(Color.BLACK);
        g.drawString(label, x1, y);
        g.drawString(value, x2, y);
    }

    private void drawBeadCount(Graphics g, int x, int y, int dx, int dy, int height, byte color, int count,
            BeadPainter painter, SimpleCoordinateCalculator coord) {
        coord.setOffsetX(x);
        coord.setOffsetY(y + dy);
        painter.paint(g, new Point(0, 0), color);
        g.setColor(Color.BLACK);
        g.setFont(defaultfont);
        g.drawString(Integer.toString(count), x + dx + 3, y + height);
    }

    @Override
    public void redraw(Point pt) {
        // empty
    }

    @Override
    public void repeatChanged(int repeat) {
        repaint();
    }

} public class SimulationPanel extends BasePanel {

    private static final long serialVersionUID = 1L;

    private int offsetx;
    private int left;
    private int w;

    public SimulationPanel(Model model, Selection selection, View view) {
        super(model, view, selection);
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                handleMouseClick(e);
            }
        });
    }

    private void handleMouseClick(MouseEvent e) {
        if (e.getButton() == MouseEvent.BUTTON1) {
            if (view.getSelectedTool().equals("fill")) {
                fillLine(new Point(e.getX(), e.getY()));
            } else {
                togglePoint(new Point(e.getX(), e.getY()));
            }
        }
    }

    @Override
    public Dimension getMinimumSize() {
        return new Dimension((getVisibleWidth() + 1) * gridx, 3 * gridy);
    }

    @Override
    public Dimension getPreferredSize() {
        return getMinimumSize();
    }

    @Override
    public Dimension getMaximumSize() {
        return getMinimumSize();
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        offsetx = getOffsetX();
        left = getLeft();
        w = getVisibleWidth();
        paintBeads(g);
    }

    private int getOffsetX() {
        return (getWidth() - 1 - (model.getWidth() + 1) * gridx / 2 + gridx / 2) / 2;
    }

    private int getLeft() {
        int left = offsetx;
        if (left < 0) left = gridx / 2;
        return left;
    }

    private int getVisibleWidth() {
        return model.getWidth() / 2;
    }

    private int x(int i) {
        return left + i * gridx;
    }

    private int y(int j) {
        return getHeight() - 1 - (j + 1) * gridy;
    }

    public int dx(int j) {
        if ((j + scroll) % 2 == 0) {
            return 0;
        } else {
            return gridx / 2;
        }
    }

    private void paintBeads(Graphics g) {
        if (scroll > model.getHeight() - 1) return;
        int width = model.getWidth();
        for (Point pt : model.getRect(scroll, model.getHeight() - 1)) {
            byte c = model.get(pt);
            pt = model.correct(pt.unscrolled(scroll).shifted(model.getShift(), width));
            if (y(pt.getY()) < -gridy) return;
            if (pt.getX() > w && pt.getX() != width) continue;
            if (scroll % 2 == 0) {
                if (pt.getY() % 2 == 0) {
                    if (pt.getX() == w) continue;
                    paintBead(g, x(pt.getX()), getHeight() - 1 - (pt.getY() + 1) * gridy, gridx, gridy, model.getColor(c));
                } else {
                    if (pt.getX() != width && pt.getX() != w) {
                        paintBead(g, x(pt.getX()) - gridx / 2, getHeight() - 1 - (pt.getY() + 1) * gridy, gridx, gridy, model.getColor(c));
                    } else if (pt.getX() == width) {
                        paintBead(g, x(0) - gridx / 2, getHeight() - 1 - (pt.getY() + 2) * gridy, gridx / 2, gridy, model.getColor(c));
                    } else {
                        paintBead(g, x(pt.getX()) - gridx / 2, getHeight() - 1 - (pt.getY() + 1) * gridy, gridx / 2, gridy, model.getColor(c));
                    }
                }
            } else {
                if (pt.getY() % 2 == 1) {
                    if (pt.getX() == w) continue;
                    paintBead(g, x(pt.getX()), getHeight() - 1 - (pt.getY() + 1) * gridy, gridx, gridy, model.getColor(c));
                } else {
                    if (pt.getX() != width && pt.getX() != w) {
                        paintBead(g, x(pt.getX()) - gridx / 2, getHeight() - 1 - (pt.getY() + 1) * gridy, gridx, gridy, model.getColor(c));
                    } else if (pt.getX() == width) {
                        paintBead(g, x(0) - gridx / 2, getHeight() - 1 - (pt.getY() + 2) * gridy, gridx / 2, gridy, model.getColor(c));
                    } else {
                        paintBead(g, x(pt.getX()) - gridx / 2, getHeight() - 1 - (pt.getY() + 1) * gridy, gridx / 2, gridy, model.getColor(c));
                    }
                }
            }
        }
    }

    private void paintBead(Graphics g, int i, int j, int w, int h, Color color) {
        g.setColor(color);
        g.fillRect(i + 1, j + 1, w - 1, h - 1);
        g.setColor(Color.DARK_GRAY);
        g.drawRect(i, j, w, h);
    }

    private int gw(int i) {
        if (i != model.getWidth() && i != w) {
            return gridx;
        } else if (i == w) {
            return gridx / 2;
        } else {
            return gridx / 2;
        }
    }

    @Override
    public void redraw(Point pt) {
        if (!isVisible()) return;
        byte c = model.get(pt);
        pt = pt.unscrolled(scroll);
        int idx = pt.getX() + model.getWidth() * pt.getY() + model.getShift();
        pt = model.correct(model.getPoint(idx));

        Graphics g = getGraphics();
        int w = getVisibleWidth();
        if (pt.getX() > w && pt.getX() != model.getWidth()) return;
        if (pt.getX() == w && dx(pt.getY()) == 0) return;
        paintBead(g, x(pt.getX()) - dx(pt.getY()), y(pt.getY()), gw(pt.getX()), gridy, model.getColor(c));
        g.dispose();
    }

    @Override
    public void shiftChanged(int scroll) {
        repaint();
    }

    public Point mouseToField(Point pt) {
        int w = getVisibleWidth();
        int j = (getHeight() - pt.getY()) / gridy;
        if (pt.getX() < offsetx || pt.getX() > offsetx + w * gridx) return null;
        int i = (pt.getX() - offsetx + dx(j)) / gridx - model.getShift();
        if (i >= model.getWidth()) {
            i -= model.getWidth();
            j++;
        } else if (i < 0) {
            i += model.getWidth();
            j--;
        }
        return new Point(i, j);
    }

    public void togglePoint(Point pt) {
        pt = mouseToField(pt);
        if (pt == null) return;
        int idx = model.getCorrectedIndex(pt);
        pt = model.getPoint(idx);
        selection.clear();
        model.setPoint(pt.unscrolled(scroll));
    }

    public void fillLine(Point pt) {
        pt = mouseToField(pt);
        if (pt == null) return;
        int idx = model.getCorrectedIndex(pt);
        pt = model.getPoint(idx);
        selection.clear();
        model.fillLine(pt.unscrolled(scroll));
    }

    public void drawColorsChanged(boolean drawColors) {
        // empty
    }

    public void drawSymbolsChanged(boolean drawSymbols) {
        // empty
    }

}
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.util.*;
import javax.swing.Action;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.filechooser.FileFilter;
import ch.jbead.action.EditArrangeAction;
import ch.jbead.action.EditDeleteAction;
import ch.jbead.action.EditDeleteRowAction;
import ch.jbead.action.EditInsertRowAction;
import ch.jbead.action.EditMirrorHorizontalAction;
import ch.jbead.action.EditMirrorVerticalAction;
import ch.jbead.action.EditRedoAction;
import ch.jbead.action.EditRotateAction;
import ch.jbead.action.EditUndoAction;
import ch.jbead.action.FileExitAction;
import ch.jbead.action.FileMRUAction;
import ch.jbead.action.FileNewAction;
import ch.jbead.action.FileOpenAction;
import ch.jbead.action.FilePageSetupAction;
import ch.jbead.action.FilePrintAction;
import ch.jbead.action.FileSaveAction;
import ch.jbead.action.FileSaveAsAction;
import ch.jbead.action.InfoAboutAction;
import ch.jbead.action.InfoTechInfosAction;
import ch.jbead.action.InfoUpdateCheckAction;
import ch.jbead.action.PatternHeightAction;
import ch.jbead.action.PatternPreferencesAction;
import ch.jbead.action.PatternWidthAction;
import ch.jbead.action.ToolFillAction;
import ch.jbead.action.ToolPencilAction;
import ch.jbead.action.ToolPipetteAction;
import ch.jbead.action.ToolSelectAction;
import ch.jbead.action.ViewCorrectedAction;
import ch.jbead.action.ViewDraftAction;
import ch.jbead.action.ViewDrawColorsAction;
import ch.jbead.action.ViewDrawSymbolsAction;
import ch.jbead.action.ViewReportAction;
import ch.jbead.action.ViewSimulationAction;
import ch.jbead.action.ViewZoomInAction;
import ch.jbead.action.ViewZoomNormalAction;
import ch.jbead.action.ViewZoomOutAction;
import ch.jbead.dialog.ArrangeDialog;
import ch.jbead.fileformat.DbbFileFilter;
import ch.jbead.fileformat.DbbFileFormat;
import ch.jbead.fileformat.FileFormat;
import ch.jbead.fileformat.JBeadFileFilter;
import ch.jbead.fileformat.JBeadFileFormat;
import ch.jbead.fileformat.JbbFileFilter;
import ch.jbead.fileformat.Memento;
import ch.jbead.print.PrintSettings;
import ch.jbead.storage.JBeadFileFormatException;
import ch.jbead.ui.Button;
import ch.jbead.ui.ColorsToolbar;
import ch.jbead.ui.MRUMenuItem;
import ch.jbead.ui.RotateLeftButton;
import ch.jbead.ui.RotateRightButton;
import ch.jbead.ui.ToolButton;
import ch.jbead.ui.ToolMenuItem;
import ch.jbead.ui.ToolsGroup;
import ch.jbead.util.Platform;
import ch.jbead.version.Version;
import ch.jbead.version.VersionChecker;
import ch.jbead.version.VersionListener;
import ch.jbead.view.CorrectedPanel;
import ch.jbead.view.DraftPanel;
import ch.jbead.view.ReportPanel;
import ch.jbead.view.SimulationPanel;
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                if (canTerminateApp()) {
                    System.exit(0);
                }
            }
        });
    }

    public void registerAction(String name, Action action) {
        actions.put(name, action);
    }

    public void addListener(ViewListener listener) {
        listeners.add(listener);
    }

    public void fireDrawColorsChanged() {
        for (ViewListener listener : listeners) {
            listener.drawColorsChanged(viewDrawColors.isSelected());
        }
    }

    public void fireDrawSymbolsChanged() {
        for (ViewListener listener : listeners) {
            listener.drawSymbolsChanged(viewDrawSymbols.isSelected());
        }
    }

    public boolean isDrawColors() {
        return viewDrawColors.isSelected();
    }

    public boolean isDrawSymbols() {
        return viewDrawSymbols.isSelected();
    }

    public void clearSelection() {
        selection.clear();
    }

    public PrintSettings getPrintSettings() {
        return printSettings;
    }

    public ResourceBundle getBundle() {
        return bundle;
    }

    public String getString(String key) {
        return getBundle().getString(key);
    }

    public int getMnemonic(String key) {
        return getBundle().getString(key).charAt(0);
    }

    public KeyStroke getKeyStroke(String key) {
        return KeyStroke.getKeyStroke(getBundle().getString(key));
    }

    public Action getAction(String name) {
        return actions.get(name);
    }

    private void createGUI() {
        createMenu();
        setLayout(new BorderLayout());
        JPanel toolbars = new JPanel();
        toolbars.setLayout(new FlowLayout(FlowLayout.LEADING));
        toolbars.add(createToolbar());
        toolbars.add(colors = createColorbar());
        add(toolbars, BorderLayout.NORTH);
        add(main, BorderLayout.CENTER);
        createMainGUI();
    }

    private void createMenu() {
        JMenuBar menubar = new JMenuBar();
        menubar.add(createFileMenu());
        menubar.add(createEditMenu());
        menubar.add(createViewMenu());
        menubar.add(createToolMenu());
        menubar.add(createPatternMenu());
        menubar.add(createInfoMenu());
        setJMenuBar(menubar);
    }

    private JMenu createFileMenu() {
        JMenu menuFile = createMenu("action.file");
        menuFile.add(new FileNewAction(this));
        menuFile.add(new FileOpenAction(this));
        menuFile.add(new FileSaveAction(this));
        menuFile.add(new FileSaveAsAction(this));
        menuFile.addSeparator();
        menuFile.add(new JMenuItem(new FilePrintAction(this)));
        menuFile.add(new FilePageSetupAction(this));
        menuFile.addSeparator();
        menuFile.add(new MRUMenuItem(new FileMRUAction(this, 0)));
        menuFile.add(new MRUMenuItem(new FileMRUAction(this, 1)));
        menuFile.add(new MRUMenuItem(new FileMRUAction(this, 2)));
        menuFile.add(new MRUMenuItem(new FileMRUAction(this, 3)));
        menuFile.add(new MRUMenuItem(new FileMRUAction(this, 4)));
        menuFile.add(new MRUMenuItem(new FileMRUAction(this, 5)));
        menuFile.addSeparator();
        FileExitAction exitAction = new FileExitAction(this); // this registers the action
        if (!Platform.isMacOSX()) {
            menuFile.add(exitAction);
        }
        return menuFile;
    }

    private JMenu createEditMenu() {
        JMenu menuEdit = createMenu("action.edit");
        menuEdit.add(new EditUndoAction(this));
        menuEdit.add(new EditRedoAction(this));
        menuEdit.add(new EditArrangeAction(this));
        menuEdit.add(new EditMirrorHorizontalAction(this));
        menuEdit.add(new EditMirrorVerticalAction(this));
        menuEdit.add(new EditRotateAction(this));