[intellij sh] IDEA-267920 Fix plugin compatibility problem
[idea/community.git] / platform / vcs-log / impl / src / com / intellij / vcs / log / ui / frame / VcsLogDiffPreview.kt
1 // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.vcs.log.ui.frame
3
4 import com.intellij.diff.impl.DiffRequestProcessor
5 import com.intellij.openapi.Disposable
6 import com.intellij.openapi.actionSystem.ActionToolbar
7 import com.intellij.openapi.application.invokeLater
8 import com.intellij.openapi.fileEditor.FileEditorManager
9 import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
10 import com.intellij.openapi.project.Project
11 import com.intellij.openapi.ui.Splitter
12 import com.intellij.openapi.util.Disposer
13 import com.intellij.openapi.util.registry.Registry
14 import com.intellij.openapi.vcs.changes.*
15 import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager
16 import com.intellij.openapi.wm.IdeFocusManager
17 import com.intellij.openapi.wm.ToolWindowManager
18 import com.intellij.ui.OnePixelSplitter
19 import com.intellij.util.ui.JBUI
20 import com.intellij.vcs.log.VcsLogBundle
21 import com.intellij.vcs.log.impl.CommonUiProperties
22 import com.intellij.vcs.log.impl.MainVcsLogUiProperties
23 import com.intellij.vcs.log.impl.VcsLogUiProperties
24 import com.intellij.vcs.log.impl.VcsLogUiProperties.PropertiesChangeListener
25 import com.intellij.vcs.log.impl.VcsLogUiProperties.VcsLogUiProperty
26 import com.intellij.vcs.log.util.VcsLogUiUtil
27 import org.jetbrains.annotations.Nls
28 import org.jetbrains.annotations.NonNls
29 import javax.swing.JComponent
30 import kotlin.math.roundToInt
31
32 private fun toggleDiffPreviewOnPropertyChange(uiProperties: VcsLogUiProperties,
33                                               parent: Disposable,
34                                               showDiffPreview: (Boolean) -> Unit) =
35   onBooleanPropertyChange(uiProperties, CommonUiProperties.SHOW_DIFF_PREVIEW, parent, showDiffPreview)
36
37
38 private fun toggleDiffPreviewOrientationOnPropertyChange(uiProperties: VcsLogUiProperties,
39                                                          parent: Disposable,
40                                                          changeShowDiffPreviewOrientation: (Boolean) -> Unit) =
41   onBooleanPropertyChange(uiProperties, MainVcsLogUiProperties.DIFF_PREVIEW_VERTICAL_SPLIT, parent, changeShowDiffPreviewOrientation)
42
43
44 private fun onBooleanPropertyChange(uiProperties: VcsLogUiProperties,
45                                     property: VcsLogUiProperty<Boolean>,
46                                     parent: Disposable,
47                                     onPropertyChangeAction: (Boolean) -> Unit) {
48   val propertiesChangeListener: PropertiesChangeListener = object : PropertiesChangeListener {
49     override fun <T> onPropertyChanged(p: VcsLogUiProperty<T>) {
50       if (property == p) {
51         onPropertyChangeAction(uiProperties.get(property))
52       }
53     }
54   }
55   uiProperties.addChangeListener(propertiesChangeListener)
56   Disposer.register(parent, Disposable { uiProperties.removeChangeListener(propertiesChangeListener) })
57 }
58
59 abstract class FrameDiffPreview<D : DiffRequestProcessor>(protected val previewDiff: D,
60                                                           uiProperties: VcsLogUiProperties,
61                                                           mainComponent: JComponent,
62                                                           @NonNls splitterProportionKey: String,
63                                                           vertical: Boolean = false,
64                                                           defaultProportion: Float = 0.7f) {
65   private val previewDiffSplitter: Splitter = OnePixelSplitter(vertical, splitterProportionKey, defaultProportion)
66
67   val mainComponent: JComponent
68     get() = previewDiffSplitter
69
70   init {
71     previewDiffSplitter.firstComponent = mainComponent
72
73     toggleDiffPreviewOnPropertyChange(uiProperties, previewDiff, ::showDiffPreview)
74     toggleDiffPreviewOrientationOnPropertyChange(uiProperties, previewDiff, ::changeDiffPreviewOrientation)
75     invokeLater { showDiffPreview(uiProperties.get(CommonUiProperties.SHOW_DIFF_PREVIEW)) }
76   }
77
78   abstract fun updatePreview(state: Boolean)
79
80   private fun showDiffPreview(state: Boolean) {
81     previewDiffSplitter.secondComponent = if (state) previewDiff.component else null
82     previewDiffSplitter.secondComponent?.let {
83       val defaultMinimumSize = it.minimumSize
84       val actionButtonSize = ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE
85       it.minimumSize = JBUI.size(defaultMinimumSize.width.coerceAtMost((actionButtonSize.width * 1.5f).roundToInt()),
86                                  defaultMinimumSize.height.coerceAtMost((actionButtonSize.height * 1.5f).roundToInt()))
87     }
88     updatePreview(state)
89   }
90
91   private fun changeDiffPreviewOrientation(bottom: Boolean) {
92     previewDiffSplitter.orientation = bottom
93   }
94 }
95
96 abstract class EditorDiffPreview(private val project: Project,
97                                  private val owner: Disposable) : DiffPreviewProvider, DiffPreview {
98
99   private val previewFileDelegate = lazy { PreviewDiffVirtualFile(this) }
100   private val previewFile by previewFileDelegate
101
102   protected fun init() {
103     @Suppress("LeakingThis")
104     addSelectionListener {
105       if (VcsLogUiUtil.isDiffPreviewInEditor(project) && Registry.`is`("show.diff.preview.as.editor.tab.with.single.click")) {
106         openPreviewInEditor(false)
107       }
108       else {
109         updatePreview(true)
110       }
111     }
112   }
113
114   override fun updatePreview(fromModelRefresh: Boolean) {
115     if (previewFileDelegate.isInitialized()) {
116       FileEditorManagerEx.getInstanceEx(project).updateFilePresentation(previewFile)
117     }
118   }
119
120   override fun setPreviewVisible(isPreviewVisible: Boolean, focus: Boolean) {
121     if (isPreviewVisible) openPreviewInEditor(focus) else closePreview()
122   }
123
124   fun openPreviewInEditor(focusEditor: Boolean) {
125     val escapeHandler = Runnable {
126       val toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ChangesViewContentManager.TOOLWINDOW_ID)
127       toolWindow?.activate({ IdeFocusManager.getInstance(project).requestFocus(getOwnerComponent(), true) }, false)
128     }
129
130     val editors = EditorTabPreview.openPreview(project, previewFile, focusEditor)
131     for (editor in editors) {
132       EditorTabPreview.registerEscapeHandler(editor, escapeHandler)
133     }
134   }
135
136   fun closePreview() {
137     if (previewFileDelegate.isInitialized()) {
138       FileEditorManager.getInstance(project).closeFile(previewFile)
139     }
140   }
141
142   override fun getOwner(): Disposable = owner
143
144   abstract fun getOwnerComponent(): JComponent
145
146   abstract fun addSelectionListener(listener: () -> Unit)
147 }
148
149 class VcsLogEditorDiffPreview(project: Project, private val changesBrowser: VcsLogChangesBrowser) :
150   EditorDiffPreview(project, changesBrowser) {
151
152   init {
153     init()
154   }
155
156   override fun createDiffRequestProcessor(): DiffRequestProcessor {
157     val preview = changesBrowser.createChangeProcessor(true)
158     preview.updatePreview(true)
159     return preview
160   }
161
162   override fun getEditorTabName(): @Nls String {
163     val change = VcsLogChangeProcessor.getSelectedOrAll(changesBrowser).userObjectsStream(Change::class.java).findFirst().orElse(null)
164
165     return if (change == null) VcsLogBundle.message("vcs.log.diff.preview.editor.empty.tab.name")
166     else VcsLogBundle.message("vcs.log.diff.preview.editor.tab.name", ChangesUtil.getFilePath(change).name)
167   }
168
169   override fun getOwnerComponent(): JComponent = changesBrowser.preferredFocusedComponent
170
171   override fun addSelectionListener(listener: () -> Unit) {
172     changesBrowser.viewer.addSelectionListener(Runnable {
173       if (changesBrowser.selectedChanges.isNotEmpty()) {
174         listener()
175       }
176     }, owner)
177     changesBrowser.addListener(VcsLogChangesBrowser.Listener { updatePreview(true) }, owner)
178   }
179 }