From 8ec907050e49ca4bb4551b750e55aa72ead6c262 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Fri, 1 Jan 2021 17:43:05 +0100 Subject: [PATCH] Theme Editor: - "Open Directory" action added - remember recently opened directories - remember recently selected file --- .../themeeditor/FlatThemeEditorPane.java | 5 + .../themeeditor/FlatThemeFileEditor.java | 314 ++++++++++++++++-- .../themeeditor/FlatThemeFileEditor.jfd | 24 +- .../flatlaf/themeeditor/icons/menu-open.svg | 3 + 4 files changed, 324 insertions(+), 22 deletions(-) create mode 100644 flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/icons/menu-open.svg 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 836e1ce5..47a13b33 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 @@ -136,6 +136,11 @@ class FlatThemeEditorPane return font.deriveFont( (float) newFontSize ); } + @Override + public boolean requestFocusInWindow() { + return textArea.requestFocusInWindow(); + } + void setBaseFiles( List baseFiles ) { textArea.propertiesSupport.setBaseFiles( baseFiles ); } 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 01bd54b3..9897d331 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 @@ -17,21 +17,27 @@ package com.formdev.flatlaf.themeeditor; import java.awt.BorderLayout; +import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.*; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; +import java.util.Objects; import java.util.function.Supplier; +import java.util.prefs.Preferences; import javax.swing.*; -import com.formdev.flatlaf.extras.components.*; import net.miginfocom.swing.*; import com.formdev.flatlaf.FlatLightLaf; import com.formdev.flatlaf.extras.FlatInspector; +import com.formdev.flatlaf.extras.FlatSVGIcon; import com.formdev.flatlaf.extras.FlatUIDefaultsInspector; +import com.formdev.flatlaf.extras.components.*; import com.formdev.flatlaf.util.StringUtils; import com.formdev.flatlaf.util.UIScale; @@ -43,21 +49,26 @@ import com.formdev.flatlaf.util.UIScale; public class FlatThemeFileEditor extends JFrame { + private 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"; + private File dir; + private Preferences state; + private boolean inLoadDirectory; public static void main( String[] args ) { - File dir = new File( args.length > 0 - ? args[0] - : "." ); + File dir = (args.length > 0) + ? new File( args[0] ) + : null; SwingUtilities.invokeLater( () -> { FlatLightLaf.install(); FlatInspector.install( "ctrl alt shift X" ); FlatUIDefaultsInspector.install( "ctrl shift alt Y" ); - FlatThemeFileEditor frame = new FlatThemeFileEditor(); - - frame.loadDirectory( dir ); + FlatThemeFileEditor frame = new FlatThemeFileEditor( dir ); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); frame.setSize( Math.min( UIScale.scale( 800 ), screenSize.width ), @@ -67,24 +78,133 @@ public class FlatThemeFileEditor } ); } - public FlatThemeFileEditor() { + private FlatThemeFileEditor( File dir ) { initComponents(); + + openDirectoryButton.setIcon( new FlatSVGIcon( "com/formdev/flatlaf/themeeditor/icons/menu-open.svg" ) ); + + restoreState(); + + // load directory + if( dir == null ) { + String recentDirectory = state.get( KEY_RECENT_DIRECTORY, null ); + if( recentDirectory != null ) + dir = new File( recentDirectory ); + } + if( dir != null && !dir.isDirectory() ) + dir = null; + if( dir != null ) + loadDirectory( dir ); + } + + private void openDirectory() { + // save all currently open editors before showing directory chooser + if( !saveAll() ) + return; + + // choose directory + JFileChooser chooser = new JFileChooser( dir ) { + @Override + public void approveSelection() { + if( !checkDirectory( this, getSelectedFile() ) ) + return; + + super.approveSelection(); + } + }; + chooser.setFileSelectionMode( JFileChooser.DIRECTORIES_ONLY ); + if( chooser.showOpenDialog( this ) != JFileChooser.APPROVE_OPTION ) + return; + + File selectedFile = chooser.getSelectedFile(); + if( selectedFile == null || selectedFile.equals( dir ) ) + return; + + // open new directory + loadDirectory( selectedFile ); + } + + private boolean checkDirectory( Component parentComponent, File dir ) { + if( !dir.isDirectory() ) { + JOptionPane.showMessageDialog( parentComponent, + "Directory '" + dir + "' does not exist.", + getTitle(), JOptionPane.INFORMATION_MESSAGE ); + return false; + } + + if( getPropertiesFiles( dir ).length == 0 ) { + JOptionPane.showMessageDialog( parentComponent, + "Directory '" + dir + "' does not contain properties files.", + getTitle(), JOptionPane.INFORMATION_MESSAGE ); + return false; + } + + return true; + } + + private void directoryChanged() { + if( inLoadDirectory ) + return; + + Object selectedItem = directoryField.getSelectedItem(); + if( selectedItem == null ) + return; + + File dir = new File( (String) selectedItem ); + if( checkDirectory( this, dir ) ) + loadDirectory( dir ); + else { + // remove from directories history + directoryField.removeItem( selectedItem ); + directoryField.setSelectedItem( this.dir.getAbsolutePath() ); + saveState(); + } } private void loadDirectory( File dir ) { + dir = getCanonicalFile( dir ); + if( Objects.equals( this.dir, dir ) || !dir.isDirectory() ) + return; + + // save all currently open editors + if( !saveAll() ) + return; + this.dir = dir; - try { - directoryField.setText( dir.getCanonicalPath() ); - } catch( IOException ex ) { - directoryField.setText( dir.getAbsolutePath() ); - } + inLoadDirectory = true; + // close all open editors + int tabCount = tabbedPane.getTabCount(); + for( int i = tabCount - 1; i >= 0; i-- ) + tabbedPane.removeTabAt( i ); + + // update directory field + DefaultComboBoxModel model = (DefaultComboBoxModel) directoryField.getModel(); + String dirStr = dir.getAbsolutePath(); + int indexOf = model.getIndexOf( dirStr ); + if( indexOf < 0 ) + model.addElement( dirStr ); + directoryField.setSelectedItem( dirStr ); + + // open all properties files in directory + String recentFile = state.get( KEY_RECENT_FILE, null ); for( File file : getPropertiesFiles( dir ) ) - openFile( file ); + openFile( file, file.getName().equals( recentFile ) ); + + FlatThemeEditorPane themeEditorPane = (FlatThemeEditorPane) tabbedPane.getSelectedComponent(); + if( themeEditorPane != null ) + themeEditorPane.requestFocusInWindow(); + + saveState(); + + inLoadDirectory = false; } private void updateDirectory() { + if( dir == null ) + return; + // update open tabs and remove tabs if file was removed HashSet openFiles = new HashSet<>(); FlatThemeEditorPane[] themeEditorPanes = getThemeEditorPanes(); @@ -99,7 +219,18 @@ public class FlatThemeFileEditor // open newly created files for( File file : getPropertiesFiles( dir ) ) { if( !openFiles.contains( file ) ) - openFile( file ); + openFile( file, false ); + } + } + + private File getCanonicalFile( File dir ) { + if( dir == null ) + return null; + + try { + return dir.getCanonicalFile(); + } catch( IOException ex ) { + return dir.getAbsoluteFile(); } } @@ -111,7 +242,7 @@ public class FlatThemeFileEditor return propertiesFiles; } - private void openFile( File file ) { + private void openFile( File file, boolean select ) { FlatThemeEditorPane themeEditorPane = new FlatThemeEditorPane(); try { themeEditorPane.load( file ); @@ -122,7 +253,6 @@ public class FlatThemeFileEditor Supplier titleFun = () -> { return (themeEditorPane.isDirty() ? "* " : "") + StringUtils.removeTrailing( themeEditorPane.getFile().getName(), ".properties" ); - }; themeEditorPane.addPropertyChangeListener( FlatThemeEditorPane.DIRTY_PROPERTY, e -> { int index = tabbedPane.indexOfComponent( themeEditorPane ); @@ -131,6 +261,18 @@ public class FlatThemeFileEditor } ); tabbedPane.addTab( titleFun.get(), null, themeEditorPane, file.getAbsolutePath() ); + + if( select ) + tabbedPane.setSelectedComponent( themeEditorPane ); + } + + private void selectedTabChanged() { + if( inLoadDirectory ) + return; + + FlatThemeEditorPane themeEditorPane = (FlatThemeEditorPane) tabbedPane.getSelectedComponent(); + String filename = (themeEditorPane != null) ? themeEditorPane.getFile().getName() : null; + putPrefsString( state, KEY_RECENT_FILE, filename ); } private boolean saveAll() { @@ -166,6 +308,9 @@ public class FlatThemeFileEditor } private void nextEditor() { + if( tabbedPane.getTabCount() == 0 ) + return; + int index = tabbedPane.getSelectedIndex() + 1; if( index >= tabbedPane.getTabCount() ) index = 0; @@ -173,6 +318,9 @@ public class FlatThemeFileEditor } private void previousEditor() { + if( tabbedPane.getTabCount() == 0 ) + return; + int index = tabbedPane.getSelectedIndex() - 1; if( index < 0 ) index = tabbedPane.getTabCount() - 1; @@ -185,10 +333,58 @@ public class FlatThemeFileEditor themeEditorPane.showFindReplaceBar(); } + private void restoreState() { + state = Preferences.userRoot().node( PREFS_ROOT_PATH ); + + // restore directories history + String[] directories = getPrefsStrings( state, KEY_DIRECTORIES ); + SortedComboBoxModel model = new SortedComboBoxModel<>( directories ); + directoryField.setModel( model ); + } + + private void saveState() { + // save directories history + ComboBoxModel model = directoryField.getModel(); + String[] directories = new String[model.getSize()]; + for( int i = 0; i < directories.length; i++ ) + directories[i] = model.getElementAt( i ); + putPrefsStrings( state, KEY_DIRECTORIES, directories ); + + // save recent directory + putPrefsString( state, KEY_RECENT_DIRECTORY, dir.getAbsolutePath() ); + } + + private static void putPrefsString( Preferences prefs, String key, String value ) { + if( !StringUtils.isEmpty( value ) ) + prefs.put( key, value ); + else + prefs.remove( key ); + } + + private static String[] getPrefsStrings( Preferences prefs, String key ) { + ArrayList arr = new ArrayList<>(); + for( int i = 0; i < 10000; i++ ) { + String s = prefs.get( key+(i+1), null ); + if( s == null ) + break; + arr.add( s ); + } + return arr.toArray( new String[arr.size()] ); + } + + private static void putPrefsStrings( Preferences prefs, String key, String[] strings ) { + for( int i = 0; i < strings.length; i++ ) + prefs.put( key+(i+1), strings[i] ); + + for( int i = strings.length; prefs.get( key+(i+1), null ) != null; i++ ) + prefs.remove( key+(i+1) ); + } + private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents menuBar = new JMenuBar(); fileMenu = new JMenu(); + openDirectoryMenuItem = new JMenuItem(); saveAllMenuItem = new JMenuItem(); exitMenuItem = new JMenuItem(); editMenu = new JMenu(); @@ -198,7 +394,8 @@ public class FlatThemeFileEditor previousEditorMenuItem = new JMenuItem(); controlPanel = new JPanel(); directoryLabel = new JLabel(); - directoryField = new JTextField(); + directoryField = new JComboBox<>(); + openDirectoryButton = new JButton(); tabbedPane = new FlatTabbedPane(); //======== this ======== @@ -228,6 +425,13 @@ public class FlatThemeFileEditor { fileMenu.setText("File"); + //---- openDirectoryMenuItem ---- + openDirectoryMenuItem.setText("Open Directory..."); + openDirectoryMenuItem.setMnemonic('O'); + openDirectoryMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + openDirectoryMenuItem.addActionListener(e -> openDirectory()); + fileMenu.add(openDirectoryMenuItem); + //---- saveAllMenuItem ---- saveAllMenuItem.setText("Save All"); saveAllMenuItem.setMnemonic('S'); @@ -288,7 +492,8 @@ public class FlatThemeFileEditor "hidemode 3", // columns "[fill]" + - "[grow,fill]", + "[grow,fill]" + + "[fill]", // rows "[]")); @@ -299,13 +504,21 @@ public class FlatThemeFileEditor //---- directoryField ---- directoryField.setEditable(false); directoryField.setFocusable(false); + directoryField.addActionListener(e -> directoryChanged()); controlPanel.add(directoryField, "cell 1 0"); + + //---- openDirectoryButton ---- + openDirectoryButton.setFocusable(false); + openDirectoryButton.addActionListener(e -> openDirectory()); + controlPanel.add(openDirectoryButton, "cell 2 0"); } contentPane.add(controlPanel, BorderLayout.NORTH); //======== tabbedPane ======== { tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + tabbedPane.setFocusable(false); + tabbedPane.addChangeListener(e -> selectedTabChanged()); } contentPane.add(tabbedPane, BorderLayout.CENTER); // JFormDesigner - End of component initialization //GEN-END:initComponents @@ -314,6 +527,7 @@ public class FlatThemeFileEditor // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JMenuBar menuBar; private JMenu fileMenu; + private JMenuItem openDirectoryMenuItem; private JMenuItem saveAllMenuItem; private JMenuItem exitMenuItem; private JMenu editMenu; @@ -323,7 +537,67 @@ public class FlatThemeFileEditor private JMenuItem previousEditorMenuItem; private JPanel controlPanel; private JLabel directoryLabel; - private JTextField directoryField; + private JComboBox directoryField; + private JButton openDirectoryButton; private FlatTabbedPane tabbedPane; // JFormDesigner - End of variables declaration //GEN-END:variables + + //---- class SortedComboBoxModel ------------------------------------------ + + private static class SortedComboBoxModel + extends DefaultComboBoxModel + { + private Comparator comparator; + + public SortedComboBoxModel( E[] items ) { + this( items, null ); + } + + public SortedComboBoxModel( E[] items, Comparator c ) { + super( sort( items, c ) ); + this.comparator = c; + } + + @Override + public void addElement( E obj ) { + if( getSize() == 0 ) { + super.addElement( obj ); + } else { + int index = binarySearch( this, obj, comparator ); + insertElementAt( obj, (index < 0) ? ((-index)-1) : index ); + } + } + + static E[] sort( E[] items, Comparator c ) { + // clone array + items = items.clone(); + + Arrays.sort( items, c ); + return items; + } + + @SuppressWarnings("unchecked") + static int binarySearch( ListModel model, E key, Comparator c ) { + int low = 0; + int high = model.getSize() - 1; + + while( low <= high ) { + int mid = (low + high) / 2; + E midVal = model.getElementAt( mid ); + int cmp; + if( c != null ) + cmp = c.compare( midVal, key ); + else + cmp = ((Comparable)midVal).compareTo( key ); + + if( cmp < 0 ) + low = mid + 1; + else if( cmp > 0 ) + high = mid - 1; + else + return mid; // key found + } + return -(low + 1); // key not found. + } + } } 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 5803684e..b8d961e0 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 @@ -14,7 +14,7 @@ new FormModel { addEvent( new FormEvent( "java.awt.event.WindowListener", "windowActivated", "windowActivated", false ) ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" - "$columnConstraints": "[fill][grow,fill]" + "$columnConstraints": "[fill][grow,fill][fill]" "$rowConstraints": "[]" } ) { name: "controlPanel" @@ -24,19 +24,32 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 0" } ) - add( new FormComponent( "javax.swing.JTextField" ) { + add( new FormComponent( "javax.swing.JComboBox" ) { name: "directoryField" "editable": false "focusable": false + auxiliary() { + "JavaCodeGenerator.typeParameters": "String" + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "directoryChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 0" } ) + add( new FormComponent( "javax.swing.JButton" ) { + name: "openDirectoryButton" + "focusable": false + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openDirectory", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0" + } ) }, new FormLayoutConstraints( class java.lang.String ) { "value": "North" } ) add( new FormContainer( "com.formdev.flatlaf.extras.components.FlatTabbedPane", new FormLayoutManager( class javax.swing.JTabbedPane ) ) { name: "tabbedPane" "tabLayoutPolicy": 1 + "focusable": false + addEvent( new FormEvent( "javax.swing.event.ChangeListener", "stateChanged", "selectedTabChanged", false ) ) }, new FormLayoutConstraints( class java.lang.String ) { "value": "Center" } ) @@ -45,6 +58,13 @@ new FormModel { add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { name: "fileMenu" "text": "File" + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "openDirectoryMenuItem" + "text": "Open Directory..." + "mnemonic": 79 + "accelerator": static javax.swing.KeyStroke getKeyStroke( 79, 4226, false ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "openDirectory", false ) ) + } ) add( new FormComponent( "javax.swing.JMenuItem" ) { name: "saveAllMenuItem" "text": "Save All" diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/icons/menu-open.svg b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/icons/menu-open.svg new file mode 100644 index 00000000..51e7f354 --- /dev/null +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/icons/menu-open.svg @@ -0,0 +1,3 @@ + + +