/* This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV This program is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program; if not, see http://www.gnu.org/licenses or write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 */ package com.servoy.j2db.util.toolbar; import java.awt.Component; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.WeakHashMap; import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.ButtonModel; import javax.swing.Icon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JMenu; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import com.servoy.j2db.Messages; import com.servoy.j2db.util.EnablePanel; import com.servoy.j2db.util.FixedToggleButtonModel; import com.servoy.j2db.util.IProvideButtonModel; /** * ToolbarPanel is a container for the <code>Toolbar</code> components. The * serialized state is used to represent a single configuration of the toolbar. */ public class ToolbarPanel extends EnablePanel implements IToolbarPanel { /** All toolbars which are represented in ToolbarPool too. */ private final WeakHashMap<String, ToolbarConstraints> allToolbars; /** List of visible toolbar rows. */ private final List<ToolbarRow> toolbarRows; /** All invisible toolbars (visibility==false || tb.isCorrect==false). */ private final Map<ToolbarConstraints, Integer> invisibleToolbars; private final Map<String, Toolbar> toolbars; /** * Toolbars which was described in DOM Document, but which aren't represented * in ToolbarPool. For example ComponentPalette and first start of IDE. */ /** Cached preferred width. */ private int prefWidth; /** toolbar layout manager for this configuration */ private final ToolbarLayout toolbarLayout; /** toolbar drag and drop listener */ private final ToolbarDnDListener toolbarListener; private final Map<String, ToolbarAction> actions; private JPopupMenu popup; private JMenu menu; private final int iMaxWidth; private final MouseAdapter madapter; /** * Create new ToolbarPanel */ public ToolbarPanel(int iWidth) { super(); iMaxWidth = iWidth; toolbarLayout = new ToolbarLayout(this); setLayout(toolbarLayout); allToolbars = new WeakHashMap<String, ToolbarConstraints>(); actions = new HashMap<String, ToolbarAction>(); toolbarRows = new ArrayList<ToolbarRow>(); invisibleToolbars = new HashMap<ToolbarConstraints, Integer>(); toolbars = new HashMap<String, Toolbar>(); toolbarListener = new ToolbarDnDListener(this); madapter = new MouseAdapter() { @Override public void mousePressed(MouseEvent me) { maybeShowPopup(me); } @Override public void mouseReleased(MouseEvent me) { maybeShowPopup(me); } }; addMouseListener(madapter); setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); } public void clear() { List<String> names = new ArrayList<String>(toolbars.keySet()); for (String name : names) { removeToolBar(name); } } public Toolbar createToolbar(String name, String displayName) { return createToolbar(name, displayName, -1); } public Toolbar createToolbar(String name, String displayName, int wantedRow) { Toolbar tb = new Toolbar(name, displayName, true); addToolbar(tb, wantedRow); return tb; } public void addToolbar(Toolbar tb, int wantedRow) { tb.setDnDListener(toolbarListener); ToolbarConstraints tc; String name; ToolbarRow newRow = null; name = tb.getName(); tc = allToolbars.get(name); if (tc == null) { if (wantedRow != -1) { while (wantedRow >= toolbarRows.size()) { createLastRow(); } newRow = toolbarRows.get(wantedRow); } else { for (int i = 0; i < toolbarRows.size(); i++) { ToolbarRow row = toolbarRows.get(i); int rowwidth = row.getPrefWidth(); int iTbSize = tb.getPreferredSize().width; // int count = row.toolbarCount(); if ( /* count < 2 && */(rowwidth + iTbSize) < iMaxWidth) { newRow = row; break; } } } /* If there is no toolbar constraints description defined yet ... */ if (newRow == null) newRow = createLastRow(); /* ... there is created a new constraints. */ tc = new ToolbarConstraints(this, name, null, Boolean.TRUE); addToolbar(newRow, tc); } add(tb, tc); revalidateWindow(); actions.put(tb.getName(), new ToolbarAction(this, tc, tb)); toolbars.put(tb.getName(), tb); tb.addMouseListener(madapter); } public Toolbar getToolBar(String name) { return toolbars.get(name); } public int getToolBarRow(String name) { ToolbarConstraints tc = getToolbarConstraints(name); if (tc.isVisible()) return tc.rowIndex(); return -1; } public int getToolbarRowIndex(String name) { for (int i = 0; i < toolbarRows.size(); i++) { ToolbarRow row = toolbarRows.get(i); int index = row.indexOf(name); if (index != -1) return index; } return -1; } public void removeToolBar(String name) { removeToolbarEx(name); } private void maybeShowPopup(MouseEvent e) { if (popup == null) popup = createMenus(); if (e.isPopupTrigger()) { popup.show(e.getComponent(), e.getX(), e.getY()); } } /** * Add toolbar to list of all toolbars. If specified toolbar constraints * represents visible component it is added to specified toolbar row. * Otherwise toolbar constraints is added to invisible toolbars. * * @param row * toolbar row of new toolbar is part * @param tc * added toolbar represented by ToolbarConstraints */ void addToolbar(ToolbarRow row, ToolbarConstraints tc) { if (tc == null) return; if (tc.isVisible()) row.addToolbar(tc); else { int rI; if (row == null) rI = toolbarRows.size(); else rI = toolbarRows.indexOf(row); invisibleToolbars.put(tc, new Integer(rI)); } allToolbars.put(tc.getName(), tc); } /** * Remove toolbar from list of all toolbars. This could mean that toolbar is * represented only in DOM document. * * @param name * name of removed toolbar */ ToolbarConstraints removeToolbarEx(String name) { ToolbarConstraints tc = allToolbars.remove(name); if (tc != null) { removeVisible(tc); popup = null; actions.remove(name); Toolbar tb = toolbars.remove(name); if (tb != null) tb.setVisible(false); Iterator<Entry<ToolbarConstraints, Integer>> it = invisibleToolbars.entrySet().iterator(); while (it.hasNext()) { if (it.next().getKey().getName().equals(name)) { it.remove(); } } if (tc.destroy()) checkToolbarRows(); } return tc; } /** * Add toolbar row as last row. * * @param row * added toolbar row */ void addRow(ToolbarRow row) { addRow(row, toolbarRows.size()); } /** * Add toolbar row to specific index. * * @param row * added toolbar row * @param index * specified index of toolbar position */ void addRow(ToolbarRow row, int index) { /* It is important to recompute row neighbourhood. */ ToolbarRow prev = null; ToolbarRow next = null; if (index > 0 && index - 1 < toolbarRows.size()) { prev = toolbarRows.get(index - 1); } if (index >= 0 && index < toolbarRows.size()) { next = toolbarRows.get(index); } if (prev != null) prev.setNextRow(row); row.setPrevRow(prev); row.setNextRow(next); if (next != null) next.setPrevRow(row); toolbarRows.add(index, row); updateBounds(row); this.setVisible(true); } /** * Remove toolbar row from list of all rows. * * @param row * removed toolbar row */ void removeRow(ToolbarRow row) { /* It is important to recompute row neighbournhood. */ ToolbarRow prev = row.getPrevRow(); ToolbarRow next = row.getNextRow(); if (prev != null) { prev.setNextRow(next); } if (next != null) { next.setPrevRow(prev); } toolbarRows.remove(row); this.setVisible(toolbarRows.size() != 0); updateBounds(next); revalidateWindow(); } /** * Update toolbar row cached bounds. * * @param toolbarRow * updated toolbarRow */ void updateBounds(ToolbarRow toolbarRow) { ToolbarRow row = toolbarRow; while (row != null) { row.updateBounds(); row = row.getNextRow(); } } /** * @param row * specified toolbar row * @return index of toolbar row */ int rowIndex(ToolbarRow row) { return toolbarRows.indexOf(row); } /** * Updates cached preferred width of toolbar configuration. */ void updatePrefWidth() { Iterator<ToolbarRow> it = toolbarRows.iterator(); prefWidth = 0; while (it.hasNext()) { prefWidth = Math.max(prefWidth, it.next().getPrefWidth()); } } /** * @return configuration preferred width */ int getPrefWidth() { return prefWidth; } /** * @return configuration preferred height. If there is no row, preferred * height is 25% of BASIC_HEIGHT. */ int getPrefHeight() { double rowCount = getRowCount(); if (rowCount == 0) rowCount = 0.25; return (ToolbarLayout.VGAP + (int)((ToolbarLayout.VGAP + Toolbar.BASIC_HEIGHT) * rowCount)); } /** * Checks toolbar rows. If there is some empty row it is removed. */ void checkToolbarRows() { Object[] rows = toolbarRows.toArray(); ToolbarRow row; for (int i = rows.length - 1; i >= 0; i--) { row = (ToolbarRow)rows[i]; if (row.isEmpty()) removeRow(row); } } /** * @return number of rows. */ int getRowCount() { return toolbarRows.size(); } /** * @param name toolbar constraints name * @return toolbar constraints of specified name */ public ToolbarConstraints getToolbarConstraints(String name) { return allToolbars.get(name); } /** * Checks toolbars constraints if there is some of specific name. If isn't * then is created new toolbar constraints. Otherwise is old toolbar * constraints confronted with new values (position, visibility). * * @param name of checked toolbar * @param position of toolbar * @param visible visibility of toolbar * @return toolbar constraints for specified toolbar name */ ToolbarConstraints checkToolbarConstraints(String name, Integer position, Boolean visible) { ToolbarConstraints tc = allToolbars.get(name); if (tc == null) tc = new ToolbarConstraints(this, name, position, visible); else tc.checkNextPosition(position, visible); return tc; } /** * Revalidates toolbar pool window. It is important for change height when * number of rows is changed. */ void revalidateWindow() { // replan to AWT thread, if needed (because of direct swing calls) if (SwingUtilities.isEventDispatchThread()) { doRevalidateWindow(); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { doRevalidateWindow(); } }); } } /** * Performs revalidating work */ private void doRevalidateWindow() { revalidate(); java.awt.Window w = SwingUtilities.windowForComponent(this); if (w != null) { w.validate(); } } /** * Removes toolbar from visible toolbars. * @param tc specified toolbar */ void removeVisible(ToolbarConstraints tc) { invisibleToolbars.put(tc, new Integer(tc.rowIndex())); if (tc.destroy()) checkToolbarRows(); tc.setVisible(false); this.setVisible(toolbarRows.size() != 0); } /** * Adds toolbar from list of invisible to visible toolbars. * @param tc specified toolbar */ void addInvisible(ToolbarConstraints tc) { int rC = toolbarRows.size(); int pos = (invisibleToolbars.remove(tc)).intValue(); tc.setVisible(true); for (int i = pos; i < pos + tc.getRowCount(); i++) { getRow(i).addToolbar(tc, tc.getPosition()); } this.setVisible(true); if (rC != toolbarRows.size()) revalidateWindow(); } /** * @param rI ndex of required row * @return toolbar row of specified index. If rI is out of bounds then new row is created. */ ToolbarRow getRow(int rI) { ToolbarRow row; int s = toolbarRows.size(); if (rI < 0) { row = new ToolbarRow(this); addRow(row, 0); } else if (rI >= s) { row = new ToolbarRow(this); addRow(row); } else { row = toolbarRows.get(rI); } return row; } /** * @return toolbar row at last row position. */ ToolbarRow createLastRow() { return getRow(toolbarRows.size()); } /** * Popup menu that should be displayed when the users presses right mouse * button on the panel. This menu can contain contains list of possible * configurations, additional actions, etc. * * @return popup menu to be displayed */ private JPopupMenu createMenus() { menu = new JMenu(Messages.getString("servoy.toolbars.text")); //$NON-NLS-1$ menu.setIcon(new EmptyIcon()); popup = new JPopupMenu(); // generate list of available actions Object[] col = actions.values().toArray(); Arrays.sort(col); for (Object element : col) { ToolbarAction tba = (ToolbarAction)element; JCheckBoxMenuItem mi = new JCheckBoxMenuItem(tba); // mi.setState(tba.isSelected()); popup.add(mi); JCheckBoxMenuItem mi2 = new JCheckBoxMenuItem(tba); menu.add(mi2); } return popup; } private static class EmptyIcon implements Icon { public int getIconHeight() { return 16; } public int getIconWidth() { return 16; } public void paintIcon(Component c, Graphics g, int x, int y) { } } /** * Popup menu that should be displayed when the users presses right mouse * button on the panel. This menu can contain contains list of possible * configurations, additional actions, etc. * * @return popup menu to be displayed */ public JMenu getMenu() { if (menu == null) createMenus(); return menu; } public Map<String, ToolbarAction> getActions() { return actions; } public void setToolbarVisible(String name, boolean visible) { ToolbarAction action = actions.get(name); if (action != null) { if ((action.isSelected() && !visible) || (!action.isSelected() && visible)) { action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "do")); //$NON-NLS-1$ } } } private static class ToolbarAction extends AbstractAction implements IProvideButtonModel, Comparable<ToolbarAction> { private final ToolbarPanel tp; private final ToolbarConstraints tc; private final Toolbar tb; private final FixedToggleButtonModel model; ToolbarAction(ToolbarPanel tp, ToolbarConstraints tc, Toolbar tb) { super(tb.getDisplayName()); this.tp = tp; this.tb = tb; this.tc = tc; model = new FixedToggleButtonModel(); model.setSelected(tb.isVisible()); } public void actionPerformed(ActionEvent ae) { boolean wasVisible = tc.isVisible(); if (wasVisible) { tp.removeVisible(tc); tb.setVisible(false); } else { tp.addInvisible(tc); tb.setVisible(true); } model.setSelected(tb.isVisible()); } public String getName() { return tb.getName(); } boolean isSelected() { return tb.isVisible(); } public ButtonModel getModel() { return model; } /* * (non-Javadoc) * * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(ToolbarAction toolbarAction) { return getName().compareToIgnoreCase(toolbarAction.getName()); } } /* * @see com.servoy.j2db.util.toolbar.IToolbarPanel#getCurrentToolBars() */ public String[] getToolBarNames() { return toolbars.keySet().toArray(new String[toolbars.size()]); } }