groups = FUStatisticsWhiteListGroupsService.parseApprovedGroups(myEditor.getDocument().getText(), build);
+ try {
+ final String parsed = parseLogAndFilter(new LogEventWhitelistFilter(groups), myEventLogPanel.getText());
+ updateOutputText(parsed.trim());
+ }
+ catch (IOException | ParseEventLogWhitelistException e) {
+ Messages.showErrorDialog(myProject, e.getMessage(), "Failed Applying Whitelist to Event Log");
+ }
+ }
+
+ private void updateOutputText(@NotNull String text) {
+ myResultPane.setText(text);
+ }
+
+ @NotNull
+ private static String parseLogAndFilter(@NotNull LogEventFilter filter, @NotNull String text)
+ throws IOException, ParseEventLogWhitelistException {
+ final File log = FileUtil.createTempFile("feature-event-log", ".log");
+ try {
+ FileUtil.writeToFile(log, text);
+ final LogEventRecordRequest request = LogEventRecordRequest.Companion.create(log, filter, true);
+ if (request == null) {
+ throw new ParseEventLogWhitelistException("Failed parsing event log");
+ }
+ return LogEventSerializer.INSTANCE.toString(request);
+ }
+ finally {
+ FileUtil.delete(log);
+ }
+ }
+
+ @Nullable
+ @Override
+ protected JComponent createCenterPanel() {
+ return myMainPanel;
+ }
+
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return myEventLogPanel;
+ }
+
+ @Override
+ public void dispose() {
+ if (!myEditor.isDisposed()) {
+ EditorFactory.getInstance().releaseEditor(myEditor);
+ }
+ super.dispose();
+ }
+
+ public static class ParseEventLogWhitelistException extends Exception {
+ public ParseEventLogWhitelistException(String s) {
+ super(s);
+ }
+ }
+}
diff --git a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.java b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.java
index 361de262f16d..9bfe6b5ba376 100644
--- a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.java
+++ b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.java
@@ -40,8 +40,8 @@ public class PsiAwareTextEditorImpl extends TextEditorImpl {
@NotNull
@Override
- protected Runnable loadEditorInBackground() {
- Runnable baseAction = super.loadEditorInBackground();
+ protected AsyncEditorLoader.LoadEditorResult loadEditorInBackground() {
+ AsyncEditorLoader.LoadEditorResult baseResult = super.loadEditorInBackground();
PsiFile psiFile = PsiManager.getInstance(myProject).findFile(myFile);
Document document = FileDocumentManager.getInstance().getDocument(myFile);
boolean shouldBuildInitialFoldings =
@@ -52,25 +52,26 @@ public class PsiAwareTextEditorImpl extends TextEditorImpl {
List extends Segment> zones = FocusModePassFactory.calcFocusZones(psiFile);
- return () -> {
- baseAction.run();
- Editor editor = getEditor();
+ return new AsyncEditorLoader.LoadEditorResult(baseResult.continuationValidator.and(d -> psiFile == null || psiFile.isValid()),
+ () -> {
+ baseResult.continuation.run();
+ Editor editor = getEditor();
- if (foldingState != null) {
- foldingState.setToEditor(editor);
- }
+ if (foldingState != null) {
+ foldingState.setToEditor(editor);
+ }
- if (zones != null) {
- FocusModePassFactory.setToEditor(zones, editor);
- if (editor instanceof EditorImpl) {
- ((EditorImpl)editor).applyFocusMode();
- }
- }
+ if (zones != null) {
+ FocusModePassFactory.setToEditor(zones, editor);
+ if (editor instanceof EditorImpl) {
+ ((EditorImpl)editor).applyFocusMode();
+ }
+ }
- if (psiFile != null && psiFile.isValid()) {
- DaemonCodeAnalyzer.getInstance(myProject).restart(psiFile);
- }
- };
+ if (psiFile != null && psiFile.isValid()) {
+ DaemonCodeAnalyzer.getInstance(myProject).restart(psiFile);
+ }
+ });
}
@NotNull
diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/componentsList/components/ScrollablePanel.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/componentsList/components/ScrollablePanel.java
index a6a0bb429103..6bc08334b4e7 100644
--- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/componentsList/components/ScrollablePanel.java
+++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/componentsList/components/ScrollablePanel.java
@@ -21,7 +21,7 @@ import java.awt.*;
public class ScrollablePanel extends JPanel implements Scrollable {
private int myUnitHeight = -1;
- private final int myUnitWidth = 10;
+ private static final int myUnitWidth = 10;
public ScrollablePanel() {
}
diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/componentsList/layout/VerticalStackLayout.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/componentsList/layout/VerticalStackLayout.java
index 8bf1dabd917e..32eb20adc95c 100644
--- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/componentsList/layout/VerticalStackLayout.java
+++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/componentsList/layout/VerticalStackLayout.java
@@ -20,7 +20,7 @@ package com.intellij.openapi.roots.ui.componentsList.layout;
import java.awt.*;
public class VerticalStackLayout implements LayoutManager2 {
- private final int myDefaultHeight = 200;
+ private static final int myDefaultHeight = 200;
/**
* Calculates the minimum size dimensions for the specified
diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionToolWrapperWithHint.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionToolWrapperWithHint.java
new file mode 100644
index 000000000000..781f98aaf97a
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionToolWrapperWithHint.java
@@ -0,0 +1,9 @@
+// Copyright 2000-2019 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.
+package com.intellij.profile.codeInspection.ui.inspectionsTree;
+
+public interface InspectionToolWrapperWithHint {
+ /**
+ * Hint will be displayed in gray after inspection name
+ */
+ String getHint();
+}
diff --git a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeRenderer.java b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeRenderer.java
index 1266aa461505..28d886f50393 100644
--- a/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeRenderer.java
+++ b/platform/lang-impl/src/com/intellij/profile/codeInspection/ui/inspectionsTree/InspectionsConfigTreeRenderer.java
@@ -61,6 +61,10 @@ public abstract class InspectionsConfigTreeRenderer extends DefaultTreeRenderer
@Nullable
private static String getHint(final Descriptor descriptor) {
final InspectionToolWrapper toolWrapper = descriptor.getToolWrapper();
+
+ if (toolWrapper.getTool() instanceof InspectionToolWrapperWithHint) {
+ return ((InspectionToolWrapperWithHint)toolWrapper.getTool()).getHint();
+ }
if (toolWrapper instanceof LocalInspectionToolWrapper ||
toolWrapper instanceof GlobalInspectionToolWrapper && !((GlobalInspectionToolWrapper)toolWrapper).worksInBatchModeOnly()) {
return null;
diff --git a/platform/platform-api/src/com/intellij/ide/HelpTooltip.java b/platform/platform-api/src/com/intellij/ide/HelpTooltip.java
index 464f88717b7f..af32d915cb50 100644
--- a/platform/platform-api/src/com/intellij/ide/HelpTooltip.java
+++ b/platform/platform-api/src/com/intellij/ide/HelpTooltip.java
@@ -68,7 +68,7 @@ import java.util.Map;
*
* No HTML tagging is allowed in title or shortcut, they are supposed to be simple text strings.
* Description is can be html formatted. You can use all possible html tagging in description just without enclosing
- * <html> and </html> tags themselves. In description it's allowed to have <p/ or <p> tags between paragraphs.
+ * <html> and </html> tags themselves. In description it's allowed to have <p/> or <p> tags between paragraphs.
* Paragraphs will be rendered with the standard (10px) offset from the title, from one another and from the link.
* To force the line break in a paragraph use <br/>. Standard font coloring and styling is also available.
*
diff --git a/platform/platform-api/src/com/intellij/ide/SelectInContext.java b/platform/platform-api/src/com/intellij/ide/SelectInContext.java
index 0ed1282037f5..a57cc553454b 100644
--- a/platform/platform-api/src/com/intellij/ide/SelectInContext.java
+++ b/platform/platform-api/src/com/intellij/ide/SelectInContext.java
@@ -38,4 +38,15 @@ public interface SelectInContext {
@Nullable
FileEditorProvider getFileEditorProvider();
+
+ /**
+ * @param target an object that supports a context selection
+ * @param requestFocus specifies whether a focus request is needed or not
+ * @return {@code true} if a selection request is approved and executed by the given target
+ */
+ default boolean selectIn(@NotNull SelectInTarget target, boolean requestFocus) {
+ if (!target.canSelect(this)) return false;
+ target.selectIn(this, requestFocus);
+ return true;
+ }
}
\ No newline at end of file
diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionPlaces.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionPlaces.java
index ca6813684e47..cc1e39a70d68 100644
--- a/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionPlaces.java
+++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/ActionPlaces.java
@@ -40,10 +40,13 @@ public abstract class ActionPlaces {
public static final String EDITOR_TOOLBAR = "EditorToolbar";
public static final String EDITOR_TAB_POPUP = "EditorTabPopup";
public static final String EDITOR_TAB = "EditorTab";
+ public static final String EDITOR_GUTTER = "ICON_NAVIGATION";
+ public static final String EDITOR_GUTTER_POPUP = "ICON_NAVIGATION_SECONDARY_BUTTON";
public static final String COMMANDER_POPUP = "CommanderPopup";
public static final String COMMANDER_TOOLBAR = "CommanderToolbar";
public static final String CONTEXT_TOOLBAR = "ContextToolbar";
public static final String TOOLWINDOW_TITLE = "ToolwindowTitle";
+ public static final String TOOLWINDOW_CONTENT = "ToolwindowContent";
public static final String PROJECT_VIEW_POPUP = "ProjectViewPopup";
public static final String PROJECT_VIEW_TOOLBAR = "ProjectViewToolbar";
@@ -151,7 +154,7 @@ public abstract class ActionPlaces {
FILEHISTORY_VIEW_TOOLBAR, RUN_CONFIGURATIONS_COMBOBOX, WELCOME_SCREEN, CHANGES_VIEW_TOOLBAR, DATABASE_VIEW_TOOLBAR,
ACTION_PLACE_QUICK_LIST_POPUP_ACTION, PHING_EXPLORER_TOOLBAR, DOCK_MENU, PHING_MESSAGES_TOOLBAR, DIFF_TOOLBAR,
ANALYZE_STACKTRACE_PANEL_TOOLBAR, TOUCHBAR_GENERAL, COMPOSER_EDITOR_NOTIFICATION_PANEL, COMPOSER_EDITOR_NOTIFICATION_PANEL_EXTRA,
- COMPOSER_LOG_RERUN
+ COMPOSER_LOG_RERUN, EDITOR_GUTTER, TOOLWINDOW_CONTENT
);
private static final Set ourPopupPlaces = ContainerUtil.newHashSet(
@@ -163,7 +166,7 @@ public abstract class ActionPlaces {
GUI_DESIGNER_PROPERTY_INSPECTOR_POPUP,
CREATE_EJB_POPUP, CHANGES_VIEW_POPUP, DATABASE_VIEW_POPUP, REMOTE_HOST_VIEW_POPUP, REMOTE_HOST_DIALOG_POPUP, TFS_TREE_POPUP,
ACTION_PLACE_VCS_QUICK_LIST_POPUP_ACTION, PHING_EXPLORER_POPUP, NAVIGATION_BAR_POPUP, JS_BUILD_TOOL_POPUP,
- V8_CPU_PROFILING_POPUP, V8_HEAP_PROFILING_POPUP, V8_HEAP_PROFILING_POPUP, RUN_DASHBOARD_POPUP
+ V8_CPU_PROFILING_POPUP, V8_HEAP_PROFILING_POPUP, V8_HEAP_PROFILING_POPUP, RUN_DASHBOARD_POPUP, EDITOR_GUTTER_POPUP
);
private static final String POPUP_PREFIX = "popup@";
diff --git a/platform/platform-api/src/com/intellij/openapi/fileEditor/OpenFileDescriptor.java b/platform/platform-api/src/com/intellij/openapi/fileEditor/OpenFileDescriptor.java
index 5ccd0deec282..3ffc591d4d17 100644
--- a/platform/platform-api/src/com/intellij/openapi/fileEditor/OpenFileDescriptor.java
+++ b/platform/platform-api/src/com/intellij/openapi/fileEditor/OpenFileDescriptor.java
@@ -167,10 +167,7 @@ public class OpenFileDescriptor implements Navigatable, Comparable%s", validationInfo.message));
- String text = v.getPreferredSpan(View.X_AXIS) > MAX_WIDTH.get() ?
- String.format("%s
", MAX_WIDTH.get(), validationInfo.message) :
- String.format("%s
", validationInfo.message);
-
- tipComponent.setContentType("text/html");
- tipComponent.setEditable(false);
- tipComponent.addHyperlinkListener(hyperlinkListener);
- tipComponent.setEditorKit(UIUtil.getHTMLEditorKit());
-
- EditorKit kit = tipComponent.getEditorKit();
- if (kit instanceof HTMLEditorKit) {
- StyleSheet css = ((HTMLEditorKit)kit).getStyleSheet();
-
- css.addRule("a, a:link {color:#" + ColorUtil.toHex(JBUI.CurrentTheme.Link.linkColor()) + ";}");
- css.addRule("a:visited {color:#" + ColorUtil.toHex(JBUI.CurrentTheme.Link.linkVisitedColor()) + ";}");
- css.addRule("a:hover {color:#" + ColorUtil.toHex(JBUI.CurrentTheme.Link.linkHoverColor()) + ";}");
- css.addRule("a:active {color:#" + ColorUtil.toHex(JBUI.CurrentTheme.Link.linkPressedColor()) + ";}");
- css.addRule("body {background-color:#" + ColorUtil.toHex(validationInfo.warning ? warningBackgroundColor() : errorBackgroundColor()) + ";}");
- }
-
- if (tipComponent.getCaret() instanceof DefaultCaret) {
- ((DefaultCaret)tipComponent.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
- }
-
- tipComponent.setCaretPosition(0);
- tipComponent.setText(text);
-
- tipComponent.setBackground(validationInfo.warning ? warningBackgroundColor() : errorBackgroundColor());
- tipComponent.setOpaque(true);
- tipComponent.setBorder(getBorder());
- tipComponent.addMouseListener(new TipComponentMouseListener());
-
- popupSize = tipComponent.getPreferredSize();
-
- popupBuilder = JBPopupFactory.getInstance().createComponentPopupBuilder(tipComponent, null).
- setBorderColor(validationInfo.warning ? warningBorderColor() : errorBorderColor()).
- setCancelOnClickOutside(false).
- setCancelOnMouseOutCallback(e -> e.getID() == MouseEvent.MOUSE_PRESSED && !withinComponent(e)).
- setShowShadow(false);
+ popupBuilder = createPopupBuilder(validationInfo, tipComponent -> {
+ tipComponent.addHyperlinkListener(hyperlinkListener);
+ tipComponent.addMouseListener(new TipComponentMouseListener());
+ popupSize = tipComponent.getPreferredSize();
+ });
getFocusable(validationInfo.component).ifPresent(fc -> {
if (fc.hasFocus()) {
@@ -253,9 +232,54 @@ public class ComponentValidator {
}
}
- private boolean withinComponent(@NotNull MouseEvent e) {
- if (validationInfo != null && validationInfo.component != null && validationInfo.component.isShowing()) {
- Rectangle screenBounds = new Rectangle(validationInfo.component.getLocationOnScreen(), validationInfo.component.getSize());
+ @NotNull
+ public static ComponentPopupBuilder createPopupBuilder(@NotNull ValidationInfo info, @Nullable Consumer configurator) {
+ JEditorPane tipComponent = new JEditorPane();
+ View v = BasicHTML.createHTMLView(tipComponent, String.format("%s", info.message));
+ String text = v.getPreferredSpan(View.X_AXIS) > MAX_WIDTH.get() ?
+ String.format("%s
", MAX_WIDTH.get(), info.message) :
+ String.format("%s
", info.message);
+
+ tipComponent.setContentType("text/html");
+ tipComponent.setEditable(false);
+ tipComponent.setEditorKit(UIUtil.getHTMLEditorKit());
+
+ EditorKit kit = tipComponent.getEditorKit();
+ if (kit instanceof HTMLEditorKit) {
+ StyleSheet css = ((HTMLEditorKit)kit).getStyleSheet();
+
+ css.addRule("a, a:link {color:#" + ColorUtil.toHex(JBUI.CurrentTheme.Link.linkColor()) + ";}");
+ css.addRule("a:visited {color:#" + ColorUtil.toHex(JBUI.CurrentTheme.Link.linkVisitedColor()) + ";}");
+ css.addRule("a:hover {color:#" + ColorUtil.toHex(JBUI.CurrentTheme.Link.linkHoverColor()) + ";}");
+ css.addRule("a:active {color:#" + ColorUtil.toHex(JBUI.CurrentTheme.Link.linkPressedColor()) + ";}");
+ css.addRule("body {background-color:#" + ColorUtil.toHex(info.warning ? warningBackgroundColor() : errorBackgroundColor()) + ";}");
+ }
+
+ if (tipComponent.getCaret() instanceof DefaultCaret) {
+ ((DefaultCaret)tipComponent.getCaret()).setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
+ }
+
+ tipComponent.setCaretPosition(0);
+ tipComponent.setText(text);
+
+ tipComponent.setBackground(info.warning ? warningBackgroundColor() : errorBackgroundColor());
+ tipComponent.setOpaque(true);
+ tipComponent.setBorder(getBorder());
+
+ if (configurator != null) {
+ configurator.accept(tipComponent);
+ }
+
+ return JBPopupFactory.getInstance().createComponentPopupBuilder(tipComponent, null).
+ setBorderColor(info.warning ? warningBorderColor() : errorBorderColor()).
+ setCancelOnClickOutside(false).
+ setShowShadow(false).
+ setCancelOnMouseOutCallback(e -> e.getID() == MouseEvent.MOUSE_PRESSED && !withinComponent(info, e));
+ }
+
+ private static boolean withinComponent(@NotNull ValidationInfo info, @NotNull MouseEvent e) {
+ if (info.component != null && info.component.isShowing()) {
+ Rectangle screenBounds = new Rectangle(info.component.getLocationOnScreen(), info.component.getSize());
return screenBounds.contains(e.getLocationOnScreen());
}
else {
@@ -279,15 +303,18 @@ public class ComponentValidator {
}
}
- private void hidePopup() {
+ private void hidePopup(boolean now) {
if (popup != null && popup.isVisible()) {
- popupAlarm.cancelAllRequests();
- popupAlarm.addRequest(() -> {
- if (popup != null && (!isOverPopup || hyperlinkListener == null)) {
- popup.cancel();
- popup = null;
- }
- }, Registry.intValue("ide.tooltip.initialDelay.highlighter"));
+ if (now || hyperlinkListener == null) {
+ popup.cancel();
+ popup = null;
+ } else {
+ popupAlarm.addRequest(() -> {
+ if (!isOverPopup || hyperlinkListener == null) {
+ hidePopup(true);
+ }
+ }, Registry.intValue("ide.tooltip.initialDelay.highlighter"));
+ }
}
}
@@ -310,7 +337,7 @@ public class ComponentValidator {
@Override
public void focusLost(FocusEvent e) {
- hidePopup();
+ hidePopup(false);
ValidationInfo info = null;
if (focusValidator != null) {
@@ -337,7 +364,7 @@ public class ComponentValidator {
if (validationInfo != null) {
getFocusable(validationInfo.component).ifPresent(fc -> {
if (!fc.hasFocus()) {
- hidePopup();
+ hidePopup(false);
}
});
}
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java
index f615532188b6..7dfe59565f24 100644
--- a/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java
+++ b/platform/platform-api/src/com/intellij/openapi/ui/DialogWrapper.java
@@ -1784,27 +1784,30 @@ public abstract class DialogWrapper {
}
private void logCloseDialogEvent(int exitCode) {
- final String dialogId = getLoggedDialogId();
- if (StringUtil.isNotEmpty(dialogId)) {
- FeatureUsageUiEventsKt.getUiEventLogger().logCloseDialog(dialogId, exitCode, getClass());
+ final boolean canRecord = canRecordDialogId();
+ if (canRecord) {
+ final String dialogId = getClass().getName();
+ if (StringUtil.isNotEmpty(dialogId)) {
+ FeatureUsageUiEventsKt.getUiEventLogger().logCloseDialog(dialogId, exitCode, getClass());
+ }
}
}
private void logShowDialogEvent() {
- final String dialogId = getLoggedDialogId();
- if (StringUtil.isNotEmpty(dialogId)) {
- FeatureUsageUiEventsKt.getUiEventLogger().logShowDialog(dialogId, getClass());
+ final boolean canRecord = canRecordDialogId();
+ if (canRecord) {
+ final String dialogId = getClass().getName();
+ if (StringUtil.isNotEmpty(dialogId)) {
+ FeatureUsageUiEventsKt.getUiEventLogger().logShowDialog(dialogId, getClass());
+ }
}
}
/**
- * The ID will be recorded in user event log, it can be used to understand how often this dialog is used.
- *
- * @return null if we shouldn't record the dialog.
+ * If dialog open/close events should be recorded in user event log, it can be used to understand how often this dialog is used.
*/
- @Nullable
- protected String getLoggedDialogId() {
- return getClass().getName();
+ protected boolean canRecordDialogId() {
+ return true;
}
/**
@@ -2050,8 +2053,8 @@ public abstract class DialogWrapper {
}
new Thread("DialogWrapper resizer") {
- final int time = 200;
- final int steps = 7;
+ static final int time = 200;
+ static final int steps = 7;
@Override
public void run() {
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java b/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java
index 130ce71d20a4..c43ef5bd384a 100644
--- a/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java
+++ b/platform/platform-api/src/com/intellij/openapi/ui/MasterDetailsComponent.java
@@ -815,8 +815,9 @@ public abstract class MasterDetailsComponent implements Configurable, DetailsCom
@NotNull
public String getDisplayName() {
final NamedConfigurable configurable = (NamedConfigurable)getUserObject();
- LOG.assertTrue(configurable != null, "Tree was already disposed");
- return configurable.getDisplayName();
+ if (configurable != null) return configurable.getDisplayName();
+ LOG.debug("Tree was already disposed"); // workaround for IDEA-206547
+ return "DISPOSED";
}
public NamedConfigurable getConfigurable() {
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/ValidationInfo.java b/platform/platform-api/src/com/intellij/openapi/ui/ValidationInfo.java
index 5a2fe565cd22..912823ddb56a 100644
--- a/platform/platform-api/src/com/intellij/openapi/ui/ValidationInfo.java
+++ b/platform/platform-api/src/com/intellij/openapi/ui/ValidationInfo.java
@@ -68,6 +68,11 @@ public final class ValidationInfo {
return this;
}
+ public ValidationInfo forComponent(@Nullable JComponent component) {
+ ValidationInfo result = new ValidationInfo(message, component);
+ return warning ? result.asWarning() : result;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/CellComponentProvider.java b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/CellComponentProvider.java
new file mode 100644
index 000000000000..84f20e2a411e
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/CellComponentProvider.java
@@ -0,0 +1,67 @@
+// Copyright 2000-2019 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.
+package com.intellij.openapi.ui.cellvalidators;
+
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.table.TableCellRenderer;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+
+@ApiStatus.Experimental
+public abstract class CellComponentProvider {
+ @NotNull protected final C owner;
+
+ public CellComponentProvider(@NotNull C owner) {
+ this.owner = owner;
+ }
+
+ @NotNull
+ public final C getOwner() {
+ return owner;
+ }
+
+ @Nullable
+ abstract public JComponent getCellRendererComponent(@NotNull Point p);
+
+ @NotNull
+ abstract public Rectangle getCellRect(@NotNull MouseEvent e);
+
+ public static CellComponentProvider forTable(JTable table) {
+ return new TableProvider(table);
+ }
+
+ /**
+ * Convenient classes with standard implementations. Don't use the class directly.
+ * It can either be created with {@link CellComponentProvider#forTable(JTable)} method or
+ * be extended.
+ */
+ public static class TableProvider extends CellComponentProvider {
+ protected TableProvider(@NotNull JTable owner) {
+ super(owner);
+ }
+
+ @Nullable
+ @Override
+ public JComponent getCellRendererComponent(@NotNull Point p) {
+ int column = owner.columnAtPoint(p);
+ int row = owner.rowAtPoint(p);
+
+ if ((column != -1) && (row != -1)) {
+ TableCellRenderer renderer = owner.getCellRenderer(row, column);
+ return (JComponent)owner.prepareRenderer(renderer, row, column);
+ } else {
+ return null;
+ }
+ }
+
+ @NotNull
+ @Override
+ public Rectangle getCellRect(@NotNull MouseEvent e) {
+ Point p = e.getPoint();
+ return owner.getCellRect(owner.rowAtPoint(p), owner.columnAtPoint(p), true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/CellTooltipManager.java b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/CellTooltipManager.java
new file mode 100644
index 000000000000..6434a5a7e922
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/CellTooltipManager.java
@@ -0,0 +1,187 @@
+// Copyright 2000-2019 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.
+package com.intellij.openapi.ui.cellvalidators;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.ui.ComponentValidator;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.ui.popup.ComponentPopupBuilder;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.ui.awt.RelativePoint;
+import com.intellij.util.Alarm;
+import com.intellij.util.ui.JBUI;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.HyperlinkListener;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.regex.Pattern;
+
+public final class CellTooltipManager {
+ private static final Pattern HTML_A_TAG_PATTERN = Pattern.compile("(?i)]+)>(.+?)");
+
+ private final Disposable parentDisposable;
+
+ private CellComponentProvider cellComponentProvider;
+ private HyperlinkListener hyperlinkListener;
+
+ private final Alarm popupAlarm = new Alarm();
+
+ private ValidationInfo validationInfo;
+ private boolean closeWithDelay;
+
+ private ComponentPopupBuilder popupBuilder;
+ private JBPopup cellPopup;
+ private Point cellLocation;
+ private Dimension popupSize;
+ private boolean isOverPopup;
+ private boolean isClosing;
+
+ @ApiStatus.Experimental
+ public CellTooltipManager(@NotNull Disposable parentDisposable) {
+ this.parentDisposable = parentDisposable;
+ }
+
+ @ApiStatus.Experimental
+ public CellTooltipManager withCellComponentProvider(@NotNull CellComponentProvider cellComponentProvider) {
+ this.cellComponentProvider = cellComponentProvider;
+ return this;
+ }
+
+ @ApiStatus.Experimental
+ public CellTooltipManager withHyperlinkListener(@NotNull HyperlinkListener hyperlinkListener) {
+ this.hyperlinkListener = hyperlinkListener;
+ return this;
+ }
+
+ @ApiStatus.Experimental
+ public void installOn(@NotNull JComponent component) {
+ MouseAdapter mouseListener = new ValidationMouseListener();
+
+ component.addMouseListener(mouseListener);
+ component.addMouseMotionListener(mouseListener);
+
+ Disposer.register(parentDisposable, () -> {
+ hidePopup(true, null);
+
+ component.removeMouseListener(mouseListener);
+ component.removeMouseMotionListener(mouseListener);
+
+ cellComponentProvider = null;
+ validationInfo = null;
+ popupBuilder = null;
+ });
+ }
+
+ private void handleMouseEvent(MouseEvent e) {
+ if (cellComponentProvider != null) {
+ JComponent cellRenderer = cellComponentProvider.getCellRendererComponent(e.getPoint());
+ ValidationInfo info = cellRenderer != null ? (ValidationInfo)cellRenderer.getClientProperty(ValidatingTableCellRendererWrapper.CELL_VALIDATION_PROPERTY) : null;
+
+ if (info != null) {
+ if (!info.equals(validationInfo)) {
+ validationInfo = info;
+ closeWithDelay = hasATag(validationInfo.message);
+
+ popupBuilder = ComponentValidator.createPopupBuilder(validationInfo, tipComponent -> {
+ if (closeWithDelay) {
+ tipComponent.addHyperlinkListener(hyperlinkListener);
+ tipComponent.addMouseListener(new TipComponentMouseListener());
+ }
+ popupSize = tipComponent.getPreferredSize();
+ });
+
+ hidePopup(false, () -> showPopup(e));
+ }
+ else if (!isShowing()) {
+ showPopup(e);
+ }
+ else if (!isClosing) { // Move popup to new location
+ Point location = cellComponentProvider.getCellRect(e).getLocation();
+ if (!location.equals(cellLocation)) {
+ cellLocation = location;
+ Point point = new Point(cellLocation.x + JBUI.scale(40), cellLocation.y - JBUI.scale(6) - popupSize.height);
+ SwingUtilities.convertPointToScreen(point, cellComponentProvider.getOwner());
+ cellPopup.setLocation(point);
+ }
+ }
+ } else {
+ validationInfo = null;
+ hidePopup(false, null);
+ }
+ }
+ }
+
+ private static boolean hasATag(@NotNull String string) {
+ return HTML_A_TAG_PATTERN.matcher(string).find();
+ }
+
+ private void showPopup(MouseEvent e) {
+ cellPopup = popupBuilder.createPopup();
+ cellLocation = cellComponentProvider.getCellRect(e).getLocation();
+ Point point = new Point(cellLocation.x + JBUI.scale(40), cellLocation.y - JBUI.scale(6) - popupSize.height);
+ cellPopup.show(new RelativePoint(cellComponentProvider.getOwner(), point));
+ }
+
+ private void hidePopup(boolean now, @Nullable Runnable onHidden) {
+ if (isShowing()) {
+ if (now || hyperlinkListener == null || !closeWithDelay) {
+ cellPopup.cancel();
+ cellPopup = null;
+
+ if (onHidden != null) {
+ onHidden.run();
+ }
+ } else if (!isClosing) {
+ isClosing = true;
+ popupAlarm.addRequest(() -> {
+ isClosing = false;
+ if (!isOverPopup) {
+ hidePopup(true, onHidden);
+ }
+ }, Registry.intValue("ide.tooltip.initialDelay.highlighter"));
+ }
+ } else if (onHidden != null) {
+ onHidden.run();
+ }
+ }
+
+ private boolean isShowing() {
+ return cellPopup != null && cellPopup.isVisible();
+ }
+
+ private class ValidationMouseListener extends MouseAdapter {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ handleMouseEvent(e);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ hidePopup(false, null);
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ handleMouseEvent(e);
+ }
+ }
+
+ private class TipComponentMouseListener extends MouseAdapter {
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ isOverPopup = true;
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ isOverPopup = false;
+ hidePopup(true, null);
+ }
+ }
+}
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/StatefulValidatingCellEditor.java b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/StatefulValidatingCellEditor.java
new file mode 100644
index 000000000000..b1d9743bfba0
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/StatefulValidatingCellEditor.java
@@ -0,0 +1,94 @@
+// Copyright 2000-2019 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.
+package com.intellij.openapi.ui.cellvalidators;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.ui.ComponentValidator;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.ui.DocumentAdapter;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.awt.*;
+import java.awt.event.ItemListener;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static com.intellij.openapi.ui.cellvalidators.ValidatingTableCellRendererWrapper.CELL_VALIDATION_PROPERTY;
+
+public class StatefulValidatingCellEditor extends DefaultCellEditor implements Supplier {
+ private Consumer stateUpdater = (vi) -> {};
+
+ public StatefulValidatingCellEditor(JTextField textField, Disposable disposable) {
+ super(textField);
+ editorComponent.putClientProperty("JComponent.compactHeight", Boolean.TRUE);
+ new ComponentValidator(disposable).withValidator(this).installOn(editorComponent);
+
+ DocumentListener dl = new DocumentAdapter() {
+ @Override
+ protected void textChanged(@NotNull DocumentEvent e) {
+ editorComponent.putClientProperty(CELL_VALIDATION_PROPERTY, null);
+ ComponentValidator.getInstance(editorComponent).ifPresent(ComponentValidator::revalidate);
+ }
+ };
+
+ textField.getDocument().addDocumentListener(dl);
+ Disposer.register(disposable, () -> textField.getDocument().removeDocumentListener(dl));
+ }
+
+ public StatefulValidatingCellEditor(JComboBox comboBox, Disposable disposable) {
+ super(comboBox);
+ editorComponent.putClientProperty("JComponent.compactHeight", Boolean.TRUE);
+ new ComponentValidator(disposable).withValidator(this).installOn(editorComponent);
+
+ ItemListener il = e -> {
+ editorComponent.putClientProperty(CELL_VALIDATION_PROPERTY, null);
+ ComponentValidator.getInstance(editorComponent).ifPresent(ComponentValidator::revalidate);
+ };
+
+ comboBox.addItemListener(il);
+ Disposer.register(disposable, () -> comboBox.removeItemListener(il));
+ }
+
+ @ApiStatus.Experimental
+ public StatefulValidatingCellEditor withStateUpdater(@NotNull Consumer stateUpdater) {
+ this.stateUpdater = stateUpdater;
+ return this;
+ }
+
+ @Override
+ public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
+ JComponent editor = (JComponent)super.getTableCellEditorComponent(table, value, isSelected, row, column);
+ JComponent renderer = (JComponent)table.getCellRenderer(row, column).getTableCellRendererComponent(table, value, isSelected, true, row, column);
+ ValidationInfo cellInfo = renderer != null ? (ValidationInfo)renderer.getClientProperty(CELL_VALIDATION_PROPERTY) : null;
+ if (cellInfo != null) {
+ editor.putClientProperty(CELL_VALIDATION_PROPERTY, cellInfo.forComponent(editor));
+ ComponentValidator.getInstance(editor).ifPresent(ComponentValidator::revalidate);
+ }
+ return editor;
+ }
+
+ @Override
+ public boolean stopCellEditing() {
+ editorComponent.putClientProperty(CELL_VALIDATION_PROPERTY, null);
+ ComponentValidator.getInstance(editorComponent).ifPresent(ComponentValidator::revalidate);
+ return super.stopCellEditing();
+ }
+
+ @Override
+ public void cancelCellEditing() {
+ editorComponent.putClientProperty(CELL_VALIDATION_PROPERTY, null);
+ ComponentValidator.getInstance(editorComponent).ifPresent(ComponentValidator::revalidate);
+ super.cancelCellEditing();
+ }
+
+ @Override
+ public ValidationInfo get() {
+ ValidationInfo info = (ValidationInfo)editorComponent.getClientProperty(CELL_VALIDATION_PROPERTY);
+ stateUpdater.accept(info);
+ return info;
+ }
+}
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/TableCellValidator.java b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/TableCellValidator.java
new file mode 100644
index 000000000000..0fbb42759f27
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/TableCellValidator.java
@@ -0,0 +1,10 @@
+// Copyright 2000-2019 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.
+package com.intellij.openapi.ui.cellvalidators;
+
+import com.intellij.openapi.ui.ValidationInfo;
+import org.jetbrains.annotations.ApiStatus;
+
+@ApiStatus.Experimental
+public interface TableCellValidator {
+ ValidationInfo validate(Object value, int row, int column);
+}
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/ValidatingTableCellRendererWrapper.java b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/ValidatingTableCellRendererWrapper.java
new file mode 100644
index 000000000000..3cf1e24afd91
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/ValidatingTableCellRendererWrapper.java
@@ -0,0 +1,84 @@
+// Copyright 2000-2019 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.
+package com.intellij.openapi.ui.cellvalidators;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.ui.CellRendererPanel;
+import com.intellij.util.ui.JBUI;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.table.TableCellRenderer;
+import java.awt.*;
+import java.util.function.Supplier;
+
+public class ValidatingTableCellRendererWrapper extends CellRendererPanel implements TableCellRenderer {
+ public static final String CELL_VALIDATION_PROPERTY = "CellRenderer.validationInfo";
+
+ private final TableCellRenderer delegate;
+ private final JLabel iconLabel = new JLabel();
+
+ private Supplier extends Dimension> editorSizeSupplier = () -> JBUI.emptySize();
+ private TableCellValidator cellValidator;
+
+ @ApiStatus.Experimental
+ public ValidatingTableCellRendererWrapper(TableCellRenderer delegate) {
+ this.delegate = delegate;
+ setLayout(new BorderLayout(0, 0));
+ add(iconLabel, BorderLayout.EAST);
+
+ iconLabel.setOpaque(false);
+ setName("Table.cellRenderer");
+ }
+
+ @ApiStatus.Experimental
+ public ValidatingTableCellRendererWrapper bindToEditorSize(@NotNull Supplier extends Dimension> editorSizeSupplier) {
+ this.editorSizeSupplier = editorSizeSupplier;
+ return this;
+ }
+
+ @ApiStatus.Experimental
+ public ValidatingTableCellRendererWrapper withCellValidator(@NotNull TableCellValidator cellValidator) {
+ this.cellValidator = cellValidator;
+ return this;
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ size.height = Math.max(size.height, editorSizeSupplier.get().height);
+ return size;
+ }
+
+ @Override
+ public final Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+ JComponent delegateRenderer = (JComponent)delegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+
+ if (cellValidator != null) {
+ ValidationInfo result = cellValidator.validate(value, row, column);
+ iconLabel.setIcon(result == null ? null : result.warning ? AllIcons.General.BalloonWarning : AllIcons.General.BalloonError);
+ iconLabel.setBorder(result == null ? null: iconBorder());
+ putClientProperty(CELL_VALIDATION_PROPERTY, result);
+ }
+
+ add(delegateRenderer, BorderLayout.CENTER);
+ setBorder(delegateRenderer.getBorder());
+ delegateRenderer.setBorder(null);
+
+ setBackground(delegateRenderer.getBackground());
+ return this;
+ }
+
+ private static Border iconBorder() {
+ return JBUI.Borders.emptyRight(UIUtil.isUnderWin10LookAndFeel() ? 4 : 3);
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ g.setColor(getBackground());
+ g.fillRect(0, 0, getWidth(), getHeight());
+ }
+}
diff --git a/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/ValidationUtils.java b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/ValidationUtils.java
new file mode 100644
index 000000000000..a987340697ca
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/openapi/ui/cellvalidators/ValidationUtils.java
@@ -0,0 +1,37 @@
+// Copyright 2000-2019 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.
+package com.intellij.openapi.ui.cellvalidators;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.ui.components.fields.ExtendableTextComponent;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public final class ValidationUtils {
+ private ValidationUtils() {}
+
+ public final static ExtendableTextComponent.Extension ERROR_EXTENSION =
+ ExtendableTextComponent.Extension.create(AllIcons.General.BalloonError, null, null);
+
+ public final static ExtendableTextComponent.Extension WARNING_EXTENSION =
+ ExtendableTextComponent.Extension.create(AllIcons.General.BalloonWarning, null, null);
+
+ public static void setExtension(@NotNull ExtendableTextComponent editor, @NotNull ExtendableTextComponent.Extension extension, boolean set) {
+ if (set) {
+ editor.addExtension(extension);
+ } else {
+ editor.removeExtension(extension);
+ }
+ }
+
+ public static void setExtension(@NotNull ExtendableTextComponent editor, @Nullable ValidationInfo vi) {
+ if (vi == null) {
+ editor.removeExtension(ERROR_EXTENSION);
+ editor.removeExtension(WARNING_EXTENSION);
+ } else if (vi.warning) {
+ editor.addExtension(WARNING_EXTENSION);
+ } else {
+ editor.addExtension(ERROR_EXTENSION);
+ }
+ }
+}
diff --git a/platform/platform-api/src/com/intellij/openapi/wm/AppIconScheme.java b/platform/platform-api/src/com/intellij/openapi/wm/AppIconScheme.java
index 2ee5e36fa467..52345dc1df53 100644
--- a/platform/platform-api/src/com/intellij/openapi/wm/AppIconScheme.java
+++ b/platform/platform-api/src/com/intellij/openapi/wm/AppIconScheme.java
@@ -26,7 +26,7 @@ public class AppIconScheme {
public interface Progress {
- static final Progress TESTS = new Progress() {
+ Progress TESTS = new Progress() {
@Override
public Color getOkColor() {
return TESTS_OK_COLOR;
@@ -38,7 +38,7 @@ public class AppIconScheme {
}
};
- static final Progress BUILD = new Progress() {
+ Progress BUILD = new Progress() {
@Override
public Color getOkColor() {
return BUILD_OK_COLOR;
@@ -50,7 +50,7 @@ public class AppIconScheme {
}
};
- static final Progress INDEXING = new Progress() {
+ Progress INDEXING = new Progress() {
@Override
public Color getOkColor() {
return INDEXING_OK_COLOR;
diff --git a/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java b/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java
index 4ad9c87ef258..3aeeb12e9a66 100644
--- a/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java
+++ b/platform/platform-api/src/com/intellij/ui/EditorNotificationPanel.java
@@ -22,6 +22,7 @@ import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorBundle;
import com.intellij.openapi.editor.colors.ColorKey;
@@ -132,16 +133,24 @@ public class EditorNotificationPanel extends JPanel implements IntentionActionPr
protected void executeAction(final String actionId) {
final AnAction action = ActionManager.getInstance().getAction(actionId);
- final AnActionEvent event = AnActionEvent.createFromAnAction(action, null, ActionPlaces.UNKNOWN,
+ final AnActionEvent event = AnActionEvent.createFromAnAction(action, null, getActionPlace(),
DataManager.getInstance().getDataContext(this));
action.beforeActionPerformedUpdate(event);
action.update(event);
if (event.getPresentation().isEnabled() && event.getPresentation().isVisible()) {
+ ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
+ actionManager.fireBeforeActionPerformed(action, event.getDataContext(), event);
action.actionPerformed(event);
+ actionManager.fireAfterActionPerformed(action, event.getDataContext(), event);
}
}
+ @NotNull
+ protected String getActionPlace() {
+ return ActionPlaces.UNKNOWN;
+ }
+
@Nullable
@Override
public IntentionActionWithOptions getIntentionAction() {
diff --git a/platform/platform-api/src/com/intellij/ui/JBCardLayout.java b/platform/platform-api/src/com/intellij/ui/JBCardLayout.java
index aca9487c472c..1e1e4cef48e0 100644
--- a/platform/platform-api/src/com/intellij/ui/JBCardLayout.java
+++ b/platform/platform-api/src/com/intellij/ui/JBCardLayout.java
@@ -37,8 +37,8 @@ public class JBCardLayout extends CardLayout {
public enum SwipeDirection {FORWARD, BACKWARD, AUTO}
private final Map myMap = new LinkedHashMap<>();
- private final int mySwipeTime = 200;//default value, provide setter if need
- private final int mySwipeSteps = 20;//default value, provide setter if need
+ private static final int mySwipeTime = 200;//default value, provide setter if need
+ private static final int mySwipeSteps = 20;//default value, provide setter if need
private final Timer myTimer = UIUtil.createNamedTimer("CardLayoutTimer",Math.max(1, mySwipeTime / mySwipeSteps));
private Component mySwipeFrom = null;
private Component mySwipeTo = null;
diff --git a/platform/platform-api/src/com/intellij/ui/ListUtil.java b/platform/platform-api/src/com/intellij/ui/ListUtil.java
index de942516d0fd..5492a49eb323 100644
--- a/platform/platform-api/src/com/intellij/ui/ListUtil.java
+++ b/platform/platform-api/src/com/intellij/ui/ListUtil.java
@@ -335,7 +335,7 @@ public class ListUtil {
return listener;
}
- public static interface RemoveNotification {
+ public interface RemoveNotification {
void itemsRemoved(List items);
}
@@ -350,7 +350,7 @@ public class ListUtil {
return null;
}
- private static interface ListModelExtension {
+ private interface ListModelExtension {
Object get(ModelType model, int index);
void remove(ModelType model, int index);
}
diff --git a/platform/platform-api/src/com/intellij/ui/ReorderableListController.java b/platform/platform-api/src/com/intellij/ui/ReorderableListController.java
index 5f6689939c8b..f610e8a053e6 100644
--- a/platform/platform-api/src/com/intellij/ui/ReorderableListController.java
+++ b/platform/platform-api/src/com/intellij/ui/ReorderableListController.java
@@ -196,7 +196,7 @@ public abstract class ReorderableListController {
}
}
- static interface ActionBehaviour {
+ interface ActionBehaviour {
T performAction(@NotNull AnActionEvent e);
void updateAction(@NotNull AnActionEvent e);
}
diff --git a/platform/platform-api/src/com/intellij/ui/TableCellState.java b/platform/platform-api/src/com/intellij/ui/TableCellState.java
index f7ee2a8af931..091907030221 100644
--- a/platform/platform-api/src/com/intellij/ui/TableCellState.java
+++ b/platform/platform-api/src/com/intellij/ui/TableCellState.java
@@ -15,9 +15,6 @@
*/
package com.intellij.ui;
-import com.intellij.util.ui.JBEmptyBorder;
-import com.intellij.util.ui.UIUtil;
-
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
@@ -42,15 +39,23 @@ public class TableCellState {
myBackground = table.getBackground();
}
- Border border = UIUtil.getTableFocusCellHighlightBorder();
if (hasFocus) {
- myCellBorder = border;
- if (table.isCellEditable(row, column)) {
- myForeground = UIUtil.getTableFocusCellForeground();
- myBackground = UIUtil.getTableFocusCellBackground();
+ Border border = null;
+ if (isSelected) {
+ border = UIManager.getBorder("Table.focusSelectedCellHighlightBorder");
}
+ if (border == null) {
+ border = UIManager.getBorder("Table.focusCellHighlightBorder");
+ }
+
+ myCellBorder = border;
+
+ //if (table.isCellEditable(row, column)) {
+ // myForeground = UIUtil.getTableFocusCellForeground();
+ // myBackground = UIUtil.getTableFocusCellBackground();
+ //}
} else {
- myCellBorder = new JBEmptyBorder(border.getBorderInsets(null));
+ myCellBorder = UIManager.getBorder("Table.cellNoFocusBorder");
}
}
diff --git a/platform/platform-api/src/com/intellij/ui/components/fields/ExtendableTextField.java b/platform/platform-api/src/com/intellij/ui/components/fields/ExtendableTextField.java
index f1ed33116eae..391da221ae0d 100644
--- a/platform/platform-api/src/com/intellij/ui/components/fields/ExtendableTextField.java
+++ b/platform/platform-api/src/com/intellij/ui/components/fields/ExtendableTextField.java
@@ -60,8 +60,11 @@ public class ExtendableTextField extends JBTextField implements ExtendableTextCo
@Override
public void addExtension(@NotNull Extension extension) {
- List extensions = new ArrayList<>(getExtensions());
- if (extensions.add(extension)) setExtensions(extensions);
+ if (!getExtensions().contains(extension)) {
+ List extensions = new ArrayList<>(getExtensions());
+ extensions.add(extension);
+ setExtensions(extensions);
+ }
}
@Override
diff --git a/platform/platform-api/src/com/intellij/ui/tabs/impl/JBTabsImpl.java b/platform/platform-api/src/com/intellij/ui/tabs/impl/JBTabsImpl.java
index 006bc106b88a..8742723972f1 100644
--- a/platform/platform-api/src/com/intellij/ui/tabs/impl/JBTabsImpl.java
+++ b/platform/platform-api/src/com/intellij/ui/tabs/impl/JBTabsImpl.java
@@ -128,7 +128,7 @@ public class JBTabsImpl extends JComponent
private boolean myPaintBlocked;
private BufferedImage myImage;
private IdeFocusManager myFocusManager;
- private final boolean myAdjustBorders = true;
+ private static final boolean myAdjustBorders = true;
boolean myAddNavigationGroup = true;
diff --git a/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/LifecycleUsageTriggerCollector.java b/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/LifecycleUsageTriggerCollector.java
index d71e40246473..4e4e4acbeeed 100644
--- a/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/LifecycleUsageTriggerCollector.java
+++ b/platform/platform-impl/src/com/intellij/featureStatistics/fusCollectors/LifecycleUsageTriggerCollector.java
@@ -3,6 +3,8 @@ package com.intellij.featureStatistics.fusCollectors;
import com.intellij.internal.statistic.eventLog.FeatureUsageData;
import com.intellij.internal.statistic.service.fus.collectors.FUCounterUsageLogger;
+import com.intellij.internal.statistic.utils.StatisticsUploadAssistant;
+import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
@@ -14,7 +16,18 @@ public class LifecycleUsageTriggerCollector {
private static final String LIFECYCLE = "lifecycle";
public static void onIdeStart() {
- FUCounterUsageLogger.getInstance().logEvent(LIFECYCLE, "ide.start");
+ final FeatureUsageData data = new FeatureUsageData().addData("eap", ApplicationManager.getApplication().isEAP());
+ addIfTrue(data, "test", StatisticsUploadAssistant.isTestStatisticsEnabled());
+ addIfTrue(data, "command_line", ApplicationManager.getApplication().isCommandLine());
+ addIfTrue(data, "internal", ApplicationManager.getApplication().isInternal());
+ addIfTrue(data, "headless", ApplicationManager.getApplication().isHeadlessEnvironment());
+ FUCounterUsageLogger.getInstance().logEvent(LIFECYCLE, "ide.start", data);
+ }
+
+ private static void addIfTrue(@NotNull FeatureUsageData data, @NotNull String key, boolean value) {
+ if (value) {
+ data.addData(key, value);
+ }
}
public static void onIdeClose(boolean restart) {
diff --git a/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationManager.java b/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationManager.java
index d38becfbb51e..93ff22ede233 100644
--- a/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationManager.java
+++ b/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationManager.java
@@ -92,12 +92,6 @@ public class RecentLocationManager implements ProjectComponent {
return item == null ? null : item.getPositionOffsetMarker();
}
- @Nullable
- EditorColorsScheme getColorScheme(@NotNull PlaceInfo placeInfo, boolean showChanged) {
- PlaceInfoPersistentItem item = getMap(showChanged).get(placeInfo);
- return item == null ? null : item.getScheme();
- }
-
private void subscribeOnExternalChange(@NotNull MessageBusConnection connection) {
connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
@Override
diff --git a/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java b/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java
index 26c0337c688d..cf07ac1f46b8 100644
--- a/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java
+++ b/platform/platform-impl/src/com/intellij/ide/actions/RecentLocationsAction.java
@@ -550,22 +550,12 @@ public class RecentLocationsAction extends AnAction {
@NotNull Document document,
@NotNull PlaceInfo placeInfo,
@NotNull TextRange textRange) {
- EditorColorsScheme colorsScheme = setupColorScheme(project, editor, placeInfo);
+ EditorColorsScheme colorsScheme = EditorColorsManager.getInstance().getGlobalScheme();
applySyntaxHighlighting(project, editor, document, colorsScheme, textRange, placeInfo);
applyHighlightingPasses(project, editor, document, colorsScheme, textRange);
}
- @NotNull
- private static EditorColorsScheme setupColorScheme(@NotNull Project project, @NotNull EditorEx editor, @NotNull PlaceInfo placeInfo) {
- EditorColorsScheme colorsScheme = RecentLocationManager.getInstance(project).getColorScheme(placeInfo, showChanged(project));
- if (colorsScheme == null) {
- colorsScheme = EditorColorsManager.getInstance().getGlobalScheme();
- }
- editor.setColorsScheme(colorsScheme);
- return colorsScheme;
- }
-
private static void applySyntaxHighlighting(@NotNull Project project,
@NotNull EditorEx editor,
@NotNull Document document,
diff --git a/platform/platform-impl/src/com/intellij/ide/actions/SaveAllAction.kt b/platform/platform-impl/src/com/intellij/ide/actions/SaveAllAction.kt
index f22e7e10ecf4..81b429e1b65a 100644
--- a/platform/platform-impl/src/com/intellij/ide/actions/SaveAllAction.kt
+++ b/platform/platform-impl/src/com/intellij/ide/actions/SaveAllAction.kt
@@ -18,9 +18,11 @@ open class SaveAllAction : AnAction(), DumbAware {
override fun actionPerformed(e: AnActionEvent) {
CommonDataKeys.EDITOR.getData(e.dataContext)?.let(::stripSpacesFromCaretLines)
+ val project = CommonDataKeys.PROJECT.getData(e.dataContext)
GlobalScope.launch(pooledThreadContext) {
- saveDocumentsAndProjectsAndApp(onlyProject = CommonDataKeys.PROJECT.getData(e.dataContext),
- isForceSavingAllSettings = true)
+ if (project == null || !project.isDisposed) {
+ saveDocumentsAndProjectsAndApp(onlyProject = project, isForceSavingAllSettings = true)
+ }
}
}
}
diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowMoveAction.java b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowMoveAction.java
index f7f2280f1aa3..31e5b1300db8 100644
--- a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowMoveAction.java
+++ b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowMoveAction.java
@@ -144,6 +144,11 @@ public class ToolWindowMoveAction extends DumbAwareAction {
super(UIBundle.message("tool.window.move.to.action.group.name"), true);
}
+ @Override
+ public boolean isDumbAware() {
+ return true;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
if (!isInitialized) {
diff --git a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowViewModeAction.java b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowViewModeAction.java
index e9a4164d25d6..9b5d5bbb450d 100644
--- a/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowViewModeAction.java
+++ b/platform/platform-impl/src/com/intellij/ide/actions/ToolWindowViewModeAction.java
@@ -147,6 +147,11 @@ public class ToolWindowViewModeAction extends DumbAwareToggleAction {
super(ActionsBundle.groupText("ViewMode"), true);
}
+ @Override
+ public boolean isDumbAware() {
+ return true;
+ }
+
@Override
public void update(@NotNull AnActionEvent e) {
if (!isInitialized) {
diff --git a/platform/platform-impl/src/com/intellij/ide/customize/CustomizeIDEWizardDialog.java b/platform/platform-impl/src/com/intellij/ide/customize/CustomizeIDEWizardDialog.java
index b8a5306a7e9b..f3eab64c4790 100644
--- a/platform/platform-impl/src/com/intellij/ide/customize/CustomizeIDEWizardDialog.java
+++ b/platform/platform-impl/src/com/intellij/ide/customize/CustomizeIDEWizardDialog.java
@@ -180,10 +180,9 @@ public class CustomizeIDEWizardDialog extends DialogWrapper implements ActionLis
super.doOKAction();
}
- @Nullable
@Override
- protected String getLoggedDialogId() {
- return null;
+ protected boolean canRecordDialogId() {
+ return false;
}
private void initCurrentStep(boolean forward) {
diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginHostsConfigurable.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginHostsConfigurable.java
index cf68206f1655..5cdd5310b6f1 100644
--- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginHostsConfigurable.java
+++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginHostsConfigurable.java
@@ -2,6 +2,7 @@
package com.intellij.ide.plugins;
import com.intellij.ide.IdeBundle;
+import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.options.Configurable;
@@ -9,17 +10,17 @@ import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
-import com.intellij.openapi.ui.ComponentValidator;
-import com.intellij.openapi.ui.DialogWrapper;
-import com.intellij.openapi.ui.popup.JBPopup;
-import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.ValidationInfo;
+import com.intellij.openapi.ui.cellvalidators.*;
import com.intellij.openapi.updateSettings.impl.UpdateSettings;
+import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.ui.AnimatedIcon;
import com.intellij.ui.ColoredTableCellRenderer;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.ui.ToolbarDecorator;
+import com.intellij.ui.components.fields.ExtendableTextField;
import com.intellij.ui.table.JBTable;
import com.intellij.util.io.URLUtil;
import com.intellij.util.ui.ColumnInfo;
@@ -33,8 +34,6 @@ import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.ChangeEvent;
import java.awt.*;
-import java.awt.event.MouseAdapter;
-import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -48,6 +47,7 @@ public class PluginHostsConfigurable implements Configurable.NoScroll, Configura
};
private final AnimatedIcon.Default myAnimatedIcon = new AnimatedIcon.Default();
+ private final Disposable myDisposable = Disposer.newDisposable();
private final JBTable myTable = new JBTable(myModel) {
@Override
@@ -135,119 +135,51 @@ public class PluginHostsConfigurable implements Configurable.NoScroll, Configura
myTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
- myTable.setDefaultRenderer(Object.class, new ColoredTableCellRenderer() {
- final SimpleTextAttributes ERROR_ATTRIBUTES =
- new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, DialogWrapper.ERROR_FOREGROUND_COLOR);
-
- @Override
- protected void customizeCellRenderer(JTable table, @Nullable Object value, boolean selected, boolean hasFocus, int row, int column) {
- if (row >= 0 && row < myModel.getRowCount()) {
- UrlInfo info = myModel.getRowValue(row);
- setBorder(null);
- setForeground(selected ? table.getSelectionForeground() : table.getForeground());
- setBackground(selected ? table.getSelectionBackground() : table.getBackground());
- append(info.name, info.errorTooltip != null && !info.progress ? ERROR_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES);
- }
- }
-
- @Override
- protected SimpleTextAttributes modifyAttributes(SimpleTextAttributes attributes) {
- return attributes;
- }
- });
-
- DefaultCellEditor editor = new DefaultCellEditor(new JTextField());
+ ExtendableTextField cellEditor = new ExtendableTextField();
+ DefaultCellEditor editor = new StatefulValidatingCellEditor(cellEditor, myDisposable).
+ withStateUpdater(vi -> ValidationUtils.setExtension(cellEditor, vi));
editor.setClickCountToStart(1);
myTable.setDefaultEditor(Object.class, editor);
- createValidatorHandler();
+ myTable.setDefaultRenderer(Object.class, new ValidatingTableCellRendererWrapper(new ColoredTableCellRenderer() {
+ { setIpad(JBUI.emptyInsets());}
+
+ @Override
+ protected void customizeCellRenderer(JTable table, @Nullable Object value, boolean selected, boolean hasFocus, int row, int column) {
+ if (row >= 0 && row < myModel.getRowCount()) {
+ UrlInfo info = myModel.getRowValue(row);
+ setForeground(selected ? table.getSelectionForeground() : table.getForeground());
+ setBackground(selected ? table.getSelectionBackground() : table.getBackground());
+ append(info.name, SimpleTextAttributes.REGULAR_ATTRIBUTES);
+ }
+ }
+
+ @Override
+ protected SimpleTextAttributes modifyAttributes(SimpleTextAttributes attributes) {
+ return attributes;
+ }
+ }).
+ bindToEditorSize(cellEditor::getPreferredSize).
+ withCellValidator((value, row, column) -> {
+ if (row >= 0 && row < myModel.getRowCount()) {
+ UrlInfo info = myModel.getRowValue(row);
+ return info.errorTooltip == null || info.progress ? null : new ValidationInfo(info.errorTooltip);
+ }
+ else {
+ return null;
+ }
+ }));
+
+ new CellTooltipManager(myDisposable).
+ withCellComponentProvider(CellComponentProvider.forTable(myTable)).
+ installOn(myTable);
return ToolbarDecorator.createDecorator(myTable).disableUpDownActions().createPanel();
}
- private void createValidatorHandler() {
- MouseAdapter listener = new MouseAdapter() {
- @Override
- public void mouseEntered(MouseEvent e) {
- showErrorPopup(e);
- }
-
- @Override
- public void mouseExited(MouseEvent event) {
- if (!myTable.contains(event.getX(), event.getY()) || myTable.rowAtPoint(event.getPoint()) == myTable.getEditingRow()) {
- hideErrorPopup();
- }
- }
-
- @Override
- public void mouseMoved(MouseEvent e) {
- showErrorPopup(e);
- }
- };
- myTable.addMouseListener(listener);
- myTable.addMouseMotionListener(listener);
- }
-
- private JBPopup myErrorPopup;
- private JLabel myErrorLabel;
-
- private void showErrorPopup(@NotNull MouseEvent event) {
- int row = myTable.rowAtPoint(event.getPoint());
- if (row == -1 || row == myTable.getEditingRow()) {
- hideErrorPopup();
- return;
- }
-
- UrlInfo item = myModel.getItem(row);
- if (item.progress || item.errorTooltip == null) {
- hideErrorPopup();
- return;
- }
-
- if (myErrorPopup != null && myErrorPopup.isVisible() && myErrorPopup.getContent().getParent() != null) {
- myErrorLabel.setText(item.errorTooltip);
- showErrorPopup(row, true);
- return;
- }
-
- hideErrorPopup();
-
- myErrorLabel = new JLabel(item.errorTooltip);
- myErrorLabel.setOpaque(true);
- myErrorLabel.setBackground(JBUI.CurrentTheme.Validator.errorBackgroundColor());
- myErrorLabel.setBorder(ComponentValidator.getBorder());
-
- myErrorPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(myErrorLabel, null)
- .setBorderColor(JBUI.CurrentTheme.Validator.errorBorderColor()).setShowShadow(false).createPopup();
-
- showErrorPopup(row, false);
- }
-
- private void showErrorPopup(int row, boolean update) {
- Rectangle cellRect = myTable.getCellRect(row, 0, false);
- Point location = new Point(cellRect.x + JBUI.scale(40), cellRect.y - myErrorLabel.getPreferredSize().height - JBUI.scale(4));
- SwingUtilities.convertPointToScreen(location, myTable);
-
- if (update) {
- myErrorPopup.pack(true, true);
- myErrorPopup.setLocation(location);
- }
- else {
- myErrorPopup.showInScreenCoordinates(myTable, location);
- }
- }
-
@Override
public void disposeUIResources() {
- hideErrorPopup();
- }
-
- private void hideErrorPopup() {
- if (myErrorPopup != null) {
- myErrorPopup.cancel();
- myErrorPopup = null;
- myErrorLabel = null;
- }
+ Disposer.dispose(myDisposable);
}
private void validateRepositories(@NotNull List urls) {
diff --git a/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.java b/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.java
index fdcd1b8df7cf..d844fd40a028 100644
--- a/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.java
+++ b/platform/platform-impl/src/com/intellij/ide/startup/impl/StartupManagerImpl.java
@@ -149,7 +149,7 @@ public class StartupManagerImpl extends StartupManagerEx {
}
private void logActivityDuration(AtomicBoolean uiFreezeWarned, StartupActivity extension) {
- long duration = TimeoutUtil.runAndGetExecutionTime(() -> extension.runActivity(myProject));
+ long duration = TimeoutUtil.measureExecutionTime(() -> extension.runActivity(myProject));
Application app = ApplicationManager.getApplication();
if (duration > 100 && !app.isUnitTestMode()) {
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaTableSelectedCellHighlightBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaTableSelectedCellHighlightBorder.java
index 83f8777f1915..c62afbf1e859 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaTableSelectedCellHighlightBorder.java
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaTableSelectedCellHighlightBorder.java
@@ -33,7 +33,7 @@ public class DarculaTableSelectedCellHighlightBorder extends CompoundBorder impl
}
protected Border createInsideBorder() {
- return JBUI.Borders.empty(3);
+ return JBUI.Borders.empty(2);
}
protected Color createFocusBorderColor() {
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java
index 04e1a85b1aba..8bfa6b441fc2 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java
@@ -262,8 +262,8 @@ public class DarculaUIUtil {
@Override
public Insets getBorderInsets(Component c) {
- return isTableCellEditor(c) || isCompact(c) ? JBUI.insets(2).asUIResource() :
- isComboBoxEditor(c) ? JBUI.insets(2, 3).asUIResource() : JBUI.insets(5, 8).asUIResource();
+ return isTableCellEditor(c) || isCompact(c) || isComboBoxEditor(c) ?
+ JBUI.insets(2, 3).asUIResource() : JBUI.insets(5, 8).asUIResource();
}
}
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/darcula.properties b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/darcula.properties
index e10cbdd8358e..9a6d9af1046c 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/darcula.properties
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/darcula.properties
@@ -64,7 +64,7 @@ TableHeaderUI=com.intellij.ide.ui.laf.darcula.DarculaTableHeaderUI
Table.gridColor=2c2c2c
Table.ascendingSortIcon=AllIcons.General.ArrowUp
Table.descendingSortIcon=AllIcons.General.SplitDown
-Table.cellNoFocusBorder=4,4,4,4
+Table.cellNoFocusBorder=3,3,3,3
Table.focusSelectedCellHighlightBorder=com.intellij.ide.ui.laf.darcula.DarculaTableSelectedCellHighlightBorder
#just to suppress border painters
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaEditorTextFieldBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaEditorTextFieldBorder.java
index 7c4cb93cf038..245bfc39abaf 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaEditorTextFieldBorder.java
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaEditorTextFieldBorder.java
@@ -119,8 +119,8 @@ public class DarculaEditorTextFieldBorder extends DarculaTextBorder implements V
@Override
public Insets getBorderInsets(Component c) {
- return isTableCellEditor(c) || isCompact(c) ? JBUI.insets(2).asUIResource() :
- isComboBoxEditor(c) ? JBUI.insets(2, 3).asUIResource() : JBUI.insets(6, 8).asUIResource();
+ return isTableCellEditor(c) || isCompact(c) || isComboBoxEditor(c) ?
+ JBUI.insets(2, 3).asUIResource() : JBUI.insets(6, 8).asUIResource();
}
@Override
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java
index c89fa4bedef3..3ab1b20e12e3 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextBorder.java
@@ -25,7 +25,7 @@ import static com.intellij.ide.ui.laf.darcula.DarculaUIUtil.*;
public class DarculaTextBorder implements Border, UIResource, ErrorBorderCapable {
@Override
public Insets getBorderInsets(Component c) {
- return JBUI.insets(isTableCellEditor(c) || isCompact(c) ? 2 : 3).asUIResource();
+ return JBUI.insets(isTableCellEditor(c) || isCompact(c) ? 2 : 3, 3).asUIResource();
}
@Override
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTableSelectedCellHighlightBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTableSelectedCellHighlightBorder.java
index c508e603c979..e92e81a3e267 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTableSelectedCellHighlightBorder.java
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTableSelectedCellHighlightBorder.java
@@ -9,6 +9,6 @@ import javax.swing.border.Border;
public class WinIntelliJTableSelectedCellHighlightBorder extends DarculaTableSelectedCellHighlightBorder {
@Override
protected Border createInsideBorder() {
- return JBUI.Borders.empty(2);
+ return JBUI.Borders.empty(2, 3, 2, 2);
}
}
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextBorder.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextBorder.java
index a6b2a05d2f7c..ca25020d3bce 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextBorder.java
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellij/WinIntelliJTextBorder.java
@@ -35,7 +35,9 @@ public class WinIntelliJTextBorder extends DarculaTextBorder {
@Override
public Insets getBorderInsets(Component c) {
- return JBUI.insets(isTableCellEditor(c) || isCompact(c) ? 0 : 1).asUIResource();
+ return isTableCellEditor(c) || isCompact(c) ?
+ JBUI.insets(1, 1, 1, 4).asUIResource() :
+ JBUI.insets(1).asUIResource();
}
@Override
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.properties b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.properties
index 35e13a1c189c..e703a742357a 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.properties
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.properties
@@ -60,7 +60,7 @@ Table.gridColor=dddddd
Table.ascendingSortIcon=AllIcons.General.ArrowUp
Table.descendingSortIcon=AllIcons.General.SplitDown
Table.background=ffffff
-Table.cellNoFocusBorder=4,4,4,4
+Table.cellNoFocusBorder=3,3,3,3
Table.focusSelectedCellHighlightBorder=com.intellij.ide.ui.laf.darcula.DarculaTableSelectedCellHighlightBorder
#just to suppress border painters
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_native.properties b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_native.properties
index aa2c33cc8e29..0ea0a0166724 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_native.properties
+++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_native.properties
@@ -62,7 +62,7 @@ Table.gridColor=dddddd
Table.ascendingSortIcon=AllIcons.General.ArrowUp
Table.descendingSortIcon=AllIcons.General.SplitDown
Table.background=ffffff
-Table.cellNoFocusBorder=4,4,4,4
+Table.cellNoFocusBorder=3,4,3,3
Table.focusSelectedCellHighlightBorder=com.intellij.ide.ui.laf.intellij.WinIntelliJTableSelectedCellHighlightBorder
LabelUI=com.intellij.ide.ui.laf.darcula.ui.DarculaLabelUI
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/search/SearchUtil.java b/platform/platform-impl/src/com/intellij/ide/ui/search/SearchUtil.java
index e36b582e8eae..417abb862ff2 100644
--- a/platform/platform-impl/src/com/intellij/ide/ui/search/SearchUtil.java
+++ b/platform/platform-impl/src/com/intellij/ide/ui/search/SearchUtil.java
@@ -47,7 +47,7 @@ public class SearchUtil {
}
private static void processConfigurables(Configurable[] configurables, Map> options) {
- for (Configurable configurable : configurables) {
+ for (final Configurable configurable : configurables) {
if (configurable instanceof SearchableConfigurable) {
//ignore invisible root nodes
//noinspection deprecation
@@ -55,24 +55,29 @@ public class SearchUtil {
continue;
}
+ final SearchableConfigurable searchableConfigurable = (SearchableConfigurable) configurable;
+
Set configurableOptions = new TreeSet<>();
- options.put((SearchableConfigurable)configurable, configurableOptions);
+ options.put(searchableConfigurable, configurableOptions);
+
+ for (TraverseUIHelper extension : TraverseUIHelper.helperExtensionPoint.getExtensions())
+ extension.beforeConfigurable(searchableConfigurable, configurableOptions);
if (configurable instanceof MasterDetails) {
final MasterDetails md = (MasterDetails)configurable;
md.initUi();
- processComponent(configurable, configurableOptions, md.getMaster());
- processComponent(configurable, configurableOptions, md.getDetails().getComponent());
+ processComponent(searchableConfigurable, configurableOptions, md.getMaster());
+ processComponent(searchableConfigurable, configurableOptions, md.getDetails().getComponent());
}
else {
- processComponent(configurable, configurableOptions, configurable.createComponent());
+ processComponent(searchableConfigurable, configurableOptions, configurable.createComponent());
final Configurable unwrapped = unwrapConfigurable(configurable);
if (unwrapped instanceof CompositeConfigurable) {
//noinspection unchecked
final List extends UnnamedConfigurable> children = ((CompositeConfigurable)unwrapped).getConfigurables();
for (final UnnamedConfigurable child : children) {
final Set childConfigurableOptions = new TreeSet<>();
- options.put(new SearchableConfigurableAdapter((SearchableConfigurable)configurable, child), childConfigurableOptions);
+ options.put(new SearchableConfigurableAdapter(searchableConfigurable, child), childConfigurableOptions);
if (child instanceof SearchableConfigurable) {
processUILabel(((SearchableConfigurable)child).getDisplayName(), childConfigurableOptions, null);
@@ -86,6 +91,9 @@ public class SearchUtil {
}
}
}
+
+ for (TraverseUIHelper extension : TraverseUIHelper.helperExtensionPoint.getExtensions())
+ extension.afterConfigurable(searchableConfigurable, configurableOptions);
}
}
}
@@ -108,10 +116,16 @@ public class SearchUtil {
return configurable;
}
- private static void processComponent(Configurable configurable, Set super OptionDescription> configurableOptions, JComponent component) {
+ private static void processComponent(SearchableConfigurable configurable, Set super OptionDescription> configurableOptions, JComponent component) {
if (component != null) {
+ for (TraverseUIHelper extension : TraverseUIHelper.helperExtensionPoint.getExtensions())
+ extension.beforeComponent(configurable, component, configurableOptions);
+
processUILabel(configurable.getDisplayName(), configurableOptions, null);
processComponent(component, configurableOptions, null);
+
+ for (TraverseUIHelper extension : TraverseUIHelper.helperExtensionPoint.getExtensions())
+ extension.afterComponent(configurable, component, configurableOptions);
}
}
diff --git a/platform/platform-impl/src/com/intellij/ide/ui/search/TraverseUIHelper.java b/platform/platform-impl/src/com/intellij/ide/ui/search/TraverseUIHelper.java
new file mode 100644
index 000000000000..07a110f5d682
--- /dev/null
+++ b/platform/platform-impl/src/com/intellij/ide/ui/search/TraverseUIHelper.java
@@ -0,0 +1,49 @@
+package com.intellij.ide.ui.search;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.options.SearchableConfigurable;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.Map;
+import java.util.Set;
+
+public interface TraverseUIHelper {
+ String EP_NAME = "com.intellij.search.traverseUiHelper";
+ ExtensionPointName helperExtensionPoint = ExtensionPointName.create(EP_NAME);
+
+ /**
+ * Invoked before indexing SearchableConfigurables
+ */
+ default void beforeStart() {}
+
+ /**
+ * Invoked after all configurables were traversed
+ */
+ default void afterTraversal(@NotNull Map> options) {}
+
+ /**
+ * Invoked after indexing all SearchableConfigurables and results are saved
+ */
+ default void afterResultsAreSaved() {}
+
+ /**
+ * Invoked before indexing a SearchableConfigurable
+ */
+ default void beforeConfigurable(@NotNull SearchableConfigurable configurable, @NotNull Set options) {}
+
+ /**
+ * Invoked after indexing a SearchableConfigurable
+ */
+ default void afterConfigurable(@NotNull SearchableConfigurable configurable, @NotNull Set options) {}
+
+ /**
+ * Invoked before indexing the root component of a SearchableConfigurable
+ */
+ default void beforeComponent(@NotNull SearchableConfigurable configurable, @NotNull JComponent component, @NotNull Set super OptionDescription> options) {}
+
+ /**
+ * Invoked after indexing the root component of a SearchableConfigurable
+ */
+ default void afterComponent(@NotNull SearchableConfigurable configurable, @NotNull JComponent component, @NotNull Set super OptionDescription> options) {}
+}
\ No newline at end of file
diff --git a/platform/platform-impl/src/com/intellij/ide/util/AppPropertiesComponentImpl.java b/platform/platform-impl/src/com/intellij/ide/util/AppPropertiesComponentImpl.java
index 6b5c404c67dd..cb756b505a86 100644
--- a/platform/platform-impl/src/com/intellij/ide/util/AppPropertiesComponentImpl.java
+++ b/platform/platform-impl/src/com/intellij/ide/util/AppPropertiesComponentImpl.java
@@ -1,12 +1,13 @@
-// Copyright 2000-2018 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.
+// Copyright 2000-2019 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.
package com.intellij.ide.util;
import com.intellij.openapi.components.RoamingType;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
@State(name = "PropertiesComponent", storages = {
- @Storage(value = Storage.NOT_ROAMABLE_FILE, roamingType = RoamingType.DISABLED),
+ @Storage(value = StoragePathMacros.NOT_ROAMABLE_FILE, roamingType = RoamingType.DISABLED),
@Storage(value = "options.xml", roamingType = RoamingType.DISABLED, deprecated = true)
})
public class AppPropertiesComponentImpl extends PropertiesComponentImpl {
diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/eventLog/EventLogConfiguration.kt b/platform/platform-impl/src/com/intellij/internal/statistic/eventLog/EventLogConfiguration.kt
index 67323521b21b..c2466f9752e5 100644
--- a/platform/platform-impl/src/com/intellij/internal/statistic/eventLog/EventLogConfiguration.kt
+++ b/platform/platform-impl/src/com/intellij/internal/statistic/eventLog/EventLogConfiguration.kt
@@ -17,7 +17,7 @@ import java.util.prefs.Preferences
object EventLogConfiguration {
private val LOG = Logger.getInstance(EventLogConfiguration::class.java)
private const val SALT_PREFERENCE_KEY = "feature_usage_event_log_salt"
- const val version: Int = 6
+ const val version: Int = 7
val sessionId: String = UUID.randomUUID().toString().shortedUUID()
diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/service/fus/collectors/CounterUsageCollectorEP.java b/platform/platform-impl/src/com/intellij/internal/statistic/service/fus/collectors/CounterUsageCollectorEP.java
index 39aaba88dbb2..a6e6cc816493 100644
--- a/platform/platform-impl/src/com/intellij/internal/statistic/service/fus/collectors/CounterUsageCollectorEP.java
+++ b/platform/platform-impl/src/com/intellij/internal/statistic/service/fus/collectors/CounterUsageCollectorEP.java
@@ -4,14 +4,24 @@ package com.intellij.internal.statistic.service.fus.collectors;
import com.intellij.openapi.extensions.AbstractExtensionPointBean;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.Nullable;
public class CounterUsageCollectorEP extends AbstractExtensionPointBean {
public static final ExtensionPointName EP_NAME =
ExtensionPointName.create("com.intellij.statistics.counterUsagesCollector");
- @Attribute("group_id")
+ @Attribute("groupId")
public String groupID;
+ @Deprecated
+ @Attribute("group_id")
+ public String legacyGroupID;
+
@Attribute("version")
public int version;
+
+ @Nullable
+ public String getGroupId() {
+ return groupID != null ? groupID : legacyGroupID;
+ }
}
diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/service/fus/collectors/FUCounterUsageLogger.java b/platform/platform-impl/src/com/intellij/internal/statistic/service/fus/collectors/FUCounterUsageLogger.java
index 738ddf252c31..e27dec90342c 100644
--- a/platform/platform-impl/src/com/intellij/internal/statistic/service/fus/collectors/FUCounterUsageLogger.java
+++ b/platform/platform-impl/src/com/intellij/internal/statistic/service/fus/collectors/FUCounterUsageLogger.java
@@ -8,6 +8,7 @@ import com.intellij.internal.statistic.eventLog.FeatureUsageGroup;
import com.intellij.internal.statistic.eventLog.FeatureUsageLogger;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -48,7 +49,10 @@ public class FUCounterUsageLogger {
}
for (CounterUsageCollectorEP ep : CounterUsageCollectorEP.EP_NAME.getExtensionList()) {
- register(new FeatureUsageGroup(ep.groupID, ep.version));
+ final String id = ep.getGroupId();
+ if (StringUtil.isNotEmpty(id)) {
+ register(new FeatureUsageGroup(id, ep.version));
+ }
}
JobScheduler.getScheduler().scheduleWithFixedDelay(
diff --git a/platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUploadAssistant.java b/platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUploadAssistant.java
index ace9675aee45..717fa7778c53 100755
--- a/platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUploadAssistant.java
+++ b/platform/platform-impl/src/com/intellij/internal/statistic/utils/StatisticsUploadAssistant.java
@@ -66,6 +66,10 @@ public class StatisticsUploadAssistant {
return (settings != null && settings.isAllowed()) || Boolean.getBoolean(ENABLE_LOCAL_STATISTICS_WITHOUT_REPORT);
}
+ public static boolean isTestStatisticsEnabled() {
+ return Boolean.getBoolean(ENABLE_LOCAL_STATISTICS_WITHOUT_REPORT);
+ }
+
public static void updateSentTime() {
UsageStatisticsPersistenceComponent.getInstance().setSentTime(System.currentTimeMillis());
}
diff --git a/platform/platform-impl/src/com/intellij/internal/ui/ComponentPanelTestAction.java b/platform/platform-impl/src/com/intellij/internal/ui/ComponentPanelTestAction.java
index d257d3d6cfff..e8f101ce35a8 100644
--- a/platform/platform-impl/src/com/intellij/internal/ui/ComponentPanelTestAction.java
+++ b/platform/platform-impl/src/com/intellij/internal/ui/ComponentPanelTestAction.java
@@ -1,6 +1,7 @@
// Copyright 2000-2018 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.
package com.intellij.internal.ui;
+import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.laf.darcula.DarculaUIUtil;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ModalityState;
@@ -9,12 +10,14 @@ import com.intellij.openapi.fileTypes.FileTypes;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.*;
-import com.intellij.openapi.ui.panel.ComponentPanel;
+import com.intellij.openapi.ui.cellvalidators.*;
import com.intellij.openapi.ui.panel.ProgressPanel;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.*;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTabbedPane;
+import com.intellij.ui.components.fields.ExtendableTextComponent;
+import com.intellij.ui.components.fields.ExtendableTextField;
import com.intellij.ui.components.labels.DropDownLink;
import com.intellij.ui.table.JBTable;
import com.intellij.util.Alarm;
@@ -30,6 +33,8 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import javax.swing.plaf.basic.BasicComboBoxEditor;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
@@ -38,6 +43,7 @@ import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.function.BiFunction;
public class ComponentPanelTestAction extends DumbAwareAction {
private enum Placement {
@@ -80,6 +86,10 @@ public class ComponentPanelTestAction extends DumbAwareAction {
"seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "abracadabra"));
private static final String[] STRING_VALUES = { "One", "Two", "Three", "Four", "Five", "Six" };
+ private static final SimpleTextAttributes WARNING_CELL_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_WAVED, null);
+
+ private static final ValidationInfo NULL_VALUE_ERROR = new ValidationInfo("Null value");
+ private static final ValidationInfo NAN_VALUE_ERROR = new ValidationInfo("Not a number");
private final Alarm myAlarm = new Alarm(getDisposable());
private ProgressTimerRequest progressTimerRequest;
@@ -177,7 +187,7 @@ public class ComponentPanelTestAction extends DumbAwareAction {
} else {
return null;
}
- }).installOn(text1);
+ }).andRegisterOnDocumentListener(text1).installOn(text1);
Dimension d = text1.getPreferredSize();
text1.setPreferredSize(new Dimension(JBUI.scale(100), d.height));
@@ -197,31 +207,11 @@ public class ComponentPanelTestAction extends DumbAwareAction {
String tt = text2.getText();
return StringUtil.isEmpty(tt) || tt.length() < 5 ?
new ValidationInfo("Message is too short.
Should contain at least 5 symbols.
Please check rules.", text2) : null;
- }).andStartOnFocusLost().installOn(text2);
+ }).andStartOnFocusLost().andRegisterOnDocumentListener(text2).installOn(text2);
gc.gridy++;
topPanel.add(UI.PanelFactory.panel(text2).withLabel("&Path:").createPanel(), gc);
- ComponentPanel cp = ComponentPanel.getComponentPanel(text2);
- text1.getDocument().addDocumentListener(new DocumentAdapter() {
- @Override
- protected void textChanged(@NotNull DocumentEvent e) {
- String text = text1.getText();
- if (cp != null) {
- cp.setCommentText(text);
- }
-
- ComponentValidator.getInstance(text1).ifPresent(v -> v.revalidate());
- }
- });
-
- text2.getDocument().addDocumentListener(new DocumentAdapter() {
- @Override
- protected void textChanged(@NotNull DocumentEvent e) {
- ComponentValidator.getInstance(text2).ifPresent(v -> v.revalidate());
- }
- });
-
JCheckBox cb1 = new JCheckBox("Scroll tab layout");
cb1.addActionListener(e -> pane.setTabLayoutPolicy(cb1.isSelected() ? JTabbedPane.SCROLL_TAB_LAYOUT : JTabbedPane.WRAP_TAB_LAYOUT));
gc.gridy++;
@@ -243,6 +233,18 @@ public class ComponentPanelTestAction extends DumbAwareAction {
topPanel.add(UI.PanelFactory.panel(new JComboBox<>(STRING_VALUES)).
withComment("Combobox comment").createPanel(), gc);
+ JBScrollPane pane = new JBScrollPane(createTable());
+ pane.setPreferredSize(JBUI.size(400, 300));
+ pane.putClientProperty(UIUtil.KEEP_BORDER_SIDES, SideBorder.ALL);
+
+ BorderLayoutPanel mainPanel = JBUI.Panels.simplePanel(UI.PanelFactory.panel(pane).
+ withLabel("Table label:").moveLabelOnTop().withComment("Table comment").resizeY(true).createPanel());
+ mainPanel.addToTop(topPanel);
+
+ return mainPanel;
+ }
+
+ private JComponent createTable() {
String[] columns = { "First column", "Second column" };
String[][] data = {{"one", "1"}, {"two", "2"}, {"three", "3"}, {"four", "4"}, {"five", "5"},
{"six", "6"}, {"seven", "7"}, {"eight", "8"}, {"nine", "9"}, {"ten", "10"}, {"eleven", "11"},
@@ -261,48 +263,91 @@ public class ComponentPanelTestAction extends DumbAwareAction {
public boolean isCellEditable(int row, int column) { return true; }
@Override
public void setValueAt(Object value, int row, int col) {
- if (col == 0 && ALLOWED_VALUES.contains(value.toString()) || col == 1) {
+ if (col == 0 || col == 1) {
data[row][col] = value.toString();
fireTableCellUpdated(row, col);
}
}
});
- JTextField cellEditor = new JTextField();
+ HyperlinkListener hyperlinkListener = e -> {
+ if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
+ System.out.println("Table cell tooltip link clicked. Desc = " + e.getDescription());
+ }
+ };
+
+ BiFunction validationInfoGenerator = (text, component) ->
+ new ValidationInfo("Illegal value: " + text + "
Please check rules.", component);
+
+ // Install custom tooltip manager for displaying error/warning tooltips
+ new CellTooltipManager(getDisposable()).
+ withCellComponentProvider(CellComponentProvider.forTable(table)).
+ withHyperlinkListener(hyperlinkListener).installOn(table);
+
+ // Configure left column
+ ExtendableTextField cellEditor = new ExtendableTextField();
+ ExtendableTextComponent.Extension browseExtension =
+ ExtendableTextComponent.Extension.create(AllIcons.General.OpenDisk, AllIcons.General.OpenDiskHover,
+ "Open file", () -> System.out.println("Table browse clicked"));
+ cellEditor.addExtension(browseExtension);
cellEditor.putClientProperty(DarculaUIUtil.COMPACT_PROPERTY, Boolean.TRUE);
- cellEditor.getDocument().addDocumentListener(new DocumentAdapter() {
- @Override
- protected void textChanged(@NotNull DocumentEvent e) {
- Object op = ALLOWED_VALUES.contains(cellEditor.getText()) ? null : "error";
- cellEditor.putClientProperty("JComponent.outline", op);
- }
- });
+ new ComponentValidator(getDisposable()).withValidator(() -> {
+ boolean isAllowed = ALLOWED_VALUES.contains(cellEditor.getText());
+ ValidationUtils.setExtension(cellEditor, ValidationUtils.ERROR_EXTENSION, !isAllowed);
+ return isAllowed ? null : validationInfoGenerator.apply(cellEditor.getText(), cellEditor);
+ }).withHyperlinkListener(hyperlinkListener).
+ andRegisterOnDocumentListener(cellEditor).
+ installOn(cellEditor);
- TableColumn col0 = table.getColumnModel().getColumn(0);
- col0.setCellEditor(new DefaultCellEditor(cellEditor));
- col0.setCellRenderer(new DefaultTableCellRenderer() {
- @Override
- public Dimension getPreferredSize() {
- Dimension size = super.getPreferredSize();
- Dimension editorSize = cellEditor.getPreferredSize();
- size.height = Math.max(size.height, editorSize.height);
- return size;
- }
- });
+ TableColumn col = table.getColumnModel().getColumn(0);
+ col.setCellEditor(new DefaultCellEditor(cellEditor));
+ col.setCellRenderer(new ValidatingTableCellRendererWrapper(new DefaultTableCellRenderer()).
+ bindToEditorSize(cellEditor::getPreferredSize).
+ withCellValidator((value, row, column) ->
+ value == null ? new ValidationInfo("Null value") :
+ ALLOWED_VALUES.contains(value.toString()) ? null :
+ validationInfoGenerator.apply(value.toString(), null)));
+ // Configure right column
JComboBox rightEditor = new ComboBox<>(Arrays.stream(data).map(i -> Integer.valueOf(i[1])).toArray(Integer[]::new));
- table.getColumnModel().getColumn(1).setCellEditor(new DefaultCellEditor(rightEditor));
+ col = table.getColumnModel().getColumn(1);
- JBScrollPane pane = new JBScrollPane(table);
- pane.setPreferredSize(JBUI.size(400, 300));
- pane.putClientProperty(UIUtil.KEEP_BORDER_SIDES, SideBorder.ALL);
+ col.setCellEditor(new StatefulValidatingCellEditor(rightEditor, getDisposable()));
+ col.setCellRenderer(new ValidatingTableCellRendererWrapper(new ColoredTableCellRenderer() {
- BorderLayoutPanel mainPanel = JBUI.Panels.simplePanel(UI.PanelFactory.panel(pane).
- withLabel("Table label:").moveLabelOnTop().withComment("Table comment").resizeY(true).createPanel());
- mainPanel.addToTop(topPanel);
+ { setIpad(JBUI.emptyInsets()); } // Reset standard pads
- return mainPanel;
+ @Override
+ protected void customizeCellRenderer(JTable table, @Nullable Object value, boolean selected,
+ boolean hasFocus, int row, int column) {
+ if (value == null) {
+ append("No data", SimpleTextAttributes.ERROR_ATTRIBUTES);
+ }
+ else {
+ try {
+ int iv = Integer.parseInt(value.toString());
+ append("value ", SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES);
+ append(value.toString(), iv <= 8 ? SimpleTextAttributes.REGULAR_ATTRIBUTES : WARNING_CELL_ATTRIBUTES);
+ } catch (NumberFormatException nfe) {
+ append(value.toString(), SimpleTextAttributes.ERROR_ATTRIBUTES);
+ }
+ }
+ }
+ }).bindToEditorSize(rightEditor::getPreferredSize).
+ withCellValidator((value, row, column) -> {
+ if (value == null) return NULL_VALUE_ERROR;
+ else {
+ try {
+ int iv = Integer.parseInt(value.toString());
+ return iv <= 8 ? null : new ValidationInfo("Value " + value.toString() + " is not preferred").asWarning();
+ } catch (NumberFormatException nfe) {
+ return NAN_VALUE_ERROR;
+ }
+ }
+ }));
+
+ return table;
}
private JComponent createComponentGridPanel() {
@@ -327,11 +372,11 @@ public class ComponentPanelTestAction extends DumbAwareAction {
withLabel("&Host:").withComment("Host comment")).
add(UI.PanelFactory.panel(new JComboBox<>(new String[]{"HTTP", "HTTPS", "FTP", "SSL"})).
- withLabel("P&rotocol:").withComment("Protocol comment").withTooltip("Protocol selection").
+ withLabel("P&rotocol:").withTooltip("Protocol selection").
withTooltipLink("Check here for more info", ()-> System.out.println("More info"))).
add(UI.PanelFactory.panel(new ComponentWithBrowseButton<>(new JTextField(), (e) -> System.out.println("Browse for text"))).
- withLabel("&Text field:").withComment("Text field comment")).
+ withLabel("&Text field:").withComment("Text field comment with link")).
add(UI.PanelFactory.panel(cbb).
withLabel("&Combobox selection:")).
@@ -421,6 +466,23 @@ public class ComponentPanelTestAction extends DumbAwareAction {
.withValidator(() -> comboBox.getSelectedIndex() % 2 == 0 ? new ValidationInfo("Can't select odd items", comboBox) : null)
.installOn(comboBox);
+ // Extendable ComboBox
+ ExtendableTextComponent.Extension browseExtension =
+ ExtendableTextComponent.Extension.create(AllIcons.General.OpenDisk, AllIcons.General.OpenDiskHover,
+ "Open file", () -> System.out.println("Browse file clicked"));
+
+ ComboBox eComboBox = new ComboBox<>(STRING_VALUES);
+ eComboBox.setEditable(true);
+ eComboBox.setEditor(new BasicComboBoxEditor(){
+ @Override
+ protected JTextField createEditorComponent() {
+ ExtendableTextField ecbEditor = new ExtendableTextField();
+ ecbEditor.addExtension(browseExtension);
+ ecbEditor.setBorder(null);
+ return ecbEditor;
+ }
+ });
+
// Panels factory
return UI.PanelFactory.grid().
add(UI.PanelFactory.panel(tfbb).
@@ -432,6 +494,9 @@ public class ComponentPanelTestAction extends DumbAwareAction {
add(UI.PanelFactory.panel(comboBox).
withLabel("&ComboBoxEditorTextField:").withComment("EditorComboBox editor")).
+ add(UI.PanelFactory.panel(eComboBox).
+ withLabel("ComboBox &extendable:").withComment("ComboBox with ExtendableTextEditor")).
+
createPanel();
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java b/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
index 4460b716915a..5cd2427db024 100644
--- a/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
+++ b/platform/platform-impl/src/com/intellij/openapi/application/ConfigImportHelper.java
@@ -6,7 +6,7 @@ import com.intellij.ide.cloudConfig.CloudConfigProvider;
import com.intellij.ide.highlighter.ArchiveFileType;
import com.intellij.ide.startup.StartupActionScriptManager;
import com.intellij.idea.Main;
-import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
@@ -44,7 +44,7 @@ public class ConfigImportHelper {
private static final String CONFIG = "config";
private static final String[] OPTIONS = {
- OPTIONS_DIRECTORY + '/' + Storage.NOT_ROAMABLE_FILE,
+ OPTIONS_DIRECTORY + '/' + StoragePathMacros.NOT_ROAMABLE_FILE,
OPTIONS_DIRECTORY + '/' + IDE_GENERAL_XML,
OPTIONS_DIRECTORY + "/options.xml"};
private static final String BIN = "bin";
diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/NonBlockingReadActionImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/NonBlockingReadActionImpl.java
index eb12721dbf71..600357a2cd61 100644
--- a/platform/platform-impl/src/com/intellij/openapi/application/impl/NonBlockingReadActionImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/NonBlockingReadActionImpl.java
@@ -1,9 +1,12 @@
// Copyright 2000-2018 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.
package com.intellij.openapi.application.impl;
+import com.google.common.annotations.VisibleForTesting;
+import com.intellij.diagnostic.ThreadDumper;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.NonBlockingReadAction;
+import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
@@ -12,26 +15,35 @@ import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.util.concurrency.Semaphore;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
import org.jetbrains.concurrency.AsyncPromise;
import org.jetbrains.concurrency.CancellablePromise;
import org.jetbrains.concurrency.Promises;
+import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
/**
* @author peter
*/
-class NonBlockingReadActionImpl implements NonBlockingReadAction {
+@VisibleForTesting
+public class NonBlockingReadActionImpl implements NonBlockingReadAction {
private final @Nullable Pair> myEdtFinish;
private final @Nullable DumbService myRequireSmartMode; //todo a more pluggable constraint API
private final BooleanSupplier myExpireCondition;
private final Callable myComputation;
+ private static final Set> ourTasks = ContainerUtil.newConcurrentSet();
+
NonBlockingReadActionImpl(@Nullable Pair> edtFinish,
@Nullable DumbService requireSmartMode,
@NotNull BooleanSupplier expireCondition,
@@ -61,6 +73,10 @@ class NonBlockingReadActionImpl implements NonBlockingReadAction {
public CancellablePromise submit(@NotNull Executor backgroundThreadExecutor) {
AsyncPromise promise = new AsyncPromise<>();
new Submission(promise, backgroundThreadExecutor).transferToBgThread();
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ ourTasks.add(promise);
+ promise.onProcessed(__ -> ourTasks.remove(promise));
+ }
return promise;
}
@@ -167,4 +183,45 @@ class NonBlockingReadActionImpl implements NonBlockingReadAction {
}
}
+
+ @TestOnly
+ public static void cancelAllTasks() {
+ while (!ourTasks.isEmpty()) {
+ for (CancellablePromise> task : ourTasks) {
+ task.cancel();
+ }
+ WriteAction.run(() -> {}); // let background threads complete
+ }
+ }
+
+ @TestOnly
+ public static void completeAsyncTasks() {
+ assert !ApplicationManager.getApplication().isWriteAccessAllowed();
+ for (CancellablePromise> task : ourTasks) {
+ waitForTask(task);
+ }
+ }
+
+ @TestOnly
+ private static void waitForTask(CancellablePromise> task) {
+ int iteration = 0;
+ while (!task.isDone() && iteration++ < 60_000) {
+ UIUtil.dispatchAllInvocationEvents();
+ try {
+ task.blockingGet(1, TimeUnit.MILLISECONDS);
+ return;
+ }
+ catch (TimeoutException ignore) {
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ if (!task.isDone()) {
+ //noinspection UseOfSystemOutOrSystemErr
+ System.err.println(ThreadDumper.dumpThreadsToString());
+ throw new AssertionError("Too long async task");
+ }
+ }
+
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java
index 43bc0de4324e..8295c8f3d836 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java
@@ -1739,7 +1739,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
: renderer.getClickAction();
}
if (clickAction != null) {
- performAction(clickAction, e, "ICON_NAVIGATION", myEditor.getDataContext());
+ performAction(clickAction, e, ActionPlaces.EDITOR_GUTTER, myEditor.getDataContext());
repaint();
e.consume();
}
@@ -1959,7 +1959,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
if (renderer != null) {
AnAction rightButtonAction = renderer.getRightButtonClickAction();
if (rightButtonAction != null) {
- performAction(rightButtonAction, e, "ICON_NAVIGATION_SECONDARY_BUTTON", myEditor.getDataContext());
+ performAction(rightButtonAction, e, ActionPlaces.EDITOR_GUTTER_POPUP, myEditor.getDataContext());
e.consume();
}
else {
diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java
index 8621f8faa73f..275ee01e8fa0 100644
--- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/FileEditorManagerImpl.java
@@ -698,6 +698,8 @@ public class FileEditorManagerImpl extends FileEditorManagerEx implements Persis
private static boolean isOpenInNewWindow() {
AWTEvent event = IdeEventQueue.getInstance().getTrueCurrentEvent();
+ // openFile... methods can be called several times in a row with the same InputEvent but we should open just one new window in this case
+ if (event instanceof InputEvent && ((InputEvent)event).isConsumed()) return false;
// Shift was used while clicking
if (event instanceof MouseEvent &&
@@ -705,14 +707,23 @@ public class FileEditorManagerImpl extends FileEditorManagerEx implements Persis
(event.getID() == MouseEvent.MOUSE_CLICKED ||
event.getID() == MouseEvent.MOUSE_PRESSED ||
event.getID() == MouseEvent.MOUSE_RELEASED)) {
+ ((MouseEvent)event).consume();
return true;
}
if (event instanceof KeyEvent) {
KeyEvent ke = (KeyEvent)event;
- Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
+ KeymapManager keymapManager = KeymapManager.getInstance();
+ if (keymapManager == null) {
+ return false;
+ }
+ Keymap keymap = keymapManager.getActiveKeymap();
String[] ids = keymap.getActionIds(KeyStroke.getKeyStroke(ke.getKeyCode(), ke.getModifiers()));
- return Arrays.asList(ids).contains("OpenElementInNewWindow");
+ boolean inNewWindow = Arrays.asList(ids).contains("OpenElementInNewWindow");
+ if (inNewWindow) {
+ ke.consume();
+ }
+ return inNewWindow;
}
return false;
diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.java
index c950a9ef4b29..afb831c46bd0 100644
--- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.java
+++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.java
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
+import java.util.function.Predicate;
public class AsyncEditorLoader {
private static final ExecutorService ourExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("AsyncEditorLoader Pool", 2);
@@ -116,10 +117,9 @@ public class AsyncEditorLoader {
*/
private LoadingProgress scheduleLoading() {
CompletableFuture continuationFuture = new CompletableFuture<>();
- Document document = myEditor.getDocument();
Future> totalProgressFuture = ourExecutor.submit(() -> {
while (!myEditorComponent.isDisposed() && !isDone()) {
- LoadEditorResult result = tryLoadEditor(document);
+ LoadEditorResult result = tryLoadEditor();
if (result != null) {
continuationFuture.complete(result.continuation);
invokeAndWait(() -> {
@@ -127,7 +127,7 @@ public class AsyncEditorLoader {
// it might happen when the caller already finished the loading manually through `continuationFuture`
return;
}
- if ((myTooLongIndicator.isCanceled() || isCommitted()) && result.docStamp == document.getModificationStamp()) {
+ if ((myTooLongIndicator.isCanceled() || isCommitted()) && result.continuationValidator.test(myEditor.getDocument())) {
loadingFinished(result.continuation);
}
});
@@ -146,24 +146,21 @@ public class AsyncEditorLoader {
return myLoadingFinished.isDone();
}
- private static class LoadEditorResult {
- final long docStamp;
+ static class LoadEditorResult {
+ @NotNull final Predicate continuationValidator;
@NotNull final Runnable continuation;
- private LoadEditorResult(long docStamp, @NotNull Runnable continuation) {
- this.docStamp = docStamp;
+ LoadEditorResult(@NotNull Predicate continuationValidator, @NotNull Runnable continuation) {
+ this.continuationValidator = continuationValidator;
this.continuation = continuation;
}
}
@Nullable
- private LoadEditorResult tryLoadEditor(@NotNull Document document) {
+ private LoadEditorResult tryLoadEditor() {
Ref ref = Ref.create();
- Runnable loadingRunnable = () -> {
- Runnable continuation =
- myProject.isDisposed() ? EmptyRunnable.INSTANCE : myTextEditor.loadEditorInBackground();
- ref.set(new LoadEditorResult(document.getModificationStamp(), continuation));
- };
+ Runnable loadingRunnable = () -> ref.set(myProject.isDisposed() ? new LoadEditorResult(document -> true, EmptyRunnable.INSTANCE)
+ : myTextEditor.loadEditorInBackground());
if (!myTooLongIndicator.isCanceled()) {
ProgressIndicatorUtils.runWithWriteActionPriority(
() -> PsiDocumentManager.getInstance(myProject).commitAndRunReadAction(loadingRunnable),
diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java
index 60d28f761ffc..cbc02bf87f34 100644
--- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java
+++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncHighlighterUpdater.java
@@ -15,9 +15,9 @@
*/
package com.intellij.openapi.fileEditor.impl.text;
-import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.impl.NonBlockingReadActionImpl;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
@@ -26,15 +26,12 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.concurrency.CancellablePromise;
import java.util.Map;
import java.util.concurrent.ExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
/**
* @author peter
@@ -66,26 +63,7 @@ public class AsyncHighlighterUpdater {
@TestOnly
public static void completeAsyncTasks() {
- assert !ApplicationManager.getApplication().isWriteAccessAllowed();
- ApplicationManager.getApplication().invokeAndWait(() -> ourHighlighterFutures.values().forEach(AsyncHighlighterUpdater::waitForFuture));
- UIUtil.dispatchAllInvocationEvents();
+ NonBlockingReadActionImpl.completeAsyncTasks();
}
- @TestOnly
- private static void waitForFuture(CancellablePromise> future) {
- int iteration = 0;
- while (!future.isDone() && iteration++ < 1000) {
- UIUtil.dispatchAllInvocationEvents();
- try {
- future.blockingGet(10, TimeUnit.MILLISECONDS);
- return;
- }
- catch (TimeoutException ignore) {
- }
- catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- assert future.isDone() : "Too long async highlighter";
- }
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.java
index 8f8e746094d1..94a8b64bdd2c 100644
--- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.java
@@ -70,18 +70,18 @@ public class TextEditorImpl extends UserDataHolderBase implements TextEditor {
* @return a continuation to be called in EDT
*/
@NotNull
- protected Runnable loadEditorInBackground() {
+ protected AsyncEditorLoader.LoadEditorResult loadEditorInBackground() {
EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
EditorHighlighter highlighter = EditorHighlighterFactory.getInstance().createEditorHighlighter(myFile, scheme, myProject);
EditorEx editor = (EditorEx)getEditor();
highlighter.setText(editor.getDocument().getImmutableCharSequence());
long stamp = editor.getDocument().getModificationStamp();
Language language = getDocumentLanguage(editor);
- return () -> {
- LOG.assertTrue(stamp == editor.getDocument().getModificationStamp());
- editor.getSettings().setLanguage(language);
- editor.setHighlighter(highlighter);
- };
+ return new AsyncEditorLoader.LoadEditorResult(document -> stamp == document.getModificationStamp(),
+ () -> {
+ editor.getSettings().setLanguage(language);
+ editor.setHighlighter(highlighter);
+ });
}
@Nullable
diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java
index afa44d86da2a..ac31d54967f9 100644
--- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java
@@ -2,6 +2,7 @@
package com.intellij.openapi.project;
import com.google.common.annotations.VisibleForTesting;
+import com.intellij.diagnostic.ThreadDumper;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.file.BatchFileChangeListener;
import com.intellij.openapi.Disposable;
@@ -454,6 +455,7 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica
return;
}
while (isDumb()) {
+ assertState(State.SCHEDULED_TASKS);
showModalProgress();
}
}
@@ -461,22 +463,34 @@ public class DumbServiceImpl extends DumbService implements Disposable, Modifica
private void showModalProgress() {
NoAccessDuringPsiEvents.checkCallContext();
try {
- ((ApplicationImpl)ApplicationManager.getApplication()).executeSuspendingWriteAction(myProject, IdeBundle.message("progress.indexing"), () ->
- runBackgroundProcess(ProgressManager.getInstance().getProgressIndicator()));
+ ((ApplicationImpl)ApplicationManager.getApplication()).executeSuspendingWriteAction(myProject, IdeBundle.message("progress.indexing"), () -> {
+ assertState(State.SCHEDULED_TASKS);
+ runBackgroundProcess(ProgressManager.getInstance().getProgressIndicator());
+ assertState(State.SMART, State.WAITING_FOR_FINISH);
+ });
+ assertState(State.SMART, State.WAITING_FOR_FINISH);
}
finally {
if (myState.get() != State.SMART) {
- assertWeAreWaitingToFinish();
+ assertState(State.WAITING_FOR_FINISH);
updateFinished();
+ assertState(State.SMART, State.SCHEDULED_TASKS);
}
}
}
- private void assertWeAreWaitingToFinish() {
- final State state = myState.get();
- if (state != State.WAITING_FOR_FINISH) {
- Attachment[] attachments = myDumbEnterTrace != null ? new Attachment[]{new Attachment("indexingStart", myDumbEnterTrace)} : Attachment.EMPTY_ARRAY;
- throw new RuntimeExceptionWithAttachments(state.toString(), attachments);
+ private void assertState(State... expected) {
+ State state = myState.get();
+ List expectedList = Arrays.asList(expected);
+ if (!expectedList.contains(state)) {
+ List attachments = new ArrayList<>();
+ if (myDumbEnterTrace != null) {
+ attachments.add(new Attachment("indexingStart", myDumbEnterTrace));
+ }
+ attachments.add(new Attachment("threadDump.txt", ThreadDumper.dumpThreadsToString()));
+ throw new RuntimeExceptionWithAttachments("Internal error, please include thread dump attachment. " +
+ "Expected " + expectedList + ", but was " + state.toString(),
+ attachments.toArray(Attachment.EMPTY_ARRAY));
}
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/project/NoAccessDuringPsiEvents.java b/platform/platform-impl/src/com/intellij/openapi/project/NoAccessDuringPsiEvents.java
index b96aed725ee2..f492fb74479f 100644
--- a/platform/platform-impl/src/com/intellij/openapi/project/NoAccessDuringPsiEvents.java
+++ b/platform/platform-impl/src/com/intellij/openapi/project/NoAccessDuringPsiEvents.java
@@ -15,6 +15,7 @@
*/
package com.intellij.openapi.project;
+import com.intellij.ProjectTopics;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -44,6 +45,8 @@ public class NoAccessDuringPsiEvents {
if (!application.isWriteAccessAllowed()) return false;
MessageBus bus = application.getMessageBus();
- return bus.hasUndeliveredEvents(VirtualFileManager.VFS_CHANGES) || bus.hasUndeliveredEvents(PsiModificationTracker.TOPIC);
+ return bus.hasUndeliveredEvents(VirtualFileManager.VFS_CHANGES) ||
+ bus.hasUndeliveredEvents(PsiModificationTracker.TOPIC) ||
+ bus.hasUndeliveredEvents(ProjectTopics.PROJECT_ROOTS);
}
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java
index b659d65fabc6..a62ad55cd976 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualDirectoryImpl.java
@@ -267,7 +267,8 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
final String realName = delegate.getCanonicallyCasedName(fake);
boolean isDirectory = attributes.isDirectory();
boolean isEmptyDirectory = isDirectory && !delegate.hasChildren(fake);
- final VFileCreateEvent event = new VFileCreateEvent(null, this, realName, isDirectory, attributes, true, isEmptyDirectory);
+ String symlinkTarget = attributes.isSymLink() ? delegate.resolveSymLink(fake) : null;
+ VFileCreateEvent event = new VFileCreateEvent(null, this, realName, isDirectory, attributes, symlinkTarget, true, isEmptyDirectory);
RefreshQueue.getInstance().processSingleEvent(event);
return findChild(realName);
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java
index 4911e9cd9659..f009f9835ad5 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/FSRecords.java
@@ -58,6 +58,7 @@ public class FSRecords {
static final String VFS_FILES_EXTENSION = System.getProperty("idea.vfs.files.extension", ".dat");
private static final boolean ourStoreRootsSeparately = SystemProperties.getBooleanProperty("idea.store.roots.separately", false);
+ //TODO[anyone] when bumping the version, please delete `ourSymlinkTargetAttr_old` and use it's value for `ourSymlinkTargetAttr`
private static final int VERSION = 22 + (weHaveContentHashes ? 0x10:0) + (IOUtil.ourByteBuffersUseNativeByteOrder ? 0x37:0) +
31 + (bulkAttrReadSupport ? 0x27:0) + (inlineAttributes ? 0x31 : 0) +
(ourStoreRootsSeparately ? 0x63 : 0) +
@@ -97,7 +98,8 @@ public class FSRecords {
private static final int CORRUPTED_MAGIC = 0xabcf7f7f;
private static final FileAttribute ourChildrenAttr = new FileAttribute("FsRecords.DIRECTORY_CHILDREN");
- private static final FileAttribute ourSymlinkTargetAttr = new FileAttribute("FsRecords.SYMLINK_TARGET");
+ private static final FileAttribute ourSymlinkTargetAttr = new FileAttribute("FsRecords.SYMLINK_TARGET_2");
+ private static final FileAttribute ourSymlinkTargetAttr_old = new FileAttribute("FsRecords.SYMLINK_TARGET");
private static final ReentrantReadWriteLock lock;
private static final ReentrantReadWriteLock.ReadLock r;
@@ -984,8 +986,12 @@ public class FSRecords {
static @Nullable String readSymlinkTarget(int id) {
return readAndHandleErrors(() -> {
try (DataInputStream stream = readAttribute(id, ourSymlinkTargetAttr)) {
- return stream != null ? StringUtil.nullize(stream.readUTF()) : null;
+ if (stream != null) return StringUtil.nullize(IOUtil.readUTF(stream));
}
+ try (DataInputStream stream = readAttribute(id, ourSymlinkTargetAttr_old)) {
+ if (stream != null) return StringUtil.nullize(stream.readUTF());
+ }
+ return null;
});
}
@@ -993,7 +999,7 @@ public class FSRecords {
writeAndHandleErrors(() -> {
DbConnection.markDirty();
try (DataOutputStream stream = writeAttribute(id, ourSymlinkTargetAttr)) {
- stream.writeUTF(StringUtil.notNullize(symlinkTarget));
+ IOUtil.writeUTF(stream, StringUtil.notNullize(symlinkTarget));
}
});
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/LocalFileSystemRefreshWorker.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/LocalFileSystemRefreshWorker.java
index 5644b19c73ab..323e722089e4 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/LocalFileSystemRefreshWorker.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/LocalFileSystemRefreshWorker.java
@@ -272,7 +272,8 @@ class LocalFileSystemRefreshWorker {
if (child == null) { // new file is created
VirtualFile parent = myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent();
- myHelper.scheduleCreation(parent, name, file, convert(file, attrs));
+ String symlinkTarget = attrs.isSymbolicLink() ? file.toRealPath().toString() : null;
+ myHelper.scheduleCreation(parent, name, file, convert(file, attrs), symlinkTarget);
return FileVisitResult.CONTINUE;
}
@@ -300,7 +301,8 @@ class LocalFileSystemRefreshWorker {
oldIsSpecial != isSpecial) { // symlink or directory or special changed
myHelper.scheduleDeletion(child);
VirtualFile parent = myFileOrDir.isDirectory() ? myFileOrDir : myFileOrDir.getParent();
- myHelper.scheduleCreation(parent, child.getName(), file, convert(file, attrs));
+ String symlinkTarget = isLink ? file.toRealPath().toString() : null;
+ myHelper.scheduleCreation(parent, child.getName(), file, convert(file, attrs), symlinkTarget);
// ignore everything else
child.markClean();
return FileVisitResult.CONTINUE;
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
index 5c93444f3d59..2324bb3224d3 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/PersistentFSImpl.java
@@ -179,7 +179,7 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
nameIds.add(nameId);
}
for (String newName : toAdd) {
- Pair childData = getChildData(null, file, newName, fs);
+ Pair childData = getChildData(fs, file, newName, null, null);
if (childData != null) {
int childId = makeChildRecord(id, newName, childData, fs);
childrenIds.add(childId);
@@ -387,7 +387,7 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
if (namesEqual(fs, childName, FSRecords.getNameSequence(childId))) return childId;
}
- Pair childData = getChildData(null, parent, childName, fs);
+ Pair childData = getChildData(fs, parent, childName, null, null);
if (childData != null) {
int childId = makeChildRecord(parentId, childName, childData, fs);
FSRecords.updateList(parentId, ArrayUtil.append(children, childId));
@@ -426,7 +426,7 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
@Override
public VirtualFile createChildDirectory(Object requestor, @NotNull VirtualFile parent, @NotNull String dir) throws IOException {
getDelegate(parent).createChildDirectory(requestor, parent, dir);
- processEvent(new VFileCreateEvent(requestor, parent, dir, true, null, false, true));
+ processEvent(new VFileCreateEvent(requestor, parent, dir, true, null, null, false, true));
final VirtualFile child = parent.findChild(dir);
if (child == null) {
@@ -439,7 +439,7 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
@Override
public VirtualFile createChildFile(Object requestor, @NotNull VirtualFile parent, @NotNull String file) throws IOException {
getDelegate(parent).createChildFile(requestor, parent, file);
- processEvent(new VFileCreateEvent(requestor, parent, file, false, null, false, false));
+ processEvent(new VFileCreateEvent(requestor, parent, file, false, null, null, false, false));
final VirtualFile child = parent.findChild(file);
if (child == null) {
@@ -967,7 +967,7 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
for (VFileCreateEvent createEvent : createEvents) {
createEvent.resetCache();
String name = createEvent.getChildName();
- Pair childData = getChildData(createEvent.getAttributes(), parent, name, delegate);
+ Pair childData = getChildData(delegate, parent, name, createEvent.getAttributes(), createEvent.getSymlinkTarget());
if (childData != null) {
int childId = makeChildRecord(parentId, name, childData, delegate);
childrenAdded.add(new ChildInfo(childId, name, childData.first, createEvent.isEmptyDirectory()));
@@ -1129,8 +1129,8 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
}
try {
if (event instanceof VFileCreateEvent) {
- final VFileCreateEvent createEvent = (VFileCreateEvent)event;
- executeCreateChild(createEvent.getParent(), createEvent.getChildName(), createEvent.getAttributes(), createEvent.isEmptyDirectory());
+ VFileCreateEvent ce = (VFileCreateEvent)event;
+ executeCreateChild(ce.getParent(), ce.getChildName(), ce.getAttributes(), ce.getSymlinkTarget(), ce.isEmptyDirectory());
}
else if (event instanceof VFileDeleteEvent) {
final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
@@ -1152,8 +1152,8 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
executeTouch(file, contentUpdateEvent.isFromRefresh(), contentUpdateEvent.getModificationStamp(), length, timestamp);
}
else if (event instanceof VFileCopyEvent) {
- final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
- executeCreateChild(copyEvent.getNewParent(), copyEvent.getNewChildName(), null, copyEvent.getFile().getChildren().length == 0);
+ VFileCopyEvent ce = (VFileCopyEvent)event;
+ executeCreateChild(ce.getNewParent(), ce.getNewChildName(), null, null, ce.getFile().getChildren().length == 0);
}
else if (event instanceof VFileMoveEvent) {
final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
@@ -1199,10 +1199,11 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
private void executeCreateChild(@NotNull VirtualFile parent,
@NotNull String name,
@Nullable FileAttributes attributes,
+ @Nullable String symlinkTarget,
boolean isEmptyDirectory) {
NewVirtualFileSystem delegate = getDelegate(parent);
int parentId = getFileId(parent);
- Pair childData = getChildData(attributes, parent, name, delegate);
+ Pair childData = getChildData(delegate, parent, name, attributes, symlinkTarget);
if (childData != null) {
int childId = makeChildRecord(parentId, name, childData, delegate);
appendIdToParentList(parentId, childId);
@@ -1224,14 +1225,16 @@ public class PersistentFSImpl extends PersistentFS implements BaseComponent, Dis
return childId;
}
- private static Pair getChildData(@Nullable FileAttributes attributes,
+ private static Pair getChildData(@NotNull NewVirtualFileSystem fs,
@NotNull VirtualFile parent,
@NotNull String name,
- @NotNull NewVirtualFileSystem fs) {
- if (attributes == null) attributes = fs.getAttributes(new FakeVirtualFile(parent, name));
- if (attributes == null) return null;
- String symlinkTarget = attributes.isSymLink() ? fs.resolveSymLink(new FakeVirtualFile(parent, name)) : null;
- return pair(attributes, symlinkTarget);
+ @Nullable FileAttributes attributes,
+ @Nullable String symlinkTarget) {
+ if (attributes == null) {
+ attributes = fs.getAttributes(new FakeVirtualFile(parent, name));
+ symlinkTarget = attributes != null && attributes.isSymLink() ? fs.resolveSymLink(new FakeVirtualFile(parent, name)) : null;
+ }
+ return attributes == null ? null : pair(attributes, symlinkTarget);
}
private static void appendIdToParentList(int parentId, int childId) {
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java
index eacc25467eb3..b48fbef968d5 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java
@@ -107,7 +107,7 @@ public class RefreshWorker {
}
NewVirtualFile parent = file.getParent();
- if (parent != null && checkAndScheduleFileTypeChange(parent, file, attributes)) {
+ if (parent != null && checkAndScheduleFileTypeChange(fs, parent, file, attributes)) {
// ignore everything else
file.markClean();
continue;
@@ -164,10 +164,16 @@ public class RefreshWorker {
OpenTHashSet actualNames = fs.isCaseSensitive() ? null : new OpenTHashSet<>(strategy, upToDateNames);
if (LOG.isTraceEnabled()) LOG.trace("current=" + Arrays.toString(currentNames) + " +" + newNames + " -" + deletedNames);
- List> addedMap = ContainerUtil.newArrayListWithCapacity(newNames.size());
+ List newKids = ContainerUtil.newArrayListWithCapacity(newNames.size());
for (String name : newNames) {
checkCancelled(dir);
- addedMap.add(pair(name, fs.getAttributes(new FakeVirtualFile(dir, name))));
+ NewChildRecord record = childRecord(fs, dir, name);
+ if (record != null) {
+ newKids.add(record);
+ }
+ else {
+ if (LOG.isTraceEnabled()) LOG.trace("[+] fs=" + fs + " dir=" + dir + " name=" + name);
+ }
}
List> updatedMap = ContainerUtil.newArrayListWithCapacity(children.length);
@@ -191,22 +197,15 @@ public class RefreshWorker {
}
}
- for (Pair pair : addedMap) {
- String name = pair.first;
- FileAttributes childAttributes = pair.second;
- if (childAttributes != null) {
- myHelper.scheduleCreation(dir, name, appendPath(dir.getPath(), name), childAttributes);
- }
- else {
- if (LOG.isTraceEnabled()) LOG.trace("[+] fs=" + fs + " dir=" + dir + " name=" + name);
- }
+ for (NewChildRecord record : newKids) {
+ myHelper.scheduleCreation(dir, record.name, record.path, record.attributes, record.symlinkTarget);
}
for (Pair pair : updatedMap) {
VirtualFile child = pair.first;
FileAttributes childAttributes = pair.second;
if (childAttributes != null) {
- checkAndScheduleChildRefresh(dir, child, childAttributes);
+ checkAndScheduleChildRefresh(fs, dir, child, childAttributes);
checkAndScheduleFileNameChange(actualNames, child);
}
else {
@@ -255,11 +254,14 @@ public class RefreshWorker {
existingMap.add(pair(child, fs.getAttributes(child)));
}
- List> wantedMap = ContainerUtil.newArrayListWithCapacity(wanted.size());
+ List newKids = ContainerUtil.newArrayListWithCapacity(wanted.size());
for (String name : wanted) {
if (name.isEmpty()) continue;
checkCancelled(dir);
- wantedMap.add(pair(name, fs.getAttributes(new FakeVirtualFile(dir, name))));
+ NewChildRecord record = childRecord(fs, dir, name);
+ if (record != null) {
+ newKids.add(record);
+ }
}
// generating events unless a directory was changed in between
@@ -273,7 +275,7 @@ public class RefreshWorker {
VirtualFile child = pair.first;
FileAttributes childAttributes = pair.second;
if (childAttributes != null) {
- checkAndScheduleChildRefresh(dir, child, childAttributes);
+ checkAndScheduleChildRefresh(fs, dir, child, childAttributes);
checkAndScheduleFileNameChange(actualNames, child);
}
else {
@@ -281,12 +283,8 @@ public class RefreshWorker {
}
}
- for (Pair pair : wantedMap) {
- String name = pair.first;
- FileAttributes childAttributes = pair.second;
- if (childAttributes != null) {
- myHelper.scheduleCreation(dir, name, appendPath(dir.getPath(), name), childAttributes);
- }
+ for (NewChildRecord record : newKids) {
+ myHelper.scheduleCreation(dir, record.name, record.path, record.attributes, record.symlinkTarget);
}
return true;
@@ -297,6 +295,28 @@ public class RefreshWorker {
}
}
+ private static class NewChildRecord {
+ final String name;
+ final Path path;
+ final FileAttributes attributes;
+ final String symlinkTarget;
+
+ NewChildRecord(String name, Path path, FileAttributes attributes, String symlinkTarget) {
+ this.name = name;
+ this.path = path;
+ this.attributes = attributes;
+ this.symlinkTarget = symlinkTarget;
+ }
+ }
+
+ private static NewChildRecord childRecord(NewVirtualFileSystem fs, VirtualFile dir, String name) {
+ FakeVirtualFile file = new FakeVirtualFile(dir, name);
+ FileAttributes attributes = fs.getAttributes(file);
+ if (attributes == null) return null;
+ String symlinkTarget = attributes.isSymLink() ? fs.resolveSymLink(file) : null;
+ return new NewChildRecord(name, appendPath(dir.getPath(), name), attributes, symlinkTarget);
+ }
+
private void checkAndScheduleFileNameChange(@Nullable OpenTHashSet actualNames, @NotNull VirtualFile child) {
if (actualNames != null) {
String currentName = child.getName();
@@ -327,10 +347,11 @@ public class RefreshWorker {
file.markDirty();
}
- private void checkAndScheduleChildRefresh(@NotNull VirtualFile parent,
+ private void checkAndScheduleChildRefresh(NewVirtualFileSystem fs,
+ @NotNull VirtualFile parent,
@NotNull VirtualFile child,
@NotNull FileAttributes childAttributes) {
- if (!checkAndScheduleFileTypeChange(parent, child, childAttributes)) {
+ if (!checkAndScheduleFileTypeChange(fs, parent, child, childAttributes)) {
boolean upToDateIsDirectory = childAttributes.isDirectory();
if (myIsRecursive || !upToDateIsDirectory) {
myRefreshQueue.addLast(pair((NewVirtualFile)child, childAttributes));
@@ -338,7 +359,8 @@ public class RefreshWorker {
}
}
- private boolean checkAndScheduleFileTypeChange(@NotNull VirtualFile parent,
+ private boolean checkAndScheduleFileTypeChange(NewVirtualFileSystem fs,
+ @NotNull VirtualFile parent,
@NotNull VirtualFile child,
@NotNull FileAttributes childAttributes) {
boolean currentIsDirectory = child.isDirectory();
@@ -350,7 +372,8 @@ public class RefreshWorker {
if (currentIsDirectory != upToDateIsDirectory || currentIsSymlink != upToDateIsSymlink || currentIsSpecial != upToDateIsSpecial) {
myHelper.scheduleDeletion(child);
- myHelper.scheduleCreation(parent, child.getName(), appendPath(parent.getPath(), child.getName()), childAttributes);
+ String symlinkTarget = upToDateIsSymlink ? fs.resolveSymLink(child) : null;
+ myHelper.scheduleCreation(parent, child.getName(), appendPath(parent.getPath(), child.getName()), childAttributes, symlinkTarget);
return true;
}
diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/VfsEventGenerationHelper.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/VfsEventGenerationHelper.java
index 4e8c8d6d130b..a09a11a6b9b4 100644
--- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/VfsEventGenerationHelper.java
+++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/VfsEventGenerationHelper.java
@@ -46,11 +46,15 @@ class VfsEventGenerationHelper {
myEvents.add(new VFileContentChangeEvent(null, file, file.getModificationStamp(), -1, oldTimestamp, newTimestamp, oldLength, newLength, true));
}
- void scheduleCreation(@NotNull VirtualFile parent, @NotNull String childName, @NotNull Path path, @NotNull FileAttributes attributes) {
+ void scheduleCreation(@NotNull VirtualFile parent,
+ @NotNull String childName,
+ @NotNull Path path,
+ @NotNull FileAttributes attributes,
+ String symlinkTarget) {
boolean isEmptyDir = attributes.isDirectory() && !LocalFileSystemBase.hasChildren(path);
if (LOG.isTraceEnabled()) LOG.trace("create parent=" + parent + " name=" + childName + " attr=" + attributes);
- myEvents.add(new VFileCreateEvent(null, parent, childName, attributes.isDirectory(), attributes, true, isEmptyDir));
+ myEvents.add(new VFileCreateEvent(null, parent, childName, attributes.isDirectory(), attributes, symlinkTarget, true, isEmptyDir));
}
void scheduleDeletion(@NotNull VirtualFile file) {
diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java
index f5ebb5b28d6d..0e36487a14ea 100644
--- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java
+++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/GlobalMenuLinux.java
@@ -103,14 +103,23 @@ interface GlobalMenuLib extends Library {
public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
private static final SimpleDateFormat ourDtf = new SimpleDateFormat("hhmmss.SSS"); // for debug only
- private static final boolean TRACE_SYSOUT = System.getProperty("linux.native.menu.debug.trace.sysout", "false").equals("true");
- private static final boolean TRACE_DISABLED = System.getProperty("linux.native.menu.debug.trace.disabled", "true").equals("true");
- private static final boolean TRACE_SYNC_STATS = System.getProperty("linux.native.menu.debug.trace.sync-stats", "false").equals("true");
- private static final boolean TRACE_EVENTS = System.getProperty("linux.native.menu.debug.trace.events", "false").equals("true");
- private static final boolean TRACE_EVENT_FILTER = System.getProperty("linux.native.menu.debug.trace.event-filter", "false").equals("true");
- private static final boolean TRACE_CLEARING = System.getProperty("linux.native.menu.debug.trace.clearing", "false").equals("true");
- private static final boolean TRACE_HIERARCHY_MISMATCHES = System.getProperty("linux.native.menu.debug.trace.hierarchy.mismatches","false").equals("true");
- private static final boolean SHOW_SWING_MENU = System.getProperty("linux.native.menu.debug.show.frame.menu", "false").equals("true");
+ private static final boolean TRACE_SYSOUT = Boolean.getBoolean("linux.native.menu.debug.trace.sysout");
+ private static final boolean TRACE_ENABLED = Boolean.getBoolean("linux.native.menu.debug.trace.enabled");
+ private static final boolean TRACE_SYNC_STATS = Boolean.getBoolean("linux.native.menu.debug.trace.sync-stats");
+ private static final boolean TRACE_EVENTS = Boolean.getBoolean("linux.native.menu.debug.trace.events");
+ private static final boolean TRACE_EVENT_FILTER = Boolean.getBoolean("linux.native.menu.debug.trace.event-filter");
+ private static final boolean TRACE_SKIPPED_EVENT = Boolean.getBoolean("linux.native.menu.debug.trace.skipped.event");
+ private static final boolean TRACE_CLEARING = Boolean.getBoolean("linux.native.menu.debug.trace.clearing");
+ private static final boolean TRACE_HIERARCHY_MISMATCHES = Boolean.getBoolean("linux.native.menu.debug.trace.hierarchy.mismatches");
+ private static final boolean SHOW_SWING_MENU = Boolean.getBoolean("linux.native.menu.debug.show.frame.menu");
+
+ private static final boolean KDE_DISABLE_ROOT_MNEMONIC_PROCESSING = Boolean.getBoolean("linux.native.menu.kde.disable.root.mnemonic");
+
+ private static final boolean SKIP_OPEN_MENU_COMMAND = Boolean.getBoolean("linux.native.menu.skip.open");
+ private static final boolean DONT_FILL_ROOTS = SystemInfo.isKDE || Boolean.getBoolean("linux.native.dont.fill.roots");
+ private static final boolean DONT_FILL_SUBMENU = Boolean.getBoolean("linux.native.menu.dont.fill.submenu");
+ private static final boolean DONT_CLOSE_POPUPS = Boolean.getBoolean("linux.native.menu.dont.close.popups");
+ private static final boolean DISABLE_EVENTS_FILTERING = Boolean.getBoolean("linux.native.menu.disable.events.filtering");
private static final Logger LOG = Logger.getInstance(GlobalMenuLinux.class);
private static final GlobalMenuLib ourLib;
@@ -128,6 +137,7 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
private boolean myIsRootsUpdated = false;
private boolean myIsEnabled = true;
private boolean myIsDisposed = false;
+ private boolean myIsFirstFilling = true; // don't filter first packet of events (it causes slow reaction of KDE applet)
private final GlobalMenuLib.JRunnable myOnWindowReleased;
private final EventFilter myEventFilter = new EventFilter();
@@ -212,7 +222,7 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
ourInstances.remove(myXid);
};
- if (SystemInfo.isKDE) {
+ if (SystemInfo.isKDE && !KDE_DISABLE_ROOT_MNEMONIC_PROCESSING) {
// root menu items doesn't catch mnemonic shortcuts (in KDE), so process them inside IDE
IdeEventQueue.getInstance().addDispatcher(e -> {
if (!(e instanceof KeyEvent))
@@ -298,15 +308,26 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
ApplicationManager.getApplication().assertIsDispatchThread();
+ int[] stats = new int[]{0, 0, 0};
final int size = roots == null ? 0 : roots.size();
final List newRoots = new ArrayList<>(size);
+
if (roots != null) {
for (ActionMenu am: roots) {
final int uid = System.identityHashCode(am);
- final MenuItemInternal mi = new MenuItemInternal(newRoots.size(), uid, GlobalMenuLib.ITEM_SUBMENU, am.getAnAction());
+ final MenuItemInternal mi = new MenuItemInternal(null, newRoots.size(), uid, GlobalMenuLib.ITEM_SUBMENU, am.getAnAction());
mi.jitem = am;
mi.setLabelFromSwingPeer(am);
newRoots.add(mi);
+
+ if (!DONT_FILL_ROOTS) {
+ final long startMs = System.currentTimeMillis();
+ am.removeAll();
+ am.fillMenu();
+ _syncChildren(mi, am, 1, stats); // NOTE: fill root menus to avoid empty submenu showing
+ final long elapsedMs = System.currentTimeMillis() - startMs;
+ if (TRACE_SYNC_STATS) _trace("filled root menu '%s', spent (in EDT) %d ms, stats: %s", String.valueOf(mi.txt), elapsedMs, _stats2str(stats));
+ }
}
}
@@ -430,20 +451,20 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
}
}
- private static MenuItemInternal _createInternalFromSwing(Component each) {
+ private static MenuItemInternal _createInternalFromSwing(MenuItemInternal parent, Component each) {
if (each == null)
return null;
MenuItemInternal result = null;
if (each instanceof ActionMenuItem) {
final ActionMenuItem ami = (ActionMenuItem)each;
- result = new MenuItemInternal(-1, System.identityHashCode(ami), ami.isToggleable() ? GlobalMenuLib.ITEM_CHECK : GlobalMenuLib.ITEM_SIMPLE, ami.getAnAction());
+ result = new MenuItemInternal(parent, -1, System.identityHashCode(ami), ami.isToggleable() ? GlobalMenuLib.ITEM_CHECK : GlobalMenuLib.ITEM_SIMPLE, ami.getAnAction());
result.jitem = ami;
} else if (each instanceof ActionMenu) {
final ActionMenu am2 = (ActionMenu)each;
- result = new MenuItemInternal(-1, System.identityHashCode(am2), GlobalMenuLib.ITEM_SUBMENU, am2.getAnAction());
+ result = new MenuItemInternal(parent, -1, System.identityHashCode(am2), GlobalMenuLib.ITEM_SUBMENU, am2.getAnAction());
result.jitem = am2;
} else if (each instanceof JSeparator) {
- result = new MenuItemInternal(-1, System.identityHashCode(each), GlobalMenuLib.ITEM_SIMPLE, null);
+ result = new MenuItemInternal(parent, -1, System.identityHashCode(each), GlobalMenuLib.ITEM_SIMPLE, null);
} else if (each instanceof StubItem) {
// System.out.println("skip StubItem");
} else {
@@ -476,7 +497,7 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
for (Component each : am.getPopupMenu().getComponents()) {
MenuItemInternal cmi = mi.findCorrespondingChild(each);
if (cmi == null) {
- cmi = _createInternalFromSwing(each);
+ cmi = _createInternalFromSwing(mi, each);
if (cmi != null) {
cmi.position = itemPos++;
mi.children.add(cmi);
@@ -493,8 +514,12 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
}
}
if (cmi != null) {
- if (deepness > 1 && (each instanceof ActionMenu))
- _syncChildren(cmi, (ActionMenu)each, deepness - 1, stats);
+ if (deepness > 1 && (each instanceof ActionMenu)) {
+ final ActionMenu jmiEach = (ActionMenu)each;
+ jmiEach.removeAll();
+ jmiEach.fillMenu();
+ _syncChildren(cmi, jmiEach, deepness - 1, stats);
+ }
}
}
}
@@ -542,8 +567,15 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
}
}
+ private static boolean _isFillEvent(int eventType) {
+ return eventType == GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW || (!SKIP_OPEN_MENU_COMMAND && eventType == GlobalMenuLib.EVENT_OPENED);
+ }
+
@Override
public void handleEvent(int uid, int eventType) {
+ _handleEvent(uid, eventType, true);
+ }
+ private void _handleEvent(int uid, int eventType, boolean doFiltering) {
// glib main-loop thread
final MenuItemInternal mi = _findMenuItem(uid);
if (mi == null) {
@@ -561,16 +593,27 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
if (TRACE_EVENTS) _trace("received event '%s' from item %s", _evtype2str(eventType), mi);
- if (eventType == GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW || eventType == GlobalMenuLib.EVENT_CLOSED) {
- final boolean check = myEventFilter.check(uid, eventType, mi);
- if (!check)
- return;
+ if (!DISABLE_EVENTS_FILTERING && !myIsFirstFilling && doFiltering && !myEventFilter.check(uid, eventType, mi))
+ return;
- if (eventType == GlobalMenuLib.SIGNAL_ABOUT_TO_SHOW) {
+ if (myIsFirstFilling) {
+ final Timer timer = new Timer(5000, e -> myIsFirstFilling = false);
+ timer.setRepeats(false);
+ timer.start();
+ }
+
+ if (_isFillEvent(eventType)) {
// glib main-loop thread
- final long startMs = System.currentTimeMillis();
- int[] stats = new int[]{0, 0, 0};
+ if (!DONT_CLOSE_POPUPS)
+ ApplicationManager.getApplication().invokeLater(() -> IdeEventQueue.getInstance().getPopupManager().closeAllPopups());
+ mi.cancelClearSwing();
+ // simple check to avoid double (or frequent) filling
+ final long timeMs = System.currentTimeMillis();
+ int[] stats = new int[]{0, 0, 0};
+ if (timeMs - mi.lastFilledMs < 1500 && mi.lastClearedMs < mi.lastFilledMs) {
+ if (TRACE_SKIPPED_EVENT) _trace("skipped fill-event for item '%s', use cached (too frequent fill-events)", String.valueOf(mi.txt));
+ } else {
ApplicationManager.getApplication().invokeAndWait(()-> {
// ETD-start
final JMenuItem jmi = mi.jitem;
@@ -583,23 +626,25 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
return;
}
+ mi.lastFilledMs = timeMs;
+
final ActionMenu am = (ActionMenu)jmi;
am.removeAll();
am.fillMenu();
- _syncChildren(mi, am, 1, stats);
+ _syncChildren(mi, am, DONT_FILL_SUBMENU ? 1 : 2, stats); // NOTE: fill next submenus level to avoid empty submenu showing (intermittent behaviour of menu-applet)
});
// glib main-loop thread
- final long elapsedMs = System.currentTimeMillis() - startMs;
- if (TRACE_SYNC_STATS) _trace("opened %s '%s', spent (in EDT) %d ms, stats: %s", (mi.isRoot() ? "root menu" : "submenu"), String.valueOf(mi.txt), elapsedMs, _stats2str(stats));
+ final long elapsedMs = System.currentTimeMillis() - timeMs;
+ if (TRACE_SYNC_STATS) _trace("filled menu %s '%s', spent (in EDT) %d ms, stats: %s", (mi.isRoot() ? "root menu" : "submenu"), String.valueOf(mi.txt), elapsedMs, _stats2str(stats));
_processChildren(mi);
- } else {
- // glib main-loop thread
- // process GlobalMenuLib.EVENT_CLOSED
- mi.scheduleClearSwing();
}
+ }
+ if (eventType == GlobalMenuLib.EVENT_CLOSED) {
+ // glib main-loop thread
+ mi.scheduleClearSwing();
return;
}
@@ -682,6 +727,7 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
final int type;
final AnAction action;
+ final MenuItemInternal parent;
final List children = new ArrayList<>();
String txt;
@@ -698,11 +744,13 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
Pointer nativePeer;
int position = -1;
- long lastClosedMs = 0;
+ long lastFilledMs = 0;
+ long lastClearedMs = 0;
Timer timerClearSwing;
- MenuItemInternal(int rootPos, int uid, int type, AnAction action) {
+ MenuItemInternal(MenuItemInternal parent, int rootPos, int uid, int type, AnAction action) {
+ this.parent = parent;
this.rootPos = rootPos;
this.uid = uid;
this.type = type;
@@ -779,7 +827,7 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
final int x11keycode = X11KeyCodes.jkeycode2X11code(jkeycode, 0);
if (x11keycode != 0)
ourLib.setItemShortcut(nativePeer, jmodifiers, x11keycode);
- else if (!TRACE_DISABLED)
+ else if (TRACE_ENABLED)
_trace("unknown x11 keycode for jcode=" + jkeycode);
}
}
@@ -865,15 +913,28 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
void scheduleClearSwing() {
// exec at glib main-loop thread
- if (timerClearSwing != null)
- timerClearSwing.stop();
+ if (timerClearSwing != null) {
+ timerClearSwing.restart();
+ if (TRACE_CLEARING) _trace("\t reset clear timer of item '%s'", toStringShort());
+ return;
+ }
- timerClearSwing = new Timer(300, (e)->_clearSwing());
+ timerClearSwing = new Timer(2000, (e)->_clearSwing());
timerClearSwing.setRepeats(false);
timerClearSwing.start();
if (TRACE_CLEARING) _trace("\t scheduled (300 ms later) to clear '%s'", toStringShort());
}
+ void cancelClearSwing() {
+ // exec at glib main-loop thread
+ if (timerClearSwing != null) {
+ timerClearSwing.stop();
+ timerClearSwing = null;
+ }
+ for (MenuItemInternal p = parent; p != null; p = p.parent)
+ p.cancelClearSwing();
+ }
+
private void _clearSwing() {
// exec at ETD
if (timerClearSwing == null)
@@ -891,59 +952,157 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
final ActionMenu am = (ActionMenu)jitem;
am.clearItems();
clearChildrenSwingRefs();
+ _onSwingCleared(System.currentTimeMillis());
if (TRACE_CLEARING) _trace("\t cleared '%s'", toStringShort());
}
+
+ private void _onSwingCleared(long timeMs) {
+ lastClearedMs = timeMs;
+ if (timerClearSwing != null) {
+ timerClearSwing.stop();
+ timerClearSwing = null;
+ }
+
+ for (MenuItemInternal kid: children)
+ kid._onSwingCleared(timeMs);
+ }
+ }
+
+ private static class QueuedEvent {
+ final int uid;
+ final int eventType;
+ final int rootId;
+ final long timeMs;
+
+ private QueuedEvent(int uid, int eventType, int rootId, long timeMs) {
+ this.uid = uid;
+ this.eventType = eventType;
+ this.rootId = rootId;
+ this.timeMs = timeMs;
+ }
+
+ static QueuedEvent of(int uid, int eventType, int rootId, long timeMs) {
+ return new QueuedEvent(uid, eventType, rootId, timeMs);
+ }
}
private class EventFilter {
+ final private ArrayList myQueued = new ArrayList<>();
private Timer myTimer;
- private long myLastFirstRootEventMs = 0;
+ private long myClosedMs = 0;
+
@SuppressWarnings("unused")
private GlobalMenuLib.JRunnable myGlibLoopRunnable; // holds runnable object
- boolean check(int uid, int eventType, @NotNull MenuItemInternal mi) {
- final long timeMs = System.currentTimeMillis();
- if (eventType == GlobalMenuLib.EVENT_CLOSED) {
- mi.lastClosedMs = timeMs;
- } else {
- if (mi.rootPos == 0) {
- if (myTimer == null) {
- myLastFirstRootEventMs = timeMs;
- // start timer to call handleEvent(uid, eventType) after several ms
- myTimer = new Timer(50, (e) -> {
- if (myTimer == null) {
- if (TRACE_EVENT_FILTER) _trace("EventFilter: skip delayed 'about-to-show' processing of first-root because timer was reset (i.e. myTimer == null)");
- return;
- }
- ourLib.execOnMainLoop(myGlibLoopRunnable = () -> handleEvent(uid, eventType));
- });
- myTimer.setRepeats(false);
- myTimer.start();
- if (TRACE_EVENT_FILTER) _trace("EventFilter: start timer to process 'about-to-show' of first-root later");
- return false;
+ private boolean _isClosed() { return myClosedMs > 0; }
+
+ private void _stopTimer() {
+ // exec at glib-main-thread
+ if (myTimer != null)
+ myTimer.stop();
+ myTimer = null;
+ }
+
+ private void _processQueue() {
+ // exec at glib-main-thread
+ for (QueuedEvent q: myQueued)
+ _handleEvent(q.uid, q.eventType, false);
+ myQueued.clear();
+ }
+
+ private void _startTimer() {
+ // exec at glib-main-thread
+ _stopTimer();
+
+ final Timer timer = new Timer(50, null);
+ timer.addActionListener((e) -> {
+ if (TRACE_EVENT_FILTER) _trace("EventFilter: start execution of timer callback"); // exec at EDT
+ if (myTimer != timer) {// check that timer wasn't reset
+ if (TRACE_EVENT_FILTER) _trace("EventFilter: skip timer-processing because timer was reset (i.e. myTimer == null)");
+ return;
+ }
+
+ ourLib.execOnMainLoop(myGlibLoopRunnable = () -> {
+ // remove continuous series of sequential root events
+ // because some of implementations (of menu applet) regularly send packets of root events ('about-to-show' or/and 'open')
+ final int lastRootId = myRoots.size() - 1;
+ int from = 0;
+ while (from < myQueued.size()) {
+ final int size = myQueued.size();
+
+ // 1. find event from second root
+ while (from < size && myQueued.get(from).rootId != 1) ++from;
+ if (from == size) break;
+
+ // 2. rewind to first root
+ int first = from - 1;
+ if (first < 0 || myQueued.get(first).rootId != 0) {
+ // no events from first root, rewind forward
+ while (from < size && myQueued.get(from).rootId == 1) ++from;
+ continue;
+ }
+ while (first >= 1 && myQueued.get(first - 1).rootId == 0 && from - first <= 1) --first;
+
+ // 3. find last root
+ int to = from + 1;
+ while (to < size && myQueued.get(to).rootId != lastRootId) ++to;
+ if (to == size) break;
+ while (to < size && myQueued.get(to).rootId == lastRootId) ++to;
+
+ // 4. remove fake segment
+ if (TRACE_EVENT_FILTER) _trace("EventFilter: remove segment [%d, %d) from queue of size=%d", first, to, myQueued.size());
+ myQueued.subList(first, to).clear();
+ from = first + 1;
}
+ if (!myQueued.isEmpty()) {
+ if (TRACE_ENABLED) _trace("EventFilter: process queued events, size=%d", myQueued.size());
+ _processQueue();
+ } else if (TRACE_ENABLED) _trace("EventFilter: queue is empty");
+
myTimer = null;
- } else if (mi.rootPos > 0) {
- if ((timeMs - myLastFirstRootEventMs) < 50) {
- if (TRACE_EVENT_FILTER) _trace("EventFilter: skip fake 'about-to-show' of root[%d]%s", mi.rootPos, myTimer != null ? " (reset timer)" : "");
- if (myTimer != null) {
- myTimer.stop();
- myTimer = null;
- }
- return false;
- }
- if (TRACE_EVENT_FILTER) _trace("EventFilter: process real 'about-to-show' on root[%d]", mi.rootPos);
+ myClosedMs = 0; // open filter
+ if (TRACE_EVENT_FILTER) _trace("EventFilter: filter is opened");
+ });
+ });
+
+ myTimer = timer;
+ myTimer.setRepeats(false);
+ myTimer.start();
+ if (TRACE_EVENT_FILTER) _trace("EventFilter: start timer");
+ }
+
+ boolean check(int uid, int eventType, @NotNull MenuItemInternal mi) {
+ // exec at glib-main-thread
+ final boolean isFillEvent = _isFillEvent(eventType);
+ final long timeMs = System.currentTimeMillis();
+
+ if (_isClosed()) {
+ if (timeMs - myClosedMs > 2000) {
+ // simple protection (open filter by timeout)
+ if (TRACE_ENABLED) _trace("EventFilter WARNING: close filter by timeout protection");
+ _processQueue();
+ _stopTimer();
+ myClosedMs = 0;
} else {
- if (TRACE_EVENT_FILTER) _trace("EventFilter: process real 'about-to-show' on non-root item '%s'%s", mi.txt, myTimer != null ? " (reset timer)" : "");
- if (myTimer != null) {
- myTimer.stop();
- myTimer = null;
- }
+ // filter is closed
+ myQueued.add(QueuedEvent.of(uid, eventType, mi.rootPos, timeMs));
+ if (myTimer != null)
+ myTimer.restart();
+ return false;
}
}
- return true;
+ // filter is opened
+ if (mi.rootPos != 0)
+ return true;
+
+ // filter is opened and first root appeared
+ if (TRACE_EVENT_FILTER) _trace("EventFilter: close filter");
+ myQueued.add(QueuedEvent.of(uid, eventType, mi.rootPos, timeMs));
+ myClosedMs = timeMs;
+ _startTimer();
+ return false;
}
}
@@ -1023,14 +1182,14 @@ public class GlobalMenuLinux implements GlobalMenuLib.EventHandler, Disposable {
}
private static void _trace(String fmt, Object... args) {
- if (TRACE_DISABLED)
+ if (!TRACE_ENABLED)
return;
final String msg = String.format(fmt, args);
_trace(msg);
}
private static void _trace(String msg) {
- if (TRACE_DISABLED)
+ if (!TRACE_ENABLED)
return;
if (TRACE_SYSOUT)
//noinspection UseOfSystemOutOrSystemErr
diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeBackgroundUtil.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeBackgroundUtil.java
index cc551e16e0d5..935895791078 100644
--- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeBackgroundUtil.java
+++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeBackgroundUtil.java
@@ -34,6 +34,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.AbstractPainter;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.StatusBar;
import com.intellij.ui.EditorTextField;
@@ -57,6 +58,7 @@ import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ImageObserver;
+import java.awt.image.VolatileImage;
import java.net.URL;
import java.util.Set;
@@ -69,6 +71,8 @@ public class IdeBackgroundUtil {
public static final String FRAME_PROP = "idea.background.frame";
public static final String TARGET_PROP = "idea.background.target";
+ public static final Key NO_BACKGROUND = Key.create("SUPPRESS_BACKGROUND");
+
public enum Fill {
PLAIN, SCALE, TILE
}
@@ -374,12 +378,14 @@ public class IdeBackgroundUtil {
void runAllPainters(int x, int y, int width, int height, @Nullable Shape sourceShape, @Nullable Object reason) {
if (width <= 1 || height <= 1) return;
- // skip painters for transparent 'reasons'
- if (reason instanceof Color && ((Color)reason).getAlpha() < 255) return;
- if (reason instanceof Image) {
- if (!(reason instanceof BufferedImage)) return;
- if (((BufferedImage)reason).getColorModel().hasAlpha()) return;
- }
+ boolean hasAlpha =
+ reason instanceof Color ? ((Color)reason).getAlpha() < 255 :
+ reason instanceof BufferedImage ? ((BufferedImage)reason).getColorModel().hasAlpha() :
+ reason instanceof VolatileImage ? ((VolatileImage)reason).getTransparency() != Transparency.OPAQUE :
+ true;
+ // skip painters when alpha is already present
+ if (hasAlpha) return;
+
Shape prevClip = getClip();
Shape tmpClip = calcTempClip(prevClip, sourceShape != null ? sourceShape : new Rectangle(x, y, width, height));
if (tmpClip == null) return;
@@ -400,6 +406,7 @@ public class IdeBackgroundUtil {
private static class MyTransform implements PairFunction