com.chrisrm.idea.MTThemeManager.java Source code

Java tutorial

Introduction

Here is the source code for com.chrisrm.idea.MTThemeManager.java

Source

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2018 Chris Magnussen and Elior Boukhobza
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 */

package com.chrisrm.idea;

import com.chrisrm.idea.messages.MaterialThemeBundle;
import com.chrisrm.idea.utils.MTUiUtils;
import com.chrisrm.idea.utils.UIReplacer;
import com.google.common.collect.ImmutableList;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.ide.ui.LafManager;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.laf.IntelliJLaf;
import com.intellij.ide.ui.laf.darcula.DarculaInstaller;
import com.intellij.ide.ui.laf.darcula.DarculaLaf;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.extensions.PluginId;
import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.status.IdeStatusBarImpl;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import sun.awt.AppContext;

import javax.swing.*;
import javax.swing.plaf.FontUIResource;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import java.awt.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static com.chrisrm.idea.tabs.MTTabsPainterPatcherComponent.*;

public final class MTThemeManager {

    private static final String[] FONT_RESOURCES = new String[] { "Button.font", "ToggleButton.font",
            "RadioButton.font", "CheckBox.font", "ColorChooser.font", "ComboBox.font", "Label.font", "List.font",
            "MenuBar.font", "MenuItem.font", "MenuItem.acceleratorFont", "RadioButtonMenuItem.font",
            "CheckBoxMenuItem.font", "Menu.font", "PopupMenu.font", "OptionPane.font", "Panel.font",
            "ProgressBar.font", "ScrollPane.font", "Viewport.font", "TabbedPane.font", "Table.font",
            "TableHeader.font", "TextField.font", "FormattedTextField.font", "Spinner.font", "PasswordField.font",
            "TextArea.font", "TextPane.font", "EditorPane.font", "TitledBorder.font", "ToolBar.font",
            "ToolTip.font", "Tree.font" };

    private static final String[] CONTRASTED_RESOURCES = new String[] { "Tree.background", "Tree.textBackground",
            //      "Table.background",
            "Viewport.background", "ToolBar.background", "SidePanel.background", "TabbedPane.background",
            "TextField.background", "PasswordField.background", "TextArea.background", "TextPane.background",
            "EditorPane.background", "ToolBar.background", "FormattedTextField.background",
            //      "RadioButton.darcula.selectionDisabledColor",
            //      "RadioButton.background",
            //      "Spinner.background",
            //      "CheckBox.background",
            //      "CheckBox.darcula.backgroundColor1",
            //      "CheckBox.darcula.backgroundColor2",
            //      "CheckBox.darcula.shadowColor",
            //      "CheckBox.darcula.shadowColorDisabled",
            //      "CheckBox.darcula.focusedArmed.backgroundColor1",
            //      "CheckBox.darcula.focusedArmed.backgroundColor2",
            //      "CheckBox.darcula.focused.backgroundColor1",
            //      "CheckBox.darcula.focused.backgroundColor2",
            //      "ComboBox.disabledBackground",
            //      "control",
            "ComboBox.background", "ComboBox.disabledBackground", "ComboBox.arrowFillColor", "window",
            "activeCaption", "desktop", "MenuBar.shadow", "MenuBar.background", "TabbedPane.darkShadow",
            "TabbedPane.shadow", "TabbedPane.borderColor", "StatusBar.background", "SplitPane.highlight",
            "ActionToolbar.background" };

