source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/ConflictResolutionDialog.java@ 11993

Last change on this file since 11993 was 10958, checked in by Don-vip, 8 years ago

fix #13479 - safer management of listeners

  • Property svn:eol-style set to native
File size: 8.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.dialogs;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.BorderLayout;
8import java.awt.Component;
9import java.awt.event.ActionEvent;
10import java.beans.PropertyChangeEvent;
11import java.beans.PropertyChangeListener;
12
13import javax.swing.AbstractAction;
14import javax.swing.Action;
15import javax.swing.JLabel;
16import javax.swing.JOptionPane;
17import javax.swing.JPanel;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.osm.OsmPrimitive;
21import org.openstreetmap.josm.gui.DefaultNameFormatter;
22import org.openstreetmap.josm.gui.ExtendedDialog;
23import org.openstreetmap.josm.gui.conflict.pair.ConflictResolver;
24import org.openstreetmap.josm.gui.help.HelpBrowser;
25import org.openstreetmap.josm.gui.help.HelpUtil;
26import org.openstreetmap.josm.tools.ImageProvider;
27
28/**
29 * This is an extended dialog for resolving conflict between {@link OsmPrimitive}s.
30 * @since 1622
31 */
32public class ConflictResolutionDialog extends ExtendedDialog implements PropertyChangeListener {
33 /** the conflict resolver component */
34 private final ConflictResolver resolver = new ConflictResolver();
35 private final JLabel titleLabel = new JLabel("", null, JLabel.CENTER);
36
37 private final ApplyResolutionAction applyResolutionAction = new ApplyResolutionAction();
38
39 private boolean isRegistered;
40
41 /**
42 * Constructs a new {@code ConflictResolutionDialog}.
43 * @param parent parent component
44 */
45 public ConflictResolutionDialog(Component parent) {
46 // We define our own actions, but need to give a hint about number of buttons
47 super(parent, tr("Resolve conflicts"), new String[] {null, null, null});
48 setDefaultButton(1);
49 setCancelButton(2);
50 build();
51 pack();
52 if (getInsets().top > 0) {
53 titleLabel.setVisible(false);
54 }
55 }
56
57 @Override
58 public void removeNotify() {
59 super.removeNotify();
60 unregisterListeners();
61 }
62
63 @Override
64 public void addNotify() {
65 super.addNotify();
66 registerListeners();
67 }
68
69 private synchronized void registerListeners() {
70 if (!isRegistered) {
71 resolver.addPropertyChangeListener(applyResolutionAction);
72 resolver.registerListeners();
73 isRegistered = true;
74 }
75 }
76
77 private synchronized void unregisterListeners() {
78 // See #13479 - See https://bugs.openjdk.java.net/browse/JDK-4387314
79 // Owner window keep a list of owned windows, and does not remove the references when the child is disposed.
80 // There's no easy way to remove ourselves from this list, so we must keep track of register state
81 if (isRegistered) {
82 resolver.removePropertyChangeListener(applyResolutionAction);
83 resolver.unregisterListeners();
84 isRegistered = false;
85 }
86 }
87
88 /**
89 * builds the GUI
90 */
91 protected void build() {
92 JPanel p = new JPanel(new BorderLayout());
93
94 p.add(titleLabel, BorderLayout.NORTH);
95
96 updateTitle();
97
98 resolver.setName("panel.conflictresolver");
99 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}
Note: See TracBrowser for help on using the repository browser.