mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
IJPL-159035 SpotlightPainter - get rid of MergingUpdateQueue
GitOrigin-RevId: 5f6c5c4cc41aec3d829284ca4a511e6c6696ceb1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3cef5eda0a
commit
696f7393dd
@@ -25,6 +25,7 @@ import com.intellij.testFramework.TestApplicationManager;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.text.Matcher;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -35,8 +36,6 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class GotoActionTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
private static final DataKey<Boolean> SHOW_HIDDEN_KEY = DataKey.create("GotoActionTest.DataKey");
|
||||
private static final Comparator<MatchedValue> MATCH_COMPARATOR = MatchedValue::compareWeights;
|
||||
@@ -292,25 +291,22 @@ public class GotoActionTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
}
|
||||
|
||||
public void testNavigableSettingsOptionsAppearInResults() {
|
||||
SearchEverywhereContributor<?> contributor = createActionContributor(getProject(), getTestRootDisposable());
|
||||
List<String> patterns = List.of("support screen readers", "show line numbers", "tab placement");
|
||||
|
||||
List<Object> errors = new ArrayList<>();
|
||||
for (String pattern : patterns) {
|
||||
List<?> elements = ChooseByNameTest.calcContributorElements(contributor, pattern);
|
||||
boolean result = false;
|
||||
for (Object t : elements) {
|
||||
if (isNavigableOption(((MatchedValue)t).value)) {
|
||||
result = true;
|
||||
break;
|
||||
SoftAssertions.assertSoftly(softly -> {
|
||||
SearchEverywhereContributor<?> contributor = createActionContributor(getProject(), getTestRootDisposable());
|
||||
for (String pattern : List.of("support screen readers", "show line numbers", "tab placement")) {
|
||||
List<?> elements = ChooseByNameTest.calcContributorElements(contributor, pattern);
|
||||
boolean result = false;
|
||||
for (Object t : elements) {
|
||||
if (isNavigableOption(((MatchedValue)t).value)) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
softly.fail("Failure for pattern '" + pattern + "' - " + elements);
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
errors.add("Failure for pattern '" + pattern + "' - " + elements);
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(errors).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
public void testUseUpdatedPresentationForMatching() {
|
||||
|
||||
@@ -1586,10 +1586,10 @@ a:com.intellij.codeInspection.ex.InspectionToolWrapper
|
||||
- a:createCopy():com.intellij.codeInspection.ex.InspectionToolWrapper
|
||||
- getDefaultEditorAttributes():java.lang.String
|
||||
- getDefaultLevel():com.intellij.codeHighlighting.HighlightDisplayLevel
|
||||
- getDescriptionContextClass():java.lang.Class
|
||||
- f:getDescriptionContextClass():java.lang.Class
|
||||
- getDisplayKey():com.intellij.codeInsight.daemon.HighlightDisplayKey
|
||||
- getDisplayName():java.lang.String
|
||||
- getExtension():com.intellij.codeInspection.InspectionEP
|
||||
- f:getExtension():com.intellij.codeInspection.InspectionEP
|
||||
- f:getFolderName():java.lang.String
|
||||
- getGroupDisplayName():java.lang.String
|
||||
- getGroupPath():java.lang.String[]
|
||||
|
||||
@@ -10,8 +10,6 @@ import com.intellij.codeInspection.InspectionProfileEntry;
|
||||
import com.intellij.diagnostic.PluginException;
|
||||
import com.intellij.l10n.LocalizationUtil;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
@@ -174,15 +172,21 @@ public abstract class InspectionToolWrapper<T extends InspectionProfileEntry, E
|
||||
}
|
||||
|
||||
public @Nls String loadDescription() {
|
||||
final String description = getStaticDescription();
|
||||
if (description != null) return description;
|
||||
String description = getStaticDescription();
|
||||
if (description != null) {
|
||||
return description;
|
||||
}
|
||||
|
||||
try {
|
||||
InputStream descriptionStream = getDescriptionStream();
|
||||
//noinspection HardCodedStringLiteral(IDEA-249976)
|
||||
return descriptionStream != null ? insertAddendum(ResourceUtil.loadText(descriptionStream),
|
||||
getTool().getDescriptionAddendum()) : null;
|
||||
if (descriptionStream != null) {
|
||||
//noinspection HardCodedStringLiteral(IDEA-249976)
|
||||
return insertAddendum(ResourceUtil.loadText(descriptionStream), getTool().getDescriptionAddendum());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
catch (IOException ignored) { }
|
||||
|
||||
return getTool().loadDescription();
|
||||
}
|
||||
@@ -200,11 +204,10 @@ public abstract class InspectionToolWrapper<T extends InspectionProfileEntry, E
|
||||
}
|
||||
|
||||
private @Nullable InputStream getDescriptionStream() {
|
||||
Application app = ApplicationManager.getApplication();
|
||||
String path = INSPECTION_DESCRIPTIONS_FOLDER + "/" + getDescriptionFileName();
|
||||
ClassLoader classLoader;
|
||||
if (myEP == null || app.isUnitTestMode() || app.isHeadlessEnvironment()) {
|
||||
classLoader = getDescriptionContextClass().getClassLoader();
|
||||
if (myEP == null) {
|
||||
classLoader = getTool().getClass().getClassLoader();
|
||||
}
|
||||
else {
|
||||
classLoader = myEP.getPluginDescriptor().getPluginClassLoader();
|
||||
@@ -220,7 +223,7 @@ public abstract class InspectionToolWrapper<T extends InspectionProfileEntry, E
|
||||
return getShortName();
|
||||
}
|
||||
|
||||
public @NotNull Class<? extends InspectionProfileEntry> getDescriptionContextClass() {
|
||||
public final @NotNull Class<? extends InspectionProfileEntry> getDescriptionContextClass() {
|
||||
return getTool().getClass();
|
||||
}
|
||||
|
||||
@@ -228,7 +231,7 @@ public abstract class InspectionToolWrapper<T extends InspectionProfileEntry, E
|
||||
return getTool().getMainToolId();
|
||||
}
|
||||
|
||||
public E getExtension() {
|
||||
public final E getExtension() {
|
||||
return myEP;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,9 +90,9 @@ object LocalizationUtil {
|
||||
|
||||
@Internal
|
||||
fun getResourceAsStream(classLoader: ClassLoader?, path: String, specialLocale: Locale? = null): InputStream? {
|
||||
if (classLoader != null) {
|
||||
val locale = specialLocale ?: getLocaleOrNullForDefault()
|
||||
if (classLoader != null && locale != null) {
|
||||
try {
|
||||
val locale = specialLocale ?: getLocaleOrNullForDefault()
|
||||
for (localizedPath in getLocalizedPaths(path, locale)) {
|
||||
classLoader.getResourceAsStream(localizedPath)?.let { return it }
|
||||
}
|
||||
@@ -101,7 +101,8 @@ object LocalizationUtil {
|
||||
thisLogger().error("Cannot find localized resource: $path", e)
|
||||
}
|
||||
}
|
||||
return getPluginClassLoader()?.getResourceAsStream(path) ?: classLoader?.getResourceAsStream(path)
|
||||
return locale?.let { getPluginClassLoader(defaultLoader = null, locale = it)?.getResourceAsStream(path) }
|
||||
?: classLoader?.getResourceAsStream(path)
|
||||
}
|
||||
|
||||
@Internal
|
||||
|
||||
@@ -304,6 +304,7 @@ internal class ActionAsyncProvider(private val model: GotoActionModel) {
|
||||
|
||||
val map = model.configurablesNames
|
||||
val registrar = serviceAsync<SearchableOptionsRegistrar>() as SearchableOptionsRegistrarImpl
|
||||
registrar.initialize()
|
||||
|
||||
val words = registrar.getProcessedWords(pattern)
|
||||
val filterOutInspections = Registry.`is`("go.to.action.filter.out.inspections", true)
|
||||
@@ -323,12 +324,12 @@ internal class ActionAsyncProvider(private val model: GotoActionModel) {
|
||||
|
||||
var registrarDescriptions: MutableSet<OptionDescription>? = null
|
||||
for (word in words) {
|
||||
val descriptions = registrar.findAcceptableDescriptions(word)?.toHashSet()
|
||||
descriptions?.removeIf {
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
it.path == "ActionManager" || filterOutInspections && it.groupName == "Inspections"
|
||||
}
|
||||
|
||||
val descriptions = registrar.findAcceptableDescriptions(word)
|
||||
?.filter {
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
!(it.path == "ActionManager" || filterOutInspections && it.groupName == "Inspections")
|
||||
}
|
||||
?.toHashSet()
|
||||
if (descriptions.isNullOrEmpty()) {
|
||||
registrarDescriptions = null
|
||||
break
|
||||
|
||||
@@ -6,9 +6,9 @@ import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
data class ConfigurableHit(
|
||||
@JvmField val nameHits: Set<Configurable>,
|
||||
@JvmField val nameFullHits: Set<Configurable>,
|
||||
@JvmField val contentHits: Set<Configurable>,
|
||||
@JvmField val nameHits: Collection<Configurable>,
|
||||
@JvmField val nameFullHits: Collection<Configurable>,
|
||||
@JvmField val contentHits: List<Configurable>,
|
||||
@JvmField val spotlightFilter: String,
|
||||
) {
|
||||
val all: Set<Configurable>
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.util.ui.update.Activatable;
|
||||
import com.intellij.util.ui.update.UiNotifyConnector;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -37,28 +38,34 @@ public final class IdeGlassPaneUtil {
|
||||
}
|
||||
|
||||
public static void installPainter(@NotNull JComponent target, @NotNull Painter painter, @NotNull Disposable parent) {
|
||||
final UiNotifyConnector connector = UiNotifyConnector.installOn(target, new Activatable() {
|
||||
private IdeGlassPane myPane;
|
||||
private Disposable myPanePainterListeners = Disposer.newDisposable();
|
||||
Activatable listeners = createPainterActivatable(target, painter);
|
||||
UiNotifyConnector connector = UiNotifyConnector.installOn(target, listeners);
|
||||
Disposer.register(parent, connector);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static @NotNull Activatable createPainterActivatable(@NotNull JComponent target, @NotNull Painter painter) {
|
||||
return new Activatable() {
|
||||
private Disposable panePainterListeners;
|
||||
|
||||
@Override
|
||||
public void showNotify() {
|
||||
IdeGlassPane pane = find(target);
|
||||
if (myPane != null && myPane != pane) {
|
||||
Disposer.dispose(myPanePainterListeners);
|
||||
if (panePainterListeners != null) {
|
||||
Disposer.dispose(panePainterListeners);
|
||||
}
|
||||
myPane = pane;
|
||||
myPanePainterListeners = Disposer.newDisposable("PanePainterListeners");
|
||||
Disposer.register(parent, myPanePainterListeners);
|
||||
myPane.addPainter(target, painter, myPanePainterListeners);
|
||||
|
||||
panePainterListeners = Disposer.newDisposable("PanePainterListeners");
|
||||
pane.addPainter(target, painter, panePainterListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideNotify() {
|
||||
Disposer.dispose(myPanePainterListeners);
|
||||
if (panePainterListeners != null) {
|
||||
Disposer.dispose(panePainterListeners);
|
||||
}
|
||||
}
|
||||
});
|
||||
Disposer.register(parent, connector);
|
||||
};
|
||||
}
|
||||
|
||||
public static boolean canBePreprocessed(@NotNull MouseEvent e) {
|
||||
|
||||
@@ -10,7 +10,8 @@ import javax.swing.*;
|
||||
* Gets notified when a component is found and should be highlighted.
|
||||
*/
|
||||
public interface ComponentHighlightingListener {
|
||||
Topic<ComponentHighlightingListener> TOPIC = Topic.create("highlightComponent", ComponentHighlightingListener.class);
|
||||
Topic<ComponentHighlightingListener> TOPIC =
|
||||
new Topic<>("highlightComponent", ComponentHighlightingListener.class, Topic.BroadcastDirection.NONE);
|
||||
|
||||
void highlight(@NotNull JComponent component, @NotNull String searchString);
|
||||
}
|
||||
|
||||
@@ -63,9 +63,8 @@ class SearchableOptionsRegistrarImpl(private val coroutineScope: CoroutineScope)
|
||||
stopWords = emptySet()
|
||||
}
|
||||
else {
|
||||
startLoading()
|
||||
|
||||
stopWords = loadStopWords()
|
||||
startLoading()
|
||||
|
||||
app.getMessageBus().simpleConnect().subscribe<DynamicPluginListener>(DynamicPluginListener.TOPIC, object : DynamicPluginListener {
|
||||
override fun pluginLoaded(pluginDescriptor: IdeaPluginDescriptor) {
|
||||
@@ -152,12 +151,12 @@ class SearchableOptionsRegistrarImpl(private val coroutineScope: CoroutineScope)
|
||||
try {
|
||||
extension.instance?.contribute(processor)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error(PluginException(e, extension.pluginDescriptor.pluginId))
|
||||
}
|
||||
catch (e: CancellationException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error(PluginException(e, extension.pluginDescriptor.pluginId))
|
||||
}
|
||||
}
|
||||
|
||||
coroutineContext.ensureActive()
|
||||
@@ -198,13 +197,20 @@ class SearchableOptionsRegistrarImpl(private val coroutineScope: CoroutineScope)
|
||||
|
||||
val identifierTable = identifierTable!!
|
||||
val groupName = if (_groupName == Short.MAX_VALUE.toInt()) null else identifierTable.fromId(_groupName)
|
||||
val configurableId = identifierTable.fromId(_id).toString()
|
||||
val hit = if (_hit == Short.MAX_VALUE.toInt()) null else identifierTable.fromId(_hit).toString()
|
||||
val path = if (_path == Short.MAX_VALUE.toInt()) null else identifierTable.fromId(_path).toString()
|
||||
val configurableId = identifierTable.fromId(_id)
|
||||
val hit = if (_hit == Short.MAX_VALUE.toInt()) null else identifierTable.fromId(_hit)
|
||||
val path = if (_path == Short.MAX_VALUE.toInt()) null else identifierTable.fromId(_path)
|
||||
|
||||
return OptionDescription(_option = null, configurableId = configurableId, hit = hit, path = path, groupName = groupName)
|
||||
}
|
||||
|
||||
@Suppress("LocalVariableName")
|
||||
private fun unpackConfigurableId(data: Long): String {
|
||||
val _id = (data shr 32 and 0xffffL).toInt()
|
||||
assert(_id < Short.Companion.MAX_VALUE)
|
||||
return identifierTable!!.fromId(_id)
|
||||
}
|
||||
|
||||
override fun getConfigurables(
|
||||
groups: List<ConfigurableGroup>,
|
||||
type: DocumentEvent.EventType?,
|
||||
@@ -276,7 +282,7 @@ class SearchableOptionsRegistrarImpl(private val coroutineScope: CoroutineScope)
|
||||
return ConfigurableHit(
|
||||
nameHits = nameHits,
|
||||
nameFullHits = nameFullHits,
|
||||
contentHits = emptySet(),
|
||||
contentHits = emptyList(),
|
||||
spotlightFilter = option,
|
||||
)
|
||||
}
|
||||
@@ -296,28 +302,39 @@ class SearchableOptionsRegistrarImpl(private val coroutineScope: CoroutineScope)
|
||||
return ConfigurableHit(
|
||||
nameHits = nameHits,
|
||||
nameFullHits = nameFullHits,
|
||||
contentHits = LinkedHashSet(contentHits),
|
||||
contentHits = contentHits,
|
||||
spotlightFilter = option,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findConfigurablesByDescriptions(descriptionOptions: Set<String>): MutableSet<String>? {
|
||||
var helpIds: MutableSet<String>? = null
|
||||
var result: MutableSet<String>? = null
|
||||
for (prefix in descriptionOptions) {
|
||||
val ids = (findAcceptableDescriptions(prefix) ?: return null).mapTo(HashSet()) { it.configurableId!! }
|
||||
if (helpIds == null) {
|
||||
helpIds = ids
|
||||
val ids = HashSet<String>()
|
||||
for (longs in findAcceptablePackedDescriptions(prefix) ?: return null) {
|
||||
for (l in longs) {
|
||||
ids.add(unpackConfigurableId(l))
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = ids
|
||||
}
|
||||
else {
|
||||
result.retainAll(ids)
|
||||
}
|
||||
helpIds.retainAll(ids)
|
||||
}
|
||||
return helpIds
|
||||
return result
|
||||
}
|
||||
|
||||
fun findAcceptableDescriptions(prefix: String): Sequence<OptionDescription>? {
|
||||
return findAcceptablePackedDescriptions(prefix)?.flatMap { data -> data.asSequence().map { unpack(it)} }
|
||||
}
|
||||
|
||||
private fun findAcceptablePackedDescriptions(prefix: String): Sequence<LongArray>? {
|
||||
val storage = storage?.takeIf { it.isCompleted }?.getCompleted()
|
||||
if (storage == null) {
|
||||
LOG.warn("Not yet initialized")
|
||||
LOG.error("Not yet initialized")
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -336,9 +353,7 @@ class SearchableOptionsRegistrarImpl(private val coroutineScope: CoroutineScope)
|
||||
}
|
||||
}
|
||||
|
||||
for (description in entry.value) {
|
||||
yield(unpack(description))
|
||||
}
|
||||
yield(entry.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -496,7 +511,7 @@ private fun findGroupsByPath(groups: List<ConfigurableGroup>, path: String): Con
|
||||
lastMatchedIndex = i
|
||||
|
||||
if (matched is Configurable.Composite) {
|
||||
current = (matched as Configurable.Composite).getConfigurables().asList()
|
||||
current = matched.getConfigurables().asList()
|
||||
}
|
||||
else {
|
||||
break
|
||||
@@ -513,7 +528,7 @@ private fun findGroupsByPath(groups: List<ConfigurableGroup>, path: String): Con
|
||||
""
|
||||
}
|
||||
|
||||
val hits = setOf(lastMatched)
|
||||
val hits = listOf(lastMatched)
|
||||
return ConfigurableHit(nameHits = hits, nameFullHits = hits, contentHits = hits, spotlightFilter = spotlightFilter)
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ public class SettingsDialog extends DialogWrapper implements UiCompatibleDataPro
|
||||
@NotNull Disposable parent,
|
||||
@NotNull Function1<? super SpotlightPainter, Unit> updater
|
||||
) {
|
||||
return new SpotlightPainter(target, parent, updater);
|
||||
return new SpotlightPainter(target, updater);
|
||||
}
|
||||
|
||||
private void init(@Nullable Configurable configurable, @Nullable Project project) {
|
||||
|
||||
@@ -244,7 +244,7 @@ private fun getUnnamedConfigurable(candidate: Configurable): UnnamedConfigurable
|
||||
return if (candidate is ConfigurableWrapper) candidate.getConfigurable() else candidate
|
||||
}
|
||||
|
||||
private fun findConfigurable(configurables: Set<Configurable>, hits: Set<Configurable>?): Configurable? {
|
||||
private fun findConfigurable(configurables: Collection<Configurable>, hits: Collection<Configurable>?): Configurable? {
|
||||
var candidate: Configurable? = null
|
||||
for (configurable in configurables) {
|
||||
if (hits != null && hits.contains(configurable)) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("ReplaceGetOrSet")
|
||||
|
||||
package com.intellij.openapi.options.newEditor
|
||||
|
||||
import com.intellij.ide.ui.search.ComponentHighlightingListener
|
||||
import com.intellij.ide.ui.search.SearchUtil.lightOptions
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import com.intellij.openapi.options.SearchableConfigurable
|
||||
@@ -13,8 +14,12 @@ import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.wm.IdeGlassPaneUtil
|
||||
import com.intellij.ui.ClientProperty
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.intellij.util.ui.update.MergingUpdateQueue
|
||||
import com.intellij.util.ui.update.Update
|
||||
import com.intellij.util.ui.showingScope
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.awt.Component
|
||||
import java.awt.Graphics2D
|
||||
@@ -26,27 +31,47 @@ import javax.swing.SwingUtilities
|
||||
|
||||
private val DO_NOT_SCROLL = Key.create<Boolean?>("SpotlightPainter.DO_NOT_SCROLL")
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
@ApiStatus.Internal
|
||||
open class SpotlightPainter constructor(
|
||||
open class SpotlightPainter(
|
||||
private val target: JComponent,
|
||||
parent: Disposable,
|
||||
private val updater: (SpotlightPainter) -> Unit,
|
||||
) : AbstractPainter(), ComponentHighlightingListener {
|
||||
) : AbstractPainter() {
|
||||
private val configurableOption = HashMap<String?, String?>()
|
||||
private val queue = MergingUpdateQueue(
|
||||
name = "SettingsSpotlight",
|
||||
mergingTimeSpan = 200,
|
||||
isActive = false,
|
||||
modalityStateComponent = target,
|
||||
parent = parent,
|
||||
activationComponent = target,
|
||||
)
|
||||
private val glassPanel = GlassPanel(target)
|
||||
private var isVisible: Boolean = false
|
||||
|
||||
private val updateRequests = MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
init {
|
||||
IdeGlassPaneUtil.installPainter(target, this, parent)
|
||||
ApplicationManager.getApplication().getMessageBus().connect(parent).subscribe(ComponentHighlightingListener.TOPIC, this)
|
||||
val activatable = IdeGlassPaneUtil.createPainterActivatable(target, this)
|
||||
target.showingScope("SpotlightPainter") {
|
||||
activatable.showNotify()
|
||||
try {
|
||||
ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(ComponentHighlightingListener.TOPIC, object : ComponentHighlightingListener {
|
||||
override fun highlight(component: JComponent, searchString: String) {
|
||||
// If several spotlight painters exist, they will receive each other updates,
|
||||
// because they share one message bus (ComponentHighlightingListener.TOPIC).
|
||||
// The painter should only draw spotlights for components in the hierarchy of `myTarget`
|
||||
if (UIUtil.isAncestor(target, component)) {
|
||||
glassPanel.addSpotlight(component)
|
||||
if (target.getClientProperty(DO_NOT_SCROLL) != true && center(component)) {
|
||||
target.putClientProperty(DO_NOT_SCROLL, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
updateRequests
|
||||
.debounce(200)
|
||||
.collectLatest {
|
||||
updateNow()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
activatable.hideNotify()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -64,11 +89,7 @@ open class SpotlightPainter constructor(
|
||||
override fun needsRepaint(): Boolean = true
|
||||
|
||||
fun updateLater() {
|
||||
queue.queue(object : Update(this) {
|
||||
override fun run() {
|
||||
updateNow()
|
||||
}
|
||||
})
|
||||
updateRequests.tryEmit(Unit)
|
||||
}
|
||||
|
||||
fun updateNow() {
|
||||
@@ -104,26 +125,8 @@ open class SpotlightPainter constructor(
|
||||
|
||||
fireNeedsRepaint(glassPanel)
|
||||
}
|
||||
|
||||
override fun highlight(component: JComponent, searchString: String) {
|
||||
// If several spotlight painters exist, they will receive each other updates,
|
||||
// because they share one message bus (ComponentHighlightingListener.TOPIC).
|
||||
// The painter should only draw spotlights for components in the hierarchy of `myTarget`
|
||||
if (UIUtil.isAncestor(target, component)) {
|
||||
glassPanel.addSpotlight(component)
|
||||
if (isScrollingEnabled(target) && center(component)) {
|
||||
disableScrolling(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableScrolling(target: JComponent) {
|
||||
target.putClientProperty(DO_NOT_SCROLL, true)
|
||||
}
|
||||
|
||||
private fun isScrollingEnabled(target: JComponent): Boolean = !ClientProperty.isTrue(target, DO_NOT_SCROLL)
|
||||
|
||||
private fun center(component: JComponent): Boolean {
|
||||
var scrollPane: JScrollPane? = null
|
||||
var c: Component? = component
|
||||
|
||||
Reference in New Issue
Block a user