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.Dimension; 010import java.awt.FlowLayout; 011import java.awt.event.ActionEvent; 012import java.beans.PropertyChangeEvent; 013import java.beans.PropertyChangeListener; 014 015import javax.swing.AbstractAction; 016import javax.swing.Action; 017import javax.swing.BorderFactory; 018import javax.swing.JButton; 019import javax.swing.JDialog; 020import javax.swing.JLabel; 021import javax.swing.JOptionPane; 022import javax.swing.JPanel; 023import javax.swing.WindowConstants; 024 025import org.openstreetmap.josm.Main; 026import org.openstreetmap.josm.command.Command; 027import org.openstreetmap.josm.data.osm.OsmPrimitive; 028import org.openstreetmap.josm.gui.DefaultNameFormatter; 029import org.openstreetmap.josm.gui.conflict.pair.ConflictResolver; 030import org.openstreetmap.josm.gui.help.HelpBrowser; 031import org.openstreetmap.josm.gui.help.HelpUtil; 032import org.openstreetmap.josm.tools.ImageProvider; 033import org.openstreetmap.josm.tools.WindowGeometry; 034 035/** 036 * This is an extended dialog for resolving conflict between {@link OsmPrimitive}s. 037 * @since 1622 038 */ 039public class ConflictResolutionDialog extends JDialog implements PropertyChangeListener { 040 /** the conflict resolver component */ 041 private ConflictResolver resolver; 042 private JLabel titleLabel; 043 044 private ApplyResolutionAction applyResolutionAction; 045 046 @Override 047 public void removeNotify() { 048 super.removeNotify(); 049 unregisterListeners(); 050 } 051 052 @Override 053 public void setVisible(boolean isVisible) { 054 String geom = getClass().getName() + ".geometry"; 055 if (isVisible) { 056 toFront(); 057 new WindowGeometry(geom, WindowGeometry.centerInWindow(Main.parent, 058 new Dimension(600, 400))).applySafe(this); 059 } else { 060 if (isShowing()) { // Avoid IllegalComponentStateException like in #8775 061 new WindowGeometry(this).remember(geom); 062 } 063 unregisterListeners(); 064 } 065 super.setVisible(isVisible); 066 } 067 068 private void closeDialog() { 069 setVisible(false); 070 dispose(); 071 } 072 073 /** 074 * builds the sub panel with the control buttons 075 * 076 * @return the panel 077 */ 078 protected JPanel buildButtonRow() { 079 JPanel pnl = new JPanel(); 080 pnl.setLayout(new FlowLayout(FlowLayout.CENTER)); 081 082 applyResolutionAction = new ApplyResolutionAction(); 083 JButton btn = new JButton(applyResolutionAction); 084 btn.setName("button.apply"); 085 pnl.add(btn); 086 087 btn = new JButton(new CancelAction()); 088 btn.setName("button.cancel"); 089 pnl.add(btn); 090 091 btn = new JButton(new HelpAction()); 092 btn.setName("button.help"); 093 pnl.add(btn); 094 095 pnl.setBorder(BorderFactory.createLoweredBevelBorder()); 096 return pnl; 097 } 098 099 private void registerListeners() { 100 resolver.addPropertyChangeListener(applyResolutionAction); 101 } 102 103 private void unregisterListeners() { 104 resolver.removePropertyChangeListener(applyResolutionAction); 105 resolver.unregisterListeners(); 106 } 107 108 /** 109 * builds the GUI 110 */ 111 protected void build() { 112 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 113 getContentPane().setLayout(new BorderLayout()); 114 115 titleLabel = new JLabel(); 116 titleLabel.setHorizontalAlignment(JLabel.CENTER); 117 getContentPane().add(titleLabel, BorderLayout.NORTH); 118 119 updateTitle(); 120 121 resolver = new ConflictResolver(); 122 resolver.setName("panel.conflictresolver"); 123 getContentPane().add(resolver, BorderLayout.CENTER); 124 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH); 125 126 resolver.addPropertyChangeListener(this); 127 HelpUtil.setHelpContext(this.getRootPane(), ht("Dialog/Conflict")); 128 129 registerListeners(); 130 } 131 132 /** 133 * Constructs a new {@code ConflictResolutionDialog}. 134 * @param parent parent component 135 */ 136 public ConflictResolutionDialog(Component parent) { 137 super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); 138 build(); 139 pack(); 140 if (getInsets().top > 0) { 141 titleLabel.setVisible(false); 142 } 143 } 144 145 /** 146 * Replies the conflict resolver component. 147 * @return the conflict resolver component 148 */ 149 public ConflictResolver getConflictResolver() { 150 return resolver; 151 } 152 153 /** 154 * Action for canceling conflict resolution 155 */ 156 class CancelAction extends AbstractAction { 157 CancelAction() { 158 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution and close the dialog")); 159 putValue(Action.NAME, tr("Cancel")); 160 putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel")); 161 setEnabled(true); 162 } 163 164 @Override 165 public void actionPerformed(ActionEvent arg0) { 166 closeDialog(); 167 } 168 } 169 170 /** 171 * Action for canceling conflict resolution 172 */ 173 static class HelpAction extends AbstractAction { 174 HelpAction() { 175 putValue(Action.SHORT_DESCRIPTION, tr("Show help information")); 176 putValue(Action.NAME, tr("Help")); 177 putValue(Action.SMALL_ICON, ImageProvider.get("help")); 178 setEnabled(true); 179 } 180 181 @Override 182 public void actionPerformed(ActionEvent arg0) { 183 HelpBrowser.setUrlForHelpTopic(ht("/Dialog/Conflict")); 184 } 185 } 186 187 /** 188 * Action for applying resolved differences in a conflict 189 * 190 */ 191 class ApplyResolutionAction extends AbstractAction implements PropertyChangeListener { 192 ApplyResolutionAction() { 193 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts and close the dialog")); 194 putValue(Action.NAME, tr("Apply Resolution")); 195 putValue(Action.SMALL_ICON, ImageProvider.get("dialogs", "conflict")); 196 updateEnabledState(); 197 } 198 199 protected void updateEnabledState() { 200 setEnabled(resolver.isResolvedCompletely()); 201 } 202 203 @Override 204 public void actionPerformed(ActionEvent arg0) { 205 if (!resolver.isResolvedCompletely()) { 206 Object[] options = { 207 tr("Close anyway"), 208 tr("Continue resolving")}; 209 int ret = JOptionPane.showOptionDialog(Main.parent, 210 tr("<html>You did not finish to merge the differences in this conflict.<br>" 211 + "Conflict resolutions will not be applied unless all differences<br>" 212 + "are resolved.<br>" 213 + "Click <strong>{0}</strong> to close anyway.<strong> Already<br>" 214 + "resolved differences will not be applied.</strong><br>" 215 + "Click <strong>{1}</strong> to return to resolving conflicts.</html>", 216 options[0].toString(), options[1].toString() 217 ), 218 tr("Conflict not resolved completely"), 219 JOptionPane.YES_NO_OPTION, 220 JOptionPane.WARNING_MESSAGE, 221 null, 222 options, 223 options[1] 224 ); 225 switch(ret) { 226 case JOptionPane.YES_OPTION: 227 closeDialog(); 228 break; 229 default: 230 return; 231 } 232 } 233 Command cmd = resolver.buildResolveCommand(); 234 Main.main.undoRedo.add(cmd); 235 closeDialog(); 236 } 237 238 @Override 239 public void propertyChange(PropertyChangeEvent evt) { 240 if (evt.getPropertyName().equals(ConflictResolver.RESOLVED_COMPLETELY_PROP)) { 241 updateEnabledState(); 242 } 243 } 244 } 245 246 protected void updateTitle() { 247 updateTitle(null); 248 } 249 250 protected void updateTitle(OsmPrimitive my) { 251 if (my == null) { 252 setTitle(tr("Resolve conflicts")); 253 } else { 254 setTitle(tr("Resolve conflicts for ''{0}''", my.getDisplayName(DefaultNameFormatter.getInstance()))); 255 } 256 } 257 258 @Override 259 public void setTitle(String title) { 260 super.setTitle(title); 261 if (titleLabel != null) { 262 titleLabel.setText(title); 263 } 264 } 265 266 @Override 267 public void propertyChange(PropertyChangeEvent evt) { 268 if (evt.getPropertyName().equals(ConflictResolver.MY_PRIMITIVE_PROP)) { 269 updateTitle((OsmPrimitive) evt.getNewValue()); 270 } 271 } 272}