IDEA-223967 new update dialog: provide "What's New" notification that opens editor with HTML after IDE restart

GitOrigin-RevId: 243fadd69f9e7efc1d25e26a6e4c7831f87214a9
This commit is contained in:
Dmitry.Krasilschikov
2019-10-10 18:32:46 +00:00
committed by intellij-monorepo-bot
parent 3c4b5284a8
commit ef891fef3d
10 changed files with 163 additions and 6 deletions
@@ -2,6 +2,7 @@
package com.intellij.internal
import com.intellij.ide.util.BrowseFilesListener
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.application.ApplicationNamesInfo
@@ -32,7 +33,7 @@ class ShowUpdateInfoDialogAction : DumbAwareAction() {
val dialog = MyDialog(e.project)
if (dialog.showAndGet()) {
try {
UpdateChecker.testPlatformUpdate(dialog.updateXmlText(), dialog.patchFilePath(), dialog.forceUpdate())
UpdateChecker.testPlatformUpdate(AnAction.getEventProject(e), dialog.updateXmlText (), dialog.patchFilePath(), dialog.forceUpdate())
}
catch (ex: Exception) {
Messages.showErrorDialog(e.project, "${ex.javaClass.name}: ${ex.message}", "Something Went Wrong")
@@ -0,0 +1,25 @@
// 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.fileEditor.impl
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorPolicy
import com.intellij.openapi.fileEditor.FileEditorProvider
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.KeyWithDefaultValue
import com.intellij.openapi.vfs.VirtualFile
class HTMLEditorProvider : FileEditorProvider, DumbAware {
override fun createEditor(project: Project, file: VirtualFile): FileEditor = HTMLFileEditor(file)
override fun accept(project: Project, file: VirtualFile) = file.getUserData(HTML_CONTENT_TYPE)!!
override fun getEditorTypeId() = "html-editor"
override fun getPolicy() = FileEditorPolicy.HIDE_DEFAULT_EDITOR
companion object {
val HTML_CONTENT_TYPE: Key<Boolean> = KeyWithDefaultValue.create("HTML_CONTENT_TYPE", false)
}
}
@@ -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.fileEditor.impl
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.fileEditor.FileEditorLocation
import com.intellij.openapi.fileEditor.FileEditorState
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import java.beans.PropertyChangeListener
import javax.swing.JComponent
import javax.swing.JEditorPane
class HTMLFileEditor(private val vFile: VirtualFile) : FileEditor {
private val component: JComponent =
JEditorPane().also {
it.editorKit = UIUtil.getHTMLEditorKit()
it.text = VfsUtil.loadText(vFile)
it.border = JBUI.Borders.empty(8, 12)
it.isEditable = false
}
override fun getComponent(): JComponent = component
override fun getPreferredFocusedComponent() = component
override fun getName() = "HTML Preview"
override fun setState(state: FileEditorState) {}
override fun isModified(): Boolean = false
override fun isValid(): Boolean = true
override fun addPropertyChangeListener(listener: PropertyChangeListener) {}
override fun <T : Any?> getUserData(key: Key<T>): T? = null
override fun <T : Any?> putUserData(key: Key<T>, value: T?) {}
override fun removePropertyChangeListener(listener: PropertyChangeListener) {}
override fun getCurrentLocation(): FileEditorLocation? = null
override fun dispose() {}
}
@@ -508,7 +508,7 @@ object UpdateChecker {
}
/** A helper method for manually testing platform updates (see [com.intellij.internal.ShowUpdateInfoDialogAction]). */
fun testPlatformUpdate(updateInfoText: String, patchFilePath: String?, forceUpdate: Boolean) {
fun testPlatformUpdate(project: Project?, updateInfoText: String, patchFilePath: String?, forceUpdate: Boolean) {
if (!ApplicationManager.getApplication().isInternal) {
throw IllegalStateException()
}
@@ -533,7 +533,7 @@ object UpdateChecker {
if (channel != null && newBuild != null) {
val patchFile = if (patchFilePath != null) File(FileUtil.toSystemDependentName(patchFilePath)) else null
UpdateInfoDialog(channel, newBuild, patches, patchFile).show()
UpdateInfoDialog(project, channel, newBuild, patches, patchFile).show()
}
else {
NoUpdatesDialog(true).show()
@@ -3,11 +3,16 @@ package com.intellij.openapi.updateSettings.impl;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationAction;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.HTMLEditorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerListener;
@@ -15,11 +20,13 @@ import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.updateSettings.UpdateStrategyCustomization;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.concurrency.NonUrgentExecutor;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.text.DateFormatUtil;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.io.File;
@@ -38,6 +45,7 @@ public final class UpdateCheckerComponent implements Runnable {
}
static final String SELF_UPDATE_STARTED_FOR_BUILD_PROPERTY = "ide.self.update.started.for.build";
@NonNls static final String UPDATE_WHATS_NEW_MESSAGE = "ide.update.whats.new.message";
private static final Logger LOG = Logger.getInstance(UpdateCheckerComponent.class);
@@ -50,13 +58,22 @@ public final class UpdateCheckerComponent implements Runnable {
Application app = ApplicationManager.getApplication();
if (!app.isCommandLine()) {
NonUrgentExecutor.getInstance().execute(() -> {
checkIfPreviousUpdateFailed();
boolean updateFailed = checkIfPreviousUpdateFailed();
updateDefaultChannel();
scheduleFirstCheck();
snapPackageNotification();
MessageBusConnection connection = app.getMessageBus().connect();
if (!updateFailed) {
connection.subscribe(ProjectManager.TOPIC, new ProjectManagerListener() {
@Override
public void projectOpened(@NotNull Project project) {
StartupManager.getInstance(project).registerPostStartupActivity(() -> { showWhatsNewNotification(project); });
}
});
}
connection.subscribe(ProjectManager.TOPIC, new ProjectManagerListener() {
@Override
public void projectOpened(@NotNull Project project) {
@@ -82,14 +99,38 @@ public final class UpdateCheckerComponent implements Runnable {
if (future != null) future.cancel(false);
}
private static void checkIfPreviousUpdateFailed() {
private static void showWhatsNewNotification(@NotNull Project project) {
PropertiesComponent properties = PropertiesComponent.getInstance();
String updateHtmlMessage = properties.getValue(UPDATE_WHATS_NEW_MESSAGE);
if (updateHtmlMessage == null) {
LOG.warn("Cannot show what's new notification: no content found.");
return;
}
String title = IdeBundle.message("update.whats.new.notification.title", ApplicationNamesInfo.getInstance().getFullProductName());
UpdateChecker.NOTIFICATIONS.createNotification(title, null, null, NotificationType.INFORMATION, null)
.addAction(new NotificationAction(IdeBundle.message("update.whats.new.notification.action")) {
@Override
public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
LightVirtualFile file = new LightVirtualFile(IdeBundle.message("update.whats.new.file.name", ApplicationInfo.getInstance().getFullVersion()), updateHtmlMessage);
file.putUserData(HTMLEditorProvider.Companion.getHTML_CONTENT_TYPE(), true);
FileEditorManager.getInstance(project).openFile(file, true);
IdeUpdateUsageTriggerCollector.trigger("update.whats.new");
}
});
properties.setValue(UPDATE_WHATS_NEW_MESSAGE, null);
}
private static boolean checkIfPreviousUpdateFailed() {
PropertiesComponent properties = PropertiesComponent.getInstance();
if (ApplicationInfo.getInstance().getBuild().asString().equals(properties.getValue(SELF_UPDATE_STARTED_FOR_BUILD_PROPERTY)) &&
new File(PathManager.getLogPath(), ERROR_LOG_FILE_NAME).length() > 0) {
IdeUpdateUsageTriggerCollector.trigger("update.failed");
LOG.info("The previous IDE update failed");
return false;
}
properties.setValue(SELF_UPDATE_STARTED_FOR_BUILD_PROPERTY, null);
return true;
}
private static void updateDefaultChannel() {
@@ -11,15 +11,19 @@ import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.*;
import com.intellij.openapi.application.impl.ApplicationImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.impl.HTMLEditorProvider;
import com.intellij.openapi.progress.PerformInBackgroundOption;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.ui.JBColor;
import com.intellij.ui.LicensingFacade;
import com.intellij.util.SystemProperties;
@@ -54,6 +58,8 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
private final Pair<String, Color> myLicenseInfo;
private final File myTestPatch;
private AbstractAction myWhatsNewAction;
UpdateInfoDialog(@NotNull UpdateChannel channel,
@NotNull BuildInfo newBuild,
@Nullable UpdateChain patches,
@@ -77,7 +83,7 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
IdeUpdateUsageTriggerCollector.triggerUpdateDialog(myPatches, ApplicationManager.getApplication().isRestartCapable());
}
UpdateInfoDialog(UpdateChannel channel, BuildInfo newBuild, UpdateChain patches, @Nullable File patchFile) {
UpdateInfoDialog(@Nullable Project project, UpdateChannel channel, BuildInfo newBuild, UpdateChain patches, @Nullable File patchFile) {
super(true);
myUpdatedChannel = channel;
myUpdatedPlugins = null;
@@ -86,6 +92,18 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
myWriteProtected = false;
myLicenseInfo = initLicensingInfo(myUpdatedChannel, myNewBuild);
myTestPatch = patchFile;
if (project != null) {
myWhatsNewAction = new AbstractAction("What's new") {
@Override
public void actionPerformed(ActionEvent e) {
String title = IdeBundle.message("update.whats.new.file.name", ApplicationInfo.getInstance().getFullVersion());
LightVirtualFile file = new LightVirtualFile(title, myNewBuild.getMessage());
file.putUserData(HTMLEditorProvider.Companion.getHTML_CONTENT_TYPE(), true);
FileEditorManager.getInstance(project).openFile(file, true);
close(OK_EXIT_CODE);
}
};
}
init();
setTitle("[TEST] " + getTitle());
}
@@ -175,6 +193,10 @@ class UpdateInfoDialog extends AbstractUpdateDialog {
actions.add(updateButton);
}
if (myWhatsNewAction != null) {
actions.add(myWhatsNewAction);
}
return actions.toArray(new Action[0]);
}
@@ -0,0 +1,26 @@
// 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.updateSettings.impl;
import com.intellij.icons.AllIcons;
import com.intellij.ide.FileIconProvider;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.fileEditor.impl.HTMLEditorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
public final class WhatsNewFileIconProvider implements FileIconProvider {
@Nullable
@Override
public Icon getIcon(@NotNull VirtualFile virtualFile, @Iconable.IconFlags int flags, @Nullable Project project) {
Boolean isHtml = virtualFile.getUserData(HTMLEditorProvider.Companion.getHTML_CONTENT_TYPE());
if (isHtml != null && isHtml && virtualFile.getName().startsWith(IdeBundle.message("update.whats.new.file.name", ""))) {
return AllIcons.General.Information;
}
return null;
}
}
@@ -1023,6 +1023,9 @@ updates.settings.current.version=Current version
updates.settings.last.check=Last checked
updates.settings.ignored=View/edit ignored updates
updates.settings.ignored.title=Ignored Updates
update.whats.new.notification.title={0} Update Installed
update.whats.new.file.name=What''s New in {0}
update.whats.new.notification.action=What''s New
update.plugin.host.url.message=Repository URL:
update.no.update.hosts=No plugin repositories configured
update.edit.plugin.host.title=Edit Repository
@@ -689,6 +689,7 @@
id="PlatformProjectConfigurator"/>
<directoryProjectConfigurator implementation="com.intellij.platform.PlatformProjectViewOpener"/>
<fileIconProvider implementation="com.intellij.openapi.updateSettings.impl.WhatsNewFileIconProvider"/>
<fileIconProvider implementation="com.intellij.ide.FileIconPatcherImpl"/>
<iconProvider implementation="com.intellij.ide.NativeIconProvider" id="native"/>
<iconProvider implementation="com.intellij.psi.impl.file.SourceRootIconProvider$DirectoryProvider" id="directory" order="last"/>
@@ -620,6 +620,7 @@
<iconLayerProvider implementation="com.intellij.ide.LockedIconLayerProvider"/>
<fileEditorProvider implementation="com.intellij.openapi.fileEditor.impl.text.LargeFileEditorProvider"/>
<fileEditorProvider implementation="com.intellij.openapi.fileEditor.impl.HTMLEditorProvider"/>
<editorNotificationProvider implementation="com.intellij.openapi.fileEditor.impl.text.LargeFileNotificationProvider"/>
<fileDocumentSynchronizationVetoer implementation="com.intellij.openapi.fileEditor.impl.LargeFileSavingVetoer"/>
<frameEditorComponentProvider implementation="com.intellij.openapi.wm.ex.DefaultFrameEditorComponentProvider"/>