001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.BorderLayout; 008import java.awt.Component; 009import java.awt.event.ActionEvent; 010import java.beans.PropertyChangeEvent; 011import java.beans.PropertyChangeListener; 012 013import javax.swing.AbstractAction; 014import javax.swing.Action; 015import javax.swing.JLabel; 016import javax.swing.JOptionPane; 017import javax.swing.JPanel; 018 019import org.openstreetmap.josm.Main; 020import org.openstreetmap.josm.data.osm.DefaultNameFormatter; 021import org.openstreetmap.josm.data.osm.OsmPrimitive; 022import org.openstreetmap.josm.gui.ExtendedDialog; 023import org.openstreetmap.josm.gui.MainApplication; 024import org.openstreetmap.josm.gui.conflict.pair.ConflictResolver; 025import org.openstreetmap.josm.gui.help.HelpBrowser; 026import org.openstreetmap.josm.gui.help.HelpUtil; 027import org.openstreetmap.josm.tools.ImageProvider; 028 029/** 030 * This is an extended dialog for resolving conflict between {@link OsmPrimitive}s. 031 * @since 1622 032 */ 033public class ConflictResolutionDialog extends ExtendedDialog implements PropertyChangeListener { 034 /** the conflict resolver component */ 035 private final ConflictResolver resolver = new ConflictResolver(); 036 private final JLabel titleLabel = new JLabel("", null, JLabel.CENTER); 037 038 private final ApplyResolutionAction applyResolutionAction = new ApplyResolutionAction(); 039 040 private boolean isRegistered; 041 042 /** 043 * Constructs a new {@code ConflictResolutionDialog}. 044 * @param parent parent component 045 */ 046 public ConflictResolutionDialog(Component parent) { 047 // We define our own actions, but need to give a hint about number of buttons 048 super(parent, tr("Resolve conflicts"), null, null, null); 049 setDefaultButton(1); 050 setCancelButton(2); 051 build(); 052 pack(); 053 if (getInsets().top > 0) { 054 titleLabel.setVisible(false); 055 } 056 } 057 058 @Override 059 public void removeNotify() { 060 super.removeNotify(); 061 unregisterListeners(); 062 } 063 064 @Override 065 public void addNotify() { 066 super.addNotify(); 067 registerListeners(); 068 } 069 070 private synchronized void registerListeners() { 071 if (!isRegistered) { 072 resolver.addPropertyChangeListener(applyResolutionAction); 073 resolver.registerListeners(); 074 isRegistered = true; 075 } 076 } 077 078 private synchronized void unregisterListeners() { 079 // See #13479 - See https://bugs.openjdk.java.net/browse/JDK-4387314 080 // Owner window keep a list of owned windows, and does not remove the references when the child is disposed. 081 // There's no easy way to remove ourselves from this list, so we must keep track of register state 082 if (isRegistered) { 083 resolver.removePropertyChangeListener(applyResolutionAction); 084 resolver.unregisterListeners(); 085 isRegistered = false; 086 } 087 } 088 089 /** 090 * builds the GUI 091 */ 092 protected void build() { 093 JPanel p = new JPanel(new BorderLayout()); 094 095 p.add(titleLabel, BorderLayout.NORTH); 096 097 updateTitle(); 098 099 resolver.setName("panel.conflictresolver"); 100 p.add(resolver, BorderLayout.CENTER); 101 102 resolver.addPropertyChangeListener(this); 103 HelpUtil.setHelpContext(this.getRootPane(), ht("Dialog/Conflict")); 104 105 setContent(p, false); 106 } 107 108 @Override 109 protected Action createButtonAction(int i) { 110 switch (i) { 111 case 0: return applyResolutionAction; 112 case 1: return new CancelAction(); 113 case 2: return new HelpAction(); 114 default: return super.createButtonAction(i); 115 } 116 } 117 118 /** 119 * Replies the conflict resolver component. 120 * @return the conflict resolver component 121 */ 122 public ConflictResolver getConflictResolver() { 123 return resolver; 124 } 125 126 /** 127 * Action for canceling conflict resolution 128 */ 129 class CancelAction extends AbstractAction { 130 CancelAction() { 131 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution and close the dialog")); 132 putValue(Action.NAME, tr("Cancel")); 133 new ImageProvider("cancel").getResource().attachImageIcon(this); 134 setEnabled(true); 135 } 136 137 @Override 138 public void actionPerformed(ActionEvent evt) { 139 buttonAction(2, evt); 140 } 141 } 142 143 /** 144 * Action for canceling conflict resolution 145 */ 146 static class HelpAction extends AbstractAction { 147 HelpAction() { 148 putValue(Action.SHORT_DESCRIPTION, tr("Show help information")); 149 putValue(Action.NAME, tr("Help")); 150 new ImageProvider("help").getResource().attachImageIcon(this); 151 setEnabled(true); 152 } 153 154 @Override 155 public void actionPerformed(ActionEvent evt) { 156 HelpBrowser.setUrlForHelpTopic(ht("/Dialog/Conflict")); 157 } 158 } 159 160 /** 161 * Action for applying resolved differences in a conflict 162 * 163 */ 164 class ApplyResolutionAction extends AbstractAction implements PropertyChangeListener { 165 ApplyResolutionAction() { 166 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts and close the dialog")); 167 putValue(Action.NAME, tr("Apply Resolution")); 168 new ImageProvider("dialogs", "conflict").getResource().attachImageIcon(this); 169 updateEnabledState(); 170 } 171 172 protected void updateEnabledState() { 173 setEnabled(resolver.isResolvedCompletely()); 174 } 175 176 @Override 177 public void actionPerformed(ActionEvent evt) { 178 if (!resolver.isResolvedCompletely()) { 179 Object[] options = { 180 tr("Close anyway"), 181 tr("Continue resolving")}; 182 int ret = JOptionPane.showOptionDialog(Main.parent, 183 tr("<html>You did not finish to merge the differences in this conflict.<br>" 184 + "Conflict resolutions will not be applied unless all differences<br>" 185 + "are resolved.<br>" 186 + "Click <strong>{0}</strong> to close anyway.<strong> Already<br>" 187 + "resolved differences will not be applied.</strong><br>" 188 + "Click <strong>{1}</strong> to return to resolving conflicts.</html>", 189 options[0].toString(), options[1].toString() 190 ), 191 tr("Conflict not resolved completely"), 192 JOptionPane.YES_NO_OPTION, 193 JOptionPane.WARNING_MESSAGE, 194 null, 195 options, 196 options[1] 197 ); 198 switch(ret) { 199 case JOptionPane.YES_OPTION: 200 buttonAction(1, evt); 201 break; 202 default: 203 return; 204 } 205 } 206 MainApplication.undoRedo.add(resolver.buildResolveCommand()); 207 buttonAction(1, evt); 208 } 209 210 @Override 211 public void propertyChange(PropertyChangeEvent evt) { 212 if (evt.getPropertyName().equals(ConflictResolver.RESOLVED_COMPLETELY_PROP)) { 213 updateEnabledState(); 214 } 215 } 216 } 217 218 protected void updateTitle() { 219 updateTitle(null); 220 } 221 222 protected void updateTitle(OsmPrimitive my) { 223 if (my == null) { 224 setTitle(tr("Resolve conflicts")); 225 } else { 226 setTitle(tr("Resolve conflicts for ''{0}''", my.getDisplayName(DefaultNameFormatter.getInstance()))); 227 } 228 } 229 230 @Override 231 public void setTitle(String title) { 232 super.setTitle(title); 233 if (titleLabel != null) { 234 titleLabel.setText(title); 235 } 236 } 237 238 @Override 239 public void propertyChange(PropertyChangeEvent evt) { 240 if (evt.getPropertyName().equals(ConflictResolver.MY_PRIMITIVE_PROP)) { 241 updateTitle((OsmPrimitive) evt.getNewValue()); 242 } 243 } 244}