From 58d92a00204170e59bba5c007d1bd1fc08348c03 Mon Sep 17 00:00:00 2001 From: weisj Date: Thu, 5 Nov 2020 19:31:33 +0100 Subject: [PATCH] Add option to add and edit entries in ThemeEditor. --- .../darklaf/layout/LayoutManagerAdapter.java | 51 +++++ core/src/test/java/theme/MapTableModel.java | 46 ++++- core/src/test/java/theme/ThemeEditor.java | 188 +++++++++++++++++- core/src/test/resources/icon/addEntry.svg | 15 ++ core/src/test/resources/icon/word.svg | 10 + 5 files changed, 297 insertions(+), 13 deletions(-) create mode 100644 core/src/main/java/com/github/weisj/darklaf/layout/LayoutManagerAdapter.java create mode 100644 core/src/test/resources/icon/addEntry.svg create mode 100644 core/src/test/resources/icon/word.svg diff --git a/core/src/main/java/com/github/weisj/darklaf/layout/LayoutManagerAdapter.java b/core/src/main/java/com/github/weisj/darklaf/layout/LayoutManagerAdapter.java new file mode 100644 index 00000000..db9f6e02 --- /dev/null +++ b/core/src/main/java/com/github/weisj/darklaf/layout/LayoutManagerAdapter.java @@ -0,0 +1,51 @@ +/* + * MIT License + * + * Copyright (c) 2020 Jannis Weis + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + */ +package com.github.weisj.darklaf.layout; + +import java.awt.*; + +public class LayoutManagerAdapter implements LayoutManager { + @Override + public void addLayoutComponent(final String name, final Component comp) { + + } + + @Override + public void removeLayoutComponent(final Component comp) { + + } + + @Override + public Dimension preferredLayoutSize(final Container parent) { + return null; + } + + @Override + public Dimension minimumLayoutSize(final Container parent) { + return null; + } + + @Override + public void layoutContainer(final Container parent) { + + } +} diff --git a/core/src/test/java/theme/MapTableModel.java b/core/src/test/java/theme/MapTableModel.java index 44468fd3..f5bbb649 100644 --- a/core/src/test/java/theme/MapTableModel.java +++ b/core/src/test/java/theme/MapTableModel.java @@ -22,15 +22,19 @@ package theme; import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Vector; import javax.swing.table.DefaultTableModel; class MapTableModel extends DefaultTableModel { private final LinkedHashMap values; + private final boolean keyEditable; - MapTableModel(final LinkedHashMap values) { + MapTableModel(final LinkedHashMap values, final boolean keyEditable) { this.values = values; + this.keyEditable = keyEditable; } @Override @@ -55,7 +59,7 @@ class MapTableModel extends DefaultTableModel { @Override public boolean isCellEditable(final int rowIndex, final int columnIndex) { - return columnIndex == 1; + return columnIndex == 1 || keyEditable; } @Override @@ -67,13 +71,45 @@ class MapTableModel extends DefaultTableModel { } } + @Override + public void insertRow(final int row, final Vector rowData) { + values.put(rowData.get(0), rowData.get(1)); + fireTableRowsInserted(getRowCount() - 1, getRowCount() - 1); + } + + @Override + public void removeRow(final int row) { + values.remove(getKeyByIndex(row)); + fireTableRowsDeleted(row, row); + } + + @Override + public void addColumn(final Object columnName, final Vector columnData) { + throw new UnsupportedOperationException(); + } + @Override public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) { - if (columnIndex == 0) return; - values.put(getKeyByIndex(rowIndex), aValue); + if (columnIndex == 0) { + if (!keyEditable) return; + @SuppressWarnings("unchecked") + Map.Entry[] entries = + (Map.Entry[]) values.entrySet().toArray(new Map.Entry[0]); + for (int i = rowIndex; i < entries.length; i++) { + values.remove(entries[i].getKey()); + } + values.put(aValue, entries[rowIndex].getValue()); + for (int i = rowIndex + 1; i < entries.length; i++) { + values.remove(entries[i].getKey()); + } + fireTableCellUpdated(rowIndex, columnIndex); + } else { + values.put(getKeyByIndex(rowIndex), aValue); + fireTableCellUpdated(rowIndex, columnIndex); + } } public String getKeyByIndex(final int index) { - return values.keySet().toArray(new String[0])[index]; + return values.keySet().toArray(new Object[0])[index].toString(); } } diff --git a/core/src/test/java/theme/ThemeEditor.java b/core/src/test/java/theme/ThemeEditor.java index 38c1f8d2..c1db3162 100644 --- a/core/src/test/java/theme/ThemeEditor.java +++ b/core/src/test/java/theme/ThemeEditor.java @@ -27,8 +27,10 @@ import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; import javax.swing.*; +import javax.swing.table.DefaultTableModel; import ui.ComponentDemo; @@ -37,15 +39,28 @@ import com.github.weisj.darklaf.LafManager; import com.github.weisj.darklaf.components.DefaultButton; import com.github.weisj.darklaf.components.OverlayScrollPane; import com.github.weisj.darklaf.components.border.DarkBorders; +import com.github.weisj.darklaf.components.button.JSplitButton; +import com.github.weisj.darklaf.graphics.GraphicsContext; +import com.github.weisj.darklaf.graphics.PaintUtil; +import com.github.weisj.darklaf.graphics.ThemedColor; +import com.github.weisj.darklaf.icons.DerivableIcon; +import com.github.weisj.darklaf.icons.IconLoader; +import com.github.weisj.darklaf.icons.OverlayIcon; +import com.github.weisj.darklaf.icons.TextIcon; +import com.github.weisj.darklaf.layout.LayoutManagerAdapter; import com.github.weisj.darklaf.theme.Theme; import com.github.weisj.darklaf.theme.ThemeDelegate; import com.github.weisj.darklaf.theme.info.AccentColorRule; import com.github.weisj.darklaf.theme.info.ColorToneRule; import com.github.weisj.darklaf.theme.info.ContrastRule; import com.github.weisj.darklaf.theme.info.FontSizeRule; +import com.github.weisj.darklaf.ui.button.ButtonConstants; import com.github.weisj.darklaf.ui.table.TableConstants; import com.github.weisj.darklaf.ui.togglebutton.ToggleButtonConstants; import com.github.weisj.darklaf.uiresource.DarkColorUIResource; +import com.github.weisj.darklaf.util.Alignment; +import com.github.weisj.darklaf.util.DarkUIUtil; +import com.github.weisj.darklaf.util.FontUtil; import defaults.UIManagerDefaults; public class ThemeEditor extends JPanel { @@ -113,11 +128,11 @@ public class ThemeEditor extends JPanel { JTabbedPane tabbedPane = new JTabbedPane(); tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); - tabbedPane.addTab("Theme defaults", createTable(themeDefaults)); - tabbedPane.addTab("Icon defaults", createTable(iconDefaults)); - tabbedPane.addTab("UI customs", createTable(uiDefaults)); - tabbedPane.addTab("Global customs", createTable(globalDefaults)); - tabbedPane.addTab("Platform customs", createTable(platformDefaults)); + tabbedPane.addTab("Theme defaults", createTable(themeDefaults, false)); + tabbedPane.addTab("Icon defaults", createTable(iconDefaults, false)); + tabbedPane.addTab("UI customs", createTable(uiDefaults, true)); + tabbedPane.addTab("Global customs", createTable(globalDefaults, true)); + tabbedPane.addTab("Platform customs", createTable(platformDefaults, true)); tabbedPane.addTab("All Defaults (Read only)", new UIManagerDefaults().createComponent()); content.add(tabbedPane, BorderLayout.CENTER); @@ -233,15 +248,172 @@ public class ThemeEditor extends JPanel { }); } - private JComponent createTable(final LinkedHashMap valueMap) { + enum EntryType { + COLOR("Add Color", Color.BLACK, DarkUIUtil.ICON_LOADER.getIcon("menu/colorChooser.svg")), + INTEGER("Add Integer", 0, ((Supplier) () -> { + Font font = FontUtil.createFont(Font.MONOSPACED, Font.BOLD, 13); + return new TextIcon("42", new ThemedColor("menuIconEnabled"), font, 16, 16); + }).get()), + BOOLEAN("Add Boolean", false, DarkUIUtil.ICON_LOADER.getIcon("control/checkboxSelectedFocused.svg", true)), + STRING("Add String", "", IconLoader.get().getIcon("icon/word.svg", true)); + + private final String s; + private final Object defaultValue; + private final Icon icon; + private int entryCount; + + EntryType(final String s, final Object defaultValue, final Icon icon) { + this.s = s; + this.defaultValue = defaultValue; + this.icon = icon; + } + + public String getText() { + return s; + } + + @SuppressWarnings("unchecked") + public DerivableIcon getIcon() { + return (DerivableIcon) icon; + } + + public void addElement(final JTable table) { + DefaultTableModel model = (DefaultTableModel) table.getModel(); + model.addRow(new Object[] {"<" + name() + "_" + (entryCount++) + ">", defaultValue}); + } + } + + private JComponent createTable(final LinkedHashMap valueMap, final boolean keyEditable) { JTable table = new JTable(); - table.setModel(new MapTableModel(valueMap)); + table.getTableHeader().setReorderingAllowed(false); + table.setShowHorizontalLines(false); + table.setModel(new MapTableModel(valueMap, keyEditable)); + table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.putClientProperty(TableConstants.KEY_CELL_VALUE_DETERMINES_EDITOR_CLASS, true); - OverlayScrollPane sp = new OverlayScrollPane(table); + + JPanel buttonPanel = keyEditable ? createButtonPanel(table) : null; + + OverlayScrollPane sp = new OverlayScrollPane(table) { + @Override + public void doLayout() { + super.doLayout(); + if (!keyEditable) return; + Dimension prefSize = buttonPanel.getPreferredSize(); + int pad = 10; + Component viewport = getScrollPane().getViewport(); + Rectangle viewBounds = new Rectangle(0, 0, viewport.getWidth(), viewport.getHeight()); + viewBounds = SwingUtilities.convertRectangle(viewport, viewBounds, this); + int x = viewBounds.x - pad - prefSize.width; + int columnEnd = table.getColumnModel().getColumn(0).getWidth(); + if (getVerticalScrollBar() != null && getVerticalScrollBar().isVisible()) { + columnEnd = Math.min(columnEnd, viewBounds.width - getVerticalScrollBar().getWidth()); + } + x += columnEnd; + buttonPanel.setBounds(x, viewBounds.y + pad, prefSize.width, prefSize.height); + buttonPanel.doLayout(); + } + }; + if (keyEditable) { + sp.add(buttonPanel, Integer.valueOf(JLayeredPane.MODAL_LAYER - 1)); + } + sp.doLayout(); sp.getScrollPane().setBorder(DarkBorders.createLineBorder(0, 0, 1, 0)); + return sp; } + private JPanel createButtonPanel(final JTable table) { + AtomicReference lastSelection = new AtomicReference<>(EntryType.COLOR); + + OverlayIcon overlayIcon = + new OverlayIcon( + DarkUIUtil.ICON_LOADER.getIcon("navigation/add.svg", true), + lastSelection.get().getIcon().derive(8, 8), + Alignment.SOUTH_EAST); + JSplitButton addButton = new JSplitButton(overlayIcon); + addButton.setFocusable(false); + addButton.setToolTipText(lastSelection.get().getText()); + + JPopupMenu menu = addButton.getActionMenu(); + for (EntryType type : EntryType.values()) { + menu.add(new JMenuItem(type.getText(), type.getIcon())).addActionListener(e -> { + lastSelection.set(type); + addButton.setToolTipText(type.getText()); + overlayIcon.setOverlay(type.getIcon().derive(8, 8)); + addButton.repaint(); + }); + } + addButton.addActionListener(e -> lastSelection.get().addElement(table)); + addButton.putClientProperty(ButtonConstants.KEY_VARIANT, ButtonConstants.VARIANT_BORDERLESS); + addButton.putClientProperty(ButtonConstants.KEY_SQUARE, true); + addButton.putClientProperty(ButtonConstants.KEY_THIN, true); + + JButton deleteButton = new JButton(); + deleteButton.setToolTipText("Remove selected entry"); + deleteButton.setIcon(DarkUIUtil.ICON_LOADER.getIcon("menu/delete.svg")); + deleteButton.setDisabledIcon(DarkUIUtil.ICON_LOADER.getIcon("menu/deleteDisabled.svg")); + deleteButton.putClientProperty(ButtonConstants.KEY_VARIANT, ButtonConstants.VARIANT_BORDERLESS); + deleteButton.putClientProperty(ButtonConstants.KEY_SQUARE, true); + deleteButton.putClientProperty(ButtonConstants.KEY_THIN, true); + deleteButton.setEnabled(table.getSelectedRow() >= 0); + deleteButton.setFocusable(false); + + deleteButton.addActionListener(e -> { + int row = table.getSelectedRow(); + ((DefaultTableModel) table.getModel()).removeRow(row); + table.clearSelection(); + int newRow = row > 0 ? row - 1 : row; + if (newRow < table.getModel().getRowCount()) { + table.addRowSelectionInterval(newRow, newRow); + } + }); + table.getSelectionModel().addListSelectionListener(e -> deleteButton.setEnabled(table.getSelectedRow() >= 0)); + + JPanel buttonPanel = new JPanel() { + @Override + protected void paintComponent(final Graphics g) { + super.paintComponent(g); + GraphicsContext context = new GraphicsContext(g); + ((Graphics2D) g).setComposite(PaintUtil.getGlowComposite()); + g.setColor(UIManager.getColor("hoverHighlight")); + PaintUtil.fillRoundRect((Graphics2D) g, 0, 0, getWidth(), getHeight(), 10, false); + context.restore(); + } + }; + buttonPanel.setLayout(new LayoutManagerAdapter() { + + private final int pad = 3; + + @Override + public void layoutContainer(final Container parent) { + Dimension addPref = addButton.getPreferredSize(); + Dimension delPref = deleteButton.getPreferredSize(); + addButton.setBounds(pad, pad, addPref.width, addPref.height); + deleteButton.setBounds(2 * pad + addPref.width, pad, delPref.width, delPref.height); + } + + @Override + public Dimension preferredLayoutSize(final Container parent) { + Dimension dim = addButton.getPreferredSize(); + Dimension deleteSize = deleteButton.getPreferredSize(); + dim.width += deleteSize.width + pad; + dim.height = Math.max(deleteSize.height, dim.height); + dim.width += 2 * pad; + dim.height += 2 * pad; + return dim; + } + + @Override + public Dimension minimumLayoutSize(final Container parent) { + return preferredLayoutSize(parent); + } + }); + buttonPanel.add(addButton); + buttonPanel.add(deleteButton); + buttonPanel.setOpaque(false); + return buttonPanel; + } + private static class MutableThemedLaf extends DarkLaf { @Override diff --git a/core/src/test/resources/icon/addEntry.svg b/core/src/test/resources/icon/addEntry.svg new file mode 100644 index 00000000..3c8d33c7 --- /dev/null +++ b/core/src/test/resources/icon/addEntry.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/core/src/test/resources/icon/word.svg b/core/src/test/resources/icon/word.svg new file mode 100644 index 00000000..9564bf3a --- /dev/null +++ b/core/src/test/resources/icon/word.svg @@ -0,0 +1,10 @@ + + + + + + + + +