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(new FlowLayout(FlowLayout.CENTER)); 080 081 applyResolutionAction = new ApplyResolutionAction(); 082 JButton btn = new JButton(applyResolutionAction); 083 btn.setName("button.apply"); 084 pnl.add(btn); 085 086 btn = new JButton(new CancelAction()); 087 btn.setName("button.cancel"); 088 pnl.add(btn); 089 090 btn = new JButton(new HelpAction()); 091 btn.setName("button.help"); 092 pnl.add(btn); 093 094 pnl.setBorder(BorderFactory.createLoweredBevelBorder()); 095 return pnl; 096 } 097 098 private void registerListeners() { 099 resolver.addPropertyChangeListener(applyResolutionAction); 100 } 101 102 private void unregisterListeners() { 103 resolver.removePropertyChangeListener(applyResolutionAction); 104 resolver.unregisterListeners(); 105 } 106 107 /** 108 * builds the GUI 109 */ 110 protected void build() { 111 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 112 getContentPane().setLayout(new BorderLayout()); 113 114 titleLabel = new JLabel(); 115 titleLabel.setHorizontalAlignment(JLabel.CENTER); 116 getContentPane().add(titleLabel, BorderLayout.NORTH); 117 118 updateTitle(); 119 120 resolver = new ConflictResolver(); 121 resolver.setName("panel.conflictresolver"); 122 getContentPane().add(resolver, BorderLayout.CENTER); 123 getContentPane().add(buildButtonRow(), BorderLayout.SOUTH); 124 125 resolver.addPropertyChangeListener(this); 126 HelpUtil.setHelpContext(this.getRootPane(), ht("Dialog/Conflict")); 127 128 registerListeners(); 129 } 130 131 /** 132 * Constructs a new {@code ConflictResolutionDialog}. 133 * @param parent parent component 134 */ 135 public ConflictResolutionDialog(Component parent) { 136 super(JOptionPane.getFrameForComponent(parent), ModalityType.DOCUMENT_MODAL); 137 build(); 138 pack(); 139 if (getInsets().top > 0) { 140 titleLabel.setVisible(false); 141 } 142 } 143 144 /** 145 * Replies the conflict resolver component. 146 * @return the conflict resolver component 147 */ 148 public ConflictResolver getConflictResolver() { 149 return resolver; 150 } 151 152 /** 153 * Action for canceling conflict resolution 154 */ 155 class CancelAction extends AbstractAction { 156 CancelAction() { 157 putValue(Action.SHORT_DESCRIPTION, tr("Cancel conflict resolution and close the dialog")); 158 putValue(Action.NAME, tr("Cancel")); 159 putValue(Action.SMALL_ICON, ImageProvider.get("", "cancel")); 160 setEnabled(true); 161 } 162 163 @Override 164 public void actionPerformed(ActionEvent arg0) { 165 closeDialog(); 166 } 167 } 168 169 /** 170 * Action for canceling conflict resolution 171 */ 172 static class HelpAction extends AbstractAction { 173 HelpAction() { 174 putValue(Action.SHORT_DESCRIPTION, tr("Show help information")); 175 putValue(Action.NAME, tr("Help")); 176 putValue(Action.SMALL_ICON, ImageProvider.get("help")); 177 setEnabled(true); 178 } 179 180 @Override 181 public void actionPerformed(ActionEvent arg0) { 182 HelpBrowser.setUrlForHelpTopic(ht("/Dialog/Conflict")); 183 } 184 } 185 186 /** 187 * Action for applying resolved differences in a conflict 188 * 189 */ 190 class ApplyResolutionAction extends AbstractAction implements PropertyChangeListener { 191 ApplyResolutionAction() { 192 putValue(Action.SHORT_DESCRIPTION, tr("Apply resolved conflicts and close the dialog")); 193 putValue(Action.NAME, tr("Apply Resolution")); 194 putValue(Action.SMALL_ICON, ImageProvider.get("dialogs", "conflict")); 195 updateEnabledState(); 196 } 197 198 protected void updateEnabledState() { 199 setEnabled(resolver.isResolvedCompletely()); 200 } 201 202 @Override 203 public void actionPerformed(ActionEvent arg0) { 204 if (!resolver.isResolvedCompletely()) { 205 Object[] options = { 206 tr("Close anyway"), 207 tr("Continue resolving")}; 208 int ret = JOptionPane.showOptionDialog(Main.parent, 209 tr("<html>You did not finish to merge the differences in this conflict.<br>" 210 + "Conflict resolutions will not be applied unless all differences<br>" 211 + "are resolved.<br>" 212 + "Click <strong>{0}</strong> to close anyway.<strong> Already<br>" 213 + "resolved differences will not be applied.</strong><br>" 214 + "Click <strong>{1}</strong> to return to resolving conflicts.</html>", 215 options[0].toString(), options[1].toString() 216 ), 217 tr("Conflict not resolved completely"), 218 JOptionPane.YES_NO_OPTION, 219 JOptionPane.WARNING_MESSAGE, 220 null, 221 options, 222 options[1] 223 ); 224 switch(ret) { 225 case JOptionPane.YES_OPTION: 226 closeDialog(); 227 break; 228 default: 229 return; 230 } 231 } 232 Command cmd = resolver.buildResolveCommand(); 233 Main.main.undoRedo.add(cmd); 234 closeDialog(); 235 } 236 237 @Override 238 public void propertyChange(PropertyChangeEvent evt) { 239 if (evt.getPropertyName().equals(ConflictResolver.RESOLVED_COMPLETELY_PROP)) { 240 updateEnabledState(); 241 } 242 } 243 } 244 245 protected void updateTitle() { 246 updateTitle(null); 247 } 248 249 protected void updateTitle(OsmPrimitive my) { 250 if (my == null) { 251 setTitle(tr("Resolve conflicts")); 252 } else { 253 setTitle(tr("Resolve conflicts for ''{0}''", my.getDisplayName(DefaultNameFormatter.getInstance()))); 254 } 255 } 256 257 @Override 258 public void setTitle(String title) { 259 super.setTitle(title); 260 if (titleLabel != null) { 261 titleLabel.setText(title); 262 } 263 } 264 265 @Override 266 public void propertyChange(PropertyChangeEvent evt) { 267 if (evt.getPropertyName().equals(ConflictResolver.MY_PRIMITIVE_PROP)) { 268 updateTitle((OsmPrimitive) evt.getNewValue()); 269 } 270 } 271}