From a2b06c0a0d067cb051ba1b11061c29c624ee581b Mon Sep 17 00:00:00 2001 From: bancika Date: Thu, 31 Dec 2020 19:36:59 +0100 Subject: [PATCH] toolbar improvements --- .../src/org/diylc/presenter/update.xml | 16 +- .../plugins/toolbox/ActionMapAction.java | 82 ++++++ .../toolbox/ComponentButtonFactory.java | 36 +++ .../plugins/toolbox/ComponentTabbedPane.java | 278 +++++++++++------- .../plugins/toolbox/ToolbarScrollPane.java | 77 +++++ 5 files changed, 379 insertions(+), 110 deletions(-) create mode 100644 diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ActionMapAction.java create mode 100644 diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ToolbarScrollPane.java diff --git a/diylc/diylc-core/src/org/diylc/presenter/update.xml b/diylc/diylc-core/src/org/diylc/presenter/update.xml index bfc2786e..25c68935 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/update.xml +++ b/diylc/diylc-core/src/org/diylc/presenter/update.xml @@ -3421,7 +3421,19 @@ NEW_FEATURE Português translation added (thanks Pedro Rizzi!) - + + + NEW_FEATURE + Building blocks and Favorites added to the component toolbar + + + NEW_FEATURE + Scroll buttons added to the component toolbar that appear when needed + + + IMPROVEMENT + Save last selected tab in the component toolbar and restore it in the next session + IMPROVEMENT When editing component properties, group Length and Width properties together @@ -3432,7 +3444,7 @@ BUG_FIX - Fixed some issues with exporting and importing building blocks and variants. If you still have issues, your variant/building block files might need to be manually updated to keep working + Fixed some issues with exporting/importing building blocks and variants. If you still have issues, your variant/building block files might need to be manually updated to keep working https://github.com/bancika/diy-layout-creator/releases diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ActionMapAction.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ActionMapAction.java new file mode 100644 index 00000000..2acf31fc --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ActionMapAction.java @@ -0,0 +1,82 @@ +/* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * DIYLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with DIYLC. If not, see + * . + */ +package org.diylc.swing.plugins.toolbox; + +import java.awt.event.ActionEvent; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JComponent; + +/* + * The ActionMapAction class is a convenience class that allows you to use an installed Action as an + * Action or ActionListener on a separate component. + * + * It can be used on components like JButton or JMenuItem that support an Action as a property of + * the component. Or it can be added to the same above components as an ActionListener. + * + * The benefit of this class is that a new ActionEvent will be created such that the source of the + * event is the component the Action belongs to, not the component that was "clicked". Otherwise in + * many cases a ClassCastException will be thrown when the Action is invoked. + */ +public class ActionMapAction extends AbstractAction { + + private static final long serialVersionUID = 1L; + + private Action originalAction; + private JComponent component; + private String actionCommand = ""; + private int repeatCount; + + /** + * Replace the default Action for the given KeyStroke with a custom Action + * + * @param name the name parameter of the Action + * @param componet the component the Action belongs to + * @param actionKey the key to identify the Action in the ActionMap + * @param repeatCount the number of time to repeat the same action on invocation + */ + public ActionMapAction(String name, JComponent component, String actionKey, int repeatCount) { + super(name); + this.repeatCount = repeatCount; + + originalAction = component.getActionMap().get(actionKey); + + if (originalAction == null) { + String message = "no Action for action key: " + actionKey; + throw new IllegalArgumentException(message); + } + + this.component = component; + } + + public void setActionCommand(String actionCommand) { + this.actionCommand = actionCommand; + } + + /** + * Invoke the original Action using the original component as the source of the event. + */ + public void actionPerformed(ActionEvent e) { + e = new ActionEvent(component, ActionEvent.ACTION_PERFORMED, actionCommand, e.getWhen(), + e.getModifiers()); + + for (int i = 0; i < repeatCount; i++) + originalAction.actionPerformed(e); + } +} diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ComponentButtonFactory.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ComponentButtonFactory.java index 643c00df..3c76df92 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ComponentButtonFactory.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ComponentButtonFactory.java @@ -37,12 +37,15 @@ import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPopupMenu; +import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; +import org.apache.log4j.Logger; import org.diylc.appframework.miscutils.Utils; import org.diylc.common.ComponentType; +import org.diylc.common.IBlockProcessor.InvalidBlockException; import org.diylc.common.IPlugInPort; import org.diylc.core.IDIYComponent; import org.diylc.core.Template; @@ -56,6 +59,8 @@ import org.diylc.swingframework.openide.DropDownButtonFactory; * @author Branislav Stojkovic */ public class ComponentButtonFactory { + + private static final Logger LOG = Logger.getLogger(ComponentButtonFactory.class); public static int MARGIN = 3; @@ -191,4 +196,35 @@ public class ComponentButtonFactory { item.add(label); return item; } + + public static JButton createBuildingBlockButton(final IPlugInPort plugInPort, final String blockName) { + JButton button = new JButton(blockName, IconLoader.Component.getIcon()); + + button.setVerticalTextPosition(SwingConstants.BOTTOM); + button.setHorizontalTextPosition(SwingConstants.CENTER); + + button.setBorder(BorderFactory.createEmptyBorder(MARGIN + 1, MARGIN + 1, MARGIN, MARGIN)); + button.setToolTipText("" + blockName + "
building block"); + + button.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + try { + plugInPort.loadBlock(blockName); + } catch (InvalidBlockException e1) { + LOG.error("Error loading building block", e1); + } + } + }); + + button.addKeyListener(new KeyAdapter() { + + @Override + public void keyPressed(KeyEvent e) { + plugInPort.keyPressed(e.getKeyCode(), Utils.isMac() ? e.isControlDown() : e.isMetaDown(), e.isShiftDown(), e.isAltDown()); + } + }); + return button; + } } diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ComponentTabbedPane.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ComponentTabbedPane.java index 9ac39644..ee7783f2 100644 --- a/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ComponentTabbedPane.java +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ComponentTabbedPane.java @@ -21,34 +21,37 @@ */ package org.diylc.swing.plugins.toolbox; -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Container; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.Container; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; -import javax.swing.BoxLayout; -import javax.swing.JMenuItem; -import javax.swing.JPanel; -import javax.swing.JPopupMenu; -import javax.swing.JTabbedPane; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import javax.swing.event.PopupMenuEvent; -import javax.swing.event.PopupMenuListener; +import javax.swing.BoxLayout; +import javax.swing.JMenuItem; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JTabbedPane; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; -import org.apache.log4j.Logger; -import org.diylc.appframework.miscutils.ConfigurationManager; -import org.diylc.appframework.miscutils.IConfigListener; -import org.diylc.common.ComponentType; -import org.diylc.common.IPlugInPort; -import org.diylc.core.IDIYComponent; -import org.diylc.core.Template; -import org.diylc.presenter.ComparatorFactory; +import org.apache.log4j.Logger; +import org.diylc.appframework.miscutils.ConfigurationManager; +import org.diylc.appframework.miscutils.IConfigListener; +import org.diylc.common.ComponentType; +import org.diylc.common.Favorite; +import org.diylc.common.Favorite.FavoriteType; +import org.diylc.common.IPlugInPort; +import org.diylc.core.IDIYComponent; +import org.diylc.core.Template; +import org.diylc.lang.LangUtil; +import org.diylc.presenter.ComparatorFactory; import org.diylc.presenter.ComponentProcessor; -import org.diylc.presenter.Presenter; /** * Tabbed pane that shows all available components categorized into tabs. @@ -59,95 +62,128 @@ class ComponentTabbedPane extends JTabbedPane { private static final long serialVersionUID = 1L; - private static final Logger LOG = Logger.getLogger(ComponentTabbedPane.class); - - public static int SCROLL_STEP = Presenter.ICON_SIZE + ComponentButtonFactory.MARGIN * 2 + 2; + private static final Logger LOG = Logger.getLogger(ComponentTabbedPane.class); + + public static String LAST_SELECTED_TAB = "lastSelectedTab"; private final IPlugInPort plugInPort; private Container recentToolbar; - private Container buildingBlocksToolbar; - private List pendingRecentComponents = null; - + private Container buildingBlocksToolbar; + private Container favoritesToolbar; + + private Map typesByClass; + + private List favorites; + private List pendingRecentComponents = null; + private List blocks; + public ComponentTabbedPane(IPlugInPort plugInPort) { super(); - this.plugInPort = plugInPort; - addTab("Recently Used", createRecentComponentsPanel()); -// addTab("Building Blocks", createBuildingBlocksPanel()); + this.plugInPort = plugInPort; + + initialize(); + + // layout UI + addTab(LangUtil.translate("(Favorites)"), createFavoritesPanel()); + addTab(LangUtil.translate("(Recently Used)"), createRecentComponentsPanel()); + addTab(LangUtil.translate("(Building Blocks)"), createBuildingBlocksPanel()); Map> componentTypes = plugInPort.getComponentTypes(); List categories = new ArrayList(componentTypes.keySet()); Collections.sort(categories); for (String category : categories) { JPanel panel = createTab((componentTypes.get(category))); addTab(category, panel); - } + } + + // restore last selected tab + int lastSelectedTab = ConfigurationManager.getInstance().readInt(LAST_SELECTED_TAB, -1); + if (lastSelectedTab >= 0) + setSelectedIndex(lastSelectedTab); + addChangeListener(new ChangeListener() { @Override - public void stateChanged(ChangeEvent e) { + public void stateChanged(ChangeEvent e) { + ConfigurationManager.getInstance().writeValue(LAST_SELECTED_TAB, getSelectedIndex()); ComponentTabbedPane.this.plugInPort.setNewComponentTypeSlot(null, null, false); // Refresh recent components if needed if (pendingRecentComponents != null) { - refreshRecentComponentsToolbar(getRecentToolbar(), pendingRecentComponents); + refreshRecentComponentsToolbar(pendingRecentComponents); getRecentToolbar().invalidate(); pendingRecentComponents = null; } } - }); + }); + + + } + + @SuppressWarnings("unchecked") + private void initialize() { + Map> componentTypes = plugInPort.getComponentTypes(); + this.typesByClass = new HashMap(); + for (Map.Entry> e : componentTypes.entrySet()) + for (ComponentType c : e.getValue()) + typesByClass.put(c.getInstanceClass().getCanonicalName(), c); + + // load building blocks + ConfigurationManager.getInstance().addConfigListener(IPlugInPort.BLOCKS_KEY, + new IConfigListener() { + + @Override + public void valueChanged(String key, Object value) { + Map>> newBlocks = + (Map>>) value; + if (newBlocks != null) { + List blockNames = new ArrayList(newBlocks.keySet()); + Collections.sort(blockNames); + if (!blockNames.equals(ComponentTabbedPane.this.blocks)) { + LOG.info("Detected block change"); + ComponentTabbedPane.this.blocks = blockNames; + refreshBuildingBlocksToolbar(); + } else + LOG.info("Detected no block change"); + } else + LOG.info("Detected no block change"); + } + }); + Map>> newBlocks = + (Map>>) ConfigurationManager.getInstance() + .readObject(IPlugInPort.BLOCKS_KEY, null); + if (newBlocks == null) + this.blocks = new ArrayList(); + else { + this.blocks = new ArrayList(newBlocks.keySet()); + Collections.sort(this.blocks); + } + + ConfigurationManager.getInstance().addConfigListener(IPlugInPort.FAVORITES_KEY, + new IConfigListener() { + + @Override + public void valueChanged(String key, Object value) { + List newFavorites = (List) value; + if (newFavorites != null && !newFavorites.equals(ComponentTabbedPane.this.favorites)) { + LOG.info("Detected favorites change"); + ComponentTabbedPane.this.favorites = new ArrayList(newFavorites); + refreshFavoritesToolbar(); + } else + LOG.info("Detected no favorites change"); + } + }); + this.favorites = new ArrayList((List) ConfigurationManager.getInstance() + .readObject(IPlugInPort.FAVORITES_KEY, new ArrayList())); } private JPanel createTab(List componentTypes) { JPanel panel = new JPanel(new BorderLayout()); - // final JScrollPane scrollPane = - // createComponentScrollBar(componentTypes); + panel.setOpaque(false); panel.add(createComponentPanel(componentTypes), BorderLayout.CENTER); - // JButton leftButton = new JButton("<"); - // leftButton.addActionListener(new ActionListener() { - // - // @Override - // public void actionPerformed(ActionEvent e) { - // Rectangle rect = scrollPane.getVisibleRect(); - // if (rect.x > SCROLL_STEP) { - // rect.translate(-SCROLL_STEP, 0); - // } else { - // rect.translate(-rect.x, 0); - // } - // } - // }); - // leftButton.setMargin(new Insets(0, 2, 0, 2)); - // JButton rightButton = new JButton(">"); - // rightButton.addActionListener(new ActionListener() { - // - // @Override - // public void actionPerformed(ActionEvent e) { - // Rectangle rect = scrollPane.getVisibleRect(); - // if (rect.x + rect.width < scrollPane.getViewport().getSize().width - - // SCROLL_STEP) { - // rect.translate(SCROLL_STEP, 0); - // } else { - // rect.translate(scrollPane.getViewport().getSize().width - rect.x - - // rect.width, - // 0); - // } - // } - // }); - // rightButton.setMargin(new Insets(0, 2, 0, 2)); - // panel.add(leftButton, BorderLayout.WEST); - // panel.add(scrollPane); - // panel.add(rightButton, BorderLayout.EAST); + return panel; } - // private JScrollPane createComponentScrollBar(List - // componentTypes) { - // JScrollPane scrollPane = new - // JScrollPane(createComponentPanel(componentTypes)); - // scrollPane.setOpaque(false); - // scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - // scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); - // return scrollPane; - // } - public Container getRecentToolbar() { if (recentToolbar == null) { recentToolbar = new Container(); @@ -162,6 +198,14 @@ class ComponentTabbedPane extends JTabbedPane { buildingBlocksToolbar.setLayout(new BoxLayout(buildingBlocksToolbar, BoxLayout.X_AXIS)); } return buildingBlocksToolbar; + } + + public Container getFavoritesToolbar() { + if (favoritesToolbar == null) { + favoritesToolbar = new Container(); + favoritesToolbar.setLayout(new BoxLayout(favoritesToolbar, BoxLayout.X_AXIS)); + } + return favoritesToolbar; } private Component createComponentPanel(List componentTypes) { @@ -177,18 +221,13 @@ class ComponentTabbedPane extends JTabbedPane { } } - return toolbar; + return new ToolbarScrollPane(toolbar); } @SuppressWarnings("unchecked") private Component createRecentComponentsPanel() { - JPanel panel = new JPanel(new BorderLayout()); - panel.setOpaque(false); - final Container toolbar = getRecentToolbar(); - refreshRecentComponentsToolbar( - toolbar, - (List) ConfigurationManager.getInstance().readObject(IPlugInPort.RECENT_COMPONENTS_KEY, + refreshRecentComponentsToolbar((List) ConfigurationManager.getInstance().readObject(IPlugInPort.RECENT_COMPONENTS_KEY, new ArrayList())); ConfigurationManager.getInstance().addConfigListener(IPlugInPort.RECENT_COMPONENTS_KEY, new IConfigListener() { @@ -200,40 +239,39 @@ class ComponentTabbedPane extends JTabbedPane { } }); - panel.add(toolbar, BorderLayout.CENTER); + JPanel panel = new ToolbarScrollPane(toolbar); return panel; } - @SuppressWarnings("unused") private Component createBuildingBlocksPanel() { + final Container toolbar = getBuildingBlocksToolbar(); + + JPanel panel = new ToolbarScrollPane(toolbar); + + refreshBuildingBlocksToolbar(); + + return panel; + } + + private Component createFavoritesPanel() { JPanel panel = new JPanel(new BorderLayout()); panel.setOpaque(false); - final Container toolbar = getBuildingBlocksToolbar(); -// refreshRecentComponentsToolbar( -// toolbar, -// (List) ConfigurationManager.getInstance().readObject(IPlugInPort.RECENT_COMPONENTS_KEY, -// new ArrayList())); -// ConfigurationManager.getInstance().addConfigListener(IPlugInPort.RECENT_COMPONENTS_KEY, new IConfigListener() { -// -// @Override -// public void valueChanged(String key, Object value) { -// // Cache the new list, we'll refresh when there's a -// // chance -// pendingRecentComponents = (List) value; -// } -// }); + final Container toolbar = getFavoritesToolbar(); panel.add(toolbar, BorderLayout.CENTER); + + refreshFavoritesToolbar(); return panel; } @SuppressWarnings("unchecked") - private void refreshRecentComponentsToolbar(Container toolbar, List recentComponentClassList) { + private void refreshRecentComponentsToolbar(List recentComponentClassList) { + Container toolbar = getRecentToolbar(); toolbar.removeAll(); for (String componentClassName : recentComponentClassList) { ComponentType componentType; @@ -247,6 +285,30 @@ class ComponentTabbedPane extends JTabbedPane { LOG.error("Could not create recent component button for " + componentClassName, e); } } + } + + private void refreshBuildingBlocksToolbar() { + Container toolbar = getBuildingBlocksToolbar(); + toolbar.removeAll(); + for (String block : this.blocks) { + Component button = ComponentButtonFactory.createBuildingBlockButton(plugInPort, block); + toolbar.add(button); + } + } + + private void refreshFavoritesToolbar() { + Container toolbar = getFavoritesToolbar(); + toolbar.removeAll(); + for (Favorite fav : this.favorites) { + Component button; + if (fav.getType() == FavoriteType.Block) { + button = ComponentButtonFactory.createBuildingBlockButton(plugInPort, fav.getName()); + } else { + ComponentType componentType = this.typesByClass.get(fav.getName()); + button = ComponentButtonFactory.create(plugInPort, componentType, createVariantPopup(componentType)); + } + toolbar.add(button); + } } private JPopupMenu createVariantPopup(final ComponentType componentType) { diff --git a/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ToolbarScrollPane.java b/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ToolbarScrollPane.java new file mode 100644 index 00000000..cbd9ca1f --- /dev/null +++ b/diylc/diylc-swing/src/org/diylc/swing/plugins/toolbox/ToolbarScrollPane.java @@ -0,0 +1,77 @@ +/* + * + * DIY Layout Creator (DIYLC). Copyright (c) 2009-2018 held jointly by the individual authors. + * + * This file is part of DIYLC. + * + * DIYLC is free software: you can redistribute it and/or modify it under the terms of the GNU + * General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * DIYLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with DIYLC. If not, see + * . + */ +package org.diylc.swing.plugins.toolbox; + +import java.awt.BorderLayout; +import java.awt.Component; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.JScrollBar; +import javax.swing.JScrollPane; +import javax.swing.UIManager; +import javax.swing.plaf.basic.BasicArrowButton; + +public class ToolbarScrollPane extends JPanel { + + private static final long serialVersionUID = 1L; + + private Component view; + private BasicArrowButton west; + private BasicArrowButton east; + + public ToolbarScrollPane(Component view) { + this.view = view; + setOpaque(false); + setLayout(new BorderLayout()); + setBorder(BorderFactory.createEmptyBorder()); + + JScrollPane scrollPane = new JScrollPane(view); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); + add(scrollPane); + + JScrollBar horizontal = scrollPane.getHorizontalScrollBar(); + + west = new BasicArrowButton(BasicArrowButton.WEST); + west.setAction(new ActionMapAction("", horizontal, "negativeUnitIncrement", 120)); + west.setBorder(BorderFactory.createLineBorder(UIManager.getColor("Button.shadow"))); + add(west, BorderLayout.WEST); + + east = new BasicArrowButton(BasicArrowButton.EAST); + east.setAction(new ActionMapAction("", horizontal, "positiveUnitIncrement", 120)); + east.setBorder(BorderFactory.createLineBorder(UIManager.getColor("Button.shadow"))); + add(east, BorderLayout.EAST); + + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + refreshScrollButtonVisibility(); + } + }); + } + + private void refreshScrollButtonVisibility() { + boolean needsScroll = getWidth() < view.getWidth(); + west.setVisible(needsScroll); + east.setVisible(needsScroll); + } +}