    public static final String[] ACCENT_RESOURCES = new String[] { "link.foreground", "ProgressBar.foreground",
            "RadioButton.darcula.selectionEnabledColor", "RadioButton.darcula.selectionEnabledShadowColor",
            "RadioButton.darcula.selectionDisabledShadowColor", "TextField.selectedSeparatorColor",
            "CheckBox.darcula.borderColor1", "CheckBox.darcula.borderColor2",
            "CheckBox.darcula.backgroundColor1.selected", "CheckBox.darcula.backgroundColor2.selected",
            "CheckBox.darcula.focusedArmed.backgroundColor1.selected",
            "CheckBox.darcula.focusedArmed.backgroundColor2.selected",
            "CheckBox.darcula.focused.backgroundColor1.selected",
            "CheckBox.darcula.focused.backgroundColor2.selected", "Hyperlink.linkColor", "Focus.color",
            "Slider.thumb", "material.tab.borderColor" };
    public static final int DEFAULT_SIDEBAR_HEIGHT = 28;
    public static final int DEFAULT_TAB_HEIGHT = 24;
    public static final boolean DEFAULT_IS_BOLD_TABS = false;
    public static final int DEFAULT_INDENT = 6;
    public static final int DEFAULT_FONT_SIZE = 12;
    public static final String DEFAULT_FONT = "Roboto";
    public static final int DEFAULT_STATUSBAR_PADDING = 8;

    private final List<String> editorColorsSchemes;

    public MTThemeManager() {
        final Collection<String> schemes = new ArrayList<>();
        for (final MTThemes theme : MTThemes.values()) {
            schemes.add(theme.getEditorColorsScheme());
        }
        editorColorsSchemes = ImmutableList.copyOf(schemes);
    }

    public static MTThemeManager getInstance() {
        return ServiceManager.getService(MTThemeManager.class);
    }

    private static String getSettingsPrefix() {
        final PluginId pluginId = PluginManager.getPluginByClassName(MTTheme.class.getName());
        return pluginId == null ? "com.chrisrm.idea.MaterialThemeUI" : pluginId.getIdString();
    }

    //region Action Toggles
    public void toggleMaterialDesign() {
        final MTConfig mtConfig = MTConfig.getInstance();
        mtConfig.setIsMaterialDesign(!mtConfig.getIsMaterialDesign());

        askForRestart();
    }

    public void toggleProjectViewDecorators() {
        final MTConfig mtConfig = MTConfig.getInstance();
        mtConfig.setUseProjectViewDecorators(!mtConfig.isUseProjectViewDecorators());
        updateFileIcons();
    }

    public void toggleMaterialTheme() {
        MTConfig.getInstance().setIsMaterialTheme(!MTConfig.getInstance().isMaterialTheme());
        getInstance().activate();
    }

    /**
     * Set contrast and reactivate theme
     */
    public void toggleContrast() {
        final MTConfig mtConfig = MTConfig.getInstance();
        mtConfig.setIsContrastMode(!mtConfig.getIsContrastMode());

        applyContrast(true);
    }

    public void toggleCompactStatusBar() {
        final boolean compactStatusBar = MTConfig.getInstance().isCompactStatusBar();
        MTConfig.getInstance().setIsCompactStatusBar(!compactStatusBar);

        setStatusBarBorders();
    }

    public void toggleHideFileIcons() {
        final boolean hideFileIcons = MTConfig.getInstance().getHideFileIcons();
        MTConfig.getInstance().setHideFileIcons(!hideFileIcons);

        updateFileIcons();
    }

    public void toggleCompactSidebar() {
        final boolean isCompactSidebar = MTConfig.getInstance().isCompactSidebar();
        MTConfig.getInstance().setCompactSidebar(!isCompactSidebar);

        applyCompactSidebar(true);
    }

    public void toggleMaterialIcons() {
        final boolean useMaterialIcons = MTConfig.getInstance().isUseMaterialIcons();
        MTConfig.getInstance().setUseMaterialIcons(!useMaterialIcons);

        updateFileIcons();
    }

    public void toggleUpperCaseTabs() {
        final MTConfig mtConfig = MTConfig.getInstance();
        mtConfig.setIsUpperCaseTabs(!mtConfig.isUpperCaseTabs());
        mtConfig.fireChanged();
    }

    public void toggleStatusBarIndicator() {
        final MTConfig mtConfig = MTConfig.getInstance();
        mtConfig.setIsStatusBarTheme(!mtConfig.isStatusBarTheme());
        mtConfig.fireChanged();
    }
    //endregion

