mirror of
https://github.com/TermoraDev/termora.git
synced 2026-01-15 18:02:58 +08:00
feat: theme sync with OS (#82)
This commit is contained in:
433
src/main/java/app/termora/SwingUtils.java
Normal file
433
src/main/java/app/termora/SwingUtils.java
Normal file
@@ -0,0 +1,433 @@
|
||||
package app.termora;/*
|
||||
* @(#)SwingUtils.java 1.02 11/15/08
|
||||
*
|
||||
*/
|
||||
//package darrylbu.util;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A collection of utility methods for Swing.
|
||||
*
|
||||
* @author Darryl Burke
|
||||
*/
|
||||
public final class SwingUtils {
|
||||
|
||||
private SwingUtils() {
|
||||
throw new Error("SwingUtils is just a container for static methods");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for searching below <code>container</code> in the
|
||||
* component hierarchy and return nested components that are instances of
|
||||
* class <code>clazz</code> it finds. Returns an empty list if no such
|
||||
* components exist in the container.
|
||||
* <P>
|
||||
* Invoking this method with a class parameter of JComponent.class
|
||||
* will return all nested components.
|
||||
* <P>
|
||||
* This method invokes getDescendantsOfType(clazz, container, true)
|
||||
*
|
||||
* @param clazz the class of components whose instances are to be found.
|
||||
* @param container the container at which to begin the search
|
||||
* @return the List of components
|
||||
*/
|
||||
public static <T extends JComponent> List<T> getDescendantsOfType(
|
||||
Class<T> clazz, Container container) {
|
||||
return getDescendantsOfType(clazz, container, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for searching below <code>container</code> in the
|
||||
* component hierarchy and return nested components that are instances of
|
||||
* class <code>clazz</code> it finds. Returns an empty list if no such
|
||||
* components exist in the container.
|
||||
* <P>
|
||||
* Invoking this method with a class parameter of JComponent.class
|
||||
* will return all nested components.
|
||||
*
|
||||
* @param clazz the class of components whose instances are to be found.
|
||||
* @param container the container at which to begin the search
|
||||
* @param nested true to list components nested within another listed
|
||||
* component, false otherwise
|
||||
* @return the List of components
|
||||
*/
|
||||
public static <T extends JComponent> List<T> getDescendantsOfType(
|
||||
Class<T> clazz, Container container, boolean nested) {
|
||||
List<T> tList = new ArrayList<T>();
|
||||
for (Component component : container.getComponents()) {
|
||||
if (clazz.isAssignableFrom(component.getClass())) {
|
||||
tList.add(clazz.cast(component));
|
||||
}
|
||||
if (nested || !clazz.isAssignableFrom(component.getClass())) {
|
||||
tList.addAll(SwingUtils.<T>getDescendantsOfType(clazz,
|
||||
(Container) component, nested));
|
||||
}
|
||||
}
|
||||
return tList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that searches below <code>container</code> in the
|
||||
* component hierarchy and returns the first found component that is an
|
||||
* instance of class <code>clazz</code> having the bound property value.
|
||||
* Returns {@code null} if such component cannot be found.
|
||||
* <P>
|
||||
* This method invokes getDescendantOfType(clazz, container, property, value,
|
||||
* true)
|
||||
*
|
||||
* @param clazz the class of component whose instance is to be found.
|
||||
* @param container the container at which to begin the search
|
||||
* @param property the className of the bound property, exactly as expressed in
|
||||
* the accessor e.g. "Text" for getText(), "Value" for getValue().
|
||||
* @param value the value of the bound property
|
||||
* @return the component, or null if no such component exists in the
|
||||
* container
|
||||
* @throws java.lang.IllegalArgumentException if the bound property does
|
||||
* not exist for the class or cannot be accessed
|
||||
*/
|
||||
public static <T extends JComponent> T getDescendantOfType(
|
||||
Class<T> clazz, Container container, String property, Object value)
|
||||
throws IllegalArgumentException {
|
||||
return getDescendantOfType(clazz, container, property, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that searches below <code>container</code> in the
|
||||
* component hierarchy and returns the first found component that is an
|
||||
* instance of class <code>clazz</code> and has the bound property value.
|
||||
* Returns {@code null} if such component cannot be found.
|
||||
*
|
||||
* @param clazz the class of component whose instance to be found.
|
||||
* @param container the container at which to begin the search
|
||||
* @param property the className of the bound property, exactly as expressed in
|
||||
* the accessor e.g. "Text" for getText(), "Value" for getValue().
|
||||
* @param value the value of the bound property
|
||||
* @param nested true to list components nested within another component
|
||||
* which is also an instance of <code>clazz</code>, false otherwise
|
||||
* @return the component, or null if no such component exists in the
|
||||
* container
|
||||
* @throws java.lang.IllegalArgumentException if the bound property does
|
||||
* not exist for the class or cannot be accessed
|
||||
*/
|
||||
public static <T extends JComponent> T getDescendantOfType(Class<T> clazz,
|
||||
Container container, String property, Object value, boolean nested)
|
||||
throws IllegalArgumentException {
|
||||
List<T> list = getDescendantsOfType(clazz, container, nested);
|
||||
return getComponentFromList(clazz, list, property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for searching below <code>container</code> in the
|
||||
* component hierarchy and return nested components of class
|
||||
* <code>clazz</code> it finds. Returns an empty list if no such
|
||||
* components exist in the container.
|
||||
* <P>
|
||||
* This method invokes getDescendantsOfClass(clazz, container, true)
|
||||
*
|
||||
* @param clazz the class of components to be found.
|
||||
* @param container the container at which to begin the search
|
||||
* @return the List of components
|
||||
*/
|
||||
public static <T extends JComponent> List<T> getDescendantsOfClass(
|
||||
Class<T> clazz, Container container) {
|
||||
return getDescendantsOfClass(clazz, container, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for searching below <code>container</code> in the
|
||||
* component hierarchy and return nested components of class
|
||||
* <code>clazz</code> it finds. Returns an empty list if no such
|
||||
* components exist in the container.
|
||||
*
|
||||
* @param clazz the class of components to be found.
|
||||
* @param container the container at which to begin the search
|
||||
* @param nested true to list components nested within another listed
|
||||
* component, false otherwise
|
||||
* @return the List of components
|
||||
*/
|
||||
public static <T extends JComponent> List<T> getDescendantsOfClass(
|
||||
Class<T> clazz, Container container, boolean nested) {
|
||||
List<T> tList = new ArrayList<T>();
|
||||
for (Component component : container.getComponents()) {
|
||||
if (clazz.equals(component.getClass())) {
|
||||
tList.add(clazz.cast(component));
|
||||
}
|
||||
if (nested || !clazz.equals(component.getClass())) {
|
||||
tList.addAll(SwingUtils.<T>getDescendantsOfClass(clazz,
|
||||
(Container) component, nested));
|
||||
}
|
||||
}
|
||||
return tList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that searches below <code>container</code> in the
|
||||
* component hierarchy in a depth first manner and returns the first
|
||||
* found component of class <code>clazz</code> having the bound property
|
||||
* value.
|
||||
* <P>
|
||||
* Returns {@code null} if such component cannot be found.
|
||||
* <P>
|
||||
* This method invokes getDescendantOfClass(clazz, container, property,
|
||||
* value, true)
|
||||
*
|
||||
* @param clazz the class of component to be found.
|
||||
* @param container the container at which to begin the search
|
||||
* @param property the className of the bound property, exactly as expressed in
|
||||
* the accessor e.g. "Text" for getText(), "Value" for getValue().
|
||||
* This parameter is case sensitive.
|
||||
* @param value the value of the bound property
|
||||
* @return the component, or null if no such component exists in the
|
||||
* container's hierarchy.
|
||||
* @throws java.lang.IllegalArgumentException if the bound property does
|
||||
* not exist for the class or cannot be accessed
|
||||
*/
|
||||
public static <T extends JComponent> T getDescendantOfClass(Class<T> clazz,
|
||||
Container container, String property, Object value)
|
||||
throws IllegalArgumentException {
|
||||
return getDescendantOfClass(clazz, container, property, value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that searches below <code>container</code> in the
|
||||
* component hierarchy in a depth first manner and returns the first
|
||||
* found component of class <code>clazz</code> having the bound property
|
||||
* value.
|
||||
* <P>
|
||||
* Returns {@code null} if such component cannot be found.
|
||||
*
|
||||
* @param clazz the class of component to be found.
|
||||
* @param container the container at which to begin the search
|
||||
* @param property the className of the bound property, exactly as expressed
|
||||
* in the accessor e.g. "Text" for getText(), "Value" for getValue().
|
||||
* This parameter is case sensitive.
|
||||
* @param value the value of the bound property
|
||||
* @param nested true to include components nested within another listed
|
||||
* component, false otherwise
|
||||
* @return the component, or null if no such component exists in the
|
||||
* container's hierarchy
|
||||
* @throws java.lang.IllegalArgumentException if the bound property does
|
||||
* not exist for the class or cannot be accessed
|
||||
*/
|
||||
public static <T extends JComponent> T getDescendantOfClass(Class<T> clazz,
|
||||
Container container, String property, Object value, boolean nested)
|
||||
throws IllegalArgumentException {
|
||||
List<T> list = getDescendantsOfClass(clazz, container, nested);
|
||||
return getComponentFromList(clazz, list, property, value);
|
||||
}
|
||||
|
||||
private static <T extends JComponent> T getComponentFromList(Class<T> clazz,
|
||||
List<T> list, String property, Object value)
|
||||
throws IllegalArgumentException {
|
||||
T retVal = null;
|
||||
Method method = null;
|
||||
try {
|
||||
method = clazz.getMethod("get" + property);
|
||||
} catch (NoSuchMethodException ex) {
|
||||
try {
|
||||
method = clazz.getMethod("is" + property);
|
||||
} catch (NoSuchMethodException ex1) {
|
||||
throw new IllegalArgumentException("Property " + property +
|
||||
" not found in class " + clazz.getName());
|
||||
}
|
||||
}
|
||||
try {
|
||||
for (T t : list) {
|
||||
Object testVal = method.invoke(t);
|
||||
if (equals(value, testVal)) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
} catch (InvocationTargetException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Error accessing property " + property +
|
||||
" in class " + clazz.getName());
|
||||
} catch (IllegalAccessException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property " + property +
|
||||
" cannot be accessed in class " + clazz.getName());
|
||||
} catch (SecurityException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Property " + property +
|
||||
" cannot be accessed in class " + clazz.getName());
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for determining whether two objects are either
|
||||
* equal or both null.
|
||||
*
|
||||
* @param obj1 the first reference object to compare.
|
||||
* @param obj2 the second reference object to compare.
|
||||
* @return true if obj1 and obj2 are equal or if both are null,
|
||||
* false otherwise
|
||||
*/
|
||||
public static boolean equals(Object obj1, Object obj2) {
|
||||
return obj1 == null ? obj2 == null : obj1.equals(obj2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for mapping a container in the hierarchy to its
|
||||
* contained components. The keys are the containers, and the values
|
||||
* are lists of contained components.
|
||||
* <P>
|
||||
* Implementation note: The returned value is a HashMap and the values
|
||||
* are of type ArrayList. This is subject to change, so callers should
|
||||
* code against the interfaces Map and List.
|
||||
*
|
||||
* @param container The JComponent to be mapped
|
||||
* @param nested true to drill down to nested containers, false otherwise
|
||||
* @return the Map of the UI
|
||||
*/
|
||||
public static Map<JComponent, List<JComponent>> getComponentMap(
|
||||
JComponent container, boolean nested) {
|
||||
HashMap<JComponent, List<JComponent>> retVal =
|
||||
new HashMap<JComponent, List<JComponent>>();
|
||||
for (JComponent component : getDescendantsOfType(JComponent.class,
|
||||
container, false)) {
|
||||
if (!retVal.containsKey(container)) {
|
||||
retVal.put(container,
|
||||
new ArrayList<JComponent>());
|
||||
}
|
||||
retVal.get(container).add(component);
|
||||
if (nested) {
|
||||
retVal.putAll(getComponentMap(component, nested));
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for retrieving a subset of the UIDefaults pertaining
|
||||
* to a particular class.
|
||||
*
|
||||
* @param clazz the class of interest
|
||||
* @return the UIDefaults of the class
|
||||
*/
|
||||
public static UIDefaults getUIDefaultsOfClass(Class clazz) {
|
||||
String name = clazz.getName();
|
||||
name = name.substring(name.lastIndexOf(".") + 2);
|
||||
return getUIDefaultsOfClass(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for retrieving a subset of the UIDefaults pertaining
|
||||
* to a particular class.
|
||||
*
|
||||
* @param className fully qualified name of the class of interest
|
||||
* @return the UIDefaults of the class named
|
||||
*/
|
||||
public static UIDefaults getUIDefaultsOfClass(String className) {
|
||||
UIDefaults retVal = new UIDefaults();
|
||||
UIDefaults defaults = UIManager.getLookAndFeelDefaults();
|
||||
List<?> listKeys = Collections.list(defaults.keys());
|
||||
for (Object key : listKeys) {
|
||||
if (key instanceof String && ((String) key).startsWith(className)) {
|
||||
String stringKey = (String) key;
|
||||
String property = stringKey;
|
||||
if (stringKey.contains(".")) {
|
||||
property = stringKey.substring(stringKey.indexOf(".") + 1);
|
||||
}
|
||||
retVal.put(property, defaults.get(key));
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for retrieving the UIDefault for a single property
|
||||
* of a particular class.
|
||||
*
|
||||
* @param clazz the class of interest
|
||||
* @param property the property to query
|
||||
* @return the UIDefault property, or null if not found
|
||||
*/
|
||||
public static Object getUIDefaultOfClass(Class clazz, String property) {
|
||||
Object retVal = null;
|
||||
UIDefaults defaults = getUIDefaultsOfClass(clazz);
|
||||
List<Object> listKeys = Collections.list(defaults.keys());
|
||||
for (Object key : listKeys) {
|
||||
if (key.equals(property)) {
|
||||
return defaults.get(key);
|
||||
}
|
||||
if (key.toString().equalsIgnoreCase(property)) {
|
||||
retVal = defaults.get(key);
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude methods that return values that are meaningless to the user
|
||||
*/
|
||||
static Set<String> setExclude = new HashSet<String>();
|
||||
static {
|
||||
setExclude.add("getFocusCycleRootAncestor");
|
||||
setExclude.add("getAccessibleContext");
|
||||
setExclude.add("getColorModel");
|
||||
setExclude.add("getGraphics");
|
||||
setExclude.add("getGraphicsConfiguration");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for obtaining most non-null human readable properties
|
||||
* of a JComponent. Array properties are not included.
|
||||
* <P>
|
||||
* Implementation note: The returned value is a HashMap. This is subject
|
||||
* to change, so callers should code against the interface Map.
|
||||
*
|
||||
* @param component the component whose proerties are to be determined
|
||||
* @return the class and value of the properties
|
||||
*/
|
||||
public static Map<Object, Object> getProperties(JComponent component) {
|
||||
Map<Object, Object> retVal = new HashMap<Object, Object>();
|
||||
Class<?> clazz = component.getClass();
|
||||
Method[] methods = clazz.getMethods();
|
||||
Object value = null;
|
||||
for (Method method : methods) {
|
||||
if (method.getName().matches("^(is|get).*") &&
|
||||
method.getParameterTypes().length == 0) {
|
||||
try {
|
||||
Class returnType = method.getReturnType();
|
||||
if (returnType != void.class &&
|
||||
!returnType.getName().startsWith("[") &&
|
||||
!setExclude.contains(method.getName())) {
|
||||
String key = method.getName();
|
||||
value = method.invoke(component);
|
||||
if (value != null && !(value instanceof Component)) {
|
||||
retVal.put(key, value);
|
||||
}
|
||||
}
|
||||
// ignore exceptions that arise if the property could not be accessed
|
||||
} catch (IllegalAccessException ex) {
|
||||
} catch (IllegalArgumentException ex) {
|
||||
} catch (InvocationTargetException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to obtain the Swing class from which this
|
||||
* component was directly or indirectly derived.
|
||||
*
|
||||
* @param component The component whose Swing superclass is to be
|
||||
* determined
|
||||
* @return The nearest Swing class in the inheritance tree
|
||||
*/
|
||||
public static <T extends JComponent> Class getJClass(T component) {
|
||||
Class<?> clazz = component.getClass();
|
||||
while (!clazz.getName().matches("javax.swing.J[^.]*$")) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
}
|
||||
@@ -129,14 +129,14 @@ class ApplicationRunner {
|
||||
}
|
||||
|
||||
val themeManager = ThemeManager.getInstance()
|
||||
val settings = Database.getDatabase()
|
||||
var theme = settings.appearance.theme
|
||||
// 如果是跟随系统或者不存在样式,那么使用默认的
|
||||
if (settings.appearance.followSystem || !themeManager.themes.containsKey(theme)) {
|
||||
val appearance = Database.getDatabase().appearance
|
||||
var theme = appearance.theme
|
||||
// 如果是跟随系统
|
||||
if (appearance.followSystem) {
|
||||
theme = if (OsThemeDetector.getDetector().isDark) {
|
||||
"Dark"
|
||||
appearance.darkTheme
|
||||
} else {
|
||||
"Light"
|
||||
appearance.lightTheme
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -551,6 +551,8 @@ class Database private constructor(private val env: Environment) : Disposable {
|
||||
* 跟随系统
|
||||
*/
|
||||
var followSystem by BooleanPropertyDelegate(true)
|
||||
var darkTheme by StringPropertyDelegate("Dark")
|
||||
var lightTheme by StringPropertyDelegate("Light")
|
||||
|
||||
/**
|
||||
* 语言
|
||||
|
||||
@@ -6,14 +6,13 @@ import com.formdev.flatlaf.FlatClientProperties
|
||||
import com.formdev.flatlaf.FlatLaf
|
||||
import com.formdev.flatlaf.util.SystemInfo
|
||||
import com.jetbrains.JBR
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Dimension
|
||||
import java.awt.Window
|
||||
import java.awt.*
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.WindowAdapter
|
||||
import java.awt.event.WindowEvent
|
||||
import javax.swing.*
|
||||
|
||||
|
||||
abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
||||
private val rootPanel = JPanel(BorderLayout())
|
||||
private val titleLabel = JLabel()
|
||||
@@ -147,6 +146,31 @@ abstract class DialogWrapper(owner: Window?) : JDialog(owner) {
|
||||
|
||||
rootPane.actionMap.put("close", object : AnAction() {
|
||||
override fun actionPerformed(evt: AnActionEvent) {
|
||||
val c = KeyboardFocusManager.getCurrentKeyboardFocusManager().focusOwner
|
||||
val popups: List<JPopupMenu> = SwingUtils.getDescendantsOfType(
|
||||
JPopupMenu::class.java,
|
||||
c as Container, true
|
||||
)
|
||||
|
||||
var openPopup = false
|
||||
for (p in popups) {
|
||||
p.isVisible = false
|
||||
openPopup = true
|
||||
}
|
||||
|
||||
val window = SwingUtilities.windowForComponent(c)
|
||||
val windows = window.ownedWindows
|
||||
for (w in windows) {
|
||||
if (w.isVisible && w.javaClass.getName().endsWith("HeavyWeightWindow")) {
|
||||
openPopup = true
|
||||
w.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
if (openPopup) {
|
||||
return
|
||||
}
|
||||
|
||||
doCancelAction()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,6 +8,11 @@ import com.formdev.flatlaf.FlatPropertiesLaf
|
||||
import com.formdev.flatlaf.util.SystemInfo
|
||||
import java.util.*
|
||||
|
||||
|
||||
interface LafTag
|
||||
interface LightLafTag : LafTag
|
||||
interface DarkLafTag : LafTag
|
||||
|
||||
class DraculaLaf : FlatPropertiesLaf("Dracula", Properties().apply {
|
||||
putAll(
|
||||
mapOf(
|
||||
@@ -16,7 +21,7 @@ class DraculaLaf : FlatPropertiesLaf("Dracula", Properties().apply {
|
||||
"@windowText" to "#eaeaea",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Basic.BACKGROUND -> 0x282935
|
||||
@@ -54,7 +59,8 @@ class DraculaLaf : FlatPropertiesLaf("Dracula", Properties().apply {
|
||||
|
||||
}
|
||||
|
||||
class LightLaf : FlatLightLaf(), ColorTheme {
|
||||
|
||||
class LightLaf : FlatLightLaf(), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0
|
||||
@@ -81,7 +87,7 @@ class LightLaf : FlatLightLaf(), ColorTheme {
|
||||
}
|
||||
|
||||
|
||||
class DarkLaf : FlatDarkLaf(), ColorTheme {
|
||||
class DarkLaf : FlatDarkLaf(), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0
|
||||
@@ -110,7 +116,7 @@ class DarkLaf : FlatDarkLaf(), ColorTheme {
|
||||
}
|
||||
}
|
||||
|
||||
class iTerm2DarkLaf : FlatDarkLaf(), ColorTheme {
|
||||
class iTerm2DarkLaf : FlatDarkLaf(), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
|
||||
@@ -158,7 +164,7 @@ class TermiusLightLaf : FlatPropertiesLaf("Termius Light", Properties().apply {
|
||||
"@windowText" to "#32364a",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
|
||||
@@ -201,7 +207,7 @@ class TermiusDarkLaf : FlatPropertiesLaf("Termius Dark", Properties().apply {
|
||||
"@windowText" to "#21b568",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
@@ -243,7 +249,7 @@ class NovelLaf : FlatPropertiesLaf("Novel", Properties().apply {
|
||||
"@windowText" to "#3b2322",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -282,7 +288,7 @@ class AtomOneDarkLaf : FlatPropertiesLaf("Atom One Dark", Properties().apply {
|
||||
"@windowText" to "#abb2bf",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -320,7 +326,7 @@ class AtomOneLightLaf : FlatPropertiesLaf("Atom One Light", Properties().apply {
|
||||
"@windowText" to "#383a42",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -358,7 +364,7 @@ class EverforestDarkLaf : FlatPropertiesLaf("Everforest Dark", Properties().appl
|
||||
"@windowText" to "#d3c6aa",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x42494e
|
||||
@@ -395,7 +401,7 @@ class EverforestLightLaf : FlatPropertiesLaf("Everforest Light", Properties().ap
|
||||
"@windowText" to "#5c6a72",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x42494e
|
||||
@@ -432,7 +438,7 @@ class NightOwlLaf : FlatPropertiesLaf("Night Owl", Properties().apply {
|
||||
"@windowText" to "#d6deeb",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x072945
|
||||
@@ -469,7 +475,7 @@ class LightOwlLaf : FlatPropertiesLaf("Light Owl", Properties().apply {
|
||||
"@windowText" to "#403f53",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x403f53
|
||||
@@ -506,7 +512,7 @@ class AuraLaf : FlatPropertiesLaf("Aura", Properties().apply {
|
||||
"@windowText" to "#edecee",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x1c1b22
|
||||
@@ -543,7 +549,7 @@ class Cobalt2Laf : FlatPropertiesLaf("Cobalt2", Properties().apply {
|
||||
"@windowText" to "#ffffff",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -580,7 +586,7 @@ class OctocatDarkLaf : FlatPropertiesLaf("Octocat Dark", Properties().apply {
|
||||
"@windowText" to "#8b949e",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -617,7 +623,7 @@ class OctocatLightLaf : FlatPropertiesLaf("Octocat Light", Properties().apply {
|
||||
"@windowText" to "#3e3e3e",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -654,7 +660,7 @@ class AyuDarkLaf : FlatPropertiesLaf("Ayu Dark", Properties().apply {
|
||||
"@windowText" to "#e6e1cf",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -691,7 +697,7 @@ class AyuLightLaf : FlatPropertiesLaf("Ayu Light", Properties().apply {
|
||||
"@windowText" to "#5c6773",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -728,7 +734,7 @@ class HomebrewLaf : FlatPropertiesLaf("Homebrew", Properties().apply {
|
||||
"@windowText" to "#00ff00",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x2e2e2e
|
||||
@@ -767,7 +773,7 @@ class ProLaf : FlatPropertiesLaf("Pro", Properties().apply {
|
||||
"@windowText" to "#f2f2f2",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x2e2e2e
|
||||
@@ -806,7 +812,7 @@ class NordLightLaf : FlatPropertiesLaf("Nord Light", Properties().apply {
|
||||
"@windowText" to "#414858",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x2c3344
|
||||
@@ -845,7 +851,7 @@ class NordDarkLaf : FlatPropertiesLaf("Nord Dark", Properties().apply {
|
||||
"@windowText" to "#d8dee9",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x3b4252
|
||||
@@ -885,7 +891,7 @@ class GitHubLightLaf : FlatPropertiesLaf("GitHub Light", Properties().apply {
|
||||
"@windowText" to "#3e3e3e",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, LightLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x3e3e3e
|
||||
@@ -924,7 +930,7 @@ class GitHubDarkLaf : FlatPropertiesLaf("GitHub Dark", Properties().apply {
|
||||
"@windowText" to "#8b949e",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x000000
|
||||
@@ -964,7 +970,7 @@ class ChalkLaf : FlatPropertiesLaf("Chalk", Properties().apply {
|
||||
"@windowText" to "#d2d8d9",
|
||||
)
|
||||
)
|
||||
}), ColorTheme {
|
||||
}), ColorTheme, DarkLafTag {
|
||||
override fun getColor(color: TerminalColor): Int {
|
||||
return when (color) {
|
||||
TerminalColor.Normal.BLACK -> 0x7d8b8f
|
||||
|
||||
@@ -17,11 +17,8 @@ import app.termora.terminal.CursorStyle
|
||||
import app.termora.terminal.DataKey
|
||||
import app.termora.terminal.panel.TerminalPanel
|
||||
import cash.z.ecc.android.bip39.Mnemonics
|
||||
import com.formdev.flatlaf.FlatLaf
|
||||
import com.formdev.flatlaf.extras.FlatSVGIcon
|
||||
import com.formdev.flatlaf.extras.components.FlatButton
|
||||
import com.formdev.flatlaf.extras.components.FlatComboBox
|
||||
import com.formdev.flatlaf.extras.components.FlatLabel
|
||||
import com.formdev.flatlaf.extras.components.*
|
||||
import com.formdev.flatlaf.util.SystemInfo
|
||||
import com.jgoodies.forms.builder.FormBuilder
|
||||
import com.jgoodies.forms.layout.FormLayout
|
||||
@@ -49,6 +46,8 @@ import java.nio.charset.StandardCharsets
|
||||
import java.util.*
|
||||
import javax.swing.*
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.event.PopupMenuEvent
|
||||
import javax.swing.event.PopupMenuListener
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
|
||||
@@ -109,6 +108,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
val themeComboBox = FlatComboBox<String>()
|
||||
val languageComboBox = FlatComboBox<String>()
|
||||
val followSystemCheckBox = JCheckBox(I18n.getString("termora.settings.appearance.follow-system"))
|
||||
val preferredThemeBtn = JButton(Icons.settings)
|
||||
private val appearance get() = database.appearance
|
||||
|
||||
init {
|
||||
@@ -119,6 +119,7 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
private fun initView() {
|
||||
|
||||
followSystemCheckBox.isSelected = appearance.followSystem
|
||||
preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected
|
||||
|
||||
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
|
||||
themeManager.themes.keys.forEach { themeComboBox.addItem(it) }
|
||||
@@ -158,19 +159,17 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
followSystemCheckBox.addActionListener {
|
||||
appearance.followSystem = followSystemCheckBox.isSelected
|
||||
themeComboBox.isEnabled = !followSystemCheckBox.isSelected
|
||||
preferredThemeBtn.isEnabled = followSystemCheckBox.isSelected
|
||||
appearance.theme = themeComboBox.selectedItem as String
|
||||
|
||||
if (followSystemCheckBox.isSelected) {
|
||||
SwingUtilities.invokeLater {
|
||||
if (OsThemeDetector.getDetector().isDark) {
|
||||
if (!FlatLaf.isLafDark()) {
|
||||
themeManager.change("Dark")
|
||||
themeComboBox.selectedItem = "Dark"
|
||||
}
|
||||
themeManager.change(appearance.darkTheme)
|
||||
themeComboBox.selectedItem = appearance.darkTheme
|
||||
} else {
|
||||
if (FlatLaf.isLafDark()) {
|
||||
themeManager.change("Light")
|
||||
themeComboBox.selectedItem = "Light"
|
||||
}
|
||||
themeManager.change(appearance.lightTheme)
|
||||
themeComboBox.selectedItem = appearance.lightTheme
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,6 +188,8 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
preferredThemeBtn.addActionListener { showPreferredThemeContextmenu() }
|
||||
}
|
||||
|
||||
override fun getIcon(isSelected: Boolean): Icon {
|
||||
@@ -203,19 +204,74 @@ class SettingsOptionsPane : OptionsPane() {
|
||||
return this
|
||||
}
|
||||
|
||||
private fun showPreferredThemeContextmenu() {
|
||||
val popupMenu = FlatPopupMenu()
|
||||
val dark = JMenu("For Dark OS")
|
||||
val light = JMenu("For Light OS")
|
||||
val darkTheme = appearance.darkTheme
|
||||
val lightTheme = appearance.lightTheme
|
||||
|
||||
for (e in themeManager.themes) {
|
||||
val clazz = Class.forName(e.value)
|
||||
val item = JCheckBoxMenuItem(e.key)
|
||||
item.isSelected = e.key == lightTheme || e.key == darkTheme
|
||||
if (clazz.interfaces.contains(DarkLafTag::class.java)) {
|
||||
dark.add(item).addActionListener {
|
||||
if (e.key != darkTheme) {
|
||||
appearance.darkTheme = e.key
|
||||
if (OsThemeDetector.getDetector().isDark) {
|
||||
themeComboBox.selectedItem = e.key
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (clazz.interfaces.contains(LightLafTag::class.java)) {
|
||||
light.add(item).addActionListener {
|
||||
if (e.key != lightTheme) {
|
||||
appearance.lightTheme = e.key
|
||||
if (!OsThemeDetector.getDetector().isDark) {
|
||||
themeComboBox.selectedItem = e.key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popupMenu.add(dark)
|
||||
popupMenu.addSeparator()
|
||||
popupMenu.add(light)
|
||||
popupMenu.addPopupMenuListener(object : PopupMenuListener {
|
||||
override fun popupMenuWillBecomeVisible(e: PopupMenuEvent) {
|
||||
|
||||
}
|
||||
|
||||
override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent) {
|
||||
}
|
||||
|
||||
override fun popupMenuCanceled(e: PopupMenuEvent) {
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
popupMenu.show(preferredThemeBtn, 0, preferredThemeBtn.height + 2)
|
||||
}
|
||||
|
||||
|
||||
private fun getFormPanel(): JPanel {
|
||||
val layout = FormLayout(
|
||||
"left:pref, $formMargin, default:grow, $formMargin, default, default:grow",
|
||||
"pref, $formMargin, pref, $formMargin"
|
||||
)
|
||||
val box = FlatToolBar()
|
||||
box.add(followSystemCheckBox)
|
||||
box.add(Box.createHorizontalStrut(2))
|
||||
box.add(preferredThemeBtn)
|
||||
|
||||
var rows = 1
|
||||
val step = 2
|
||||
return FormBuilder.create().layout(layout)
|
||||
.add("${I18n.getString("termora.settings.appearance.theme")}:").xy(1, rows)
|
||||
.add(themeComboBox).xy(3, rows)
|
||||
.add(followSystemCheckBox).xy(5, rows).apply { rows += step }
|
||||
.add(box).xy(5, rows).apply { rows += step }
|
||||
.add("${I18n.getString("termora.settings.appearance.language")}:").xy(1, rows)
|
||||
.add(languageComboBox).xy(3, rows)
|
||||
.add(Hyperlink(object : AnAction(I18n.getString("termora.settings.appearance.i-want-to-translate")) {
|
||||
|
||||
@@ -28,6 +28,7 @@ class ThemeManager private constructor() {
|
||||
}
|
||||
}
|
||||
|
||||
val appearance by lazy { Database.getDatabase().appearance }
|
||||
val themes = mapOf(
|
||||
"Light" to LightLaf::class.java.name,
|
||||
"Dark" to DarkLaf::class.java.name,
|
||||
@@ -79,18 +80,16 @@ class ThemeManager private constructor() {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
OsThemeDetector.getDetector().registerListener(object : Consumer<Boolean> {
|
||||
override fun accept(isDark: Boolean) {
|
||||
if (!Database.getDatabase().appearance.followSystem) {
|
||||
if (!appearance.followSystem) {
|
||||
return
|
||||
}
|
||||
|
||||
if (FlatLaf.isLafDark() && isDark) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isDark) {
|
||||
SwingUtilities.invokeLater { change("Dark") }
|
||||
} else {
|
||||
SwingUtilities.invokeLater { change("Light") }
|
||||
SwingUtilities.invokeLater {
|
||||
if (isDark) {
|
||||
change(appearance.darkTheme)
|
||||
} else {
|
||||
change(appearance.lightTheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user