diff --git a/images/src/META-INF/ImagesPlugin.xml b/images/src/META-INF/ImagesPlugin.xml index dbcf2506bf9d..ecfe40b8103c 100644 --- a/images/src/META-INF/ImagesPlugin.xml +++ b/images/src/META-INF/ImagesPlugin.xml @@ -40,6 +40,11 @@ text="Show Chessboard" description="Show a chessboard on transparent image parts"> + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/images/src/org/intellij/images/editor/actions/SetBackgroundImageDialog.java b/images/src/org/intellij/images/editor/actions/SetBackgroundImageDialog.java new file mode 100644 index 000000000000..08ba729520ce --- /dev/null +++ b/images/src/org/intellij/images/editor/actions/SetBackgroundImageDialog.java @@ -0,0 +1,362 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.intellij.images.editor.actions; + +import com.intellij.application.options.colors.ColorAndFontOptions; +import com.intellij.application.options.colors.SimpleEditorPreview; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.fileChooser.FileChooser; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter; +import com.intellij.openapi.options.colors.ColorSettingsPage; +import com.intellij.openapi.options.colors.ColorSettingsPages; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.wm.impl.IdeBackgroundUtil; +import com.intellij.ui.CollectionComboBoxModel; +import com.intellij.ui.ComboboxWithBrowseButton; +import com.intellij.ui.DocumentAdapter; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBRadioButton; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ui.JBSwingUtilities; +import com.intellij.util.ui.update.UiNotifyConnector; +import org.intellij.images.fileTypes.ImageFileTypeManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.text.JTextComponent; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class SetBackgroundImageDialog extends DialogWrapper { + private final String myPropertyTmp; + private JPanel myRoot; + private JBRadioButton myEditorRb; + private JSlider myOpacitySlider; + private JSpinner myOpacitySpinner; + private JPanel myPreviewPanel; + private ComboboxWithBrowseButton myPathField; + private JBRadioButton myScaleRb; + + boolean myAdjusting; + private String mySelectedPath; + private Map myResults = ContainerUtil.newHashMap(); + + private final SimpleEditorPreview myEditorPreview; + private final JComponent myIdePreview; + + public SetBackgroundImageDialog(@NotNull Project project, @Nullable String selectedPath) { + super(project, true); + setTitle("Background Image"); + mySelectedPath = selectedPath; + myEditorPreview = createEditorPreview(); + myIdePreview = createIdePreview(); + myPropertyTmp = IdeBackgroundUtil.BG_PROPERTY_PREFIX + project.getLocationHash(); + UiNotifyConnector.doWhenFirstShown(myRoot, new Runnable() { + @Override + public void run() { + IdeBackgroundUtil.createTemporaryBackgroundTransform(myRoot, myPropertyTmp, getDisposable()); + } + }); + setupComponents(); + restoreRecentImages(); + setSelectedPath(mySelectedPath); + targetChanged(null); + init(); + } + + private void createUIComponents() { + ComboBox comboBox = new ComboBox<>(new CollectionComboBoxModel(), 100); + myPathField = new ComboboxWithBrowseButton(comboBox); + } + + @Override + protected void dispose() { + super.dispose(); + myEditorPreview.disposeUIResources(); + System.getProperties().remove(myPropertyTmp); + } + + @NotNull + private static SimpleEditorPreview createEditorPreview() { + EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); + ColorAndFontOptions options = new ColorAndFontOptions(); + options.reset(); + options.selectScheme(scheme.getName()); + ColorSettingsPage[] pages = ColorSettingsPages.getInstance().getRegisteredPages(); + int round = (int)Math.round(Math.random() * (pages.length - 1)); + return new SimpleEditorPreview(options, pages[round], false); + } + + @NotNull + private static JComponent createIdePreview() { + return new JBLabel() { + EditorEmptyTextPainter p = ServiceManager.getService(EditorEmptyTextPainter.class); + + @Override + protected Graphics getComponentGraphics(Graphics g) { + return JBSwingUtilities.runGlobalCGTransform(this, super.getComponentGraphics(g)); + } + + @Override + public void paint(Graphics gg) { + Graphics g = getComponentGraphics(gg); + g.setColor(getBackground()); + g.fillRect(0, 0, getWidth(), getHeight()); + g.translate(0, -50); + p.paintEmptyText(this, g); + } + }; + } + + @Nullable + @Override + protected String getDimensionServiceKey() { + return getClass().getName(); + } + + @NotNull + private String getRecentItemsKey() { + return getDimensionServiceKey() + "#recent"; + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myRoot; + } + + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myPathField; + } + + private void setupComponents() { + myAdjusting = true; + myPreviewPanel.setLayout(new CardLayout()); + myPreviewPanel.add(myEditorPreview.getPanel(), "editor"); + myPreviewPanel.add(myIdePreview, "ide"); + ((CardLayout)myPreviewPanel.getLayout()).show(myPreviewPanel, "editor"); + myPathField.getComboBox().setEditable(true); + myPathField.getButton().setAction(new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, true, false) + .withFileFilter(file -> ImageFileTypeManager.getInstance().isImage(file)); + VirtualFile file = FileChooser.chooseFile(descriptor, myRoot, null, null); + if (file != null) { + setSelectedPath(file.getPath()); + } + } + }); + JTextComponent textComponent = getComboEditor(); + textComponent.getDocument().addDocumentListener(new DocumentAdapter() { + @Override + protected void textChanged(DocumentEvent e) { + if (myAdjusting) return; + imagePathChanged(); + } + }); + for (Enumeration e = getTargetRbGroup().getElements(); e.hasMoreElements();) { + AbstractButton button = e.nextElement(); + button.setActionCommand(button.getText()); + button.addActionListener(this::targetChanged); + } + for (Enumeration e = getTypeRbGroup().getElements(); e.hasMoreElements();) { + AbstractButton button = e.nextElement(); + button.setActionCommand(button.getText()); + button.addActionListener(this::fillTypeChanged); + } + ChangeListener opacitySync = new ChangeListener() { + + @Override + public void stateChanged(ChangeEvent e) { + if (myAdjusting) return; + myAdjusting = true; + boolean b = e.getSource() == myOpacitySpinner; + if (b) { + int value = (Integer)myOpacitySpinner.getValue(); + myOpacitySpinner.setValue(Math.min(Math.max(0, value), 100)); + myOpacitySlider.setValue(value); + } + else { + myOpacitySpinner.setValue(myOpacitySlider.getValue()); + } + myAdjusting = false; + opacityChanged(); + } + }; + myOpacitySpinner.addChangeListener(opacitySync); + myOpacitySlider.addChangeListener(opacitySync); + myOpacitySlider.setValue(15); + myOpacitySpinner.setValue(15); + myScaleRb.setSelected(true); + myEditorRb.setSelected(true); + myAdjusting = false; + } + + private void opacityChanged() { + updatePreview(); + } + + private void imagePathChanged() { + updatePreview(); + } + + private void fillTypeChanged(ActionEvent event) { + updatePreview(); + } + + private void targetChanged(ActionEvent event) { + if (StringUtil.isEmptyOrSpaces(mySelectedPath)) { + retrieveExistingValue(); + } + + ((CardLayout)myPreviewPanel.getLayout()).show(myPreviewPanel, myEditorRb.isSelected() ? "editor" : "ide"); + if (myEditorRb.isSelected()) { + myEditorPreview.updateView(); + } + updatePreview(); + } + + public void setSelectedPath(String path) { + mySelectedPath = path; + if (StringUtil.isEmptyOrSpaces(path)) return; + CollectionComboBoxModel comboModel = getComboModel(); + if (!comboModel.getItems().contains(path)) { + comboModel.add(path); + } + comboModel.setSelectedItem(path); + getComboEditor().setCaretPosition(0); + } + + private void retrieveExistingValue() { + myAdjusting = true; + String prop = getSystemProp(); + String value = StringUtil.notNullize(myResults.get(prop), getProperty(prop)); + String[] split = value.split(","); + setSelectedPath(split[0]); + mySelectedPath = null; + int opacity = split.length > 1 ? StringUtil.parseInt(split[1], 15) : 15; + myOpacitySlider.setValue(opacity); + myOpacitySpinner.setValue(opacity); + String type = split.length > 2 ? split[2] : "scale"; + for (Enumeration e = getTypeRbGroup().getElements(); e.hasMoreElements(); ) { + AbstractButton button = e.nextElement(); + String s = button.getActionCommand().replace(' ', '_'); + if (s.equalsIgnoreCase(type)) { + button.setSelected(true); + break; + } + } + myAdjusting = false; + } + + @Override + public void doCancelAction() { + storeRecentImages(); + super.doCancelAction(); + } + + @Override + protected void doOKAction() { + storeRecentImages(); + String value = calcNewValue(); + String prop = getSystemProp(); + myResults.put(prop, value); + + if (value.startsWith(",")) return; + PropertiesComponent.getInstance().setValue(prop, value); + + super.doOKAction(); + } + + private void storeRecentImages() { + List items = getComboModel().getItems(); + PropertiesComponent.getInstance().setValue( + getRecentItemsKey(), + StringUtil.join(items.subList(0, Math.min(items.size(), 5)), "\n")); + } + + private CollectionComboBoxModel getComboModel() { + //noinspection unchecked + return (CollectionComboBoxModel)myPathField.getComboBox().getModel(); + } + + private JTextComponent getComboEditor() { + return (JTextComponent)myPathField.getComboBox().getEditor().getEditorComponent(); + } + + private void restoreRecentImages() { + String value = PropertiesComponent.getInstance().getValue(getRecentItemsKey()); + if (value == null) return; + for (String s : value.split("\n")) { + //noinspection unchecked + getComboModel().add(s); + } + } + + private static String getProperty(@NotNull String prop) { + return StringUtil.notNullize(PropertiesComponent.getInstance().getValue(prop), System.getProperty(prop, "")); + } + + @NotNull + private String getSystemProp() { + return IdeBackgroundUtil.BG_PROPERTY_PREFIX + (myEditorRb.isSelected() ? "editor" : "ide"); + } + + private void updatePreview() { + String prop = getSystemProp(); + String value = calcNewValue(); + System.setProperty(myPropertyTmp, value); + myResults.put(prop, value); + myPreviewPanel.validate(); + myPreviewPanel.repaint(); + } + + @NotNull + private String calcNewValue() { + String path = (String)myPathField.getComboBox().getEditor().getItem(); + String type = getTypeRbGroup().getSelection().getActionCommand() + .replace(' ', '_').toLowerCase(Locale.ENGLISH); + + return path.trim() + "," + myOpacitySpinner.getValue() + "," + type; + } + + private ButtonGroup getTypeRbGroup() { + return ((DefaultButtonModel)myScaleRb.getModel()).getGroup(); + } + + private ButtonGroup getTargetRbGroup() { + return ((DefaultButtonModel)myEditorRb.getModel()).getGroup(); + } +} 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 75fc475ba11e..49e952afb692 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 @@ -15,6 +15,7 @@ */ package com.intellij.openapi.wm.impl; +import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionToolbar; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataProvider; @@ -26,6 +27,7 @@ import com.intellij.openapi.editor.impl.EditorComponentImpl; import com.intellij.openapi.fileEditor.impl.EditorEmptyTextPainter; import com.intellij.openapi.fileEditor.impl.EditorsSplitters; import com.intellij.openapi.ui.AbstractPainter; +import com.intellij.openapi.util.Disposer; import com.intellij.ui.EditorTextField; import com.intellij.ui.Graphics2DDelegate; import com.intellij.ui.tabs.JBTabs; @@ -49,7 +51,7 @@ import java.net.URL; */ public class IdeBackgroundUtil { - public static final String BG_PROPERTY_PREFIX = "idea.wallpaper."; + public static final String BG_PROPERTY_PREFIX = "idea.background."; static { JBSwingUtilities.addGlobalCGTransform(new MyTransform()); @@ -142,6 +144,18 @@ public class IdeBackgroundUtil { return UIUtil.isUnderDarcula() ? new Color(40, 40, 41) : UIUtil.getSlightlyDarkerColor(UIUtil.getSlightlyDarkerColor(result)); } + public static void createTemporaryBackgroundTransform(JPanel root, String tmp, Disposable disposable) { + PaintersHelper paintersHelper = new PaintersHelper(root); + PaintersHelper.initWallpaperPainter(tmp, paintersHelper); + Disposer.register(disposable, JBSwingUtilities.addGlobalCGTransform(new PairFunction() { + @Override + public Graphics2D fun(JComponent t, Graphics2D v) { + if (!UIUtil.isAncestor(root, t)) return v; + return MyGraphics.wrap(v, paintersHelper, t); + } + })); + } + private static class MyGraphics extends Graphics2DDelegate { final PaintersHelper helper; final int[] offsets; diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/PaintersHelper.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/PaintersHelper.java index 7b049e270379..2530a16ab8ef 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/PaintersHelper.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/PaintersHelper.java @@ -15,7 +15,9 @@ */ package com.intellij.openapi.wm.impl; +import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.ui.AbstractPainter; @@ -157,11 +159,12 @@ final class PaintersHelper implements Painter.Listener { } public static void initWallpaperPainter(@NotNull String propertyName, @NotNull PaintersHelper painters) { - ImagePainter painter = (ImagePainter)newWallpaperPainter(propertyName); + ModalityState modalityState = ModalityState.stateForComponent(painters.myRootComponent); + ImagePainter painter = (ImagePainter)newWallpaperPainter(propertyName, modalityState); painters.addPainter(painter, null); } - private static AbstractPainter newWallpaperPainter(@NotNull final String propertyName) { + private static AbstractPainter newWallpaperPainter(@NotNull final String propertyName, @NotNull final ModalityState modalityState) { return new ImagePainter() { Image image; float alpha; @@ -182,7 +185,8 @@ final class PaintersHelper implements Painter.Listener { } boolean ensureImageLoaded() { - String value = System.getProperty(propertyName); + String value = StringUtil.notNullize(PropertiesComponent.getInstance().getValue(propertyName), + System.getProperty(propertyName, "")); if (!Comparing.equal(value, current)) { current = value; loadImageAsync(value); @@ -224,7 +228,7 @@ final class PaintersHelper implements Painter.Listener { public void run() { resetImage(propertyValue, m, newAlpha, newFillType); } - }); + }, modalityState); } }); }