svn: Fixed "TreeConflictRefreshablePanel" disposing
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / difftool / SvnDiffViewer.java
1 package org.jetbrains.idea.svn.difftool;
2
3 import com.intellij.diff.DiffContext;
4 import com.intellij.diff.FrameDiffTool.DiffViewer;
5 import com.intellij.diff.FrameDiffTool.ToolbarComponents;
6 import com.intellij.diff.contents.DiffContent;
7 import com.intellij.diff.contents.EmptyContent;
8 import com.intellij.diff.requests.DiffRequest;
9 import com.intellij.diff.requests.ErrorDiffRequest;
10 import com.intellij.diff.tools.ErrorDiffTool;
11 import com.intellij.diff.util.DiffUtil;
12 import com.intellij.ide.DataManager;
13 import com.intellij.ide.impl.DataManagerImpl;
14 import com.intellij.openapi.actionSystem.AnAction;
15 import com.intellij.openapi.actionSystem.AnActionEvent;
16 import com.intellij.openapi.actionSystem.DataProvider;
17 import com.intellij.openapi.actionSystem.ToggleAction;
18 import com.intellij.openapi.actionSystem.ex.ActionUtil;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.project.DumbAware;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.ui.Divider;
23 import com.intellij.openapi.ui.Splitter;
24 import com.intellij.openapi.util.Comparing;
25 import com.intellij.openapi.util.Disposer;
26 import com.intellij.openapi.util.Key;
27 import com.intellij.ui.EditorNotificationPanel;
28 import com.intellij.ui.JBColor;
29 import com.intellij.ui.components.panels.Wrapper;
30 import com.intellij.util.containers.HashMap;
31 import com.intellij.util.ui.JBUI;
32 import com.intellij.util.ui.UIUtil;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36 import org.jetbrains.idea.svn.difftool.SvnDiffSettingsHolder.SvnDiffSettings;
37 import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest;
38 import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffViewer;
39 import org.jetbrains.idea.svn.properties.PropertyData;
40 import org.jetbrains.idea.svn.properties.PropertyValue;
41
42 import javax.swing.*;
43 import java.awt.*;
44 import java.awt.event.FocusAdapter;
45 import java.awt.event.FocusEvent;
46 import java.awt.event.FocusListener;
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
50 import java.util.Map;
51
52 public class SvnDiffViewer implements DiffViewer {
53   private static final Logger LOG = Logger.getInstance(SvnDiffViewer.class);
54
55   public static final Key<MyPropertyContext> PROPERTY_CONTEXT_KEY = Key.create("MyPropertyContext");
56   public static final Key<Boolean> FOCUSED_VIEWER_KEY = Key.create("SvnFocusedViewer");
57
58   @Nullable private final Project myProject;
59
60   @NotNull private final DiffContext myContext;
61   @NotNull private final DiffRequest myPropertyRequest;
62
63   @NotNull private final SvnDiffSettings mySettings;
64
65   @NotNull private final JPanel myPanel;
66   @NotNull private final Splitter mySplitter;
67   @NotNull private final Wrapper myNotificationPanel;
68
69   @NotNull private final DiffViewer myContentViewer;
70   @NotNull private final DiffViewer myPropertiesViewer;
71
72   @NotNull private final FocusListener myContentFocusListener = new MyFocusListener(false);
73   @NotNull private final FocusListener myPropertiesFocusListener = new MyFocusListener(true);
74
75   private boolean myPropertiesViewerFocused; // False - content viewer, True - properties
76   private boolean myDumbContentViewer;
77
78   public SvnDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest propertyRequest, @NotNull DiffViewer wrappingViewer) {
79     myProject = context.getProject();
80     myContext = context;
81     myPropertyRequest = propertyRequest;
82     myContentViewer = wrappingViewer;
83
84     myPropertyRequest.onAssigned(true);
85
86     mySettings = initSettings(context);
87
88     mySplitter = new MySplitter("Property Changes");
89     mySplitter.setProportion(mySettings.getSplitterProportion());
90     mySplitter.setFirstComponent(myContentViewer.getComponent());
91
92     myNotificationPanel = new Wrapper();
93
94     MyPropertyContext propertyContext = initPropertyContext(context);
95     myPropertiesViewer = createPropertiesViewer(propertyRequest, propertyContext);
96
97     myPanel = new JPanel(new BorderLayout());
98     myPanel.add(mySplitter, BorderLayout.CENTER);
99     myPanel.add(myNotificationPanel, BorderLayout.SOUTH);
100     DataManager.registerDataProvider(myPanel, new DataProvider() {
101       @Override
102       public Object getData(@NonNls String dataId) {
103         DataProvider propertiesDataProvider = DataManagerImpl.getDataProviderEx(myPropertiesViewer.getComponent());
104         DataProvider contentDataProvider = DataManagerImpl.getDataProviderEx(myContentViewer.getComponent());
105         DataProvider defaultDP = myPropertiesViewerFocused ? propertiesDataProvider : contentDataProvider;
106         DataProvider fallbackDP = myPropertiesViewerFocused ? contentDataProvider : propertiesDataProvider;
107         return DiffUtil.getData(defaultDP, fallbackDP, dataId);
108       }
109     });
110
111     updatePropertiesPanel();
112   }
113
114   @NotNull
115   private static DiffViewer createPropertiesViewer(@NotNull DiffRequest propertyRequest, @NotNull MyPropertyContext propertyContext) {
116     if (propertyRequest instanceof SvnPropertiesDiffRequest) {
117       return SvnPropertiesDiffViewer.create(propertyContext, ((SvnPropertiesDiffRequest)propertyRequest), true);
118     }
119     else {
120       return ErrorDiffTool.INSTANCE.createComponent(propertyContext, propertyRequest);
121     }
122   }
123
124   @NotNull
125   @Override
126   public ToolbarComponents init() {
127     installListeners();
128
129     processContextHints();
130
131     ToolbarComponents properties = myPropertiesViewer.init();
132     ToolbarComponents components = new ToolbarComponents();
133     components.toolbarActions = createToolbar(properties.toolbarActions);
134     return components;
135   }
136
137   @Override
138   public void dispose() {
139     destroyListeners();
140
141     updateContextHints();
142
143     Disposer.dispose(myPropertiesViewer);
144
145     myPropertyRequest.onAssigned(false);
146   }
147
148   private void processContextHints() {
149     if (myContext.getUserData(FOCUSED_VIEWER_KEY) == Boolean.TRUE) myPropertiesViewerFocused = true;
150     myDumbContentViewer = myContentViewer.getPreferredFocusedComponent() == null;
151   }
152
153   private void updateContextHints() {
154     if (!myDumbContentViewer && !mySettings.isHideProperties()) myContext.putUserData(FOCUSED_VIEWER_KEY, myPropertiesViewerFocused);
155     mySettings.setSplitterProportion(mySplitter.getProportion());
156   }
157
158   //
159   // Diff
160   //
161
162   @Nullable
163   private JComponent createNotification() {
164     if (myPropertyRequest instanceof ErrorDiffRequest) {
165       return createNotification(((ErrorDiffRequest)myPropertyRequest).getMessage());
166     }
167
168     List<DiffContent> contents = ((SvnPropertiesDiffRequest)myPropertyRequest).getContents();
169
170     Map<String, PropertyValue> before = getProperties(contents.get(0));
171     Map<String, PropertyValue> after = getProperties(contents.get(1));
172
173     if (before.isEmpty() && after.isEmpty()) return null;
174
175     if (!before.keySet().equals(after.keySet())) {
176       return createNotification("SVN Properties changed");
177     }
178
179     for (String key : before.keySet()) {
180       if (!Comparing.equal(before.get(key), after.get(key))) return createNotification("SVN Properties changed");
181     }
182
183     return null;
184   }
185
186   @NotNull
187   private static Map<String, PropertyValue> getProperties(@NotNull DiffContent content) {
188     if (content instanceof EmptyContent) return Collections.emptyMap();
189
190     List<PropertyData> properties = ((SvnPropertiesDiffRequest.PropertyContent)content).getProperties();
191
192     Map<String, PropertyValue> map = new HashMap<>();
193
194     for (PropertyData data : properties) {
195       if (map.containsKey(data.getName())) LOG.warn("Duplicated property: " + data.getName());
196       map.put(data.getName(), data.getValue());
197     }
198
199     return map;
200   }
201
202   @NotNull
203   private static JPanel createNotification(@NotNull String text) {
204     return new EditorNotificationPanel().text(text);
205   }
206
207   //
208   // Misc
209   //
210
211   private void updatePropertiesPanel() {
212     boolean wasFocused = myContext.isFocused();
213     if (!mySettings.isHideProperties()) {
214       mySplitter.setSecondComponent(myPropertiesViewer.getComponent());
215       myNotificationPanel.setContent(null);
216     }
217     else {
218       mySplitter.setSecondComponent(null);
219       myNotificationPanel.setContent(createNotification());
220     }
221     if (wasFocused) myContext.requestFocus();
222   }
223
224   @NotNull
225   private List<AnAction> createToolbar(@Nullable List<AnAction> propertiesActions) {
226     List<AnAction> result = new ArrayList<>();
227
228     if (propertiesActions != null) result.addAll(propertiesActions);
229
230     result.add(new ToggleHidePropertiesAction());
231
232     return result;
233   }
234
235   @NotNull
236   private static SvnDiffSettings initSettings(@NotNull DiffContext context) {
237     SvnDiffSettings settings = context.getUserData(SvnDiffSettingsHolder.KEY);
238     if (settings == null) {
239       settings = SvnDiffSettings.getSettings();
240       context.putUserData(SvnDiffSettingsHolder.KEY, settings);
241     }
242     return settings;
243   }
244
245   @NotNull
246   private MyPropertyContext initPropertyContext(@NotNull DiffContext context) {
247     MyPropertyContext propertyContext = context.getUserData(PROPERTY_CONTEXT_KEY);
248     if (propertyContext == null) {
249       propertyContext = new MyPropertyContext();
250       context.putUserData(PROPERTY_CONTEXT_KEY, propertyContext);
251     }
252     return propertyContext;
253   }
254
255   private void installListeners() {
256     myContentViewer.getComponent().addFocusListener(myContentFocusListener);
257     myPropertiesViewer.getComponent().addFocusListener(myPropertiesFocusListener);
258   }
259
260   private void destroyListeners() {
261     myContentViewer.getComponent().removeFocusListener(myContentFocusListener);
262     myPropertiesViewer.getComponent().removeFocusListener(myPropertiesFocusListener);
263   }
264
265   //
266   // Getters
267   //
268
269   @NotNull
270   @Override
271   public JComponent getComponent() {
272     return myPanel;
273   }
274
275   @Nullable
276   @Override
277   public JComponent getPreferredFocusedComponent() {
278     if (myPropertiesViewerFocused) {
279       JComponent component = getPropertiesPreferredFocusedComponent();
280       if (component != null) return component;
281       return myContentViewer.getPreferredFocusedComponent();
282     }
283     else {
284       JComponent component = myContentViewer.getPreferredFocusedComponent();
285       if (component != null) return component;
286       return getPropertiesPreferredFocusedComponent();
287     }
288   }
289
290   @Nullable
291   private JComponent getPropertiesPreferredFocusedComponent() {
292     if (mySettings.isHideProperties()) return null;
293     return myPropertiesViewer.getPreferredFocusedComponent();
294   }
295
296   //
297   // Actions
298   //
299
300   private class ToggleHidePropertiesAction extends ToggleAction implements DumbAware {
301     public ToggleHidePropertiesAction() {
302       ActionUtil.copyFrom(this, "Subversion.TogglePropertiesDiff");
303     }
304
305     @Override
306     public boolean isSelected(AnActionEvent e) {
307       return !mySettings.isHideProperties();
308     }
309
310     @Override
311     public void setSelected(AnActionEvent e, boolean state) {
312       mySettings.setHideProperties(!state);
313       updatePropertiesPanel();
314     }
315   }
316
317   //
318   // Helpers
319   //
320
321   private class MyPropertyContext extends DiffContext {
322     @Nullable
323     @Override
324     public Project getProject() {
325       return myContext.getProject();
326     }
327
328     @Override
329     public boolean isWindowFocused() {
330       return myContext.isWindowFocused();
331     }
332
333     @Override
334     public boolean isFocused() {
335       return DiffUtil.isFocusedComponent(getProject(), myPropertiesViewer.getComponent());
336     }
337
338     @Override
339     public void requestFocus() {
340       DiffUtil.requestFocus(getProject(), myPropertiesViewer.getPreferredFocusedComponent());
341     }
342   }
343
344   private class MyFocusListener extends FocusAdapter {
345     private final boolean myValue;
346
347     public MyFocusListener(boolean value) {
348       myValue = value;
349     }
350
351     @Override
352     public void focusGained(FocusEvent e) {
353       myPropertiesViewerFocused = myValue;
354     }
355   }
356
357   private static class MySplitter extends Splitter {
358     @NotNull private final String myLabelText;
359
360     public MySplitter(@NotNull String text) {
361       super(true);
362       myLabelText = text;
363     }
364
365     @Override
366     protected Divider createDivider() {
367       return new DividerImpl() {
368         @Override
369         public void setOrientation(boolean isVerticalSplit) {
370           if (!isVertical()) LOG.warn("unsupported state: splitter should be vertical");
371
372           removeAll();
373
374           setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
375
376           JLabel label = new JLabel(myLabelText);
377           label.setFont(UIUtil.getOptionPaneMessageFont());
378           label.setForeground(UIUtil.getLabelForeground());
379           add(label, new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.CENTER, GridBagConstraints.NONE, JBUI.insets(2), 0, 0));
380           setDividerWidth(label.getPreferredSize().height + JBUI.scale(4));
381
382           revalidate();
383           repaint();
384         }
385
386         @Override
387         protected void paintComponent(Graphics g) {
388           super.paintComponent(g);
389           g.setColor(JBColor.border());
390           UIUtil.drawLine(g, 0, 0, getWidth(), 0);
391         }
392       };
393     }
394   }
395 }