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
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
32 private fun toggleDiffPreviewOnPropertyChange(uiProperties: VcsLogUiProperties,
34 showDiffPreview: (Boolean) -> Unit) =
35 onBooleanPropertyChange(uiProperties, CommonUiProperties.SHOW_DIFF_PREVIEW, parent, showDiffPreview)
38 private fun toggleDiffPreviewOrientationOnPropertyChange(uiProperties: VcsLogUiProperties,
40 changeShowDiffPreviewOrientation: (Boolean) -> Unit) =
41 onBooleanPropertyChange(uiProperties, MainVcsLogUiProperties.DIFF_PREVIEW_VERTICAL_SPLIT, parent, changeShowDiffPreviewOrientation)
44 private fun onBooleanPropertyChange(uiProperties: VcsLogUiProperties,
45 property: VcsLogUiProperty<Boolean>,
47 onPropertyChangeAction: (Boolean) -> Unit) {
48 val propertiesChangeListener: PropertiesChangeListener = object : PropertiesChangeListener {
49 override fun <T> onPropertyChanged(p: VcsLogUiProperty<T>) {
51 onPropertyChangeAction(uiProperties.get(property))
55 uiProperties.addChangeListener(propertiesChangeListener)
56 Disposer.register(parent, Disposable { uiProperties.removeChangeListener(propertiesChangeListener) })
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)
67 val mainComponent: JComponent
68 get() = previewDiffSplitter
71 previewDiffSplitter.firstComponent = mainComponent
73 toggleDiffPreviewOnPropertyChange(uiProperties, previewDiff, ::showDiffPreview)
74 toggleDiffPreviewOrientationOnPropertyChange(uiProperties, previewDiff, ::changeDiffPreviewOrientation)
75 invokeLater { showDiffPreview(uiProperties.get(CommonUiProperties.SHOW_DIFF_PREVIEW)) }
78 abstract fun updatePreview(state: Boolean)
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()))
91 private fun changeDiffPreviewOrientation(bottom: Boolean) {
92 previewDiffSplitter.orientation = bottom
96 abstract class EditorDiffPreview(private val project: Project,
97 private val owner: Disposable) : DiffPreviewProvider, DiffPreview {
99 private val previewFileDelegate = lazy { PreviewDiffVirtualFile(this) }
100 private val previewFile by previewFileDelegate
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)
114 override fun updatePreview(fromModelRefresh: Boolean) {
115 if (previewFileDelegate.isInitialized()) {
116 FileEditorManagerEx.getInstanceEx(project).updateFilePresentation(previewFile)
120 override fun setPreviewVisible(isPreviewVisible: Boolean, focus: Boolean) {
121 if (isPreviewVisible) openPreviewInEditor(focus) else closePreview()
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)
130 val editors = EditorTabPreview.openPreview(project, previewFile, focusEditor)
131 for (editor in editors) {
132 EditorTabPreview.registerEscapeHandler(editor, escapeHandler)
137 if (previewFileDelegate.isInitialized()) {
138 FileEditorManager.getInstance(project).closeFile(previewFile)
142 override fun getOwner(): Disposable = owner
144 abstract fun getOwnerComponent(): JComponent
146 abstract fun addSelectionListener(listener: () -> Unit)
149 class VcsLogEditorDiffPreview(project: Project, private val changesBrowser: VcsLogChangesBrowser) :
150 EditorDiffPreview(project, changesBrowser) {
156 override fun createDiffRequestProcessor(): DiffRequestProcessor {
157 val preview = changesBrowser.createChangeProcessor(true)
158 preview.updatePreview(true)
162 override fun getEditorTabName(): @Nls String {
163 val change = VcsLogChangeProcessor.getSelectedOrAll(changesBrowser).userObjectsStream(Change::class.java).findFirst().orElse(null)
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)
169 override fun getOwnerComponent(): JComponent = changesBrowser.preferredFocusedComponent
171 override fun addSelectionListener(listener: () -> Unit) {
172 changesBrowser.viewer.addSelectionListener(Runnable {
173 if (changesBrowser.selectedChanges.isNotEmpty()) {
177 changesBrowser.addListener(VcsLogChangesBrowser.Listener { updatePreview(true) }, owner)