Compare commits
22 Commits
751f878a32
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c5c473e7f | |||
| d80decfca9 | |||
| 24ee72cce3 | |||
| e6419ad938 | |||
| 12e360b7b6 | |||
| c1ceaec939 | |||
| 42e51e6328 | |||
| c9be279695 | |||
| 517fd6baa8 | |||
| d5f6efaa99 | |||
| 2b7eaae81b | |||
| b3d7bbb90e | |||
| e12c3f0380 | |||
| f830cecc6b | |||
| 3c9d14ca39 | |||
| 0f95cdbf9b | |||
| 10c7ddfaff | |||
| e4d0c8b543 | |||
|
2fdf160ed3
|
|||
|
f15534c4d5
|
|||
|
fdbcb56b1b
|
|||
|
b9c4c6f3e7
|
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Ask2AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
7
.idea/libraries/lib.xml
generated
7
.idea/libraries/lib.xml
generated
@@ -2,9 +2,12 @@
|
|||||||
<library name="lib">
|
<library name="lib">
|
||||||
<CLASSES>
|
<CLASSES>
|
||||||
<root url="jar://$PROJECT_DIR$/lib/flatlaf-3.7.jar!/" />
|
<root url="jar://$PROJECT_DIR$/lib/flatlaf-3.7.jar!/" />
|
||||||
<root url="jar://$PROJECT_DIR$/lib/flatlaf-3.7-javadoc.jar!/" />
|
<root url="jar://$PROJECT_DIR$/lib/flatlaf-extras-3.7.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/lib/flatlaf-extras-3.7-sources.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/lib/flatlaf-3.7-sources.jar!/" />
|
||||||
|
</SOURCES>
|
||||||
</library>
|
</library>
|
||||||
</component>
|
</component>
|
||||||
277
src/main/java/com/formdev/flatlaf/demo/DemoPrefs.java
Normal file
277
src/main/java/com/formdev/flatlaf/demo/DemoPrefs.java
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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 com.formdev.flatlaf.demo;
|
||||||
|
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.DatagramPacket;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.MulticastSocket;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import com.formdev.flatlaf.FlatLaf;
|
||||||
|
import com.formdev.flatlaf.FlatLightLaf;
|
||||||
|
import com.formdev.flatlaf.FlatPropertiesLaf;
|
||||||
|
import com.formdev.flatlaf.IntelliJTheme;
|
||||||
|
import com.formdev.flatlaf.util.LoggingFacade;
|
||||||
|
import com.formdev.flatlaf.util.StringUtils;
|
||||||
|
import com.formdev.flatlaf.util.SystemInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
public class DemoPrefs
|
||||||
|
{
|
||||||
|
public static final String KEY_LAF_CLASS_NAME = "lafClassName";
|
||||||
|
public static final String KEY_LAF_THEME_FILE = "lafThemeFile";
|
||||||
|
public static final String KEY_SYSTEM_SCALE_FACTOR = "systemScaleFactor";
|
||||||
|
public static final String KEY_LAF_SYNC = "lafSync";
|
||||||
|
|
||||||
|
private static Preferences state;
|
||||||
|
|
||||||
|
public static Preferences getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void init( String rootPath ) {
|
||||||
|
state = Preferences.userRoot().node( rootPath );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setupLaf( String[] args ) {
|
||||||
|
// com.formdev.flatlaf.demo.intellijthemes.IJThemesDump.install();
|
||||||
|
|
||||||
|
// set look and feel
|
||||||
|
try {
|
||||||
|
if( args.length > 0 )
|
||||||
|
UIManager.setLookAndFeel( args[0] );
|
||||||
|
else
|
||||||
|
restoreLaf();
|
||||||
|
} catch( Throwable ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
FlatLightLaf.setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember active look and feel
|
||||||
|
UIManager.addPropertyChangeListener( e -> {
|
||||||
|
if( "lookAndFeel".equals( e.getPropertyName() ) ) {
|
||||||
|
state.put( KEY_LAF_CLASS_NAME, UIManager.getLookAndFeel().getClass().getName() );
|
||||||
|
notifyLafChange();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void restoreLaf()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
String lafClassName = state.get( KEY_LAF_CLASS_NAME, FlatLightLaf.class.getName() );
|
||||||
|
if( FlatPropertiesLaf.class.getName().equals( lafClassName ) ||
|
||||||
|
IntelliJTheme.ThemeLaf.class.getName().equals( lafClassName ) )
|
||||||
|
{
|
||||||
|
String themeFileName = state.get( KEY_LAF_THEME_FILE, "" );
|
||||||
|
if( !themeFileName.isEmpty() ) {
|
||||||
|
File themeFile = new File( themeFileName );
|
||||||
|
|
||||||
|
if( themeFileName.endsWith( ".properties" ) ) {
|
||||||
|
String themeName = StringUtils.removeTrailing( themeFile.getName(), ".properties" );
|
||||||
|
FlatLaf.setup( new FlatPropertiesLaf( themeName, themeFile ) );
|
||||||
|
} else
|
||||||
|
FlatLaf.setup( IntelliJTheme.createLaf( new FileInputStream( themeFile ) ) );
|
||||||
|
} else
|
||||||
|
FlatLightLaf.setup();
|
||||||
|
} else
|
||||||
|
UIManager.setLookAndFeel( lafClassName );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initSystemScale() {
|
||||||
|
if( System.getProperty( "sun.java2d.uiScale" ) == null ) {
|
||||||
|
String scaleFactor = state.get( KEY_SYSTEM_SCALE_FACTOR, null );
|
||||||
|
if( scaleFactor != null ) {
|
||||||
|
System.setProperty( "sun.java2d.uiScale", scaleFactor );
|
||||||
|
|
||||||
|
System.out.println( "FlatLaf: setting 'sun.java2d.uiScale' to " + scaleFactor );
|
||||||
|
System.out.println( " use 'Alt+Shift+F1...12' to change it to 1x...4x" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* register Alt+Shift+F1, F2, ... F12 keys to change system scale factor
|
||||||
|
*/
|
||||||
|
public static void registerSystemScaleFactors( JFrame frame ) {
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F1", null );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F2", "1" );
|
||||||
|
|
||||||
|
if( SystemInfo.isWindows ) {
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F3", "1.25" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F4", "1.5" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F5", "1.75" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F6", "2" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F7", "2.25" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F8", "2.5" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F9", "2.75" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F10", "3" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F11", "3.5" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F12", "4" );
|
||||||
|
} else {
|
||||||
|
// Java on macOS and Linux supports only integer scale factors
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F3", "2" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F4", "3" );
|
||||||
|
registerSystemScaleFactor( frame, "alt shift F5", "4" );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerSystemScaleFactor( JFrame frame, String keyStrokeStr, String scaleFactor ) {
|
||||||
|
KeyStroke keyStroke = KeyStroke.getKeyStroke( keyStrokeStr );
|
||||||
|
if( keyStroke == null )
|
||||||
|
throw new IllegalArgumentException( "Invalid key stroke '" + keyStrokeStr + "'" );
|
||||||
|
|
||||||
|
((JComponent)frame.getContentPane()).registerKeyboardAction(
|
||||||
|
e -> applySystemScaleFactor( frame, scaleFactor ),
|
||||||
|
keyStroke,
|
||||||
|
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applySystemScaleFactor( JFrame frame, String scaleFactor ) {
|
||||||
|
if( JOptionPane.showConfirmDialog( frame,
|
||||||
|
"Change system scale factor to "
|
||||||
|
+ (scaleFactor != null ? scaleFactor : "default")
|
||||||
|
+ " and exit?",
|
||||||
|
frame.getTitle(), JOptionPane.YES_NO_OPTION ) != JOptionPane.YES_OPTION )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( scaleFactor != null )
|
||||||
|
state.put( KEY_SYSTEM_SCALE_FACTOR, scaleFactor );
|
||||||
|
else
|
||||||
|
state.remove( KEY_SYSTEM_SCALE_FACTOR );
|
||||||
|
|
||||||
|
System.exit( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
//---- inter-process Laf change notification/synchronisation --------------
|
||||||
|
|
||||||
|
// used for FlatLaf development when running multiple testing apps
|
||||||
|
|
||||||
|
private static final String MULTICAST_ADDRESS = "224.63.31.41";
|
||||||
|
private static final int MULTICAST_PORT = 36584;
|
||||||
|
private static final long PROCESS_ID = System.nanoTime();
|
||||||
|
|
||||||
|
private static Thread thread;
|
||||||
|
private static boolean notifyEnabled = true;
|
||||||
|
|
||||||
|
public static void initLafSync( JFrame frame ) {
|
||||||
|
((JComponent)frame.getContentPane()).registerKeyboardAction(
|
||||||
|
e -> enableDisableLafSync(),
|
||||||
|
KeyStroke.getKeyStroke( "alt shift S" ),
|
||||||
|
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
|
||||||
|
|
||||||
|
if( state.getBoolean( KEY_LAF_SYNC, false ) ) {
|
||||||
|
System.out.println( "FlatLaf: theme sync enabled; use Alt+Shift+S to disable it" );
|
||||||
|
listenToLafChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void enableDisableLafSync() {
|
||||||
|
boolean enabled = !state.getBoolean( KEY_LAF_SYNC, false );
|
||||||
|
state.putBoolean( KEY_LAF_SYNC, enabled );
|
||||||
|
|
||||||
|
if( enabled )
|
||||||
|
listenToLafChange();
|
||||||
|
else if( thread != null) {
|
||||||
|
thread.interrupt();
|
||||||
|
thread = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void listenToLafChange() {
|
||||||
|
if( thread != null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
thread = new Thread( "FlatLaf Laf change listener" ) {
|
||||||
|
MulticastSocket socket;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try( MulticastSocket socket = new MulticastSocket( MULTICAST_PORT ) ) {
|
||||||
|
this.socket = socket;
|
||||||
|
socket.joinGroup( InetAddress.getByName( MULTICAST_ADDRESS ) );
|
||||||
|
|
||||||
|
byte[] buffer = new byte[Long.BYTES];
|
||||||
|
for(;;) {
|
||||||
|
DatagramPacket packet = new DatagramPacket( buffer, buffer.length );
|
||||||
|
socket.receive( packet );
|
||||||
|
|
||||||
|
long id = ByteBuffer.wrap( buffer ).getLong();
|
||||||
|
if( id == PROCESS_ID )
|
||||||
|
continue; // was sent from this process
|
||||||
|
|
||||||
|
EventQueue.invokeLater( () -> {
|
||||||
|
notifyEnabled = false;
|
||||||
|
try {
|
||||||
|
restoreLaf();
|
||||||
|
FlatLaf.updateUI();
|
||||||
|
} catch( Throwable ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
} finally {
|
||||||
|
notifyEnabled = true;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
if( ex instanceof SocketException && "Socket closed".equals( ex.getMessage() ) )
|
||||||
|
return; // interrupted
|
||||||
|
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void interrupt() {
|
||||||
|
super.interrupt();
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
thread.setDaemon( true );
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void notifyLafChange() {
|
||||||
|
if( thread == null || !notifyEnabled )
|
||||||
|
return;
|
||||||
|
|
||||||
|
EventQueue.invokeLater( () -> {
|
||||||
|
try( MulticastSocket socket = new MulticastSocket() ) {
|
||||||
|
InetAddress address = InetAddress.getByName( MULTICAST_ADDRESS );
|
||||||
|
byte[] buffer = ByteBuffer.wrap( new byte[Long.BYTES] ).putLong( PROCESS_ID ).array();
|
||||||
|
DatagramPacket packet = new DatagramPacket( buffer, buffer.length, address, MULTICAST_PORT );
|
||||||
|
socket.send( packet );
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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 com.formdev.flatlaf.intellijthemes;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
class IJThemeInfo
|
||||||
|
{
|
||||||
|
final String name;
|
||||||
|
final String resourceName;
|
||||||
|
final boolean discontinued;
|
||||||
|
final boolean dark;
|
||||||
|
final String license;
|
||||||
|
final String licenseFile;
|
||||||
|
final String pluginUrl;
|
||||||
|
final String sourceCodeUrl;
|
||||||
|
final String sourceCodePath;
|
||||||
|
final File themeFile;
|
||||||
|
final String lafClassName;
|
||||||
|
|
||||||
|
IJThemeInfo( String name, boolean dark, String lafClassName ) {
|
||||||
|
this( name, null, false, dark, null, null, null, null, null, null, lafClassName );
|
||||||
|
}
|
||||||
|
|
||||||
|
IJThemeInfo( String name, String resourceName, boolean discontinued, boolean dark,
|
||||||
|
String license, String licenseFile,
|
||||||
|
String pluginUrl, String sourceCodeUrl, String sourceCodePath,
|
||||||
|
File themeFile, String lafClassName )
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.resourceName = resourceName;
|
||||||
|
this.discontinued = discontinued;
|
||||||
|
this.dark = dark;
|
||||||
|
this.license = license;
|
||||||
|
this.licenseFile = licenseFile;
|
||||||
|
this.pluginUrl = pluginUrl;
|
||||||
|
this.sourceCodeUrl = sourceCodeUrl;
|
||||||
|
this.sourceCodePath = sourceCodePath;
|
||||||
|
this.themeFile = themeFile;
|
||||||
|
this.lafClassName = lafClassName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.formdev.flatlaf.intellijthemes;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import com.formdev.flatlaf.util.LoggingFacade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tool creates look and feel classes for all themes listed in themes.json.
|
||||||
|
*
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
public class IJThemesClassGenerator
|
||||||
|
{
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
IJThemesManager themesManager = new IJThemesManager();
|
||||||
|
themesManager.loadBundledThemes();
|
||||||
|
|
||||||
|
String toPath = "../flatlaf-intellij-themes/src/main/java" + IJThemesPanel.THEMES_PACKAGE + "..";
|
||||||
|
|
||||||
|
StringBuilder allInfos = new StringBuilder();
|
||||||
|
StringBuilder markdownTable = new StringBuilder();
|
||||||
|
markdownTable.append( "Name | Class\n" );
|
||||||
|
markdownTable.append( "-----|------\n" );
|
||||||
|
|
||||||
|
for( IJThemeInfo ti : themesManager.bundledThemes ) {
|
||||||
|
if( ti.sourceCodeUrl == null )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
generateClass( ti, toPath, allInfos, markdownTable );
|
||||||
|
}
|
||||||
|
|
||||||
|
Path out = new File( toPath, "FlatAllIJThemes.java" ).toPath();
|
||||||
|
String allThemes = (CLASS_HEADER + ALL_THEMES_TEMPLATE)
|
||||||
|
.replace( "${subPackage}", "" )
|
||||||
|
.replace( "${allInfos}", allInfos );
|
||||||
|
writeFile( out, allThemes );
|
||||||
|
|
||||||
|
System.out.println( markdownTable );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void generateClass( IJThemeInfo ti, String toPath, StringBuilder allInfos, StringBuilder markdownTable ) {
|
||||||
|
String resourceName = ti.resourceName;
|
||||||
|
String resourcePath = null;
|
||||||
|
int resSep = resourceName.indexOf( '/' );
|
||||||
|
if( resSep >= 0 ) {
|
||||||
|
resourcePath = resourceName.substring( 0, resSep );
|
||||||
|
resourceName = resourceName.substring( resSep + 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = ti.name;
|
||||||
|
int nameSep = name.indexOf( '/' );
|
||||||
|
if( nameSep >= 0 )
|
||||||
|
name = name.substring( nameSep + 1 ).trim();
|
||||||
|
|
||||||
|
String themeName = name;
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
if( "material-theme-ui-lite".equals( resourcePath ) ) {
|
||||||
|
themeName += " (Material)";
|
||||||
|
buf.append( "MT" );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( String n : name.split( "[ \\-]" ) ) {
|
||||||
|
if( n.length() == 0 || n.equals( "-" ) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if( Character.isUpperCase( n.charAt( 0 ) ) )
|
||||||
|
buf.append( n );
|
||||||
|
else
|
||||||
|
buf.append( Character.toUpperCase( n.charAt( 0 ) ) ).append( n.substring( 1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
String subPackage = (resourcePath != null) ? '.' + resourcePath.replace( "-", "" ) : "";
|
||||||
|
String themeClass = "Flat" + buf + "IJTheme";
|
||||||
|
String themeFile = resourceName;
|
||||||
|
|
||||||
|
String classBody = (CLASS_HEADER + CLASS_TEMPLATE)
|
||||||
|
.replace( "${subPackage}", subPackage )
|
||||||
|
.replace( "${themeClass}", themeClass )
|
||||||
|
.replace( "${themeFile}", themeFile )
|
||||||
|
.replace( "${themeName}", themeName );
|
||||||
|
|
||||||
|
File toDir = new File( toPath );
|
||||||
|
if( resourcePath != null )
|
||||||
|
toDir = new File( toDir, resourcePath.replace( "-", "" ) );
|
||||||
|
|
||||||
|
Path out = new File( toDir, themeClass + ".java" ).toPath();
|
||||||
|
writeFile( out, classBody );
|
||||||
|
|
||||||
|
if( allInfos.length() > 0 )
|
||||||
|
allInfos.append( '\n' );
|
||||||
|
allInfos.append( THEME_TEMPLATE
|
||||||
|
.replace( "${subPackage}", subPackage )
|
||||||
|
.replace( "${themeClass}", themeClass )
|
||||||
|
.replace( "${themeName}", themeName )
|
||||||
|
.replace( "${dark}", Boolean.toString( ti.dark ) ) );
|
||||||
|
|
||||||
|
markdownTable.append( String.format( "[%s](%s) | `com.formdev.flatlaf.intellijthemes%s.%s`\n",
|
||||||
|
themeName, ti.sourceCodeUrl, subPackage, themeClass ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeFile( Path out, String content ) {
|
||||||
|
try {
|
||||||
|
Files.write( out, content.getBytes( StandardCharsets.ISO_8859_1 ),
|
||||||
|
StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING );
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String CLASS_HEADER =
|
||||||
|
"/*\n" +
|
||||||
|
" * Copyright 2020 FormDev Software GmbH\n" +
|
||||||
|
" *\n" +
|
||||||
|
" * Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
|
||||||
|
" * you may not use this file except in compliance with the License.\n" +
|
||||||
|
" * You may obtain a copy of the License at\n" +
|
||||||
|
" *\n" +
|
||||||
|
" * https://www.apache.org/licenses/LICENSE-2.0\n" +
|
||||||
|
" *\n" +
|
||||||
|
" * Unless required by applicable law or agreed to in writing, software\n" +
|
||||||
|
" * distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
|
||||||
|
" * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
|
||||||
|
" * See the License for the specific language governing permissions and\n" +
|
||||||
|
" * limitations under the License.\n" +
|
||||||
|
" */\n" +
|
||||||
|
"\n" +
|
||||||
|
"package com.formdev.flatlaf.intellijthemes${subPackage};\n" +
|
||||||
|
"\n" +
|
||||||
|
"//\n" +
|
||||||
|
"// DO NOT MODIFY\n" +
|
||||||
|
"// Generated with com.formdev.flatlaf.demo.intellijthemes.IJThemesClassGenerator\n" +
|
||||||
|
"//\n" +
|
||||||
|
"\n";
|
||||||
|
|
||||||
|
private static final String CLASS_TEMPLATE =
|
||||||
|
"import com.formdev.flatlaf.IntelliJTheme;\n" +
|
||||||
|
"\n" +
|
||||||
|
"/**\n" +
|
||||||
|
" * @author Karl Tauber\n" +
|
||||||
|
" */\n" +
|
||||||
|
"public class ${themeClass}\n" +
|
||||||
|
" extends IntelliJTheme.ThemeLaf\n" +
|
||||||
|
"{\n" +
|
||||||
|
" public static final String NAME = \"${themeName}\";\n" +
|
||||||
|
"\n" +
|
||||||
|
" public static boolean setup() {\n" +
|
||||||
|
" try {\n" +
|
||||||
|
" return setup( new ${themeClass}() );\n" +
|
||||||
|
" } catch( RuntimeException ex ) {\n" +
|
||||||
|
" return false;\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
"\n" +
|
||||||
|
" public static void installLafInfo() {\n" +
|
||||||
|
" installLafInfo( NAME, ${themeClass}.class );\n" +
|
||||||
|
" }\n" +
|
||||||
|
"\n" +
|
||||||
|
" public ${themeClass}() {\n" +
|
||||||
|
" super( Utils.loadTheme( \"${themeFile}\" ) );\n" +
|
||||||
|
" }\n" +
|
||||||
|
"\n" +
|
||||||
|
" @Override\n" +
|
||||||
|
" public String getName() {\n" +
|
||||||
|
" return NAME;\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}\n";
|
||||||
|
|
||||||
|
private static final String ALL_THEMES_TEMPLATE =
|
||||||
|
"import javax.swing.UIManager.LookAndFeelInfo;\n" +
|
||||||
|
"\n" +
|
||||||
|
"/**\n" +
|
||||||
|
" * @author Karl Tauber\n" +
|
||||||
|
" */\n" +
|
||||||
|
"public class FlatAllIJThemes\n" +
|
||||||
|
"{\n" +
|
||||||
|
" public static final FlatIJLookAndFeelInfo[] INFOS = {\n" +
|
||||||
|
"${allInfos}\n" +
|
||||||
|
" };\n" +
|
||||||
|
"\n" +
|
||||||
|
" //---- class FlatIJLookAndFeelInfo ----------------------------------------\n" +
|
||||||
|
"\n" +
|
||||||
|
" public static class FlatIJLookAndFeelInfo\n" +
|
||||||
|
" extends LookAndFeelInfo\n" +
|
||||||
|
" {\n" +
|
||||||
|
" private final boolean dark;\n" +
|
||||||
|
"\n" +
|
||||||
|
" public FlatIJLookAndFeelInfo( String name, String className, boolean dark ) {\n" +
|
||||||
|
" super( name, className );\n" +
|
||||||
|
" this.dark = dark;\n" +
|
||||||
|
" }\n" +
|
||||||
|
"\n" +
|
||||||
|
" public boolean isDark() {\n" +
|
||||||
|
" return dark;\n" +
|
||||||
|
" }\n" +
|
||||||
|
" }\n" +
|
||||||
|
"}\n";
|
||||||
|
|
||||||
|
private static final String THEME_TEMPLATE =
|
||||||
|
" new FlatIJLookAndFeelInfo( \"${themeName}\", \"com.formdev.flatlaf.intellijthemes${subPackage}.${themeClass}\", ${dark} ),";
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.formdev.flatlaf.intellijthemes;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import javax.swing.LookAndFeel;
|
||||||
|
import javax.swing.UIDefaults;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import com.formdev.flatlaf.FlatLaf;
|
||||||
|
import com.formdev.flatlaf.IntelliJTheme;
|
||||||
|
import com.formdev.flatlaf.util.LoggingFacade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
public class IJThemesDump
|
||||||
|
{
|
||||||
|
// same as UIDefaultsLoader.KEY_PROPERTIES
|
||||||
|
private static final String KEY_PROPERTIES = "FlatLaf.internal.properties";
|
||||||
|
|
||||||
|
public static void enablePropertiesRecording() {
|
||||||
|
System.setProperty( KEY_PROPERTIES, "true" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void install() {
|
||||||
|
enablePropertiesRecording();
|
||||||
|
|
||||||
|
UIManager.addPropertyChangeListener( e -> {
|
||||||
|
if( "lookAndFeel".equals( e.getPropertyName() ) ) {
|
||||||
|
LookAndFeel lookAndFeel = UIManager.getLookAndFeel();
|
||||||
|
if( lookAndFeel instanceof IntelliJTheme.ThemeLaf ) {
|
||||||
|
IntelliJTheme theme = (lookAndFeel.getClass() == IntelliJTheme.ThemeLaf.class)
|
||||||
|
? ((IntelliJTheme.ThemeLaf)lookAndFeel).getTheme()
|
||||||
|
: null;
|
||||||
|
String name = (theme != null) ? theme.name : lookAndFeel.getClass().getSimpleName();
|
||||||
|
File dir = new File( "dumps/properties" );
|
||||||
|
dumpProperties( dir, name, UIManager.getLookAndFeelDefaults() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void dumpProperties( File dir, String name, UIDefaults defaults ) {
|
||||||
|
String content = dumpPropertiesToString( defaults );
|
||||||
|
if( content == null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// write to file
|
||||||
|
File file = new File( dir, name + ".properties" );
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
try( Writer fileWriter = new OutputStreamWriter(
|
||||||
|
new FileOutputStream( file ), StandardCharsets.UTF_8 ) )
|
||||||
|
{
|
||||||
|
fileWriter.write( content );
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String dumpPropertiesToString( UIDefaults defaults ) {
|
||||||
|
Properties properties = (Properties) defaults.get( KEY_PROPERTIES );
|
||||||
|
if( properties == null )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// dump to string
|
||||||
|
StringWriter stringWriter = new StringWriter( 100000 );
|
||||||
|
PrintWriter out = new PrintWriter( stringWriter );
|
||||||
|
out.printf( "@baseTheme = %s%n", FlatLaf.isLafDark() ? "dark" : "light" );
|
||||||
|
AtomicReference<String> lastPrefix = new AtomicReference<>();
|
||||||
|
properties.entrySet().stream()
|
||||||
|
.sorted( (e1, e2) -> ((String)e1.getKey()).compareTo( (String) e2.getKey() ) )
|
||||||
|
.forEach( e -> {
|
||||||
|
String key = (String) e.getKey();
|
||||||
|
String value = (String) e.getValue();
|
||||||
|
String prefix = keyPrefix( key );
|
||||||
|
if( !prefix.equals( lastPrefix.get() ) ) {
|
||||||
|
lastPrefix.set( prefix );
|
||||||
|
out.printf( "%n%n#---- %s ----%n%n", prefix );
|
||||||
|
}
|
||||||
|
|
||||||
|
out.printf( "%-50s = %s%n", key, value.replace( ";", "; \\\n\t" ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
return stringWriter.toString().replace( "\r", "" );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String keyPrefix( String key ) {
|
||||||
|
int dotIndex = key.indexOf( '.' );
|
||||||
|
return (dotIndex > 0)
|
||||||
|
? key.substring( 0, dotIndex )
|
||||||
|
: key.endsWith( "UI" )
|
||||||
|
? key.substring( 0, key.length() - 2 )
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* https://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 com.formdev.flatlaf.intellijthemes;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.formdev.flatlaf.json.Json;
|
||||||
|
import com.formdev.flatlaf.json.ParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tool checks whether there are duplicate name fields in all theme .json files.
|
||||||
|
*
|
||||||
|
* This is important for following file, where the name is used for theme specific UI defaults:
|
||||||
|
* flatlaf-core/src/main/resources/com/formdev/flatlaf/IntelliJTheme$ThemeLaf.properties
|
||||||
|
*
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
public class IJThemesDuplicateNameChecker
|
||||||
|
{
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
IJThemesManager themesManager = new IJThemesManager();
|
||||||
|
themesManager.loadBundledThemes();
|
||||||
|
|
||||||
|
HashSet<String> names = new HashSet<>();
|
||||||
|
for( IJThemeInfo ti : themesManager.bundledThemes ) {
|
||||||
|
if( ti.sourceCodeUrl == null || ti.sourceCodePath == null )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String jsonPath = "../flatlaf-intellij-themes/src/main/resources" + IJThemesPanel.THEMES_PACKAGE + ti.resourceName;
|
||||||
|
String name;
|
||||||
|
try {
|
||||||
|
name = readNameFromJson( jsonPath );
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
System.err.println( "Failed to read '" + jsonPath + "'" );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( names.contains( name ) )
|
||||||
|
System.out.println( "Duplicate name '" + name + "'" );
|
||||||
|
names.add( name );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readNameFromJson( String jsonPath ) throws IOException {
|
||||||
|
try( Reader reader = new InputStreamReader( new FileInputStream( jsonPath ), StandardCharsets.UTF_8 ) ) {
|
||||||
|
@SuppressWarnings( "unchecked" )
|
||||||
|
Map<String, Object> json = (Map<String, Object>) Json.parse( reader );
|
||||||
|
return (String) json.get( "name" );
|
||||||
|
} catch( ParseException ex ) {
|
||||||
|
throw new IOException( ex.getMessage(), ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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 com.formdev.flatlaf.intellijthemes;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import com.formdev.flatlaf.json.Json;
|
||||||
|
import com.formdev.flatlaf.util.LoggingFacade;
|
||||||
|
import com.formdev.flatlaf.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
class IJThemesManager
|
||||||
|
{
|
||||||
|
final List<IJThemeInfo> bundledThemes = new ArrayList<>();
|
||||||
|
final List<IJThemeInfo> moreThemes = new ArrayList<>();
|
||||||
|
private final Map<File,Long> lastModifiedMap = new HashMap<>();
|
||||||
|
|
||||||
|
@SuppressWarnings( "unchecked" )
|
||||||
|
void loadBundledThemes() {
|
||||||
|
bundledThemes.clear();
|
||||||
|
|
||||||
|
// load themes.json
|
||||||
|
Map<String, Object> json;
|
||||||
|
try( Reader reader = new InputStreamReader( getClass().getResourceAsStream( "themes.json" ), StandardCharsets.UTF_8 ) ) {
|
||||||
|
json = (Map<String, Object>) Json.parse( reader );
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add info about bundled themes
|
||||||
|
for( Map.Entry<String, Object> e : json.entrySet() ) {
|
||||||
|
String resourceName = e.getKey();
|
||||||
|
Map<String, String> value = (Map<String, String>) e.getValue();
|
||||||
|
String name = value.get( "name" );
|
||||||
|
boolean discontinued = Boolean.parseBoolean( value.get( "discontinued" ) );
|
||||||
|
boolean dark = Boolean.parseBoolean( value.get( "dark" ) );
|
||||||
|
String lafClassName = value.get( "lafClassName" );
|
||||||
|
String license = value.get( "license" );
|
||||||
|
String licenseFile = value.get( "licenseFile" );
|
||||||
|
String pluginUrl = value.get( "pluginUrl" );
|
||||||
|
String sourceCodeUrl = value.get( "sourceCodeUrl" );
|
||||||
|
String sourceCodePath = value.get( "sourceCodePath" );
|
||||||
|
|
||||||
|
bundledThemes.add( new IJThemeInfo( name, resourceName, discontinued, dark,
|
||||||
|
license, licenseFile, pluginUrl, sourceCodeUrl, sourceCodePath, null, lafClassName ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadThemesFromDirectory() {
|
||||||
|
// get current working directory
|
||||||
|
File directory = new File( "" ).getAbsoluteFile();
|
||||||
|
|
||||||
|
File[] themeFiles = directory.listFiles( (dir, name) -> {
|
||||||
|
return name.endsWith( ".theme.json" ) || name.endsWith( ".properties" );
|
||||||
|
} );
|
||||||
|
if( themeFiles == null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
lastModifiedMap.clear();
|
||||||
|
lastModifiedMap.put( directory, directory.lastModified() );
|
||||||
|
|
||||||
|
moreThemes.clear();
|
||||||
|
for( File f : themeFiles ) {
|
||||||
|
String fname = f.getName();
|
||||||
|
String name = fname.endsWith( ".properties" )
|
||||||
|
? StringUtils.removeTrailing( fname, ".properties" )
|
||||||
|
: StringUtils.removeTrailing( fname, ".theme.json" );
|
||||||
|
moreThemes.add( new IJThemeInfo( name, null, false, false, null, null, null, null, null, f, null ) );
|
||||||
|
lastModifiedMap.put( f, f.lastModified() );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasThemesFromDirectoryChanged() {
|
||||||
|
for( Map.Entry<File, Long> e : lastModifiedMap.entrySet() ) {
|
||||||
|
if( e.getKey().lastModified() != e.getValue().longValue() )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,555 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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 com.formdev.flatlaf.intellijthemes;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Desktop;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.awt.event.WindowListener;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.border.CompoundBorder;
|
||||||
|
import javax.swing.event.*;
|
||||||
|
import com.formdev.flatlaf.FlatClientProperties;
|
||||||
|
import com.formdev.flatlaf.FlatDarculaLaf;
|
||||||
|
import com.formdev.flatlaf.FlatDarkLaf;
|
||||||
|
import com.formdev.flatlaf.FlatIntelliJLaf;
|
||||||
|
import com.formdev.flatlaf.FlatLaf;
|
||||||
|
import com.formdev.flatlaf.FlatLightLaf;
|
||||||
|
import com.formdev.flatlaf.FlatPropertiesLaf;
|
||||||
|
import com.formdev.flatlaf.IntelliJTheme;
|
||||||
|
import com.formdev.flatlaf.demo.DemoPrefs;
|
||||||
|
import com.formdev.flatlaf.extras.FlatAnimatedLafChange;
|
||||||
|
import com.formdev.flatlaf.extras.FlatSVGIcon;
|
||||||
|
import com.formdev.flatlaf.themes.*;
|
||||||
|
import com.formdev.flatlaf.ui.FlatListUI;
|
||||||
|
import com.formdev.flatlaf.util.LoggingFacade;
|
||||||
|
import net.miginfocom.swing.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
public class IJThemesPanel
|
||||||
|
extends JPanel
|
||||||
|
{
|
||||||
|
public static final String THEMES_PACKAGE = "/com/formdev/flatlaf/intellijthemes/themes/";
|
||||||
|
|
||||||
|
private final IJThemesManager themesManager = new IJThemesManager();
|
||||||
|
private final List<IJThemeInfo> themes = new ArrayList<>();
|
||||||
|
private final HashMap<Integer, String> categories = new HashMap<>();
|
||||||
|
private final PropertyChangeListener lafListener = this::lafChanged;
|
||||||
|
private final WindowListener windowListener = new WindowAdapter() {
|
||||||
|
@Override
|
||||||
|
public void windowActivated( WindowEvent e ) {
|
||||||
|
IJThemesPanel.this.windowActivated();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private Window window;
|
||||||
|
|
||||||
|
private boolean isAdjustingThemesList;
|
||||||
|
private long lastLafChangeTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
public IJThemesPanel() {
|
||||||
|
initComponents();
|
||||||
|
|
||||||
|
pluginButton.setEnabled( false );
|
||||||
|
sourceCodeButton.setEnabled( false );
|
||||||
|
|
||||||
|
// create renderer
|
||||||
|
themesList.setCellRenderer( new DefaultListCellRenderer() {
|
||||||
|
private int index;
|
||||||
|
private boolean isSelected;
|
||||||
|
private int titleHeight;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getListCellRendererComponent( JList<?> list, Object value,
|
||||||
|
int index, boolean isSelected, boolean cellHasFocus )
|
||||||
|
{
|
||||||
|
this.index = index;
|
||||||
|
this.isSelected = isSelected;
|
||||||
|
this.titleHeight = 0;
|
||||||
|
|
||||||
|
String title = categories.get( index );
|
||||||
|
String name = ((IJThemeInfo)value).name;
|
||||||
|
int sep = name.indexOf( '/' );
|
||||||
|
if( sep >= 0 )
|
||||||
|
name = name.substring( sep + 1 ).trim();
|
||||||
|
|
||||||
|
JComponent c = (JComponent) super.getListCellRendererComponent( list, name, index, isSelected, cellHasFocus );
|
||||||
|
c.setToolTipText( buildToolTip( (IJThemeInfo) value ) );
|
||||||
|
if( title != null ) {
|
||||||
|
Border titledBorder = new ListCellTitledBorder( themesList, title );
|
||||||
|
c.setBorder( new CompoundBorder( titledBorder, c.getBorder() ) );
|
||||||
|
titleHeight = titledBorder.getBorderInsets( c ).top;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpaque() {
|
||||||
|
return !isSelectedTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent( Graphics g ) {
|
||||||
|
if( isSelectedTitle() ) {
|
||||||
|
g.setColor( getBackground() );
|
||||||
|
FlatListUI.paintCellSelection( themesList, g, index, 0, titleHeight, getWidth(), getHeight() - titleHeight );
|
||||||
|
}
|
||||||
|
|
||||||
|
super.paintComponent( g );
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSelectedTitle() {
|
||||||
|
return titleHeight > 0 && isSelected && UIManager.getLookAndFeel() instanceof FlatLaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildToolTip( IJThemeInfo ti ) {
|
||||||
|
if( ti.themeFile != null )
|
||||||
|
return ti.themeFile.getPath();
|
||||||
|
if( ti.license == null )
|
||||||
|
return ti.name;
|
||||||
|
|
||||||
|
return "Name: " + ti.name
|
||||||
|
+ "\nLicense: " + ti.license
|
||||||
|
+ "\nSource Code: " + ti.sourceCodeUrl;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
updateThemesList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateThemesList() {
|
||||||
|
int filterLightDark = filterComboBox.getSelectedIndex();
|
||||||
|
boolean showLight = (filterLightDark != 2);
|
||||||
|
boolean showDark = (filterLightDark != 1);
|
||||||
|
|
||||||
|
// load theme infos
|
||||||
|
themesManager.loadBundledThemes();
|
||||||
|
themesManager.loadThemesFromDirectory();
|
||||||
|
|
||||||
|
// sort themes by name
|
||||||
|
Comparator<? super IJThemeInfo> comparator = (t1, t2) -> t1.name.compareToIgnoreCase( t2.name );
|
||||||
|
themesManager.bundledThemes.sort( comparator );
|
||||||
|
themesManager.moreThemes.sort( comparator );
|
||||||
|
|
||||||
|
// remember selection (must be invoked before clearing themes field)
|
||||||
|
IJThemeInfo oldSel = themesList.getSelectedValue();
|
||||||
|
|
||||||
|
themes.clear();
|
||||||
|
categories.clear();
|
||||||
|
|
||||||
|
// add core themes at beginning
|
||||||
|
categories.put( themes.size(), "Core Themes" );
|
||||||
|
if( showLight )
|
||||||
|
themes.add( new IJThemeInfo( "FlatLaf Light", false, FlatLightLaf.class.getName() ) );
|
||||||
|
if( showDark )
|
||||||
|
themes.add( new IJThemeInfo( "FlatLaf Dark", true, FlatDarkLaf.class.getName() ) );
|
||||||
|
if( showLight )
|
||||||
|
themes.add( new IJThemeInfo( "FlatLaf IntelliJ", false, FlatIntelliJLaf.class.getName() ) );
|
||||||
|
if( showDark )
|
||||||
|
themes.add( new IJThemeInfo( "FlatLaf Darcula", true, FlatDarculaLaf.class.getName() ) );
|
||||||
|
|
||||||
|
if( showLight )
|
||||||
|
themes.add( new IJThemeInfo( "FlatLaf macOS Light", false, FlatMacLightLaf.class.getName() ) );
|
||||||
|
if( showDark )
|
||||||
|
themes.add( new IJThemeInfo( "FlatLaf macOS Dark", true, FlatMacDarkLaf.class.getName() ) );
|
||||||
|
|
||||||
|
// add themes from directory
|
||||||
|
categories.put( themes.size(), "Current Directory" );
|
||||||
|
themes.addAll( themesManager.moreThemes );
|
||||||
|
|
||||||
|
// add uncategorized bundled themes
|
||||||
|
categories.put( themes.size(), "IntelliJ Themes" );
|
||||||
|
for( IJThemeInfo ti : themesManager.bundledThemes ) {
|
||||||
|
boolean show = (showLight && !ti.dark) || (showDark && ti.dark);
|
||||||
|
if( show && !ti.name.contains( "/" ) )
|
||||||
|
themes.add( ti );
|
||||||
|
}
|
||||||
|
|
||||||
|
// add categorized bundled themes
|
||||||
|
String lastCategory = null;
|
||||||
|
for( IJThemeInfo ti : themesManager.bundledThemes ) {
|
||||||
|
boolean show = (showLight && !ti.dark) || (showDark && ti.dark);
|
||||||
|
int sep = ti.name.indexOf( '/' );
|
||||||
|
if( !show || sep < 0 )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
String category = ti.name.substring( 0, sep ).trim();
|
||||||
|
if( !Objects.equals( lastCategory, category ) ) {
|
||||||
|
lastCategory = category;
|
||||||
|
categories.put( themes.size(), category );
|
||||||
|
}
|
||||||
|
|
||||||
|
themes.add( ti );
|
||||||
|
}
|
||||||
|
|
||||||
|
// fill themes list
|
||||||
|
themesList.setModel( new AbstractListModel<IJThemeInfo>() {
|
||||||
|
@Override
|
||||||
|
public int getSize() {
|
||||||
|
return themes.size();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public IJThemeInfo getElementAt( int index ) {
|
||||||
|
return themes.get( index );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// restore selection
|
||||||
|
if( oldSel != null ) {
|
||||||
|
for( int i = 0; i < themes.size(); i++ ) {
|
||||||
|
IJThemeInfo theme = themes.get( i );
|
||||||
|
if( oldSel.name.equals( theme.name ) &&
|
||||||
|
Objects.equals( oldSel.themeFile, theme.themeFile ) &&
|
||||||
|
Objects.equals( oldSel.lafClassName, theme.lafClassName ) )
|
||||||
|
{
|
||||||
|
themesList.setSelectedIndex( i );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// select first theme if none selected
|
||||||
|
if( themesList.getSelectedIndex() < 0 )
|
||||||
|
themesList.setSelectedIndex( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
// scroll selection into visible area
|
||||||
|
int sel = themesList.getSelectedIndex();
|
||||||
|
if( sel >= 0 ) {
|
||||||
|
Rectangle bounds = themesList.getCellBounds( sel, sel );
|
||||||
|
if( bounds != null )
|
||||||
|
themesList.scrollRectToVisible( bounds );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectPreviousTheme() {
|
||||||
|
int sel = themesList.getSelectedIndex();
|
||||||
|
if( sel > 0 )
|
||||||
|
themesList.setSelectedIndex( sel - 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void selectNextTheme() {
|
||||||
|
int sel = themesList.getSelectedIndex();
|
||||||
|
themesList.setSelectedIndex( sel + 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void themesListValueChanged( ListSelectionEvent e ) {
|
||||||
|
IJThemeInfo themeInfo = themesList.getSelectedValue();
|
||||||
|
pluginButton.setEnabled( themeInfo != null && themeInfo.pluginUrl != null );
|
||||||
|
sourceCodeButton.setEnabled( themeInfo != null && themeInfo.sourceCodePath != null );
|
||||||
|
|
||||||
|
if( e.getValueIsAdjusting() || isAdjustingThemesList )
|
||||||
|
return;
|
||||||
|
|
||||||
|
EventQueue.invokeLater( () -> {
|
||||||
|
setTheme( themeInfo, false );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTheme( IJThemeInfo themeInfo, boolean reload ) {
|
||||||
|
if( themeInfo == null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
// change look and feel
|
||||||
|
if( themeInfo.lafClassName != null ) {
|
||||||
|
if( !reload && themeInfo.lafClassName.equals( UIManager.getLookAndFeel().getClass().getName() ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( !reload )
|
||||||
|
FlatAnimatedLafChange.showSnapshot();
|
||||||
|
|
||||||
|
try {
|
||||||
|
UIManager.setLookAndFeel( themeInfo.lafClassName );
|
||||||
|
} catch( Exception ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
showInformationDialog( "Failed to create '" + themeInfo.lafClassName + "'.", ex );
|
||||||
|
}
|
||||||
|
} else if( themeInfo.themeFile != null ) {
|
||||||
|
if( !reload )
|
||||||
|
FlatAnimatedLafChange.showSnapshot();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if( themeInfo.themeFile.getName().endsWith( ".properties" ) )
|
||||||
|
FlatLaf.setup( new FlatPropertiesLaf( themeInfo.name, themeInfo.themeFile ) );
|
||||||
|
else
|
||||||
|
FlatLaf.setup( IntelliJTheme.createLaf( new FileInputStream( themeInfo.themeFile ) ) );
|
||||||
|
|
||||||
|
DemoPrefs.getState().put( DemoPrefs.KEY_LAF_THEME_FILE, themeInfo.themeFile.getAbsolutePath() );
|
||||||
|
} catch( Exception ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
showInformationDialog( "Failed to load '" + themeInfo.themeFile + "'.", ex );
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
JOptionPane.showMessageDialog( SwingUtilities.windowForComponent( this ),
|
||||||
|
"Missing lafClassName for '" + themeInfo.name + "'",
|
||||||
|
"FlatLaf", JOptionPane.INFORMATION_MESSAGE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update all components
|
||||||
|
FlatLaf.updateUI();
|
||||||
|
|
||||||
|
if( !reload )
|
||||||
|
FlatAnimatedLafChange.hideSnapshotWithAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browsePlugin() {
|
||||||
|
IJThemeInfo themeInfo = themesList.getSelectedValue();
|
||||||
|
if( themeInfo == null || themeInfo.pluginUrl == null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
browse( themeInfo.pluginUrl );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browseSourceCode() {
|
||||||
|
IJThemeInfo themeInfo = themesList.getSelectedValue();
|
||||||
|
if( themeInfo == null || themeInfo.sourceCodeUrl == null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
String themeUrl = themeInfo.sourceCodeUrl;
|
||||||
|
if( themeInfo.sourceCodePath != null )
|
||||||
|
themeUrl += '/' + themeInfo.sourceCodePath;
|
||||||
|
browse( themeUrl );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void browse( String url ) {
|
||||||
|
url = url.replace( " ", "%20" );
|
||||||
|
try {
|
||||||
|
Desktop.getDesktop().browse( new URI( url ) );
|
||||||
|
} catch( IOException | URISyntaxException ex ) {
|
||||||
|
showInformationDialog( "Failed to browse '" + url + "'.", ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showInformationDialog( String message, Exception ex ) {
|
||||||
|
JOptionPane.showMessageDialog( SwingUtilities.windowForComponent( this ),
|
||||||
|
message + "\n\n" + ex.getMessage(),
|
||||||
|
"FlatLaf", JOptionPane.INFORMATION_MESSAGE );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addNotify() {
|
||||||
|
super.addNotify();
|
||||||
|
|
||||||
|
selectedCurrentLookAndFeel();
|
||||||
|
UIManager.addPropertyChangeListener( lafListener );
|
||||||
|
|
||||||
|
window = SwingUtilities.windowForComponent( this );
|
||||||
|
if( window != null )
|
||||||
|
window.addWindowListener( windowListener );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeNotify() {
|
||||||
|
super.removeNotify();
|
||||||
|
|
||||||
|
UIManager.removePropertyChangeListener( lafListener );
|
||||||
|
|
||||||
|
if( window != null ) {
|
||||||
|
window.removeWindowListener( windowListener );
|
||||||
|
window = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lafChanged( PropertyChangeEvent e ) {
|
||||||
|
if( "lookAndFeel".equals( e.getPropertyName() ) ) {
|
||||||
|
// use invokeLater() because KEY_LAF_THEME_FILE is updated after this event
|
||||||
|
EventQueue.invokeLater( () -> {
|
||||||
|
selectedCurrentLookAndFeel();
|
||||||
|
lastLafChangeTime = System.currentTimeMillis();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void windowActivated() {
|
||||||
|
// refresh themes list on window activation
|
||||||
|
if( themesManager.hasThemesFromDirectoryChanged() )
|
||||||
|
updateThemesList();
|
||||||
|
else {
|
||||||
|
// check whether core .properties files of current Laf have changed
|
||||||
|
// in development environment since last Laf change and reload theme
|
||||||
|
LookAndFeel laf = UIManager.getLookAndFeel();
|
||||||
|
if( laf instanceof FlatLaf ) {
|
||||||
|
List<Class<?>> lafClasses = new ArrayList<>();
|
||||||
|
|
||||||
|
// same as in UIDefaultsLoader.getLafClassesForDefaultsLoading()
|
||||||
|
for( Class<?> lafClass = laf.getClass();
|
||||||
|
FlatLaf.class.isAssignableFrom( lafClass );
|
||||||
|
lafClass = lafClass.getSuperclass() )
|
||||||
|
{
|
||||||
|
lafClasses.add( 0, lafClass );
|
||||||
|
}
|
||||||
|
|
||||||
|
// same as in IntelliJTheme.ThemeLaf.getLafClassesForDefaultsLoading()
|
||||||
|
if( laf instanceof IntelliJTheme.ThemeLaf ) {
|
||||||
|
boolean dark = ((FlatLaf)laf).isDark();
|
||||||
|
lafClasses.add( 1, dark ? FlatDarkLaf.class : FlatLightLaf.class );
|
||||||
|
lafClasses.add( 2, dark ? FlatDarculaLaf.class : FlatIntelliJLaf.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean reload = false;
|
||||||
|
for( Class<?> lafClass : lafClasses ) {
|
||||||
|
String propertiesName = '/' + lafClass.getName().replace( '.', '/' ) + ".properties";
|
||||||
|
URL url = lafClass.getResource( propertiesName );
|
||||||
|
if( url != null && "file".equals( url.getProtocol() ) ) {
|
||||||
|
try {
|
||||||
|
File file = new File( url.toURI() );
|
||||||
|
if( file.lastModified() > lastLafChangeTime ) {
|
||||||
|
reload = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch( URISyntaxException ex ) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( reload )
|
||||||
|
setTheme( themesList.getSelectedValue(), true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectedCurrentLookAndFeel() {
|
||||||
|
Predicate<IJThemeInfo> test;
|
||||||
|
String lafClassName = UIManager.getLookAndFeel().getClass().getName();
|
||||||
|
if( FlatPropertiesLaf.class.getName().equals( lafClassName ) ||
|
||||||
|
IntelliJTheme.ThemeLaf.class.getName().equals( lafClassName ) )
|
||||||
|
{
|
||||||
|
String themeFileName = DemoPrefs.getState().get( DemoPrefs.KEY_LAF_THEME_FILE, "" );
|
||||||
|
if( themeFileName == null )
|
||||||
|
return;
|
||||||
|
|
||||||
|
File themeFile = new File( themeFileName );
|
||||||
|
test = ti -> Objects.equals( ti.themeFile, themeFile );
|
||||||
|
} else
|
||||||
|
test = ti -> Objects.equals( ti.lafClassName, lafClassName );
|
||||||
|
|
||||||
|
int newSel = -1;
|
||||||
|
for( int i = 0; i < themes.size(); i++ ) {
|
||||||
|
if( test.test( themes.get( i ) ) ) {
|
||||||
|
newSel = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isAdjustingThemesList = true;
|
||||||
|
if( newSel >= 0 ) {
|
||||||
|
if( newSel != themesList.getSelectedIndex() )
|
||||||
|
themesList.setSelectedIndex( newSel );
|
||||||
|
} else
|
||||||
|
themesList.clearSelection();
|
||||||
|
isAdjustingThemesList = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filterChanged() {
|
||||||
|
updateThemesList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initComponents() {
|
||||||
|
// JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents
|
||||||
|
JLabel themesLabel = new JLabel();
|
||||||
|
toolBar = new JToolBar();
|
||||||
|
pluginButton = new JButton();
|
||||||
|
sourceCodeButton = new JButton();
|
||||||
|
filterComboBox = new JComboBox<>();
|
||||||
|
themesScrollPane = new JScrollPane();
|
||||||
|
themesList = new JList<>();
|
||||||
|
|
||||||
|
//======== this ========
|
||||||
|
setLayout(new MigLayout(
|
||||||
|
"insets dialog,hidemode 3",
|
||||||
|
// columns
|
||||||
|
"[grow,fill]",
|
||||||
|
// rows
|
||||||
|
"[]3" +
|
||||||
|
"[grow,fill]"));
|
||||||
|
|
||||||
|
//---- themesLabel ----
|
||||||
|
themesLabel.setText("Themes:");
|
||||||
|
add(themesLabel, "cell 0 0");
|
||||||
|
|
||||||
|
//======== toolBar ========
|
||||||
|
{
|
||||||
|
toolBar.setFloatable(false);
|
||||||
|
|
||||||
|
//---- pluginButton ----
|
||||||
|
pluginButton.setToolTipText("Opens the IntelliJ plugin page of selected IntelliJ theme in the browser.");
|
||||||
|
pluginButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/plugin.svg"));
|
||||||
|
pluginButton.addActionListener(e -> browsePlugin());
|
||||||
|
toolBar.add(pluginButton);
|
||||||
|
|
||||||
|
//---- sourceCodeButton ----
|
||||||
|
sourceCodeButton.setToolTipText("Opens the source code repository of selected IntelliJ theme in the browser.");
|
||||||
|
sourceCodeButton.setIcon(new FlatSVGIcon("com/formdev/flatlaf/demo/icons/github.svg"));
|
||||||
|
sourceCodeButton.addActionListener(e -> browseSourceCode());
|
||||||
|
toolBar.add(sourceCodeButton);
|
||||||
|
}
|
||||||
|
add(toolBar, "cell 0 0,alignx right,growx 0");
|
||||||
|
|
||||||
|
//---- filterComboBox ----
|
||||||
|
filterComboBox.setModel(new DefaultComboBoxModel<>(new String[] {
|
||||||
|
"all",
|
||||||
|
"light",
|
||||||
|
"dark"
|
||||||
|
}));
|
||||||
|
filterComboBox.putClientProperty(FlatClientProperties.MINIMUM_WIDTH, 0);
|
||||||
|
filterComboBox.setFocusable(false);
|
||||||
|
filterComboBox.addActionListener(e -> filterChanged());
|
||||||
|
add(filterComboBox, "cell 0 0,alignx right,growx 0");
|
||||||
|
|
||||||
|
//======== themesScrollPane ========
|
||||||
|
{
|
||||||
|
|
||||||
|
//---- themesList ----
|
||||||
|
themesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
themesList.addListSelectionListener(e -> themesListValueChanged(e));
|
||||||
|
themesScrollPane.setViewportView(themesList);
|
||||||
|
}
|
||||||
|
add(themesScrollPane, "cell 0 1");
|
||||||
|
// JFormDesigner - End of component initialization //GEN-END:initComponents
|
||||||
|
}
|
||||||
|
|
||||||
|
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
|
||||||
|
private JToolBar toolBar;
|
||||||
|
private JButton pluginButton;
|
||||||
|
private JButton sourceCodeButton;
|
||||||
|
private JComboBox<String> filterComboBox;
|
||||||
|
private JScrollPane themesScrollPane;
|
||||||
|
private JList<IJThemeInfo> themesList;
|
||||||
|
// JFormDesigner - End of variables declaration //GEN-END:variables
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
JFDML JFormDesigner: "8.3" encoding: "UTF-8"
|
||||||
|
|
||||||
|
new FormModel {
|
||||||
|
contentType: "form/swing"
|
||||||
|
root: new FormRoot {
|
||||||
|
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
|
||||||
|
"$layoutConstraints": "insets dialog,hidemode 3"
|
||||||
|
"$columnConstraints": "[grow,fill]"
|
||||||
|
"$rowConstraints": "[]3[grow,fill]"
|
||||||
|
} ) {
|
||||||
|
name: "this"
|
||||||
|
add( new FormComponent( "javax.swing.JLabel" ) {
|
||||||
|
name: "themesLabel"
|
||||||
|
"text": "Themes:"
|
||||||
|
auxiliary() {
|
||||||
|
"JavaCodeGenerator.variableLocal": true
|
||||||
|
}
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 0"
|
||||||
|
} )
|
||||||
|
add( new FormContainer( "javax.swing.JToolBar", new FormLayoutManager( class javax.swing.JToolBar ) ) {
|
||||||
|
name: "toolBar"
|
||||||
|
"floatable": false
|
||||||
|
add( new FormComponent( "javax.swing.JButton" ) {
|
||||||
|
name: "pluginButton"
|
||||||
|
"toolTipText": "Opens the IntelliJ plugin page of selected IntelliJ theme in the browser."
|
||||||
|
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/plugin.svg" )
|
||||||
|
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "browsePlugin", false ) )
|
||||||
|
} )
|
||||||
|
add( new FormComponent( "javax.swing.JButton" ) {
|
||||||
|
name: "sourceCodeButton"
|
||||||
|
"toolTipText": "Opens the source code repository of selected IntelliJ theme in the browser."
|
||||||
|
"icon": new com.jformdesigner.model.SwingIcon( 0, "/com/formdev/flatlaf/demo/icons/github.svg" )
|
||||||
|
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "browseSourceCode", false ) )
|
||||||
|
} )
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 0,alignx right,growx 0"
|
||||||
|
} )
|
||||||
|
add( new FormComponent( "javax.swing.JComboBox" ) {
|
||||||
|
name: "filterComboBox"
|
||||||
|
"model": new javax.swing.DefaultComboBoxModel {
|
||||||
|
selectedItem: "all"
|
||||||
|
addElement( "all" )
|
||||||
|
addElement( "light" )
|
||||||
|
addElement( "dark" )
|
||||||
|
}
|
||||||
|
"$client.JComponent.minimumWidth": 0
|
||||||
|
"focusable": false
|
||||||
|
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "filterChanged", false ) )
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 0,alignx right,growx 0"
|
||||||
|
} )
|
||||||
|
add( new FormContainer( "javax.swing.JScrollPane", new FormLayoutManager( class javax.swing.JScrollPane ) ) {
|
||||||
|
name: "themesScrollPane"
|
||||||
|
add( new FormComponent( "javax.swing.JList" ) {
|
||||||
|
name: "themesList"
|
||||||
|
"selectionMode": 0
|
||||||
|
auxiliary() {
|
||||||
|
"JavaCodeGenerator.typeParameters": "IJThemeInfo"
|
||||||
|
}
|
||||||
|
addEvent( new FormEvent( "javax.swing.event.ListSelectionListener", "valueChanged", "themesListValueChanged", true ) )
|
||||||
|
} )
|
||||||
|
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
|
||||||
|
"value": "cell 0 1"
|
||||||
|
} )
|
||||||
|
}, new FormLayoutConstraints( null ) {
|
||||||
|
"location": new java.awt.Point( 0, 0 )
|
||||||
|
"size": new java.awt.Dimension( 400, 300 )
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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 com.formdev.flatlaf.intellijthemes;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import com.formdev.flatlaf.util.LoggingFacade;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tool updates all IntelliJ themes listed in themes.json by downloading
|
||||||
|
* them from the source code repositories.
|
||||||
|
*
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
public class IJThemesUpdater
|
||||||
|
{
|
||||||
|
public static void main( String[] args ) {
|
||||||
|
IJThemesManager themesManager = new IJThemesManager();
|
||||||
|
themesManager.loadBundledThemes();
|
||||||
|
|
||||||
|
for( IJThemeInfo ti : themesManager.bundledThemes ) {
|
||||||
|
if( ti.discontinued )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if( ti.sourceCodeUrl == null || ti.sourceCodePath == null ) {
|
||||||
|
System.out.println( " " + ti.name + " NOT downloaded. Needs manual update from release on JetBrains Plugin portal." );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fromUrl = ti.sourceCodeUrl + "/" + ti.sourceCodePath;
|
||||||
|
if( fromUrl.contains( "github.com" ) )
|
||||||
|
fromUrl += "?raw=true";
|
||||||
|
else if( fromUrl.contains( "gitlab.com" ) )
|
||||||
|
fromUrl = fromUrl.replace( "/blob/", "/raw/" );
|
||||||
|
|
||||||
|
String toPath = "../flatlaf-intellij-themes/src/main/resources" + IJThemesPanel.THEMES_PACKAGE + ti.resourceName;
|
||||||
|
|
||||||
|
download( fromUrl, toPath );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void download( String fromUrl, String toPath ) {
|
||||||
|
System.out.println( "Download " + fromUrl );
|
||||||
|
|
||||||
|
Path out = new File( toPath ).toPath();
|
||||||
|
try {
|
||||||
|
URL url = new URL( fromUrl.replace( " ", "%20" ) );
|
||||||
|
URLConnection con = url.openConnection();
|
||||||
|
Files.copy( con.getInputStream(), out, StandardCopyOption.REPLACE_EXISTING );
|
||||||
|
} catch( IOException ex ) {
|
||||||
|
LoggingFacade.INSTANCE.logSevere( null, ex );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 FormDev Software GmbH
|
||||||
|
*
|
||||||
|
* 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 com.formdev.flatlaf.intellijthemes;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
import javax.swing.JList;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import com.formdev.flatlaf.ui.FlatUIUtils;
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Karl Tauber
|
||||||
|
*/
|
||||||
|
class ListCellTitledBorder
|
||||||
|
implements Border
|
||||||
|
{
|
||||||
|
private final JList<?> list;
|
||||||
|
private final String title;
|
||||||
|
|
||||||
|
ListCellTitledBorder( JList<?> list, String title ) {
|
||||||
|
this.list = list;
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBorderOpaque() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Insets getBorderInsets( Component c ) {
|
||||||
|
int height = c.getFontMetrics( list.getFont() ).getHeight();
|
||||||
|
return new Insets( height, 0, 0, 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
|
||||||
|
FontMetrics fm = c.getFontMetrics( list.getFont() );
|
||||||
|
int titleWidth = fm.stringWidth( title );
|
||||||
|
int titleHeight = fm.getHeight();
|
||||||
|
|
||||||
|
// fill background
|
||||||
|
g.setColor( list.getBackground() );
|
||||||
|
g.fillRect( x, y, width, titleHeight );
|
||||||
|
|
||||||
|
int gap = UIScale.scale( 4 );
|
||||||
|
|
||||||
|
Graphics2D g2 = (Graphics2D) g.create();
|
||||||
|
try {
|
||||||
|
FlatUIUtils.setRenderingHints( g2 );
|
||||||
|
|
||||||
|
g2.setColor( UIManager.getColor( "Label.disabledForeground" ) );
|
||||||
|
|
||||||
|
// paint separator lines
|
||||||
|
int sepWidth = (width - titleWidth) / 2 - gap - gap;
|
||||||
|
if( sepWidth > 0 ) {
|
||||||
|
int sy = y + Math.round( titleHeight / 2f );
|
||||||
|
float sepHeight = UIScale.scale( (float) 1 );
|
||||||
|
|
||||||
|
g2.fill( new Rectangle2D.Float( x + gap, sy, sepWidth, sepHeight ) );
|
||||||
|
g2.fill( new Rectangle2D.Float( x + width - gap - sepWidth, sy, sepWidth, sepHeight ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw title
|
||||||
|
int xt = x + ((width - titleWidth) / 2);
|
||||||
|
int yt = y + fm.getAscent();
|
||||||
|
|
||||||
|
FlatUIUtils.drawString( list, g2, title, xt, yt );
|
||||||
|
} finally {
|
||||||
|
g2.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/main/java/dev/sillyangel/calc/BaseConverter.java
Normal file
110
src/main/java/dev/sillyangel/calc/BaseConverter.java
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package dev.sillyangel.calc;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.FlowLayout;
|
||||||
|
import java.awt.GridLayout;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
|
||||||
|
public class BaseConverter {
|
||||||
|
private Calculator ci;
|
||||||
|
private JDialog BaseConverterDialog;
|
||||||
|
private JTextField inputField;
|
||||||
|
private JComboBox<String> fromBaseComboBox;
|
||||||
|
private JComboBox<String> toBaseComboBox;
|
||||||
|
private JLabel resultLabel;
|
||||||
|
|
||||||
|
public BaseConverter(Calculator ci) {
|
||||||
|
System.out.println("Initialized BaseConverter");
|
||||||
|
this.ci = ci;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BaseConverterGUI() {
|
||||||
|
BaseConverterDialog = new JDialog(this.ci, "Base System Converter");
|
||||||
|
BaseConverterDialog.setSize(400, 250);
|
||||||
|
BaseConverterDialog.setLocationRelativeTo(this.ci);
|
||||||
|
BaseConverterDialog.setLayout(new BorderLayout(10, 10));
|
||||||
|
|
||||||
|
|
||||||
|
JPanel inputPanel = new JPanel();
|
||||||
|
inputPanel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||||
|
inputPanel.setLayout(new GridLayout(0, 2, 10, 10));
|
||||||
|
|
||||||
|
inputPanel.add(new JLabel("Input Number:"));
|
||||||
|
inputField = new JTextField(20);
|
||||||
|
inputPanel.add(inputField);
|
||||||
|
|
||||||
|
String[] bases = {"Binary (2)", "Octal (8)", "Decimal (10)", "Hexadecimal (16)"};
|
||||||
|
inputPanel.add(new JLabel("From Base:"));
|
||||||
|
fromBaseComboBox = new JComboBox<>(bases);
|
||||||
|
inputPanel.add(fromBaseComboBox);
|
||||||
|
|
||||||
|
inputPanel.add(new JLabel("To Base:"));
|
||||||
|
toBaseComboBox = new JComboBox<>(bases);
|
||||||
|
toBaseComboBox.setSelectedIndex(2);
|
||||||
|
inputPanel.add(toBaseComboBox);
|
||||||
|
|
||||||
|
BaseConverterDialog.add(inputPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
|
||||||
|
JButton convertButton = new JButton("Convert");
|
||||||
|
convertButton.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
convert();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
buttonPanel.add(convertButton);
|
||||||
|
BaseConverterDialog.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
|
||||||
|
// --- Result Panel ---
|
||||||
|
JPanel resultPanel = new JPanel();
|
||||||
|
resultPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
|
||||||
|
resultPanel.add(new JLabel("Result:"));
|
||||||
|
resultLabel = new JLabel("0");
|
||||||
|
resultPanel.add(resultLabel);
|
||||||
|
BaseConverterDialog.add(resultPanel, BorderLayout.NORTH);
|
||||||
|
BaseConverterDialog.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void convert() {
|
||||||
|
String input = inputField.getText().trim();
|
||||||
|
if (input.isEmpty()) {
|
||||||
|
JOptionPane.showMessageDialog(BaseConverterDialog, "Please enter a number.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fromBase = getBaseFromComboBox(fromBaseComboBox);
|
||||||
|
int toBase = getBaseFromComboBox(toBaseComboBox);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Convert from source base string to a standard long
|
||||||
|
long decimalValue = Long.parseLong(input, fromBase);
|
||||||
|
|
||||||
|
// 2. Convert from the standard long to the target base string
|
||||||
|
String result = Long.toString(decimalValue, toBase).toUpperCase();
|
||||||
|
|
||||||
|
resultLabel.setText(result);
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
JOptionPane.showMessageDialog(BaseConverterDialog, "Invalid input for the selected 'From Base'. Please check your number.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
} catch (Exception e) {
|
||||||
|
JOptionPane.showMessageDialog(BaseConverterDialog, "An unexpected error occurred: " + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getBaseFromComboBox(JComboBox<String> comboBox) {
|
||||||
|
String selected = (String) comboBox.getSelectedItem();
|
||||||
|
// Extract the number from the string e.g., "Binary (2)" -> 2
|
||||||
|
return Integer.parseInt(selected.replaceAll("[^0-9]", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ package dev.sillyangel.calc;
|
|||||||
// java to javax, com, dev
|
// java to javax, com, dev
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.*;
|
import java.awt.event.*;
|
||||||
|
import java.io.Serial;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -16,6 +17,7 @@ import javax.swing.GroupLayout;
|
|||||||
import javax.swing.GroupLayout.Alignment;
|
import javax.swing.GroupLayout.Alignment;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import com.formdev.flatlaf.FlatDarculaLaf;
|
import com.formdev.flatlaf.FlatDarculaLaf;
|
||||||
|
import com.formdev.flatlaf.themes.FlatMacDarkLaf;
|
||||||
import com.formdev.flatlaf.FlatIntelliJLaf;
|
import com.formdev.flatlaf.FlatIntelliJLaf;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
import com.formdev.flatlaf.FlatLaf;
|
import com.formdev.flatlaf.FlatLaf;
|
||||||
@@ -23,39 +25,51 @@ import com.formdev.flatlaf.extras.FlatAnimatedLafChange;
|
|||||||
import com.formdev.flatlaf.FlatClientProperties;
|
import com.formdev.flatlaf.FlatClientProperties;
|
||||||
import com.formdev.flatlaf.util.SystemInfo;
|
import com.formdev.flatlaf.util.SystemInfo;
|
||||||
import dev.sillyangel.calc.themes.*;
|
import dev.sillyangel.calc.themes.*;
|
||||||
|
import dev.sillyangel.calc.utils.Utils;
|
||||||
|
|
||||||
public class Calculator extends JFrame implements KeyListener {
|
public class Calculator extends JFrame implements KeyListener {
|
||||||
|
|
||||||
private static final String PREF_NODE_NAME = "dev/sillyangel/calc";
|
|
||||||
private static boolean
|
|
||||||
private static int
|
|
||||||
public static final Preferences prefs = Preferences.userRoot().node(PREF_NODE_NAME);
|
|
||||||
public static String APPILCATION_VERSION = "1.0.0pre";
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
private JPanel contentPane;
|
|
||||||
private JTextField display;
|
|
||||||
private String operand1;
|
|
||||||
private String operator;
|
|
||||||
private String operand2;
|
|
||||||
private JButton btnDiv;
|
|
||||||
private JButton btnMul;
|
|
||||||
private JButton btnMin;
|
|
||||||
private JButton btnPlus;
|
|
||||||
protected boolean resultVisible;
|
|
||||||
protected double result;
|
|
||||||
private String[] Modes = new String[] {"Standard", "Scientific", "Data calculation"};
|
|
||||||
private JComboBox<String> modeselect;
|
|
||||||
private final CalculatorHistory history;
|
|
||||||
|
|
||||||
public static final String getVersion() {
|
@Serial
|
||||||
return APPILCATION_VERSION;
|
private static final long serialVersionUID = 1L;
|
||||||
|
private static final String PREF_NODE_NAME = "dev/sillyangel/calc";
|
||||||
|
private static final String PREF_THEME = "theme";
|
||||||
|
private static final String PREF_FONT_SIZE = "fontSize";
|
||||||
|
private static final String PREF_ALWAYS_ON_TOP = "alwaysOnTop";
|
||||||
|
public static final Preferences prefs = Preferences.userRoot().node(PREF_NODE_NAME);
|
||||||
|
public static String APPLICATION_VERSION = "1.1.0";
|
||||||
|
public JTextField display;
|
||||||
|
public String operand1;
|
||||||
|
public String operator;
|
||||||
|
public String operand2;
|
||||||
|
public boolean resultVisible;
|
||||||
|
public double result;
|
||||||
|
public final CalculatorHistory history;
|
||||||
|
public final List<String> undoStack = new ArrayList<>();
|
||||||
|
public int undoIndex = -1;
|
||||||
|
public Utils calculatorUtils;
|
||||||
|
public BaseConverter bconvert;
|
||||||
|
|
||||||
|
public static String getVersion() {
|
||||||
|
return APPLICATION_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// IntelliJTheme.setup(Calculator.class.getResourceAsStream("/DarkPurple.theme.json"));
|
// Load saved theme preference
|
||||||
// FlatMacDarkLaf.setup();
|
String savedTheme = prefs.get(PREF_THEME, "MacDarkBlue");
|
||||||
// MacDarkRed.setup();
|
// Apply saved theme on startup
|
||||||
MacDarkBlue.setup();
|
try {
|
||||||
|
switch (savedTheme) {
|
||||||
|
case "MacDarkBlue" -> MacDarkBlue.setup();
|
||||||
|
case "MacDarkRed" -> MacDarkRed.setup();
|
||||||
|
case "MacLightBlue" -> MacLightBlue.setup();
|
||||||
|
case "MacLightRed" -> MacLightRed.setup();
|
||||||
|
case "Default" -> FlatMacDarkLaf.setup();
|
||||||
|
default -> FlatMacDarkLaf.setup();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
FlatMacDarkLaf.setup();
|
||||||
|
}
|
||||||
|
|
||||||
System.out.println("\nangel's awesome calculator (acc) " + Calculator.getVersion());
|
System.out.println("\nangel's awesome calculator (acc) " + Calculator.getVersion());
|
||||||
System.out.println("created by angel");
|
System.out.println("created by angel");
|
||||||
System.out.println("---------------------------------");
|
System.out.println("---------------------------------");
|
||||||
@@ -67,17 +81,12 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
if( SystemInfo.isLinux ) { // why is linux different
|
if( SystemInfo.isLinux ) { // why is linux different
|
||||||
JFrame.setDefaultLookAndFeelDecorated(true);
|
JFrame.setDefaultLookAndFeelDecorated(true);
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
// UIManager.setLookAndFeel("com.formdev.flatlaf.themes.FlatMacDarkLaf");
|
|
||||||
UIManager.put("defaultFont", new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
} catch (Throwable e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
EventQueue.invokeLater(new Runnable() {
|
EventQueue.invokeLater(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
Calculator frame = new Calculator();
|
Calculator frame = new Calculator();
|
||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
@@ -86,17 +95,19 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
}
|
}
|
||||||
public Calculator() {
|
public Calculator() {
|
||||||
history = new CalculatorHistory();
|
history = new CalculatorHistory();
|
||||||
|
calculatorUtils = new Utils(this);
|
||||||
|
bconvert = new BaseConverter(this);
|
||||||
|
|
||||||
setBackground(new Color(32, 32, 32));
|
setBackground(new Color(32, 32, 32));
|
||||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
//setIconImage(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("/images/appIcon.png")));
|
//setIconImage(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("/images/appIcon.png")));
|
||||||
List<Image> icons = new ArrayList<>();
|
List<Image> icons = new ArrayList<>();
|
||||||
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("/images/appIcon16.png")));
|
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("appIcon16.png")));
|
||||||
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("/images/appIcon32.png")));
|
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("appIcon32.png")));
|
||||||
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("/images/appIcon48.png")));
|
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("appIcon48.png")));
|
||||||
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("/images/appIcon256.png")));
|
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("appIcon256.png")));
|
||||||
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("/images/appIcon512.png")));
|
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("appIcon512.png")));
|
||||||
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("/images/appIcon1024.png")));
|
icons.add(Toolkit.getDefaultToolkit().getImage(Calculator.class.getResource("appIcon1024.png")));
|
||||||
setIconImages(icons);
|
setIconImages(icons);
|
||||||
|
|
||||||
setTitle("AAC");
|
setTitle("AAC");
|
||||||
@@ -104,11 +115,12 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
setFocusable(true);
|
setFocusable(true);
|
||||||
addKeyListener(this);
|
addKeyListener(this);
|
||||||
|
|
||||||
contentPane = new JPanel();
|
JPanel contentPane = new JPanel();
|
||||||
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
|
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||||
setContentPane(contentPane);
|
setContentPane(contentPane);
|
||||||
|
|
||||||
display = new JTextField();
|
display = new JTextField();
|
||||||
|
display.setFocusable(false);
|
||||||
display.setEditable(false);
|
display.setEditable(false);
|
||||||
display.setFont(new Font("Segoe UI", Font.PLAIN, 22));
|
display.setFont(new Font("Segoe UI", Font.PLAIN, 22));
|
||||||
display.setColumns(10);
|
display.setColumns(10);
|
||||||
@@ -151,7 +163,7 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
// Menu Bar
|
// Menu Bar
|
||||||
JMenuBar menubar = new JMenuBar();
|
JMenuBar menubar = new JMenuBar();
|
||||||
|
|
||||||
JMenu help = new JMenu("Help");
|
JMenu view = new JMenu("View");
|
||||||
JMenu file = new JMenu("File");
|
JMenu file = new JMenu("File");
|
||||||
JMenu edit = new JMenu("Edit");
|
JMenu edit = new JMenu("Edit");
|
||||||
/*
|
/*
|
||||||
@@ -170,10 +182,20 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
JMenuItem Exit = new JMenuItem("Exit");
|
JMenuItem Exit = new JMenuItem("Exit");
|
||||||
Exit.addActionListener(e -> System.exit(1));
|
Exit.addActionListener(e -> System.exit(1));
|
||||||
Exit.setMnemonic('E');
|
Exit.setMnemonic('E');
|
||||||
|
JMenuItem hmi = new JMenuItem("View History");
|
||||||
|
hmi.addActionListener(e -> calculatorUtils.showHistoryDialog());
|
||||||
|
|
||||||
JMenuItem about = new JMenuItem("About");
|
JMenuItem about = new JMenuItem("About");
|
||||||
about.setMnemonic('A');
|
about.setMnemonic('A');
|
||||||
about.addActionListener(e -> aboutActionPerformed());
|
about.addActionListener(e -> aboutActionPerformed());
|
||||||
|
|
||||||
|
JMenuItem baseconv = new JMenuItem("Base Converter");
|
||||||
|
baseconv.setMnemonic('B');
|
||||||
|
baseconv.addActionListener(e -> bconvert.BaseConverterGUI());
|
||||||
|
|
||||||
|
JMenuItem viewhistory = new JMenuItem("View History");
|
||||||
|
viewhistory.setMnemonic('V');
|
||||||
|
viewhistory.addActionListener(e -> calculatorUtils.showHistoryDialog());
|
||||||
|
|
||||||
JMenuItem undo = new JMenuItem("Undo");
|
JMenuItem undo = new JMenuItem("Undo");
|
||||||
JMenuItem redo = new JMenuItem("Redo");
|
JMenuItem redo = new JMenuItem("Redo");
|
||||||
@@ -181,11 +203,16 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
JMenuItem copy = new JMenuItem("Copy");
|
JMenuItem copy = new JMenuItem("Copy");
|
||||||
JMenuItem paste = new JMenuItem("Paste");
|
JMenuItem paste = new JMenuItem("Paste");
|
||||||
JMenuItem delete = new JMenuItem("Delete");
|
JMenuItem delete = new JMenuItem("Delete");
|
||||||
delete.addActionListener(e -> clearEntry());
|
delete.addActionListener(e -> calculatorUtils.clearEntry());
|
||||||
JMenuItem preferencesm = new JMenuItem("Preferences");
|
JMenuItem preferencesm = new JMenuItem("Preferences");
|
||||||
preferencesm.setMnemonic('P');
|
preferencesm.setMnemonic('P');
|
||||||
preferencesm.addActionListener(e -> PreferencesAction());
|
preferencesm.addActionListener(e -> PreferencesAction());
|
||||||
|
undo.addActionListener(e -> calculatorUtils.undoAction());
|
||||||
|
redo.addActionListener(e -> calculatorUtils.redoAction());
|
||||||
|
cut.addActionListener(e -> calculatorUtils.cutAction());
|
||||||
|
copy.addActionListener(e -> calculatorUtils.copyAction());
|
||||||
|
paste.addActionListener(e -> calculatorUtils.pasteAction());
|
||||||
|
|
||||||
edit.add(undo);
|
edit.add(undo);
|
||||||
edit.add(redo);
|
edit.add(redo);
|
||||||
edit.add(cut);
|
edit.add(cut);
|
||||||
@@ -196,15 +223,18 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
|
|
||||||
file.add(Exit);
|
file.add(Exit);
|
||||||
file.setMnemonic('F');
|
file.setMnemonic('F');
|
||||||
help.add(about);
|
view.add(about);
|
||||||
help.setMnemonic('H');
|
view.add(baseconv);
|
||||||
|
view.add(viewhistory);
|
||||||
|
view.setMnemonic('H');
|
||||||
edit.setMnemonic('E');
|
edit.setMnemonic('E');
|
||||||
menubar.add(file);
|
menubar.add(file);
|
||||||
menubar.add(edit);
|
menubar.add(edit);
|
||||||
menubar.add(help);
|
menubar.add(view);
|
||||||
setJMenuBar(menubar);
|
setJMenuBar(menubar);
|
||||||
|
|
||||||
modeselect = new JComboBox<>(Modes);
|
String[] modes = new String[]{"Standard", "Scientific", "Data calculation"};
|
||||||
|
JComboBox<String> modeselect = new JComboBox<>(modes);
|
||||||
modeselect.addActionListener(new ActionListener() {
|
modeselect.addActionListener(new ActionListener() {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
@@ -252,249 +282,78 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
JButton btn8 = new JButton("8");
|
JButton btn8 = new JButton("8");
|
||||||
JButton btn9 = new JButton("9");
|
JButton btn9 = new JButton("9");
|
||||||
|
|
||||||
btnDiv = new JButton("/");
|
JButton btnDiv = new JButton("/");
|
||||||
btnMul = new JButton("*");
|
JButton btnMul = new JButton("*");
|
||||||
btnMin = new JButton("-");
|
JButton btnMin = new JButton("-");
|
||||||
btnPlus = new JButton("+");
|
JButton btnPlus = new JButton("+");
|
||||||
|
|
||||||
btnDot.addActionListener(new ActionListener() {
|
btnDot.addActionListener(new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
String n = display.getText(); // get the text on the display
|
String n = display.getText();
|
||||||
if (n.contains(".")) return; // if it already contains a "." skip rest of this
|
if (n.contains(".")) return;
|
||||||
processDigit(e.getActionCommand());
|
calculatorUtils.processDigit(e.getActionCommand());
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnEq.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
math();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnBackspace.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
backspace();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnClearEntry.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
clearEntry();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnClear.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
clearAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnPlus.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
setOperator(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnDiv.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
setOperator(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnMul.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
setOperator(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnMin.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
setOperator(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn0.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn1.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn2.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn3.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn4.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn5.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
btn6.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn7.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn8.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btn9.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
processDigit(e.getActionCommand());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
btnPlusMin.addActionListener(new ActionListener() {
|
btnPlusMin.addActionListener(new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
String tmp = display.getText();
|
|
||||||
if (tmp == null || tmp.isEmpty()) return;
|
|
||||||
char first = tmp.charAt(0);
|
|
||||||
if (first == '-') {
|
|
||||||
tmp = tmp.substring(1, tmp.length());
|
|
||||||
display.setText(tmp);
|
|
||||||
} else {
|
|
||||||
display.setText("-" + tmp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
btnPercent.addActionListener(new ActionListener() {
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
setOperator("%");
|
String tmp = display.getText();
|
||||||
|
if (tmp == null || tmp.isEmpty()) return;
|
||||||
|
char first = tmp.charAt(0);
|
||||||
|
if (first == '-') {
|
||||||
|
tmp = tmp.substring(1);
|
||||||
|
display.setText(tmp);
|
||||||
|
} else {
|
||||||
|
display.setText("-" + tmp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
btnReciprocal.addActionListener(new ActionListener() {
|
btnClear.addActionListener(e -> calculatorUtils.clearAll());
|
||||||
public void actionPerformed(ActionEvent e) {
|
btnReciprocal.addActionListener(e -> calculatorUtils.reciprocal());
|
||||||
reciprocal();
|
btnSquare.addActionListener(e -> calculatorUtils.square());
|
||||||
}
|
btnSquareRoot.addActionListener(e -> calculatorUtils.squareRoot());
|
||||||
});
|
btnEq.addActionListener(e -> calculatorUtils.math());
|
||||||
|
btnBackspace.addActionListener(e -> calculatorUtils.backspace());
|
||||||
|
btnClearEntry.addActionListener(e -> display.setText(""));
|
||||||
|
|
||||||
btnSquare.addActionListener(new ActionListener() {
|
JButton[] buttons = {
|
||||||
public void actionPerformed(ActionEvent e) {
|
btnPercent, btnClearEntry, btnClear, btnBackspace, btnReciprocal, btnSquare,
|
||||||
square();
|
btnSquareRoot, btnDiv, btn7, btn8, btn9, btnMul, btn4, btn5, btn6, btnMin,
|
||||||
}
|
btn1, btn2, btn3, btnPlus, btnPlusMin, btn0, btnDot, btnEq
|
||||||
});
|
};
|
||||||
|
JButton[] operatorButtons = {
|
||||||
btnSquareRoot.addActionListener(new ActionListener() {
|
btnPlus, btnDiv, btnMul, btnMin, btnPercent
|
||||||
public void actionPerformed(ActionEvent e) {
|
};
|
||||||
squareRoot();
|
JButton[] digitButtons = {
|
||||||
}
|
btn0, btn1, btn2, btn3, btn4, btn5, btn6, btn7, btn8,
|
||||||
});
|
btn9
|
||||||
|
};
|
||||||
|
// Add action listeners to each digit button to process the number input
|
||||||
|
for (JButton button : digitButtons)
|
||||||
// btnBackspace.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
button.addActionListener(e -> calculatorUtils.processDigit(e.getActionCommand()));
|
||||||
// btnClear.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
// Add action listeners to each operator to process what operator button has been clicked
|
||||||
// btnClearEntry.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
for (JButton button : operatorButtons)
|
||||||
// btnPlusMin.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
button.addActionListener(e -> calculatorUtils.setOperator(e.getActionCommand()));
|
||||||
// btnEq.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
// add the buttons to the main display panel
|
||||||
// btnDot.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
for(JButton button: buttons) {
|
||||||
// btn0.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
button.setFocusable(false);
|
||||||
// btn1.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
button.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
||||||
// btn2.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
buttonPanel.add(button);
|
||||||
// btn3.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
}
|
||||||
// btn4.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btn5.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btn6.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btn7.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btn8.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btn9.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btnMul.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btnMin.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btnDiv.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btnPlus.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btnPercent.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btnReciprocal.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btnSquare.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
// btnSquareRoot.setFont(new Font("Segoe UI", Font.PLAIN, 29));
|
|
||||||
|
|
||||||
// Make all buttons non-focusable so keyboard input always goes to the frame because if we don't it breaks
|
|
||||||
btnBackspace.setFocusable(false);
|
|
||||||
btnClear.setFocusable(false);
|
|
||||||
btnClearEntry.setFocusable(false);
|
|
||||||
btnPlusMin.setFocusable(false);
|
|
||||||
btnEq.setFocusable(false);
|
|
||||||
btnDot.setFocusable(false);
|
|
||||||
btn0.setFocusable(false);
|
|
||||||
btn1.setFocusable(false);
|
|
||||||
btn2.setFocusable(false);
|
|
||||||
btn3.setFocusable(false);
|
|
||||||
btn4.setFocusable(false);
|
|
||||||
btn5.setFocusable(false);
|
|
||||||
btn6.setFocusable(false);
|
|
||||||
btn7.setFocusable(false);
|
|
||||||
btn8.setFocusable(false);
|
|
||||||
btn9.setFocusable(false);
|
|
||||||
btnMul.setFocusable(false);
|
|
||||||
btnMin.setFocusable(false);
|
|
||||||
btnDiv.setFocusable(false);
|
|
||||||
btnPlus.setFocusable(false);
|
|
||||||
btnPercent.setFocusable(false);
|
|
||||||
btnReciprocal.setFocusable(false);
|
|
||||||
btnSquare.setFocusable(false);
|
|
||||||
btnSquareRoot.setFocusable(false);
|
|
||||||
|
|
||||||
buttonPanel.add(btnPercent);
|
|
||||||
buttonPanel.add(btnClearEntry);
|
|
||||||
buttonPanel.add(btnClear);
|
|
||||||
buttonPanel.add(btnBackspace);
|
|
||||||
buttonPanel.add(btnReciprocal);
|
|
||||||
buttonPanel.add(btnSquare);
|
|
||||||
buttonPanel.add(btnSquareRoot);
|
|
||||||
buttonPanel.add(btnDiv);
|
|
||||||
buttonPanel.add(btn7);
|
|
||||||
buttonPanel.add(btn8);
|
|
||||||
buttonPanel.add(btn9);
|
|
||||||
buttonPanel.add(btnMul);
|
|
||||||
buttonPanel.add(btn4);
|
|
||||||
buttonPanel.add(btn5);
|
|
||||||
buttonPanel.add(btn6);
|
|
||||||
buttonPanel.add(btnMin);
|
|
||||||
buttonPanel.add(btn1);
|
|
||||||
buttonPanel.add(btn2);
|
|
||||||
buttonPanel.add(btn3);
|
|
||||||
buttonPanel.add(btnPlus);
|
|
||||||
buttonPanel.add(btnPlusMin);
|
|
||||||
buttonPanel.add(btn0);
|
|
||||||
buttonPanel.add(btnDot);
|
|
||||||
buttonPanel.add(btnEq);
|
|
||||||
|
|
||||||
contentPane.setLayout(gl_contentPane);
|
contentPane.setLayout(gl_contentPane);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Undo/Redo functionality
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void PreferencesAction() {
|
private void PreferencesAction() {
|
||||||
JDialog dialog = new JDialog(this, "Preferences", true);
|
JDialog dialog = new JDialog(this, "Preferences", true);
|
||||||
dialog.setSize(320, 220);
|
dialog.setSize(400, 280);
|
||||||
dialog.setLocationRelativeTo(this);
|
dialog.setLocationRelativeTo(this);
|
||||||
dialog.setLayout(new BorderLayout(10, 10));
|
dialog.setLayout(new BorderLayout(10, 10));
|
||||||
|
|
||||||
@@ -502,9 +361,31 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
panel.setBorder(new EmptyBorder(10, 10, 10, 10));
|
||||||
panel.setLayout(new GridLayout(0, 2, 8, 8));
|
panel.setLayout(new GridLayout(0, 2, 8, 8));
|
||||||
|
|
||||||
// Theme selector
|
// Color theme selector
|
||||||
JComboBox<String> themeBox = new JComboBox<>(new String[]{"Dark", "Light"});
|
JComboBox<String> colorThemeBox = new JComboBox<>(new String[]{
|
||||||
themeBox.setSelectedItem(prefs.get(PREF_THEME, "Dark"));
|
"Default", "MacDarkBlue", "MacDarkRed", "MacLightBlue", "MacLightRed"
|
||||||
|
});
|
||||||
|
String savedTheme = prefs.get(PREF_THEME, "MacDarkBlue");
|
||||||
|
colorThemeBox.setSelectedItem(savedTheme);
|
||||||
|
|
||||||
|
// Light/Dark mode toggle
|
||||||
|
JComboBox<String> modeBox = new JComboBox<>(new String[]{"Dark", "Light"});
|
||||||
|
boolean isDark = isThemeDark(savedTheme);
|
||||||
|
modeBox.setSelectedItem(isDark ? "Dark" : "Light");
|
||||||
|
|
||||||
|
// Update color theme when mode changes
|
||||||
|
modeBox.addActionListener(e -> {
|
||||||
|
String currentTheme = (String) colorThemeBox.getSelectedItem();
|
||||||
|
String mode = (String) modeBox.getSelectedItem();
|
||||||
|
String counterpart = getThemeCounterpart(currentTheme, "Light".equals(mode));
|
||||||
|
colorThemeBox.setSelectedItem(counterpart);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update mode when color theme changes
|
||||||
|
colorThemeBox.addActionListener(e -> {
|
||||||
|
String theme = (String) colorThemeBox.getSelectedItem();
|
||||||
|
modeBox.setSelectedItem(isThemeDark(theme) ? "Dark" : "Light");
|
||||||
|
});
|
||||||
|
|
||||||
// Font size
|
// Font size
|
||||||
JSpinner fontSizeSpinner = new JSpinner(
|
JSpinner fontSizeSpinner = new JSpinner(
|
||||||
@@ -514,26 +395,36 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Always on top
|
// Always on top
|
||||||
JCheckBox alwaysOnTopBox = new JCheckBox(
|
JCheckBox alwaysOnTopBox = new JCheckBox(
|
||||||
"Always on top",
|
"Always on top",
|
||||||
prefs.getBoolean(PREF_ALWAYS_ON_TOP, false)
|
prefs.getBoolean(PREF_ALWAYS_ON_TOP, false)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
JButton cleear = new JButton("Clear");
|
||||||
|
cleear.addActionListener(e -> history.clearHistory());
|
||||||
|
|
||||||
|
|
||||||
panel.add(new JLabel("Theme"));
|
panel.add(new JLabel("Color Theme:"));
|
||||||
panel.add(themeBox);
|
panel.add(colorThemeBox);
|
||||||
|
|
||||||
panel.add(new JLabel("Font size"));
|
panel.add(new JLabel("Mode:"));
|
||||||
|
panel.add(modeBox);
|
||||||
|
|
||||||
|
panel.add(new JLabel("Font Size:"));
|
||||||
panel.add(fontSizeSpinner);
|
panel.add(fontSizeSpinner);
|
||||||
|
|
||||||
panel.add(new JLabel(""));
|
panel.add(new JLabel(""));
|
||||||
panel.add(alwaysOnTopBox);
|
panel.add(alwaysOnTopBox);
|
||||||
|
|
||||||
|
panel.add(new JLabel("Clear History"));
|
||||||
|
panel.add(cleear);
|
||||||
|
|
||||||
JButton apply = new JButton("Apply");
|
JButton apply = new JButton("Apply");
|
||||||
JButton close = new JButton("Close");
|
JButton close = new JButton("Close");
|
||||||
|
|
||||||
apply.addActionListener(e -> {
|
apply.addActionListener(e -> {
|
||||||
String theme = (String) themeBox.getSelectedItem();
|
String theme = (String) colorThemeBox.getSelectedItem();
|
||||||
int fontSize = (int) fontSizeSpinner.getValue();
|
int fontSize = (int) fontSizeSpinner.getValue();
|
||||||
boolean alwaysOnTop = alwaysOnTopBox.isSelected();
|
boolean alwaysOnTop = alwaysOnTopBox.isSelected();
|
||||||
|
|
||||||
@@ -560,11 +451,20 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
FlatAnimatedLafChange.showSnapshot();
|
FlatAnimatedLafChange.showSnapshot();
|
||||||
if ("Light".equals(theme)) {
|
switch (theme) {
|
||||||
FlatIntelliJLaf.setup();
|
case "Default" -> {
|
||||||
} else {
|
if (FlatLaf.isLafDark()) {
|
||||||
FlatDarculaLaf.setup();
|
FlatDarculaLaf.setup();
|
||||||
}
|
} else {
|
||||||
|
FlatIntelliJLaf.setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "MacDarkBlue" -> MacDarkBlue.setup();
|
||||||
|
case "MacDarkRed" -> MacDarkRed.setup();
|
||||||
|
case "MacLightBlue" -> MacLightBlue.setup();
|
||||||
|
case "MacLightRed" -> MacLightRed.setup();
|
||||||
|
default -> FlatDarculaLaf.setup();
|
||||||
|
}
|
||||||
FlatLaf.updateUI();
|
FlatLaf.updateUI();
|
||||||
FlatAnimatedLafChange.hideSnapshotWithAnimation();
|
FlatAnimatedLafChange.hideSnapshotWithAnimation();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -606,161 +506,60 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateMode(String Mode) {
|
private void UpdateMode(String Mode) {
|
||||||
if (Mode == "Standard") {
|
if ("Standard".equals(Mode)) {
|
||||||
System.out.println("User has Selected Standard");
|
System.out.println("User has Selected Standard");
|
||||||
} else if (Mode == "Scientific") {
|
} else if ("Scientific".equals(Mode)) {
|
||||||
// System.out.println("User has Selected Scientific Calculator");
|
|
||||||
CalculatorModes.Scientific();
|
CalculatorModes.Scientific();
|
||||||
} else if (Mode == "Data calculation") {
|
} else if ("Data calculation".equals(Mode)) {
|
||||||
// System.out.println("User has Selected Date calculation mode");
|
|
||||||
CalculatorModes.DateCalculation();
|
CalculatorModes.DateCalculation();
|
||||||
} else {
|
} else {
|
||||||
System.out.println(Mode);
|
System.out.println(Mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void backspace() {
|
|
||||||
String tmp = display.getText();
|
/**
|
||||||
if (tmp.length()>1) // if there is more than 1 character
|
* Determines if a theme is dark mode
|
||||||
display.setText( tmp.substring(0, tmp.length()-1) );
|
*/
|
||||||
else // otherwise just make display empty
|
private boolean isThemeDark(String theme) {
|
||||||
display.setText("");
|
if (theme == null) return true;
|
||||||
|
return theme.toLowerCase().contains("dark") ||
|
||||||
|
"Default".equals(theme) && FlatLaf.isLafDark();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearEntry() {
|
/**
|
||||||
// Clear only the current display (entry)
|
* Gets the light/dark counterpart of a theme
|
||||||
display.setText("");
|
* For example: MacDarkBlue <-> MacLightBlue
|
||||||
}
|
*/
|
||||||
|
private String getThemeCounterpart(String theme, boolean toLight) {
|
||||||
private void clearAll() {
|
if (theme == null || "Default".equals(theme)) {
|
||||||
// Clear everything including operands and operator
|
return "Default";
|
||||||
display.setText("");
|
|
||||||
operand1 = "";
|
|
||||||
operand2 = "";
|
|
||||||
operator = "";
|
|
||||||
resultVisible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reciprocal() {
|
|
||||||
String tmp = display.getText();
|
|
||||||
if (tmp == null || tmp.isEmpty()) return;
|
|
||||||
double value = Double.parseDouble(tmp);
|
|
||||||
if (value != 0) {
|
|
||||||
result = 1.0 / value;
|
|
||||||
display.setText("" + result);
|
|
||||||
resultVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void square() {
|
|
||||||
String tmp = display.getText();
|
|
||||||
if (tmp == null || tmp.isEmpty()) return;
|
|
||||||
double value = Double.parseDouble(tmp);
|
|
||||||
result = value * value;
|
|
||||||
display.setText("" + result);
|
|
||||||
resultVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void squareRoot() {
|
|
||||||
String tmp = display.getText();
|
|
||||||
if (tmp == null || tmp.isEmpty()) return;
|
|
||||||
double value = Double.parseDouble(tmp);
|
|
||||||
if (value >= 0) {
|
|
||||||
result = Math.sqrt(value);
|
|
||||||
display.setText("" + result);
|
|
||||||
resultVisible = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void processDigit(String actionCommand) {
|
|
||||||
if (resultVisible == true) {
|
|
||||||
display.setText("");
|
|
||||||
resultVisible = false;
|
|
||||||
}
|
|
||||||
display.setText(display.getText() + actionCommand);
|
|
||||||
|
|
||||||
}
|
|
||||||
private void setOperator(String daop) {
|
|
||||||
operand1 = display.getText();
|
|
||||||
operator = daop;
|
|
||||||
display.setText("");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void math() {
|
|
||||||
operand2 = display.getText();
|
|
||||||
double op1 = Double.parseDouble(operand1);
|
|
||||||
double op2 = Double.parseDouble(operand2);
|
|
||||||
|
|
||||||
if (operator == "+") {
|
|
||||||
result = op1+op2;
|
|
||||||
} else if (operator == "-") {
|
|
||||||
result = op1-op2;
|
|
||||||
} else if (operator == "*") {
|
|
||||||
result = op1*op2;
|
|
||||||
} else if (operator == "/") {
|
|
||||||
result = op1/op2;
|
|
||||||
} else if (operator == "%") {
|
|
||||||
result = op1 % op2;
|
|
||||||
} else {
|
|
||||||
result = op2;
|
|
||||||
System.out.println("Op: " + op1);
|
|
||||||
System.out.println("Op2: " + op2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
operator = "";
|
// Handle Mac themes
|
||||||
operand1 = "";
|
if (theme.startsWith("Mac")) {
|
||||||
operand2 = "";
|
if (toLight) {
|
||||||
|
// Switch to light variant
|
||||||
|
return theme.replace("Dark", "Light");
|
||||||
|
} else {
|
||||||
|
// Switch to dark variant
|
||||||
|
return theme.replace("Light", "Dark");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resultVisible = true;
|
return theme;
|
||||||
display.setText(""+result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadPreferences() {
|
// Implement the keyPressed method
|
||||||
int fontSize = prefs.getInt(PREF_FONT_SIZE, 22);
|
|
||||||
boolean alwaysOnTop = prefs.getBoolean(PREF_ALWAYS_ON_TOP, false);
|
|
||||||
|
|
||||||
display.setFont(new Font("Segoe UI", Font.PLAIN, fontSize));
|
|
||||||
setAlwaysOnTop(alwaysOnTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void changeThemes(String theme) {
|
|
||||||
boolean dark = switch (theme.toLowerCase()) {
|
|
||||||
case "dark", "darcula", "macdarkblue", "macdarkred" -> true;
|
|
||||||
default -> false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only switch if needed
|
|
||||||
if (FlatLaf.isLafDark() != dark || isCustomMacTheme(theme)) {
|
|
||||||
EventQueue.invokeLater(() -> {
|
|
||||||
FlatAnimatedLafChange.showSnapshot();
|
|
||||||
try {
|
|
||||||
switch (theme.toLowerCase()) {
|
|
||||||
case "light" -> FlatIntelliJLaf.setup();
|
|
||||||
case "dark", "darcula" -> FlatDarculaLaf.setup();
|
|
||||||
case "macdarkblue" -> MacDarkBlue.setup();
|
|
||||||
case "macdarkred" -> MacDarkRed.setup();
|
|
||||||
default -> FlatDarculaLaf.setup();
|
|
||||||
}
|
|
||||||
FlatLaf.updateUI();
|
|
||||||
} finally {
|
|
||||||
FlatAnimatedLafChange.hideSnapshotWithAnimation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Implement the keyPressed method
|
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
int keyCode = e.getKeyCode();
|
int keyCode = e.getKeyCode();
|
||||||
// System.out.println("Key Pressed: " + KeyEvent.getKeyText(keyCode));
|
// System.out.println("Key Pressed: " + KeyEvent.getKeyText(keyCode));
|
||||||
if (KeyEvent.getKeyText(keyCode) == "Enter") {
|
if ("Enter".equals(KeyEvent.getKeyText(keyCode))) {
|
||||||
math();
|
calculatorUtils.math();
|
||||||
} else if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
|
} else if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
|
||||||
backspace();
|
calculatorUtils.backspace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement the keyReleased method
|
// Implement the keyReleased method
|
||||||
@@ -777,20 +576,19 @@ public class Calculator extends JFrame implements KeyListener {
|
|||||||
// System.out.println("Key Typed: " + keyChar);
|
// System.out.println("Key Typed: " + keyChar);
|
||||||
System.out.println(c + " KT");
|
System.out.println(c + " KT");
|
||||||
if (c >= '0' && c <= '9') {
|
if (c >= '0' && c <= '9') {
|
||||||
processDigit(String.valueOf(c));
|
calculatorUtils.processDigit(String.valueOf(c));
|
||||||
}
|
}
|
||||||
if (c == '+') {
|
if (c == '+') {
|
||||||
setOperator("+");
|
calculatorUtils.setOperator("+");
|
||||||
} else if (c == '*') {
|
} else if (c == '*') {
|
||||||
setOperator("*");
|
calculatorUtils.setOperator("*");
|
||||||
} else if (c == '/') {
|
} else if (c == '/') {
|
||||||
setOperator("/");
|
calculatorUtils.setOperator("/");
|
||||||
} else if (c == '-') {
|
} else if (c == '-') {
|
||||||
setOperator("-");
|
calculatorUtils.setOperator("-");
|
||||||
}
|
}
|
||||||
if (c == '=') {
|
if (c == '=') {
|
||||||
math();
|
calculatorUtils.math();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ public class CalculatorHistory {
|
|||||||
* Initializes the history file and directory structure.
|
* Initializes the history file and directory structure.
|
||||||
* Creates the directory and file if they don't exist, and loads existing history.
|
* Creates the directory and file if they don't exist, and loads existing history.
|
||||||
*/
|
*/
|
||||||
private void initializeHistoryFile() {
|
public void initializeHistoryFile() {
|
||||||
try {
|
try {
|
||||||
// Create directory if it doesn't exist
|
// Create directory if it doesn't exist
|
||||||
Path dir = Paths.get(HISTORY_DIR);
|
Path dir = Paths.get(HISTORY_DIR);
|
||||||
@@ -75,7 +75,7 @@ public class CalculatorHistory {
|
|||||||
/**
|
/**
|
||||||
* Loads history from the file into memory.
|
* Loads history from the file into memory.
|
||||||
*/
|
*/
|
||||||
private void loadHistory() {
|
public void loadHistory() {
|
||||||
try {
|
try {
|
||||||
if (Files.exists(historyFilePath)) {
|
if (Files.exists(historyFilePath)) {
|
||||||
List<String> lines = Files.readAllLines(historyFilePath);
|
List<String> lines = Files.readAllLines(historyFilePath);
|
||||||
@@ -134,5 +134,4 @@ public class CalculatorHistory {
|
|||||||
public String getHistoryFilePath() {
|
public String getHistoryFilePath() {
|
||||||
return historyFilePath.toString();
|
return historyFilePath.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
20
src/main/java/dev/sillyangel/calc/themes/MacLightBlue.java
Normal file
20
src/main/java/dev/sillyangel/calc/themes/MacLightBlue.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package dev.sillyangel.calc.themes;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.themes.FlatMacLightLaf;
|
||||||
|
|
||||||
|
public class MacLightBlue extends FlatMacLightLaf {
|
||||||
|
public static final String NAME = "MacLightBlue";
|
||||||
|
|
||||||
|
public static boolean setup() {
|
||||||
|
return setup( new MacLightBlue() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void installLafInfo() {
|
||||||
|
installLafInfo( NAME, MacLightBlue.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# base theme (light, dark, intellij, darcula, maclight or macdark); only used by theme editor
|
||||||
|
@baseTheme = macdark
|
||||||
|
|
||||||
|
@accentColor = #0e59c3ff
|
||||||
20
src/main/java/dev/sillyangel/calc/themes/MacLightRed.java
Normal file
20
src/main/java/dev/sillyangel/calc/themes/MacLightRed.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package dev.sillyangel.calc.themes;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.themes.FlatMacLightLaf;
|
||||||
|
|
||||||
|
public class MacLightRed extends FlatMacLightLaf {
|
||||||
|
public static final String NAME = "MacLightRed";
|
||||||
|
|
||||||
|
public static boolean setup() {
|
||||||
|
return setup( new MacLightRed() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void installLafInfo() {
|
||||||
|
installLafInfo( NAME, MacLightRed.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
@baseTheme = macdark
|
||||||
|
@accentColor = #a83e32
|
||||||
254
src/main/java/dev/sillyangel/calc/utils/Utils.java
Normal file
254
src/main/java/dev/sillyangel/calc/utils/Utils.java
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
package dev.sillyangel.calc.utils;
|
||||||
|
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import java.awt.datatransfer.Clipboard;
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
import java.awt.datatransfer.StringSelection;
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.border.EmptyBorder;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
import javax.swing.JDialog;
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.FlowLayout;
|
||||||
|
import java.awt.Font;
|
||||||
|
import dev.sillyangel.calc.Calculator;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
// private double result;
|
||||||
|
// private boolean resultVisible;
|
||||||
|
private Calculator ci;
|
||||||
|
public Utils(Calculator ci) {
|
||||||
|
System.out.printf("");
|
||||||
|
this.ci = ci;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reciprocal() {
|
||||||
|
String tmp = ci.display.getText();
|
||||||
|
if (tmp == null || tmp.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
double value = Double.parseDouble(tmp);
|
||||||
|
if (value != 0) {
|
||||||
|
ci.result = 1.0 / value;
|
||||||
|
ci.display.setText("" + ci.result);
|
||||||
|
ci.resultVisible = true;
|
||||||
|
} else {
|
||||||
|
ci.display.setText("Error: Div by Zero");
|
||||||
|
ci.resultVisible = false;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
ci.display.setText("Error: Invalid Input");
|
||||||
|
ci.resultVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearAll() {
|
||||||
|
ci.display.setText("");
|
||||||
|
ci.operand1 = "";
|
||||||
|
ci.operand2 = "";
|
||||||
|
ci.operator = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void square() {
|
||||||
|
String tmp = ci.display.getText();
|
||||||
|
if (tmp == null || tmp.isEmpty()) return;
|
||||||
|
double value = Double.parseDouble(tmp);
|
||||||
|
ci.result = value * value;
|
||||||
|
ci.display.setText("" + ci.result);
|
||||||
|
ci.resultVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void squareRoot() {
|
||||||
|
String tmp = ci.display.getText();
|
||||||
|
if (tmp == null || tmp.isEmpty()) return;
|
||||||
|
double value = Double.parseDouble(tmp);
|
||||||
|
if (value >= 0) {
|
||||||
|
ci.result = Math.sqrt(value);
|
||||||
|
ci.display.setText("" + ci.result);
|
||||||
|
ci.resultVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void backspace() {
|
||||||
|
String tmp = ci.display.getText();
|
||||||
|
if (tmp.length()>1) // if there is more than 1 character
|
||||||
|
ci.display.setText( tmp.substring(0, tmp.length()-1) );
|
||||||
|
else // otherwise just make display empty
|
||||||
|
ci.display.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void math() {
|
||||||
|
ci.operand2 = ci.display.getText();
|
||||||
|
double op1 = Double.parseDouble(ci.operand1);
|
||||||
|
double op2 = Double.parseDouble(ci.operand2);
|
||||||
|
|
||||||
|
if ("+".equals(ci.operator)) {
|
||||||
|
ci.result = op1+op2;
|
||||||
|
} else if ("-".equals(ci.operator)) {
|
||||||
|
ci.result = op1-op2;
|
||||||
|
} else if ("*".equals(ci.operator)) {
|
||||||
|
ci.result = op1*op2;
|
||||||
|
} else if ("/".equals(ci.operator)) {
|
||||||
|
ci.result = op1/op2;
|
||||||
|
} else if ("%".equals(ci.operator)) {
|
||||||
|
ci.result = op1 % op2;
|
||||||
|
} else {
|
||||||
|
ci.result = op2;
|
||||||
|
System.out.println("Op: " + op1);
|
||||||
|
System.out.println("Op2: " + op2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save calculation to history
|
||||||
|
if (!ci.operator.isEmpty()) {
|
||||||
|
String calculation = ci.operand1 + " " + ci.operator + " " + ci.operand2 + " = " + ci.result;
|
||||||
|
ci.history.saveToHistory(calculation);
|
||||||
|
}
|
||||||
|
|
||||||
|
ci.operator = "";
|
||||||
|
ci.operand1 = "";
|
||||||
|
ci.operand2 = "";
|
||||||
|
|
||||||
|
ci.resultVisible = true;
|
||||||
|
ci.display.setText(""+ci.result);
|
||||||
|
saveToUndoStack(""+ci.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveToUndoStack(String value) {
|
||||||
|
// Remove any states after current index (if user undid and then made a new action)
|
||||||
|
if (ci.undoIndex < ci.undoStack.size() - 1) {
|
||||||
|
ci.undoStack.subList(ci.undoIndex + 1, ci.undoStack.size()).clear();
|
||||||
|
}
|
||||||
|
ci.undoStack.add(value);
|
||||||
|
ci.undoIndex = ci.undoStack.size() - 1;
|
||||||
|
}
|
||||||
|
public void undoAction() {
|
||||||
|
if (ci.undoIndex > 0) {
|
||||||
|
ci.undoIndex--;
|
||||||
|
ci.display.setText(ci.undoStack.get(ci.undoIndex));
|
||||||
|
} else {
|
||||||
|
System.out.println("Nothing to undo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void redoAction() {
|
||||||
|
if (ci.undoIndex < ci.undoStack.size() - 1) {
|
||||||
|
ci.undoIndex++;
|
||||||
|
ci.display.setText(ci.undoStack.get(ci.undoIndex));
|
||||||
|
} else {
|
||||||
|
System.out.println("Nothing to redo");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clipboard operations
|
||||||
|
public void cutAction() {
|
||||||
|
copyAction();
|
||||||
|
ci.display.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyAction() {
|
||||||
|
String text = ci.display.getText();
|
||||||
|
if (text != null && !text.isEmpty()) {
|
||||||
|
StringSelection selection = new StringSelection(text);
|
||||||
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
clipboard.setContents(selection, null);
|
||||||
|
System.out.println("Copied: " + text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void pasteAction() {
|
||||||
|
try {
|
||||||
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
||||||
|
String text = (String) clipboard.getData(DataFlavor.stringFlavor);
|
||||||
|
if (text != null && !text.isEmpty()) {
|
||||||
|
// Validate that pasted content is a valid number
|
||||||
|
try {
|
||||||
|
Double.parseDouble(text);
|
||||||
|
ci.display.setText(text);
|
||||||
|
saveToUndoStack(text);
|
||||||
|
} catch (NumberFormatException ex) {
|
||||||
|
JOptionPane.showMessageDialog(ci,
|
||||||
|
"Pasted content is not a valid number",
|
||||||
|
"Invalid Input", JOptionPane.ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
System.err.println("Error pasting: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void processDigit(String actionCommand) {
|
||||||
|
if (ci.resultVisible) {
|
||||||
|
ci.display.setText("");
|
||||||
|
ci.resultVisible = false;
|
||||||
|
}
|
||||||
|
String newValue = ci.display.getText() + actionCommand;
|
||||||
|
ci.display.setText(newValue);
|
||||||
|
saveToUndoStack(newValue);
|
||||||
|
}
|
||||||
|
public void setOperator(String daop) {
|
||||||
|
ci.operand1 = ci.display.getText();
|
||||||
|
ci.operator = daop;
|
||||||
|
ci.display.setText("");
|
||||||
|
}
|
||||||
|
public void clearEntry() {
|
||||||
|
ci.display.setText("");
|
||||||
|
}
|
||||||
|
public void showHistoryDialog() {
|
||||||
|
JDialog historyDialog = new JDialog(this.ci, "Calculation History", true);
|
||||||
|
historyDialog.setSize(500, 400);
|
||||||
|
historyDialog.setLocationRelativeTo(this.ci);
|
||||||
|
historyDialog.setLayout(new BorderLayout(10, 10));
|
||||||
|
|
||||||
|
List<String> historyList = ci.history.getCalculationHistory();
|
||||||
|
|
||||||
|
if (historyList.isEmpty()) {
|
||||||
|
JLabel emptyLabel = new JLabel("No calculation history yet", SwingConstants.CENTER);
|
||||||
|
emptyLabel.setFont(new Font("Segoe UI", Font.PLAIN, 14));
|
||||||
|
historyDialog.add(emptyLabel, BorderLayout.CENTER);
|
||||||
|
} else {
|
||||||
|
JTextArea historyTextArea = new JTextArea();
|
||||||
|
historyTextArea.setEditable(false);
|
||||||
|
historyTextArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
|
||||||
|
|
||||||
|
StringBuilder historyText = new StringBuilder();
|
||||||
|
for (String entry : historyList) {
|
||||||
|
historyText.append(entry).append("\n");
|
||||||
|
}
|
||||||
|
historyTextArea.setText(historyText.toString());
|
||||||
|
|
||||||
|
JScrollPane scrollPane = new JScrollPane(historyTextArea);
|
||||||
|
historyDialog.add(scrollPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// Status label
|
||||||
|
JLabel statusLabel = new JLabel("Total calculations: " + historyList.size());
|
||||||
|
statusLabel.setBorder(new EmptyBorder(5, 10, 5, 10));
|
||||||
|
historyDialog.add(statusLabel, BorderLayout.NORTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
|
JButton closeButton = new JButton("Close");
|
||||||
|
closeButton.addActionListener(e -> historyDialog.dispose());
|
||||||
|
buttonPanel.add(closeButton);
|
||||||
|
|
||||||
|
historyDialog.add(buttonPanel, BorderLayout.SOUTH);
|
||||||
|
historyDialog.setVisible(true);
|
||||||
|
}
|
||||||
|
public void clearHistoryAction() {
|
||||||
|
int result = JOptionPane.showConfirmDialog(this.ci,
|
||||||
|
"Are you sure you want to clear all calculation history?",
|
||||||
|
"Clear History", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
|
||||||
|
|
||||||
|
if (result == JOptionPane.YES_OPTION) {
|
||||||
|
ci.history.clearHistory();
|
||||||
|
JOptionPane.showMessageDialog(this.ci,
|
||||||
|
"History cleared successfully",
|
||||||
|
"Success", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user