mirror of https://github.com/weisJ/darklaf.git
darculadarcula-themefeelguihacktoberfestintellijintellij-themelaflooklookandfeelnativesolarizedsolarized-dark-themesolarized-light-themesvgswingthemethemes
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
641 lines
22 KiB
641 lines
22 KiB
/* |
|
* This programs uses the information found in the UIManager |
|
* to create a table of key/value pairs for each Swing component. |
|
*/ |
|
|
|
import com.weis.darklaf.DarkLafInfo; |
|
import com.weis.darklaf.LafManager; |
|
import com.weis.darklaf.ui.cell.DarkCellRendererToggleButton; |
|
import com.weis.darklaf.ui.table.DarkColorTableCellRendererEditor; |
|
import org.jdesktop.swingx.JXTaskPane; |
|
import org.jdesktop.swingx.JXTaskPaneContainer; |
|
import org.jetbrains.annotations.Contract; |
|
import org.jetbrains.annotations.NotNull; |
|
import org.jetbrains.annotations.Nullable; |
|
|
|
import javax.swing.*; |
|
import javax.swing.border.Border; |
|
import javax.swing.border.EmptyBorder; |
|
import javax.swing.plaf.ColorUIResource; |
|
import javax.swing.table.DefaultTableModel; |
|
import javax.swing.table.TableCellRenderer; |
|
import java.awt.*; |
|
import java.awt.event.ActionEvent; |
|
import java.awt.event.ItemEvent; |
|
import java.awt.event.ItemListener; |
|
import java.awt.event.KeyEvent; |
|
import java.awt.image.BufferedImage; |
|
import java.lang.reflect.InvocationTargetException; |
|
import java.util.HashMap; |
|
import java.util.HashSet; |
|
import java.util.Map; |
|
import java.util.TreeMap; |
|
import java.util.Vector; |
|
|
|
public class UIManagerDefaults implements ItemListener { |
|
private static final String[] COLUMN_NAMES = {"Key", "Value", "Sample"}; |
|
private static String selectedItem; |
|
|
|
private final JComponent contentPane; |
|
private final TreeMap<String, TreeMap<String, Object>> items; |
|
private final HashMap<String, DefaultTableModel> models; |
|
private JMenuBar menuBar; |
|
private JComboBox<String> comboBox; |
|
private JRadioButton byComponent; |
|
private JTable table; |
|
|
|
/* |
|
* Constructor |
|
*/ |
|
private UIManagerDefaults() { |
|
new JXTaskPaneContainer(); |
|
new JXTaskPane(); |
|
items = new TreeMap<>(); |
|
models = new HashMap<>(); |
|
|
|
contentPane = new JPanel(new BorderLayout()); |
|
contentPane.add(buildNorthComponent(), BorderLayout.NORTH); |
|
contentPane.add(buildCenterComponent(), BorderLayout.CENTER); |
|
|
|
resetComponents(); |
|
} |
|
|
|
public static void main(final String[] args) { |
|
UIManager.installLookAndFeel(new DarkLafInfo()); |
|
LafManager.install(); |
|
SwingUtilities.invokeLater(UIManagerDefaults::createAndShowGUI); |
|
} |
|
|
|
/* |
|
* Build a GUI using the content pane and menu bar of UIManagerDefaults |
|
*/ |
|
private static void createAndShowGUI() { |
|
final UIManagerDefaults application = new UIManagerDefaults(); |
|
final JFrame frame = new JFrame("UIManager Defaults"); |
|
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); |
|
frame.setJMenuBar(application.getMenuBar()); |
|
frame.getContentPane().add(application.getContentPane()); |
|
frame.pack(); |
|
frame.setLocationRelativeTo(null); |
|
frame.setVisible(true); |
|
} |
|
|
|
/* |
|
* A menu can also be added which provides the ability to switch |
|
* between different LAF's. |
|
*/ |
|
private JMenuBar getMenuBar() { |
|
if (menuBar == null) { |
|
menuBar = createMenuBar(); |
|
} |
|
|
|
return menuBar; |
|
} |
|
|
|
/* |
|
* The content pane should be added to a high level container |
|
*/ |
|
@NotNull |
|
private JComponent getContentPane() { |
|
return contentPane; |
|
} |
|
|
|
/** |
|
* Create menu bar |
|
*/ |
|
@NotNull |
|
private JMenuBar createMenuBar() { |
|
final JMenuBar menuBar = new JMenuBar(); |
|
menuBar.add(createFileMenu()); |
|
menuBar.add(createLAFMenu()); |
|
return menuBar; |
|
} |
|
|
|
/** |
|
* Create menu items for the Application menu |
|
*/ |
|
@NotNull |
|
private JMenu createFileMenu() { |
|
final JMenu menu = new JMenu("Application"); |
|
menu.setMnemonic('A'); |
|
var item = new ExitAction(); |
|
menu.add(item); |
|
return menu; |
|
} |
|
|
|
/** |
|
* Create menu items for the Look & Feel menu |
|
*/ |
|
@NotNull |
|
private JMenu createLAFMenu() { |
|
final ButtonGroup bg = new ButtonGroup(); |
|
final JMenu menu = new JMenu("Look & Feel"); |
|
menu.setMnemonic('L'); |
|
final String lafId = UIManager.getLookAndFeel().getID(); |
|
final UIManager.LookAndFeelInfo[] lafInfo = UIManager.getInstalledLookAndFeels(); |
|
|
|
for (final UIManager.LookAndFeelInfo lookAndFeelInfo : lafInfo) { |
|
final String laf = lookAndFeelInfo.getClassName(); |
|
final String name = lookAndFeelInfo.getName(); |
|
final Action action = new ChangeLookAndFeelAction(this, laf, name); |
|
final JRadioButtonMenuItem mi = new JRadioButtonMenuItem(action); |
|
menu.add(mi); |
|
bg.add(mi); |
|
if (name.equals(lafId)) { |
|
mi.setSelected(true); |
|
} |
|
} |
|
return menu; |
|
} |
|
|
|
/* |
|
* This panel is added to the North of the content pane |
|
*/ |
|
@NotNull |
|
private JComponent buildNorthComponent() { |
|
comboBox = new JComboBox<>() { |
|
@Override |
|
public Dimension getPreferredSize() { |
|
return new Dimension(200, getUI().getPreferredSize(this).height); |
|
} |
|
}; |
|
|
|
final JLabel label = new JLabel("Select Item:"); |
|
label.setDisplayedMnemonic('S'); |
|
label.setLabelFor(comboBox); |
|
|
|
ItemListener itemListener = e -> { |
|
selectedItem = null; |
|
if (table.isEditing()) { |
|
table.getCellEditor().stopCellEditing(); |
|
} |
|
table.clearSelection(); |
|
resetComponents(); |
|
}; |
|
|
|
byComponent = new JRadioButton("By Component", true); |
|
byComponent.setMnemonic('C'); |
|
byComponent.addItemListener(itemListener); |
|
|
|
final JRadioButton byValueType = new JRadioButton("By Value Type"); |
|
byValueType.setMnemonic('V'); |
|
byValueType.addItemListener(itemListener); |
|
|
|
final ButtonGroup group = new ButtonGroup(); |
|
group.add(byComponent); |
|
group.add(byValueType); |
|
|
|
final JPanel panel = new JPanel(); |
|
panel.setBorder(new EmptyBorder(15, 0, 15, 0)); |
|
panel.add(label); |
|
panel.add(comboBox); |
|
panel.add(byComponent); |
|
panel.add(byValueType); |
|
return panel; |
|
} |
|
|
|
/* |
|
* This panel is added to the Center of the content pane |
|
*/ |
|
@Contract(" -> new") |
|
@NotNull |
|
private JComponent buildCenterComponent() { |
|
final DefaultTableModel model = new DefaultTableModel(COLUMN_NAMES, 0); |
|
table = new JTable(model); |
|
table.setAutoCreateColumnsFromModel(false); |
|
table.setShowHorizontalLines(false); |
|
table.getColumnModel().getColumn(0).setPreferredWidth(250); |
|
table.getColumnModel().getColumn(1).setPreferredWidth(500); |
|
|
|
table.getColumnModel().getColumn(2).setPreferredWidth(100); |
|
table.getColumnModel().getColumn(2).setCellRenderer(new SampleRenderer()); |
|
table.getColumnModel().getColumn(2).setCellEditor(new DarkColorTableCellRendererEditor()); |
|
|
|
final Dimension d = table.getPreferredSize(); |
|
d.height = 350; |
|
table.setPreferredScrollableViewportSize(d); |
|
|
|
return new JScrollPane(table); |
|
} |
|
|
|
/* |
|
* When the LAF is changed we need to reset the content pane |
|
*/ |
|
private void resetComponents() { |
|
items.clear(); |
|
models.clear(); |
|
((DefaultTableModel) table.getModel()).setRowCount(0); |
|
|
|
buildItemsMap(); |
|
|
|
final Vector<String> comboBoxItems = new Vector<>(50); |
|
|
|
for (final Object key : items.keySet()) { |
|
comboBoxItems.add((String) key); |
|
} |
|
|
|
comboBox.removeItemListener(this); |
|
comboBox.setModel(new DefaultComboBoxModel<>(comboBoxItems)); |
|
comboBox.setSelectedIndex(-1); |
|
comboBox.addItemListener(this); |
|
|
|
if (selectedItem != null) { |
|
comboBox.setSelectedItem(selectedItem); |
|
} |
|
} |
|
|
|
/* |
|
* The item map will contain items for each component or |
|
* items for each attribute type. |
|
*/ |
|
private void buildItemsMap() { |
|
final UIDefaults defaults = UIManager.getLookAndFeelDefaults(); |
|
// Build of Map of items and a Map of attributes for each item |
|
for (final Object key : new HashSet<>(defaults.keySet())) { |
|
final Object value = defaults.get(key); |
|
final String itemName = getItemName(key.toString(), value); |
|
if (itemName == null) { |
|
continue; |
|
} |
|
// Get the attribute map for this componenent, or |
|
// create a map when one is not found |
|
final TreeMap<String, Object> attributeMap = |
|
items.computeIfAbsent(itemName, k -> new TreeMap<>()); |
|
// Add the attribute to the map for this componenent |
|
attributeMap.put(key.toString(), value); |
|
} |
|
} |
|
|
|
/* |
|
* Parse the key to determine the item name to use |
|
*/ |
|
@Nullable |
|
private String getItemName(@NotNull final String key, final Object value) { |
|
// Seems like this is an old check required for JDK1.4.2 |
|
if (key.startsWith("class") || key.startsWith("javax")) { |
|
return null; |
|
} |
|
if (byComponent.isSelected()) { |
|
return getComponentName(key, value); |
|
} else { |
|
return getValueName(key, value); |
|
} |
|
} |
|
|
|
private String getComponentName(@NotNull final String key, final Object value) { |
|
// The key is of the form: |
|
// "componentName.componentProperty", or |
|
// "componentNameUI", or |
|
// "someOtherString" |
|
String componentName; |
|
final int pos = componentNameEndOffset(key); |
|
if (pos != -1) { |
|
componentName = key.substring(0, pos); |
|
} else if (key.endsWith("UI")) { |
|
componentName = key.substring(0, key.length() - 2); |
|
} else if (value instanceof ColorUIResource) { |
|
componentName = "System Colors"; |
|
} else { |
|
componentName = "Miscellaneous"; |
|
} |
|
// Fix inconsistency |
|
if (componentName.equals("Checkbox")) { |
|
componentName = "CheckBox"; |
|
} |
|
return componentName; |
|
} |
|
|
|
private int componentNameEndOffset(@NotNull final String key) { |
|
// Handle Nimbus properties first |
|
// "ComboBox.scrollPane", "Table.editor" and "Tree.cellEditor" |
|
// have different format even within the Nimbus properties. |
|
// (the component name is specified in quotes) |
|
if (key.startsWith("\"")) { |
|
return key.indexOf("\"", 1) + 1; |
|
} |
|
int pos = key.indexOf(":"); |
|
if (pos != -1) { |
|
return pos; |
|
} |
|
pos = key.indexOf("["); |
|
if (pos != -1) { |
|
return pos; |
|
} |
|
// Handle normal properties |
|
return key.indexOf("."); |
|
} |
|
|
|
@NotNull |
|
private String getValueName(@NotNull final String key, final Object value) { |
|
if (value instanceof Icon) { |
|
return "Icon"; |
|
} else if (value instanceof Font) { |
|
return "Font"; |
|
} else if (value instanceof Border) { |
|
return "Border"; |
|
} else if (value instanceof Color) { |
|
return "Color"; |
|
} else if (value instanceof Insets) { |
|
return "Insets"; |
|
} else if (value instanceof Boolean) { |
|
return "Boolean"; |
|
} else if (value instanceof Dimension) { |
|
return "Dimension"; |
|
} else if (value instanceof Number) { |
|
return "Number"; |
|
} else if (value instanceof Painter) { |
|
return "Painter"; |
|
} else if (key.endsWith("UI")) { |
|
return "UI"; |
|
} else if (key.endsWith("InputMap")) { |
|
return "InputMap"; |
|
} else if (key.endsWith("RightToLeft")) { |
|
return "InputMap"; |
|
} else if (key.endsWith("radient")) { |
|
return "Gradient"; |
|
} else { |
|
return "The Rest"; |
|
} |
|
} |
|
|
|
/* |
|
* Implement the ItemListener interface |
|
*/ |
|
public void itemStateChanged(@NotNull final ItemEvent e) { |
|
final String itemName = (String) e.getItem(); |
|
changeTableModel(itemName); |
|
updateRowHeights(); |
|
selectedItem = itemName; |
|
} |
|
|
|
/* |
|
* Change the TableModel in the table for the selected item |
|
*/ |
|
private void changeTableModel(final String itemName) { |
|
// The model has been created previously so just use it |
|
DefaultTableModel model = models.get(itemName); |
|
if (model != null) { |
|
table.setModel(model); |
|
return; |
|
} |
|
// Create a new model for the requested item |
|
// and addAtHead the attributes of the item to the model |
|
model = new DefaultTableModel(COLUMN_NAMES, 0); |
|
final Map<String, Object> attributes = items.get(itemName); |
|
for (final Object o : attributes.keySet()) { |
|
final String attribute = (String) o; |
|
Object value = attributes.get(attribute); |
|
final Vector<Object> row = new Vector<>(3); |
|
row.add(attribute); |
|
if (value != null) { |
|
row.add(value instanceof Boolean ? value : value.toString()); |
|
if (value instanceof Icon) { |
|
value = new SafeIcon((Icon) value); |
|
} |
|
row.add(value); |
|
} else { |
|
row.add("null"); |
|
row.add(""); |
|
} |
|
model.addRow(row); |
|
} |
|
table.setModel(model); |
|
models.put(itemName, model); |
|
} |
|
|
|
/* |
|
* Some rows containing icons, may need to be sized taller to fully |
|
* display the icon. |
|
*/ |
|
private void updateRowHeights() { |
|
try { |
|
for (int row = 0; row < table.getRowCount(); row++) { |
|
int rowHeight = table.getRowHeight(); |
|
|
|
for (int column = 0; column < table.getColumnCount(); column++) { |
|
final Component comp = |
|
table.prepareRenderer(table.getCellRenderer(row, column), row, column); |
|
rowHeight = Math.max(rowHeight, comp.getPreferredSize().height); |
|
} |
|
table.setRowHeight(row, rowHeight); |
|
} |
|
} catch (@NotNull final ClassCastException ignored) { |
|
} |
|
} |
|
|
|
/** |
|
* Thanks to Jeanette for the use of this code found at: |
|
* |
|
* <p>https://jdnc-incubator.dev.java.net/source/browse/jdnc-incubator/src/kleopatra/java/org |
|
* /jdesktop/swingx/renderer/UIPropertiesViewer.java?rev=1.2&view=markup |
|
* |
|
* <p>Some ui-icons misbehave in that they unconditionally class-cast to the component type they |
|
* are mostly painted on. Consequently they blow up if we are trying to paint them anywhere else |
|
* (f.i. in a renderer). |
|
* |
|
* <p>This Icon is an adaption of a cool trick by Darryl Burke found at |
|
* http://tips4java.wordpress.com/2008/12/18/icon-table-cell-renderer |
|
* |
|
* <p>The base idea is to instantiate a component of the type expected by the icon, let it paint |
|
* into the graphics of a bufferedImage and create an ImageIcon from it. In subsequent calls the |
|
* ImageIcon is used. |
|
*/ |
|
private static final class SafeIcon implements Icon { |
|
private final Icon wrappee; |
|
private Icon standIn; |
|
|
|
@Contract(pure = true) |
|
private SafeIcon(final Icon wrappee) { |
|
this.wrappee = wrappee; |
|
} |
|
|
|
@Override |
|
public void paintIcon(final Component c, @NotNull final Graphics g, final int x, final int y) { |
|
if (standIn == this) { |
|
paintFallback(c, g, x, y); |
|
} else if (standIn != null) { |
|
standIn.paintIcon(c, g, x, y); |
|
} else { |
|
try { |
|
wrappee.paintIcon(c, g, x, y); |
|
} catch (@NotNull final ClassCastException e) { |
|
createStandIn(e); |
|
standIn.paintIcon(c, g, x, y); |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
public int getIconWidth() { |
|
return wrappee.getIconWidth(); |
|
} |
|
|
|
@Override |
|
public int getIconHeight() { |
|
return wrappee.getIconHeight(); |
|
} |
|
|
|
private void paintFallback( |
|
final Component c, @NotNull final Graphics g, final int x, final int y) { |
|
g.drawRect(x, y, getIconWidth(), getIconHeight()); |
|
g.drawLine(x, y, x + getIconWidth(), y + getIconHeight()); |
|
g.drawLine(x + getIconWidth(), y, x, y + getIconHeight()); |
|
} |
|
|
|
private void createStandIn(@NotNull final ClassCastException e) { |
|
try { |
|
final Class<?> clazz = getClass(e); |
|
final JComponent standInComponent = getSubstitute(clazz); |
|
standIn = createImageIcon(standInComponent); |
|
} catch (@NotNull final Exception e1) { |
|
// something went wrong - fallback to this painting |
|
standIn = this; |
|
} |
|
} |
|
|
|
private Class<?> getClass(@NotNull final ClassCastException e) throws ClassNotFoundException { |
|
String className = e.getMessage(); |
|
className = className.substring(className.lastIndexOf(" ") + 1); |
|
return Class.forName(className); |
|
} |
|
|
|
private JComponent getSubstitute(@NotNull final Class<?> clazz) throws IllegalAccessException { |
|
JComponent standInComponent = null; |
|
try { |
|
standInComponent = (JComponent) clazz.getDeclaredConstructor().newInstance(); |
|
} catch (@NotNull final InstantiationException e) { |
|
standInComponent = new AbstractButton() { |
|
}; |
|
((AbstractButton) standInComponent).setModel(new DefaultButtonModel()); |
|
} catch (NoSuchMethodException | InvocationTargetException e) { |
|
e.printStackTrace(); |
|
} |
|
return standInComponent; |
|
} |
|
|
|
@Contract("_ -> new") |
|
@NotNull |
|
private Icon createImageIcon(final JComponent standInComponent) { |
|
final BufferedImage image = |
|
new BufferedImage(getIconWidth(), getIconHeight(), BufferedImage.TYPE_INT_ARGB); |
|
final Graphics g = image.createGraphics(); |
|
try { |
|
wrappee.paintIcon(standInComponent, g, 0, 0); |
|
return new ImageIcon(image); |
|
} finally { |
|
g.dispose(); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* Render the value based on its class. |
|
*/ |
|
private static final class SampleRenderer extends JLabel implements TableCellRenderer { |
|
|
|
private final DarkCellRendererToggleButton booleanRenderer = |
|
new DarkCellRendererToggleButton(new DarkCellRendererToggleButton.CellEditorCheckBox()); |
|
|
|
private SampleRenderer() { |
|
super(); |
|
setHorizontalAlignment(SwingConstants.CENTER); |
|
setOpaque(true); |
|
} |
|
|
|
@Contract("_, _, _, _, _, _ -> this") |
|
@NotNull |
|
public Component getTableCellRendererComponent( |
|
final JTable table, |
|
final Object sample, |
|
final boolean isSelected, |
|
final boolean hasFocus, |
|
final int row, |
|
final int column) { |
|
setBackground(null); |
|
setBorder(null); |
|
setIcon(null); |
|
setText(""); |
|
|
|
if (sample instanceof Color) { |
|
setBackground((Color) sample); |
|
} else if (sample instanceof Border) { |
|
setBorder((Border) sample); |
|
} else if (sample instanceof Font) { |
|
setText("Sample"); |
|
setFont((Font) sample); |
|
} else if (sample instanceof Icon) { |
|
setIcon((Icon) sample); |
|
} else if (sample instanceof Boolean) { |
|
return booleanRenderer.getTableCellRendererComponent(table, sample, isSelected, hasFocus, row, column); |
|
} |
|
return this; |
|
} |
|
|
|
/* |
|
* Some icons are painted using inner classes and are not meant to be |
|
* shared by other items. This code will catch the |
|
* ClassCastException that is thrown. |
|
*/ |
|
public void paint(final Graphics g) { |
|
try { |
|
super.paint(g); |
|
} catch (@NotNull final Exception e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* Change the LAF and recreate the UIManagerDefaults so that the properties |
|
* of the new LAF are correctly displayed. |
|
*/ |
|
private static final class ChangeLookAndFeelAction extends AbstractAction { |
|
private final UIManagerDefaults defaults; |
|
private final String laf; |
|
|
|
private ChangeLookAndFeelAction(final UIManagerDefaults defaults, final String laf, final String name) { |
|
this.defaults = defaults; |
|
this.laf = laf; |
|
putValue(Action.NAME, name); |
|
putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME)); |
|
} |
|
|
|
public void actionPerformed(@NotNull final ActionEvent e) { |
|
try { |
|
UIManager.setLookAndFeel(laf); |
|
defaults.resetComponents(); |
|
|
|
final JRootPane rootPane = SwingUtilities.getRootPane(defaults.getContentPane()); |
|
SwingUtilities.updateComponentTreeUI(rootPane); |
|
// Use custom decorations when supported by the LAF |
|
final JFrame frame = (JFrame) SwingUtilities.windowForComponent(rootPane); |
|
frame.dispose(); |
|
|
|
if (UIManager.getLookAndFeel().getSupportsWindowDecorations()) { |
|
frame.setUndecorated(true); |
|
frame.getRootPane().setWindowDecorationStyle(JRootPane.FRAME); |
|
} else { |
|
frame.setUndecorated(false); |
|
} |
|
frame.setVisible(true); |
|
} catch (@NotNull final Exception ex) { |
|
ex.printStackTrace(); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
* Close the frame |
|
*/ |
|
private static final class ExitAction extends AbstractAction { |
|
private ExitAction() { |
|
putValue(Action.NAME, "Exit"); |
|
putValue(Action.SHORT_DESCRIPTION, getValue(Action.NAME)); |
|
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_X); |
|
} |
|
|
|
public void actionPerformed(final ActionEvent e) { |
|
System.exit(0); |
|
} |
|
} |
|
}
|
|
|