    //region File Icons support
    public void updateFileIcons() {
        ApplicationManager.getApplication().runWriteAction(() -> {
            final FileTypeManagerEx instanceEx = FileTypeManagerEx.getInstanceEx();
            instanceEx.fireFileTypesChanged();
            ActionToolbarImpl.updateAllToolbarsImmediately();
        });
    }
    //endregion

    //region Status bar support

    /**
     * Change status bar borders
     */
    public void setStatusBarBorders() {
        final boolean compactSidebar = MTConfig.getInstance().isCompactStatusBar();

        ApplicationManager.getApplication().invokeLater(() -> {
            final JComponent component = WindowManager.getInstance().findVisibleFrame().getRootPane();
            if (component != null) {
                final IdeStatusBarImpl ideStatusBar = UIUtil.findComponentOfType(component, IdeStatusBarImpl.class);
                if (ideStatusBar != null) {
                    ideStatusBar.setBorder(compactSidebar ? JBUI.Borders.empty()
                            : JBUI.Borders.empty(DEFAULT_STATUSBAR_PADDING, 0));
                }
            }
        });
    }
    //endregion

    //region Theme activation and deactivation

    /**
     * Activate selected theme or deactivate current
     */
    public void activate() {
        final MTThemes mtTheme = MTConfig.getInstance().getSelectedTheme();
        if (!MTConfig.getInstance().isMaterialTheme()) {
            removeTheme(mtTheme);
            applyAccents(false);
            return;
        }

        activate(mtTheme, false);
    }

    public void activate(final MTThemes mtTheme) {
        activate(mtTheme, false);
    }

    /**
     * Activate theme
     *
     * @param mtTheme
     */
    public void activate(final MTThemes mtTheme, final boolean switchColorScheme) {
        MTThemes newTheme = mtTheme;
        if (newTheme == null) {
            newTheme = MTThemes.OCEANIC;
        }

        MTConfig.getInstance().setSelectedTheme(newTheme);

        newTheme.getTheme().activate();
        switchScheme(newTheme, switchColorScheme);

        // Because the DarculaInstaller overrides this
        final EditorColorsScheme currentScheme = EditorColorsManager.getInstance().getGlobalScheme();

        PropertiesComponent.getInstance().setValue(getSettingsPrefix() + ".theme", newTheme.getId());
        applyContrast(false);
        applyCompactSidebar(false);
        applyCustomTreeIndent();
        applyAccents(false);
        setBoldTabs();

        // We need this to update parts of the UI that do not change
        if (UIUtil.isUnderDarcula()) {
            DarculaInstaller.uninstall();
            DarculaInstaller.install();
        } else {
            DarculaInstaller.uninstall();
        }
        LafManager.getInstance().updateUI();

        // Because the DarculaInstaller overrides this
        EditorColorsManager.getInstance().setGlobalScheme(currentScheme);

        applyFonts();

        // Documentation styles
        patchStyledEditorKit();

        UIReplacer.patchUI();
    }

    private void switchScheme(final MTThemes mtTheme, final boolean switchColorScheme) {
        if (switchColorScheme) {
            final EditorColorsScheme themeScheme = EditorColorsManager.getInstance()
                    .getScheme(mtTheme.getEditorColorsScheme());
            EditorColorsManager.getInstance().setGlobalScheme(themeScheme);
        }
    }

    public void applyAccents(final boolean reloadUI) {
        final String accentColor = MTConfig.getInstance().getAccentColor();
        final Color accentColorColor = ColorUtil.fromHex(accentColor);
        for (final String resource : ACCENT_RESOURCES) {
            UIManager.put(resource, accentColorColor);
        }
        // override for transparency
        UIManager.put("Focus.color", ColorUtil.toAlpha(accentColorColor, 70));

        //    if (reloadUI) {
        //      final MTTheme mtTheme = MTConfig.getInstance().getSelectedTheme().getTheme();
        //      reloadUI(mtTheme);
        //    }
    }

