/* * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.diff.tools.util; import com.intellij.diff.DiffContext; import com.intellij.diff.impl.DiffSettingsHolder.DiffSettings; import com.intellij.diff.requests.DiffRequest; import com.intellij.diff.tools.holders.BinaryEditorHolder; import com.intellij.icons.AllIcons; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorState; import com.intellij.openapi.fileEditor.FileEditorStateLevel; import com.intellij.openapi.fileEditor.TransferableFileEditorState; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.ui.ToggleActionButton; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.List; import java.util.Map; import java.util.Set; public class TransferableFileEditorStateSupport { @NotNull private static final Key<Map<String, Map<String, String>>> TRANSFERABLE_FILE_EDITOR_STATE = Key.create("Diff.TransferableFileEditorState"); private static final Condition<BinaryEditorHolder> IS_SUPPORTED = holder -> { return getEditorState(holder.getEditor()) != null; }; @NotNull private final DiffSettings mySettings; @NotNull private final List<BinaryEditorHolder> myHolders; private final boolean mySupported; public TransferableFileEditorStateSupport(@NotNull DiffSettings settings, @NotNull List<BinaryEditorHolder> holders, @NotNull Disposable disposable) { mySettings = settings; myHolders = holders; mySupported = ContainerUtil.or(myHolders, IS_SUPPORTED); new MySynchronizer(ContainerUtil.filter(myHolders, IS_SUPPORTED)).install(disposable); } public boolean isSupported() { return mySupported; } public boolean isEnabled() { return mySettings.isSyncBinaryEditorSettings(); } public void setEnabled(boolean enabled) { mySettings.setSyncBinaryEditorSettings(enabled); } public void processContextHints(@NotNull DiffRequest request, @NotNull DiffContext context) { if (!isEnabled()) return; for (BinaryEditorHolder holder : myHolders) { FileEditor editor = holder.getEditor(); TransferableFileEditorState state = getEditorState(holder.getEditor()); if (state != null) { readContextData(context, editor, state); } } } public void updateContextHints(@NotNull DiffRequest request, @NotNull DiffContext context) { if (!isEnabled()) return; Set<String> updated = ContainerUtil.newHashSet(); for (BinaryEditorHolder holder : myHolders) { TransferableFileEditorState state = getEditorState(holder.getEditor()); if (state != null) { boolean processed = !updated.add(state.getEditorId()); if (!processed) writeContextData(context, state); } } } @NotNull public AnAction createToggleAction() { return new ToggleSynchronousEditorStatesAction(this); } private static void readContextData(@NotNull DiffContext context, @NotNull FileEditor editor, @NotNull TransferableFileEditorState state) { Map<String, Map<String, String>> map = context.getUserData(TRANSFERABLE_FILE_EDITOR_STATE); Map<String, String> options = map != null ? map.get(state.getEditorId()) : null; if (options == null) return; state.setTransferableOptions(options); editor.setState(state); } private static void writeContextData(@NotNull DiffContext context, @NotNull TransferableFileEditorState state) { Map<String, Map<String, String>> map = context.getUserData(TRANSFERABLE_FILE_EDITOR_STATE); if (map == null) { map = ContainerUtil.newHashMap(); context.putUserData(TRANSFERABLE_FILE_EDITOR_STATE, map); } map.put(state.getEditorId(), state.getTransferableOptions()); } @Nullable private static TransferableFileEditorState getEditorState(@NotNull FileEditor editor) { FileEditorState state = editor.getState(FileEditorStateLevel.FULL); return state instanceof TransferableFileEditorState ? (TransferableFileEditorState)state : null; } private class MySynchronizer implements PropertyChangeListener { @NotNull private final List<? extends FileEditor> myEditors; private boolean myDuringUpdate = false; public MySynchronizer(@NotNull List<BinaryEditorHolder> editors) { myEditors = ContainerUtil.map(editors, holder -> holder.getEditor()); } public void install(@NotNull Disposable disposable) { if (myEditors.size() < 2) return; for (FileEditor editor : myEditors) { editor.addPropertyChangeListener(this); } Disposer.register(disposable, new Disposable() { @Override public void dispose() { for (FileEditor editor : myEditors) { editor.removePropertyChangeListener(MySynchronizer.this); } } }); } @Override public void propertyChange(PropertyChangeEvent evt) { if (myDuringUpdate || !isEnabled()) return; if (!(evt.getSource() instanceof FileEditor)) return; TransferableFileEditorState sourceState = getEditorState(((FileEditor)evt.getSource())); if (sourceState == null) return; Map<String, String> options = sourceState.getTransferableOptions(); String id = sourceState.getEditorId(); for (FileEditor editor : myEditors) { if (evt.getSource() != editor) { updateEditor(editor, id, options); } } } private void updateEditor(@NotNull FileEditor editor, @NotNull String id, @NotNull Map<String, String> options) { try { myDuringUpdate = true; TransferableFileEditorState state = getEditorState(editor); if (state != null && state.getEditorId().equals(id)) { state.setTransferableOptions(options); editor.setState(state); } } finally { myDuringUpdate = false; } } } private class ToggleSynchronousEditorStatesAction extends ToggleActionButton implements DumbAware { @NotNull private final TransferableFileEditorStateSupport mySupport; public ToggleSynchronousEditorStatesAction(@NotNull TransferableFileEditorStateSupport support) { super("Synchronize Editors Settings", AllIcons.Actions.SyncPanels); mySupport = support; } @Override public boolean isVisible() { return mySupport.isSupported(); } @Override public boolean isSelected(AnActionEvent e) { return mySupport.isEnabled(); } @Override public void setSelected(AnActionEvent e, boolean state) { mySupport.setEnabled(state); } } }