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