    private void askForRestart() {
        final String title = MaterialThemeBundle.message("mt.restartDialog.title");
        final String message = MaterialThemeBundle.message("mt.restartDialog.content");

        final int answer = Messages.showYesNoDialog(message, title, Messages.getQuestionIcon());
        if (answer == Messages.YES) {
            MTUiUtils.restartIde();
        }
    }

    /**
     * Completely remove theme
     *
     * @param mtTheme
     */
    private void removeTheme(final MTThemes mtTheme) {
        try {
            resetContrast();

            if (UIUtil.isUnderDarcula()) {
                UIManager.setLookAndFeel(new DarculaLaf());
            } else {
                UIManager.setLookAndFeel(new IntelliJLaf());
            }

            JBColor.setDark(mtTheme.isDark());
            IconLoader.setUseDarkIcons(mtTheme.isDark());
            PropertiesComponent.getInstance().unsetValue(getSettingsPrefix() + ".theme");

            // We need this to update parts of the UI that do not change
            if (UIUtil.isUnderDarcula()) {
                DarculaInstaller.uninstall();
                DarculaInstaller.install();
            } else {
                DarculaInstaller.uninstall();
            }
        } catch (final UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }

    /**
     * Apply custom fonts
     *
     * @param uiDefaults
     * @param fontFace
     * @param fontSize
     */
    private void applyCustomFonts(final UIDefaults uiDefaults, final String fontFace, final int fontSize) {
        uiDefaults.put("Tree.ancestorInputMap", null);
        final FontUIResource uiFont = new FontUIResource(fontFace, Font.PLAIN, fontSize);
        final FontUIResource textFont = new FontUIResource("Serif", Font.PLAIN, fontSize);
        final FontUIResource monoFont = new FontUIResource("Fira Code", Font.PLAIN, fontSize);

        for (final String fontResource : FONT_RESOURCES) {
            uiDefaults.put(fontResource, uiFont);
        }

        uiDefaults.put("PasswordField.font", monoFont);
        uiDefaults.put("TextArea.font", monoFont);
        uiDefaults.put("TextPane.font", textFont);
        uiDefaults.put("EditorPane.font", textFont);
    }

    private void applyFonts() {
        final UISettings uiSettings = UISettings.getInstance();
        final UIDefaults lookAndFeelDefaults = UIManager.getLookAndFeelDefaults();

        if (!MTConfig.getInstance().getIsMaterialDesign()) {
            return;
        }

        final boolean useMaterialFont = MTConfig.getInstance().isUseMaterialFont();
        toggleBiggerFont(useMaterialFont);

        if (uiSettings.getOverrideLafFonts()) {
            applyCustomFonts(lookAndFeelDefaults, uiSettings.getFontFace(), uiSettings.getFontSize());
        } else if (useMaterialFont) {
            final Font roboto = MTUiUtils.findFont(DEFAULT_FONT);
            if (roboto != null) {
                applyCustomFonts(lookAndFeelDefaults, DEFAULT_FONT, JBUI.scale(DEFAULT_FONT_SIZE));
            }
        }
    }
    //endregion

    //region Contrast support

    /**
     * Apply contrast
     *
     * @param reloadUI
     */
    private void applyContrast(final boolean reloadUI) {
        final boolean apply = MTConfig.getInstance().getIsContrastMode();
        final MTTheme mtTheme = MTConfig.getInstance().getSelectedTheme().getTheme();
        for (final String resource : CONTRASTED_RESOURCES) {
            final Color contrastedColor = apply ? mtTheme.getContrastColor() : mtTheme.getBackgroundColor();
            UIManager.put(resource, contrastedColor);
        }

        if (reloadUI) {
            reloadUI(mtTheme);
        }
    }

    /**
     * Reset contrast
     */
    private void resetContrast() {
        for (final String resource : CONTRASTED_RESOURCES) {
            UIManager.put(resource, null);
        }
    }
    //endregion

    //region Custom tree indents support

    /**
     * Apply custom tree indent
     */
    private void applyCustomTreeIndent() {
        final MTConfig mtConfig = MTConfig.getInstance();

        if (mtConfig.isCustomTreeIndentEnabled) {
            UIManager.put("Tree.rightChildIndent", mtConfig.customTreeIndent);
        } else {
            UIManager.put("Tree.rightChildIndent", DEFAULT_INDENT);
        }
    }
    //endregion

    //region Compact Sidebar support

    /**
     * Use compact sidebar option
     */
    private void applyCompactSidebar(final boolean reloadUI) {
        final boolean isCustomSidebarHeight = MTConfig.getInstance().isCompactSidebar();
        final int customSidebarHeight = MTConfig.getInstance().getCustomSidebarHeight();
        final int rowHeight = isCustomSidebarHeight ? JBUI.scale(customSidebarHeight)
                : JBUI.scale(DEFAULT_SIDEBAR_HEIGHT);
        UIManager.put("Tree.rowHeight", rowHeight);

        if (reloadUI) {
            final MTTheme mtTheme = MTConfig.getInstance().getSelectedTheme().getTheme();
            reloadUI(mtTheme);
        }
    }

    private void toggleBiggerFont(final boolean isEnabled) {
        Registry.get("bigger.font.in.project.view").setValue(isEnabled);
    }
    //endregion

    //region Accents supports

    /**
     * Override patch style editor kit for custom accent support
     */
    private void patchStyledEditorKit() {
        final UIDefaults defaults = UIManager.getLookAndFeelDefaults();
        final MTConfig mtConfig = MTConfig.getInstance();
        final MTTheme selectedTheme = mtConfig.getSelectedTheme().getTheme();

        // Load css
        final URL url = selectedTheme.getClass()
                .getResource(selectedTheme.getId() + (JBUI.isUsrHiDPI() ? "@2x.css" : ".css"));
        final StyleSheet styleSheet = UIUtil.loadStyleSheet(url);

        // Add custom accent color
        assert styleSheet != null;
        final String accentColor = ObjectUtils.notNull(mtConfig.getAccentColor(), "80CBC4");
        styleSheet.addRule(String.format("a, address, b { color: #%s; }", accentColor));
        defaults.put("StyledEditorKit.JBDefaultStyle", styleSheet);

        try {
            final Field keyField = HTMLEditorKit.class.getDeclaredField("DEFAULT_STYLES_KEY");
            keyField.setAccessible(true);
            AppContext.getAppContext().put(keyField.get(null), styleSheet);
        } catch (final Exception ignored) {
        }
    }
    //endregion

    //region Tabs Height support
    public void setTabsHeight() {
        PropertiesComponent.getInstance().setValue(TABS_HEIGHT, MTConfig.getInstance().getTabsHeight(),
                DEFAULT_TAB_HEIGHT);
    }

    public void setTabsHeight(final int newTabsHeight) {
        MTConfig.getInstance().setTabsHeight(newTabsHeight);
        setTabsHeight();
    }

    public void setBoldTabs() {
        PropertiesComponent.getInstance().setValue(BOLD_TABS, MTConfig.getInstance().isUpperCaseTabs(),
                DEFAULT_IS_BOLD_TABS);
    }

    public void setHackToolWindow(final boolean newValue) {
        PropertiesComponent.getInstance().setValue(WINDOW_HEADER_HACK, newValue, false);
        askForRestart();

    }
    //endregion

    /**
     * Trigger a reloadUI event
     *
     * @param mtTheme
     */
    private void reloadUI(final MTTheme mtTheme) {
        try {
            UIManager.setLookAndFeel(new MTLaf(MTConfig.getInstance().getSelectedTheme().getTheme()));

            applyFonts();

            DarculaInstaller.uninstall();
            if (UIUtil.isUnderDarcula()) {
                DarculaInstaller.install();
            }
        } catch (final UnsupportedLookAndFeelException e) {
            e.printStackTrace();
        }
    }
}