diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextArea.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextArea.java index 74d907ab..c6fe8d44 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextArea.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextArea.java @@ -34,6 +34,7 @@ import org.fife.ui.rsyntaxtextarea.Token; import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RTextAreaUI; import com.formdev.flatlaf.UIDefaultsLoaderAccessor; +import com.formdev.flatlaf.themeeditor.FlatSyntaxTextAreaActions.InsertColorAction; import com.formdev.flatlaf.themeeditor.FlatSyntaxTextAreaActions.DuplicateLinesAction; import com.formdev.flatlaf.themeeditor.FlatSyntaxTextAreaActions.IncrementNumberAction; @@ -64,6 +65,7 @@ class FlatSyntaxTextArea actionMap.put( FlatSyntaxTextAreaActions.duplicateLinesDownAction, new DuplicateLinesAction( FlatSyntaxTextAreaActions.duplicateLinesDownAction, false ) ); actionMap.put( FlatSyntaxTextAreaActions.incrementNumberAction, new IncrementNumberAction( FlatSyntaxTextAreaActions.incrementNumberAction, true ) ); actionMap.put( FlatSyntaxTextAreaActions.decrementNumberAction, new IncrementNumberAction( FlatSyntaxTextAreaActions.decrementNumberAction, false ) ); + actionMap.put( FlatSyntaxTextAreaActions.insertColorAction, new InsertColorAction( FlatSyntaxTextAreaActions.insertColorAction ) ); // add editor key strokes InputMap inputMap = getInputMap(); diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextAreaActions.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextAreaActions.java index d5614c78..43428898 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextAreaActions.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatSyntaxTextAreaActions.java @@ -16,10 +16,19 @@ package com.formdev.flatlaf.themeeditor; +import java.awt.Color; +import java.awt.Component; +import java.awt.Window; import java.awt.event.ActionEvent; +import java.util.prefs.Preferences; +import javax.swing.JColorChooser; +import javax.swing.JDialog; +import javax.swing.JTabbedPane; +import javax.swing.SwingUtilities; import javax.swing.text.BadLocationException; import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RecordableTextAction; +import com.formdev.flatlaf.UIDefaultsLoaderAccessor; /** * @author Karl Tauber @@ -30,6 +39,70 @@ class FlatSyntaxTextAreaActions static final String duplicateLinesDownAction = "FlatLaf.DuplicateLinesDownAction"; static final String incrementNumberAction = "FlatLaf.IncrementNumberAction"; static final String decrementNumberAction = "FlatLaf.DecrementNumberAction"; + static final String insertColorAction = "FlatLaf.InsertColorAction"; + + static int[] findColorAt( RTextArea textArea, int position ) { + try { + int start = position; + int end = position; + + // find first '#' or hex digit + for( int i = position - 1; i >= 0; i-- ) { + char ch = textArea.getText( i, 1 ).charAt( 0 ); + if( ch != '#' && !isHexDigit( ch ) ) + break; + start = i; + } + + // find last hex digit + int length = textArea.getDocument().getLength(); + for( int i = position; i < length; i++ ) { + if( !isHexDigit( textArea.getText( i, 1 ).charAt( 0 ) ) ) + break; + end = i + 1; + } + + // check for valid length (#RGB, #RGBA, #RRGGBB or #RRGGBBAA) + int len = end - start; + if( len != 4 && len != 5 && len != 7 && len != 9 ) + return null; + + // check whether starts with '#' + if( textArea.getText( start, 1 ).charAt( 0 ) != '#' ) + return null; + + return new int[] { start, end - start }; + } catch( BadLocationException | IndexOutOfBoundsException | NumberFormatException ex ) { + ex.printStackTrace(); + return null; + } + } + + static boolean isHexDigit( char ch ) { + return Character.isDigit( ch ) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + } + + static String colorToString( Color color ) { + int rgb = color.getRGB(); + int alpha = color.getAlpha(); + String format; + if( (rgb & 0xf) == ((rgb >> 4) & 0xf) && + ((rgb >> 8) & 0xf) == ((rgb >> 12) & 0xf) && + ((rgb >> 16) & 0xf) == ((rgb >> 20) & 0xf) && + ((rgb >> 24) & 0xf) == ((rgb >> 28) & 0xf) ) + { + // short format (#RGB or #RGBA) + format = (alpha != 255) ? "#%03x%01x" : "#%03x"; + rgb = (rgb & 0xf) | ((rgb >> 4) & 0xf0) | ((rgb >> 8) & 0xf00); + alpha &= 0xf; + } else { + // long format (#RRGGBB or #RRGGBBAA) + format = (alpha != 255) ? "#%06x%02x" : "#%06x"; + rgb &= 0xffffff; + } + + return String.format( format, rgb, alpha ); + } //---- class DuplicateLinesAction ----------------------------------------- @@ -143,33 +216,12 @@ class FlatSyntaxTextAreaActions private boolean incrementRGBColor( RTextArea textArea ) { try { int caretPosition = textArea.getCaretPosition(); - int start = caretPosition; - int end = caretPosition; - - // find first '#' or hex digit - for( int i = caretPosition - 1; i >= 0; i-- ) { - char ch = textArea.getText( i, 1 ).charAt( 0 ); - if( ch != '#' && !isHexDigit( ch ) ) - break; - start = i; - } - - // find last hex digit - int length = textArea.getDocument().getLength(); - for( int i = caretPosition; i < length; i++ ) { - if( !isHexDigit( textArea.getText( i, 1 ).charAt( 0 ) ) ) - break; - end = i + 1; - } - - // check for valid length (#RGB, #RGBA, #RRGGBB or #RRGGBBAA) - int len = end - start; - if( len != 4 && len != 5 && len != 7 && len != 9 ) + int[] result = findColorAt( textArea, caretPosition ); + if( result == null ) return false; - // check whether starts with '#' - if( textArea.getText( start, 1 ).charAt( 0 ) != '#' ) - return false; + int start = result[0]; + int len = result[1]; // find start of color part that should be changed (red, green, blue or alpha) int start2; @@ -212,8 +264,66 @@ class FlatSyntaxTextAreaActions } } - private boolean isHexDigit( char ch ) { - return Character.isDigit( ch ) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); + @Override + public String getMacroID() { + return getName(); + } + } + + //---- class InsertColorAction -------------------------------------------- + + static class InsertColorAction + extends RecordableTextAction + { + private static final String KEY_SELECTED_TAB = "colorchooser.selectedTab"; + + InsertColorAction( String name ) { + super( name ); + } + + @Override + public void actionPerformedImpl( ActionEvent e, RTextArea textArea ) { + try { + // find current color at caret + Color currentColor = Color.white; + int caretPosition = textArea.getCaretPosition(); + int start; + int len; + int[] result = findColorAt( textArea, caretPosition ); + if( result != null ) { + start = result[0]; + len = result[1]; + + String str = textArea.getText( start, len ); + int rgb = UIDefaultsLoaderAccessor.parseColorRGBA( str ); + currentColor = new Color( rgb, true ); + } else { + start = caretPosition; + len = 0; + } + + // create color chooser + JColorChooser chooser = new JColorChooser( currentColor ); + Component tabbedPane = chooser.getComponent( 0 ); + Preferences state = Preferences.userRoot().node( FlatThemeFileEditor.PREFS_ROOT_PATH ); + int selectedTab = state.getInt( KEY_SELECTED_TAB, -1 ); + if( tabbedPane instanceof JTabbedPane && selectedTab >= 0 && selectedTab < ((JTabbedPane)tabbedPane).getTabCount() ) + ((JTabbedPane)tabbedPane).setSelectedIndex( selectedTab ); + + // show color chooser dialog + Window window = SwingUtilities.windowForComponent( textArea ); + JDialog dialog = JColorChooser.createDialog( window, "Insert Color", true, chooser, e2 -> { + // update editor + textArea.replaceRange( colorToString( chooser.getColor() ), start, start + len ); + + // remember selected tab + if( tabbedPane instanceof JTabbedPane ) + state.putInt( KEY_SELECTED_TAB, ((JTabbedPane)tabbedPane).getSelectedIndex() ); + }, null ); + dialog.setVisible( true ); + } catch( BadLocationException | IndexOutOfBoundsException | NumberFormatException ex ) { + ex.printStackTrace(); + } } @Override diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java index db6ca0f5..12ddd143 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeEditorPane.java @@ -20,9 +20,11 @@ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.Window; +import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.InputMap; import javax.swing.JComponent; @@ -290,6 +292,12 @@ class FlatThemeEditorPane revalidate(); } + void notifyTextAreaAction( String actionKey ) { + Action action = textArea.getActionMap().get( actionKey ); + if( action != null && action.isEnabled() ) + action.actionPerformed( new ActionEvent( textArea, ActionEvent.ACTION_PERFORMED, null ) ); + } + //---- class FlatSyntaxScheme --------------------------------------------- private static class FlatSyntaxScheme diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java index bc4cc3c8..087b56fd 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.java @@ -32,6 +32,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.function.Supplier; import java.util.prefs.Preferences; @@ -56,7 +57,7 @@ import com.formdev.flatlaf.util.UIScale; public class FlatThemeFileEditor extends JFrame { - private static final String PREFS_ROOT_PATH = "/flatlaf-theme-editor"; + static final String PREFS_ROOT_PATH = "/flatlaf-theme-editor"; private static final String KEY_DIRECTORIES = "directories"; private static final String KEY_RECENT_DIRECTORY = "recentDirectory"; private static final String KEY_RECENT_FILE = "recentFile"; @@ -78,6 +79,9 @@ public class FlatThemeFileEditor ? new File( args[0] ) : null; + Locale.setDefault( Locale.ENGLISH ); + System.setProperty( "user.language", "en" ); + SwingUtilities.invokeLater( () -> { FlatLaf.registerCustomDefaultsSource( "com.formdev.flatlaf.themeeditor" ); @@ -375,6 +379,12 @@ public class FlatThemeFileEditor themeEditorPane.showFindReplaceBar(); } + private void insertColor() { + FlatThemeEditorPane themeEditorPane = (FlatThemeEditorPane) tabbedPane.getSelectedComponent(); + if( themeEditorPane != null ) + themeEditorPane.notifyTextAreaAction( FlatSyntaxTextAreaActions.insertColorAction ); + } + private void showHidePreview() { boolean show = previewMenuItem.isSelected(); for( FlatThemeEditorPane themeEditorPane : getThemeEditorPanes() ) @@ -562,6 +572,7 @@ public class FlatThemeFileEditor exitMenuItem = new JMenuItem(); editMenu = new JMenu(); findMenuItem = new JMenuItem(); + insertColorMenuItem = new JMenuItem(); viewMenu = new JMenu(); previewMenuItem = new JCheckBoxMenuItem(); lightLafMenuItem = new JRadioButtonMenuItem(); @@ -643,6 +654,13 @@ public class FlatThemeFileEditor findMenuItem.setMnemonic('F'); findMenuItem.addActionListener(e -> find()); editMenu.add(findMenuItem); + editMenu.addSeparator(); + + //---- insertColorMenuItem ---- + insertColorMenuItem.setText("Insert Color"); + insertColorMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_G, KeyEvent.CTRL_DOWN_MASK)); + insertColorMenuItem.addActionListener(e -> insertColor()); + editMenu.add(insertColorMenuItem); } menuBar.add(editMenu); @@ -786,6 +804,7 @@ public class FlatThemeFileEditor private JMenuItem exitMenuItem; private JMenu editMenu; private JMenuItem findMenuItem; + private JMenuItem insertColorMenuItem; private JMenu viewMenu; private JCheckBoxMenuItem previewMenuItem; private JRadioButtonMenuItem lightLafMenuItem; diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.jfd b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.jfd index 8606a861..4a7f7b1c 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.jfd +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatThemeFileEditor.jfd @@ -94,6 +94,15 @@ new FormModel { "mnemonic": 70 addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "find", false ) ) } ) + add( new FormComponent( "javax.swing.JPopupMenu$Separator" ) { + name: "separator5" + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "insertColorMenuItem" + "text": "Insert Color" + "accelerator": static javax.swing.KeyStroke getKeyStroke( 71, 130, false ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "insertColor", false ) ) + } ) } ) add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { name: "viewMenu"