001/*
002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
003 * Copyright (C) 2000-2011 The Gridarta Developers.
004 *
005 * This program is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 2 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License along
016 * with this program; if not, write to the Free Software Foundation, Inc.,
017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
018 */
019
020package net.sf.gridarta.gui.dialog.replace;
021
022import java.awt.Component;
023import java.awt.Container;
024import java.awt.FlowLayout;
025import java.awt.event.ItemEvent;
026import java.awt.event.ItemListener;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Iterator;
030import java.util.List;
031import javax.swing.BorderFactory;
032import javax.swing.Box;
033import javax.swing.BoxLayout;
034import javax.swing.Icon;
035import javax.swing.JButton;
036import javax.swing.JComboBox;
037import javax.swing.JComponent;
038import javax.swing.JDialog;
039import javax.swing.JLabel;
040import javax.swing.JOptionPane;
041import javax.swing.JPanel;
042import javax.swing.JTextField;
043import javax.swing.WindowConstants;
044import javax.swing.text.JTextComponent;
045import net.sf.gridarta.gui.copybuffer.CopyBuffer;
046import net.sf.gridarta.gui.map.mapview.MapView;
047import net.sf.gridarta.gui.panel.objectchooser.ObjectChooser;
048import net.sf.gridarta.gui.panel.objectchooser.ObjectChooserListener;
049import net.sf.gridarta.model.archetype.Archetype;
050import net.sf.gridarta.model.baseobject.BaseObject;
051import net.sf.gridarta.model.face.FaceObjectProviders;
052import net.sf.gridarta.model.face.FaceObjectProvidersListener;
053import net.sf.gridarta.model.gameobject.GameObject;
054import net.sf.gridarta.model.maparchobject.MapArchObject;
055import net.sf.gridarta.model.mapcontrol.MapControl;
056import net.sf.gridarta.model.mapmodel.InsertionModeSet;
057import net.sf.gridarta.model.mapmodel.MapModel;
058import net.sf.gridarta.model.mapmodel.MapSquare;
059import net.sf.gridarta.model.select.ArchetypeNameMatchCriteria;
060import net.sf.gridarta.model.select.MatchCriteria;
061import net.sf.gridarta.model.select.ObjectNameMatchCriteria;
062import net.sf.gridarta.utils.ActionBuilderUtils;
063import net.sf.gridarta.utils.RandomUtils;
064import net.sf.japi.swing.action.ActionBuilder;
065import net.sf.japi.swing.action.ActionBuilderFactory;
066import net.sf.japi.swing.action.ActionMethod;
067import org.jetbrains.annotations.NotNull;
068import org.jetbrains.annotations.Nullable;
069
070/**
071 * This dialog manages the replace action.
072 * @author <a href="mailto:andi.vogl@gmx.net">Andreas Vogl</a>
073 * @author <a href="mailto:cher@riedquat.de">Christian.Hujer</a>
074 */
075public class ReplaceDialog<G extends GameObject<G, A, R>, A extends MapArchObject<A>, R extends Archetype<G, A, R>> extends JOptionPane {
076
077    /**
078     * Serial Version UID.
079     */
080    private static final long serialVersionUID = 1L;
081
082    /**
083     * Action Builder.
084     */
085    @NotNull
086    private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
087
088    /**
089     * Index for {@link #replaceWithBox}: replace with object chooser
090     * selection.
091     */
092    private static final int REPLACE_WITH_OBJECT_CHOOSER = 0;
093
094    /**
095     * Index for {@link #replaceWithBox}: replace with copy buffer contents.
096     */
097    private static final int REPLACE_WITH_COPY_BUFFER = 1;
098
099    /**
100     * Index for {@link #replaceWithBox}: replace with pickmap contents.
101     */
102    private static final int REPLACE_WITH_PICKMAP = 2;
103
104    /**
105     * Index for {@link #replaceWithBox}: delete matching game objects.
106     */
107    private static final int REPLACE_WITH_NOTHING = 3;
108
109    /**
110     * Index for {@link #replaceEntireBox}: replace map.
111     */
112    private static final int REPLACE_ON_MAP = 0;
113
114    /**
115     * Index for {@link #replaceEntireBox}: replace selection.
116     */
117    private static final int REPLACE_ON_SELECTION = 1;
118
119    /**
120     * The {@link InsertionModeSet} to use.
121     */
122    @NotNull
123    private final InsertionModeSet<G, A, R> insertionModeSet;
124
125    /**
126     * The dialog instance.
127     */
128    @NotNull
129    private final JDialog dialog;
130
131    /**
132     * The parent component for dialogs.
133     */
134    @NotNull
135    private final Component parent;
136
137    /**
138     * The {@link CopyBuffer}.
139     */
140    @NotNull
141    private final CopyBuffer<G, A, R> copyBuffer;
142
143    /**
144     * The object chooser to use.
145     */
146    @NotNull
147    private final ObjectChooser<G, A, R> objectChooser;
148
149    /**
150     * The {@link FaceObjectProviders} for looking up faces.
151     */
152    @NotNull
153    private final FaceObjectProviders faceObjectProviders;
154
155    /**
156     * Whether this replace dialog has been displayed.
157     */
158    private boolean isBuilt;
159
160    /**
161     * The {@link MapView} to operate on.
162     */
163    @NotNull
164    private MapView<G, A, R> mapView;
165
166    /**
167     * Objects will be replaced with this game object.
168     */
169    @Nullable
170    private BaseObject<G, A, R, ?> replaceArch;
171
172    @NotNull
173    private List<G> replaceCopyBuffer; // objects in CopyBuffer
174
175    @NotNull
176    private List<? extends BaseObject<G, A, R, ?>> replacePickmap; // selected objects in pickmap or all if none is selected
177
178    @NotNull
179    private JLabel rfHeading;
180
181    @Nullable
182    private JLabel rfArchName;
183
184    @NotNull
185    private JLabel iconLabel;
186
187    @NotNull
188    private JLabel colonLabel;
189
190    @NotNull
191    private JComboBox replaceCriteria;
192
193    @NotNull
194    private JComboBox replaceWithBox;
195
196    @NotNull
197    private JComboBox replaceEntireBox;
198
199    @NotNull
200    private JTextComponent replaceInput1;
201
202    /**
203     * Input field for replace density value.
204     */
205    @NotNull
206    private JTextComponent replaceDensityInput;
207
208    private int lastSelectedIndex = REPLACE_WITH_OBJECT_CHOOSER;
209
210    /**
211     * The {@link ObjectChooserListener} for tracking the selection.
212     */
213    @NotNull
214    private final ObjectChooserListener<G, A, R> objectChooserListener = new ObjectChooserListener<G, A, R>() {
215
216        @Override
217        public void pickmapActiveChanged(final boolean pickmapActive) {
218            // ignore
219        }
220
221        @Override
222        public void selectionChanged(@Nullable final BaseObject<G, A, R, ?> gameObject) {
223            updateArchSelection(gameObject, false);
224        }
225
226    };
227
228    /**
229     * The {@link FaceObjectProvidersListener} for detecting reloaded faces.
230     */
231    @NotNull
232    private final FaceObjectProvidersListener faceObjectProvidersListener = new FaceObjectProvidersListener() {
233
234        @Override
235        public void facesReloaded() {
236            updateArchSelection(objectChooser.getSelection(), false);
237        }
238
239    };
240
241    /**
242     * Creates a new instance.
243     * @param parent the parent component for dialogs
244     * @param copyBuffer the copy buffer's
245     * @param objectChooser the object chooser to use
246     * @param faceObjectProviders the face object providers for looking up
247     * faces
248     * @param insertionModeSet the insertion mode set to use
249     */
250    public ReplaceDialog(@NotNull final Component parent, @NotNull final CopyBuffer<G, A, R> copyBuffer, @NotNull final ObjectChooser<G, A, R> objectChooser, @NotNull final FaceObjectProviders faceObjectProviders, @NotNull final InsertionModeSet<G, A, R> insertionModeSet) {
251        this.insertionModeSet = insertionModeSet;
252        dialog = createDialog(parent, ActionBuilderUtils.getString(ACTION_BUILDER, "replaceTitle"));
253        dialog.setModal(false);
254        dialog.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
255        this.parent = parent;
256        this.copyBuffer = copyBuffer;
257        this.objectChooser = objectChooser;
258        this.faceObjectProviders = faceObjectProviders;
259        objectChooser.addObjectChooserListener(objectChooserListener);
260        faceObjectProviders.addFaceObjectProvidersListener(faceObjectProvidersListener);
261    }
262
263    /**
264     * Replace objects on the map.
265     * @param mapView map view of the active map where the action was invoked
266     */
267    public void display(@NotNull final MapView<G, A, R> mapView) {
268        replaceArch = objectChooser.getCursorSelection(); // highlighted arch
269        replacePickmap = objectChooser.getSelections(); // selected arches
270        replaceCopyBuffer = copyBuffer.getAllGameObjects();
271
272        if (isBuilt) {
273            // just set fields and show
274            rfHeading.setText("\"" + mapView.getMapControl().getMapModel().getMapArchObject().getMapName() + "\":");
275            replaceInput1.setText("");
276
277            this.mapView = mapView;
278            if (replaceArch == null) {
279                replaceWithBox.setSelectedIndex(REPLACE_WITH_COPY_BUFFER);
280                iconLabel.setIcon(null);
281                rfArchName.setText("");
282                colonLabel.setText("");
283            } else {
284                replaceWithBox.setSelectedIndex(REPLACE_WITH_OBJECT_CHOOSER);
285                iconLabel.setIcon(faceObjectProviders.getFace(replaceArch));
286                rfArchName.setText(" " + replaceArch.getBestName());
287                colonLabel.setText(":");
288            }
289
290            if (mapView.getSelectedSquares().size() > 1) {
291                replaceEntireBox.setSelectedIndex(REPLACE_ON_SELECTION);
292            } else {
293                replaceEntireBox.setSelectedIndex(REPLACE_ON_MAP);
294            }
295
296            replaceDensityInput.setText("100");
297
298            dialog.pack();
299            dialog.toFront();
300        } else {
301            this.mapView = mapView;
302            final JPanel mainPanel = new JPanel();
303            mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
304            mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 2, 5));
305
306            // first line: heading
307            final Container line1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
308            final JComponent labelOn = ActionBuilderUtils.newLabel(ACTION_BUILDER, "replaceOn");
309            labelOn.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceOn.shortdescription"));
310            line1.add(labelOn);
311            line1.add(Box.createVerticalStrut(3));
312            replaceEntireBox = new JComboBox(new String[] { ActionBuilderUtils.getString(ACTION_BUILDER, "replaceOnMap"), ActionBuilderUtils.getString(ACTION_BUILDER, "replaceOnSelection") });
313            replaceEntireBox.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceOn.shortdescription"));
314            if (mapView.getSelectedSquares().size() > 1) {
315                replaceEntireBox.setSelectedIndex(REPLACE_ON_SELECTION);
316            } else {
317                replaceEntireBox.setSelectedIndex(REPLACE_ON_MAP);
318            }
319            line1.add(replaceEntireBox);
320            line1.add(Box.createVerticalStrut(3));
321            rfHeading = new JLabel("\"" + mapView.getMapControl().getMapModel().getMapArchObject().getMapName() + "\":");
322            line1.add(rfHeading);
323            mainPanel.add(line1);
324
325            // second line: replace what?
326            final Container line2 = new JPanel(new FlowLayout(FlowLayout.LEFT));
327            final JComponent label1 = ActionBuilderUtils.newLabel(ACTION_BUILDER, "replaceDelete");
328            label1.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceDelete.shortdescription"));
329            line2.add(label1);
330            line2.add(Box.createVerticalStrut(5));
331
332            replaceCriteria = new JComboBox(new String[] { ActionBuilderUtils.getString(ACTION_BUILDER, "replaceArchetype"), ActionBuilderUtils.getString(ACTION_BUILDER, "replaceName") });
333            replaceCriteria.setSelectedIndex(0);
334            replaceCriteria.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceDelete.shortdescription"));
335            line2.add(replaceCriteria);
336            line2.add(Box.createVerticalStrut(5));
337
338            replaceInput1 = new JTextField(20);
339            replaceInput1.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceInput1.shortdescription"));
340            line2.add(replaceInput1);
341            mainPanel.add(line2);
342
343            // third line: replace by?
344            final Container line3 = new JPanel(new FlowLayout(FlowLayout.LEFT));
345            final JComponent label2 = ActionBuilderUtils.newLabel(ACTION_BUILDER, "replaceBy");
346            label2.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceBy.shortdescription"));
347            line3.add(label2);
348            line3.add(Box.createVerticalStrut(5));
349            replaceWithBox = new JComboBox(new String[] { ActionBuilderUtils.getString(ACTION_BUILDER, "replaceByObject"), ActionBuilderUtils.getString(ACTION_BUILDER, "replaceByCopyBuffer"), ActionBuilderUtils.getString(ACTION_BUILDER, "replaceByPickmap"), ActionBuilderUtils.getString(ACTION_BUILDER, "replaceByNothing") });
350            replaceWithBox.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceBy.shortdescription"));
351            //noinspection VariableNotUsedInsideIf
352            if (replaceArch == null) {
353                replaceWithBox.setSelectedIndex(REPLACE_WITH_NOTHING);
354            } else {
355                replaceWithBox.setSelectedIndex(REPLACE_WITH_OBJECT_CHOOSER);
356            }
357            replaceWithBox.addItemListener(new ReplaceWithBoxItemListener());
358            lastSelectedIndex = replaceWithBox.getSelectedIndex();
359            line3.add(replaceWithBox);
360
361            iconLabel = new JLabel();
362            if (replaceArch != null) {
363                colonLabel = new JLabel(":");
364                iconLabel.setIcon(faceObjectProviders.getFace(replaceArch));
365                rfArchName = new JLabel(" " + replaceArch.getBestName());
366            } else {
367                colonLabel = new JLabel("");
368                rfArchName = new JLabel("");
369            }
370            line3.add(colonLabel);
371            line3.add(Box.createVerticalStrut(5));
372            line3.add(iconLabel);
373            line3.add(rfArchName);
374            mainPanel.add(line3);
375
376            // fourth line: replace density
377            final Container line4 = new JPanel(new FlowLayout(FlowLayout.LEFT));
378            final JComponent label4 = ActionBuilderUtils.newLabel(ACTION_BUILDER, "replaceDensity");
379            label4.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceDensity.shortdescription"));
380            line4.add(label4);
381            line4.add(Box.createVerticalStrut(5));
382            replaceDensityInput = new JTextField(5);
383            replaceDensityInput.setToolTipText(ActionBuilderUtils.getString(ACTION_BUILDER, "replaceDensity.shortdescription"));
384            line4.add(replaceDensityInput);
385            replaceDensityInput.setText("100");
386            mainPanel.add(line4);
387
388            final JButton okButton = new JButton(ACTION_BUILDER.createAction(false, "replaceOk", this));
389            final JButton applyButton = new JButton(ACTION_BUILDER.createAction(false, "replaceApply", this));
390            final JButton cancelButton = new JButton(ACTION_BUILDER.createAction(false, "replaceCancel", this));
391
392            setMessage(mainPanel);
393            setOptions(new Object[] { okButton, applyButton, cancelButton });
394            dialog.getRootPane().setDefaultButton(okButton);
395            dialog.pack();
396            dialog.setLocationRelativeTo(parent);
397            isBuilt = true;
398        }
399        dialog.setVisible(true);
400    }
401
402    /**
403     * Update which arch is displayed as replace object.
404     * @param newArch the new 'replaceArch' to be shown and stored
405     * @param alwaysPack if false, the frame is packed only when icon size
406     * changed if true, the frame is always packed (packing resizes but also
407     * causes flicker)
408     */
409    private void updateArchSelection(@Nullable final BaseObject<G, A, R, ?> newArch, final boolean alwaysPack) {
410        if (isShowing() && replaceWithBox.getSelectedIndex() == REPLACE_WITH_OBJECT_CHOOSER) {
411            replaceArch = newArch;
412            if (newArch != null) {
413                final Icon oldIcon = iconLabel.getIcon();
414
415                iconLabel.setIcon(faceObjectProviders.getFace(newArch));
416                rfArchName.setText(" " + newArch.getBestName());
417                colonLabel.setText(":");
418
419                // pack frame only if height of icon changed
420                if (alwaysPack || (oldIcon == null && iconLabel.getIcon() != null) || (oldIcon != null && iconLabel.getIcon() == null) || (oldIcon != iconLabel.getIcon() && oldIcon != null && oldIcon.getIconHeight() != iconLabel.getIcon().getIconHeight())) {
421                    dialog.pack();
422                }
423            } else {
424                iconLabel.setIcon(null);
425                rfArchName.setText("");
426                colonLabel.setText("");
427            }
428        }
429    }
430
431    /**
432     * This method performs the actual replace action on a map.
433     * @param matchCriteria matching criteria for replace
434     * @param entireMap if true, the entire map is affected - if false, only
435     * highlighted area
436     * @param deleteOnly if true matching arches get only deleted and not
437     * replaced
438     * @param replaceDensity the replace density in %
439     * @return number of arches that have been replaced
440     */
441    private int doReplace(@NotNull final MatchCriteria<G, A, R> matchCriteria, final boolean entireMap, final boolean deleteOnly, final int replaceDensity) {
442        @Nullable final List<? extends BaseObject<G, A, R, ?>> replaceList;
443        switch (lastSelectedIndex) {
444        case REPLACE_WITH_OBJECT_CHOOSER:
445            if (replaceArch == null) {
446                replaceList = null;
447            } else {
448                replaceList = newList(replaceArch);
449            }
450            break;
451
452        case REPLACE_WITH_COPY_BUFFER:
453            replaceList = replaceCopyBuffer;
454            break;
455
456        case REPLACE_WITH_PICKMAP:
457            replaceList = replacePickmap;
458            break;
459
460        default: // REPLACE_WITH_NOTHING
461            replaceList = null;
462            break;
463        }
464        final Collection<G> objectsToReplace = new ArrayList<G>();
465        final int replaceListSize = replaceList == null ? 0 : replaceList.size();
466        final MapControl<G, A, R> mapControl = mapView.getMapControl();
467        final MapModel<G, A, R> mapModel = mapControl.getMapModel();
468        mapModel.beginTransaction("Replace"); // TODO: I18N/L10N
469        try {
470            int replaceCount = 0;
471            for (final MapSquare<G, A, R> square : entireMap ? mapModel : mapView.getSelectedSquares()) {
472                // Operate on a copy of the nodes to prevent ConcurrentModificationException
473
474                // find objects to replace
475                objectsToReplace.clear();
476                for (final G node : square) {
477                    if (node.isHead() && matchCriteria.matches(node)) {
478                        if (replaceDensity > RandomUtils.rnd.nextInt(100)) {
479                            objectsToReplace.add(node);
480                        }
481                    }
482                }
483
484                // actually replace the objects
485                for (final G objectToReplace : objectsToReplace) {
486                    final Iterator<G> it = square.iterator();
487                    G prevArch = null;
488                    G node = null;
489                    while (it.hasNext()) {
490                        node = it.next();
491
492                        if (node == objectToReplace) {
493                            break;
494                        }
495
496                        prevArch = node;
497                    }
498                    assert node != null;
499
500                    // first, delete the old arch
501                    node.remove();
502
503                    if (replaceListSize > 0 && !deleteOnly) {
504                        final BaseObject<G, A, R, ?> randomArch;
505                        if (replaceListSize == 1) {
506                            randomArch = replaceList.get(0);
507                        } else {
508                            randomArch = replaceList.get(RandomUtils.rnd.nextInt(replaceList.size()));
509                        }
510                        // insert replacement object
511                        if (randomArch.isMulti()) {
512                            mapModel.insertBaseObject(randomArch, square.getMapLocation(), false, false, insertionModeSet.getTopmostInsertionMode());
513                        } else {
514                            mapModel.insertArchToMap(randomArch, prevArch, square.getMapLocation(), false);
515                        }
516                    }
517                    replaceCount++;
518                }
519            }
520            return replaceCount;
521        } finally {
522            mapModel.endTransaction();
523        }
524    }
525
526    /**
527     * Creates a new list containing one element.
528     * @param element the element
529     * @return the list
530     */
531    @NotNull
532    private static <T> List<T> newList(@NotNull final T element) {
533        final List<T> list = new ArrayList<T>(1);
534        list.add(element);
535        return list;
536    }
537
538    /**
539     * Item-listener for the "replace with"-selection box.
540     */
541    private class ReplaceWithBoxItemListener implements ItemListener {
542
543        @Override
544        public void itemStateChanged(@NotNull final ItemEvent e) {
545            final int selectedIndex = replaceWithBox.getSelectedIndex();
546            if (e.getStateChange() == ItemEvent.SELECTED && lastSelectedIndex != selectedIndex) {
547                final int size;
548                switch (selectedIndex) {
549                case REPLACE_WITH_OBJECT_CHOOSER:
550                    replaceArch = objectChooser.getSelection(); // selected arch
551                    updateArchSelection(replaceArch, true);
552                    break;
553
554                case REPLACE_WITH_COPY_BUFFER:
555                    replaceCopyBuffer = copyBuffer.getAllGameObjects();
556                    iconLabel.setIcon(null);
557                    size = replaceCopyBuffer.size();
558                    rfArchName.setText(String.valueOf(size));
559                    colonLabel.setText(":");
560                    dialog.pack();
561                    break;
562
563                case REPLACE_WITH_PICKMAP:
564                    replacePickmap = objectChooser.getSelections(); // selected arches
565                    iconLabel.setIcon(null);
566                    size = replacePickmap.size();
567                    rfArchName.setText(String.valueOf(size));
568                    colonLabel.setText(":");
569                    dialog.pack();
570                    break;
571
572                case REPLACE_WITH_NOTHING:
573                    iconLabel.setIcon(null);
574                    rfArchName.setText("");
575                    colonLabel.setText("");
576                    dialog.pack();
577                    break;
578                }
579                lastSelectedIndex = selectedIndex;
580            }
581        }
582
583    }
584
585    /**
586     * Action method for Ok button.
587     */
588    @ActionMethod
589    public void replaceOk() {
590        if (doReplace()) {
591            dialog.setVisible(false);
592        }
593    }
594
595    /**
596     * Action method for Apply button.
597     */
598    @ActionMethod
599    public void replaceApply() {
600        doReplace();
601    }
602
603    /**
604     * Executes one replace operation.
605     * @return whether the replace operation was successful
606     */
607    private boolean doReplace() {
608        final String matchString = replaceInput1.getText().trim();
609        final boolean deleteOnly = replaceWithBox.getSelectedIndex() == REPLACE_WITH_NOTHING;
610        final boolean entireMap = replaceEntireBox.getSelectedIndex() == REPLACE_ON_MAP;
611
612        if (!entireMap && mapView.getMapGrid().getSelectedRec() == null) {
613            // user selected "replace highlighted" but nothing is highlighted
614            ACTION_BUILDER.showMessageDialog(this, "replaceMapNoSelection", mapView.getMapControl().getMapModel().getMapArchObject().getMapName());
615            return false;
616        }
617
618        final MatchCriteria<G, A, R> matchCriteria;
619        if (replaceCriteria.getSelectedIndex() == 0) {
620            matchCriteria = new ArchetypeNameMatchCriteria<G, A, R>(matchString);
621        } else if (replaceCriteria.getSelectedIndex() == 1) {
622            matchCriteria = new ObjectNameMatchCriteria<G, A, R>(matchString);
623        } else {
624            return false;
625        }
626
627        final int replaceDensity;
628        try {
629            replaceDensity = Integer.parseInt(replaceDensityInput.getText());
630        } catch (final NumberFormatException ignored) {
631            ACTION_BUILDER.showMessageDialog(this, "replaceInvalidDensity");
632            return false;
633        }
634        if (replaceDensity < 1 || replaceDensity > 100) {
635            ACTION_BUILDER.showMessageDialog(this, "replaceInvalidDensity");
636            return false;
637        }
638
639        final int replaceCount = doReplace(matchCriteria, entireMap, deleteOnly, replaceDensity);
640        if (replaceCount <= 0) {
641            ACTION_BUILDER.showMessageDialog(this, "replacedZero");
642            return false;
643        }
644
645        if (replaceCount == 1) {
646            ACTION_BUILDER.showMessageDialog(this, "replacedOne");
647        } else {
648            ACTION_BUILDER.showMessageDialog(this, "replacedMany", replaceCount);
649        }
650        return true;
651    }
652
653    /**
654     * Action method for Cancel button.
655     */
656    @ActionMethod
657    public void replaceCancel() {
658        dialog.setVisible(false);
659    }
660
661    /**
662     * Disposes the replace dialog.
663     * @param mapView the map view to dispose the dialog of; do nothing if no
664     */
665    public void dispose(@NotNull final MapView<G, A, R> mapView) {
666        if (mapView == this.mapView) {
667            dialog.setVisible(false);
668        }
669    }
670
671}