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