/*
 * Copyright (c) 2005-2007 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of 
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.jvnet.lafwidget.menu;

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;

import javax.swing.*;
import javax.swing.JInternalFrame.JDesktopIcon;

import org.jvnet.lafwidget.*;

/**
 * Adds menu search panel to menu bars.
 * 
 * @author Kirill Grouchnikov
 */
public class MenuSearchWidget extends LafWidgetAdapter implements Resettable {
	/**
	 * The associated menu bar.
	 */
	protected JMenuBar jmb;

	/**
	 * Boolean flag to prevent infinite loop. Maybe need to use something more
	 * elegant.
	 */
	private static boolean inEvent = false;

	// /**
	// * Map of all search panels (for all non-GC'd menu bars). Is used for
	// * resetting the icons on theme change. Key is {@link JMenuBar}, value is
	// * {@link SearchPanel}.
	// */
	// private static WeakHashMap panels = new WeakHashMap();
	//
	// private static LocaleChangeListener localeChangeListener;

	/**
	 * Listens on changes to the component orientation.
	 */
	protected PropertyChangeListener propertyListener;

	/**
	 * The associated search panel.
	 */
	private SearchPanel searchPanel;

	/**
	 * Panel for searching the menus.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class SearchPanel extends JPanel {
		/**
		 * Toggle button for showing / hiding search controls.
		 */
		private JToggleButton searchButton;

		/**
		 * Text field for entering search string.
		 */
		private JTextField searchStringField;

		// /**
		// * The associated menu bar.
		// */
		// private JMenuBar menuBar;
		//
		/**
		 * The result buttons. Key is {@link Integer}, value is {@link JButton}.
		 */
		private Map resultButtons;

		/**
		 * Simple constructor.
		 * 
		 * @param menuBar
		 *            The associated menu bar.
		 */
		public SearchPanel(final JMenuBar menuBar) {
			// this.menuBar = menuBar;
			this.setLayout(new SearchResultsLayout(this));

			// Search button (toggle) with tooltip.
			LafWidgetSupport support = LafWidgetRepository.getRepository()
					.getLafSupport();
			int iconDim = support.getLookupIconSize();
			int buttonDim = support.getLookupButtonSize();
			Icon searchIcon = (support == null) ? LafWidgetUtilities
					.getSearchIcon(iconDim, jmb.getComponentOrientation()
							.isLeftToRight()) : support.getSearchIcon(iconDim,
					jmb.getComponentOrientation());
			this.searchButton = new JToggleButton(searchIcon);
			this.searchButton.setPreferredSize(new Dimension(buttonDim,
					buttonDim));
			this.searchButton.setToolTipText(LafWidgetRepository
					.getLabelBundle().getString("Tooltip.menuSearchButton"));
			this.searchButton.setFocusable(false);
			if (support != null)
				support.markButtonAsFlat(this.searchButton);
			this.add(this.searchButton);

			// Add action listener on the toggle button. Based on the
			// state of the toggle button, the search field and result buttons
			// will be set visible or invisible.
			this.searchButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							boolean toShow = SearchPanel.this.searchButton
									.isSelected();
							SearchPanel.this.searchStringField
									.setVisible(toShow);
							SearchPanel.this.searchStringField.requestFocus();
							for (Iterator it = SearchPanel.this.resultButtons
									.values().iterator(); it.hasNext();) {
								JButton resultButton = (JButton) it.next();
								resultButton.setVisible(toShow);
							}
							SearchPanel.this.repaint();
							SearchPanel.this.revalidate();
						}
					});
				}
			});
			// add mouse listener to remove the search panel on mouse
			// click when CTRL button is pressed.
			this.searchButton.addMouseListener(new MouseAdapter() {
				public void mousePressed(MouseEvent e) {
					if ((e.getModifiers() & InputEvent.CTRL_MASK) != 0) {
						SwingUtilities.invokeLater(new Runnable() {
							public void run() {
								SearchPanel.this.removeAll();
								SearchPanel.this.repaint();
								jmb.revalidate();
							}
						});
					}
				}
			});

			// Search field.
			this.searchStringField = new JTextField();
			this.searchStringField.setColumns(10);
			this.add(this.searchStringField);
			this.searchStringField.setVisible(false);
			this.searchStringField.setToolTipText(LafWidgetRepository
					.getLabelBundle().getString("Tooltip.menuSearchField"));

			// Map to hold the result buttons (need for the icon reset
			// on theme change and layout manager).
			this.resultButtons = new HashMap();
			this.searchStringField.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					String searchString = SearchPanel.this.searchStringField
							.getText().toLowerCase();
					// See if there is at least one non-white space character.
					// This is fix for bug 54
					if (searchString.trim().length() == 0) {
						return;
					}

					// remove all old buttons
					for (Iterator it = SearchPanel.this.resultButtons.values()
							.iterator(); it.hasNext();) {
						JButton resultButton = (JButton) it.next();
						SearchPanel.this.remove(resultButton);
					}
					SearchPanel.this.resultButtons.clear();
					// find all matching menu items / menus
					LinkedList searchResults = SearchPanel.this
							.findOccurences(searchString);
					int count = 0;
					for (Iterator it = searchResults.iterator(); it.hasNext();) {
						SearchResult searchResult = (SearchResult) it.next();
						// show only first 16 results.
						if (count == 16)
							break;
						// create new button with binary icon
						LafWidgetSupport support = LafWidgetRepository
								.getRepository().getLafSupport();
						Icon markerIcon = support.getNumberIcon(count + 1);
						JButton resultButton = new JButton(markerIcon);
						// set action listener (to show the menu).
						resultButton
								.addActionListener(new SearchResultListener(
										searchResult));
						// check if the path to the menu (item) has
						// only enabled items.
						resultButton.setEnabled(searchResult.isEnabled());
						SearchPanel.this.add(resultButton);
						SearchPanel.this.resultButtons.put(new Integer(
								count + 1), resultButton);
						resultButton.setToolTipText("<html><body><b>"
								+ searchResult.toString()
								+ "</b><br>"
								+ LafWidgetRepository.getLabelBundle()
										.getString("Tooltip.menuSearchTooltip")
								+ "</html>");
						if (support != null)
							support.markButtonAsFlat(resultButton);
						count++;
					}
					SearchPanel.this.repaint();
					jmb.revalidate();
				}
			});
		}

		/**
		 * Returns all occurences of the specified string in the menus and menu
		 * items of the associated menu bar.
		 * 
		 * @param searchPattern
		 *            Pattern to search (no wildcards yet).
		 * @return All occurences of the specified string in the menus and menu
		 *         items of the associated menu bar.
		 */
		private LinkedList findOccurences(String searchPattern) {
			LinkedList result = new LinkedList();

			LinkedList currentPath = new LinkedList();

			for (int i = 0; i < jmb.getComponentCount(); i++) {
				Component component = jmb.getComponent(i);
				if (component instanceof JMenu) {
					JMenu menu = (JMenu) component;
					this.checkMenu(currentPath, menu, searchPattern, result);
				}
			}

			return result;
		}

		/**
		 * Recursively scans the specified menu (item) and updates the list that
		 * contains all occurences of the specified string in the contained
		 * menus and menu items.
		 * 
		 * @param currentPath
		 *            The path to the current menu (item). Contains
		 *            {@link JMenu}s.
		 * @param menuItem
		 *            The menu (item) itself that is being tested.
		 * @param searchPattern
		 *            Pattern to search (no wildcards yet).
		 * @param matchingResults
		 *            All occurences of the specified string up until now. After
		 *            <code>this</code> function returns, will also contain
		 *            all occurences of the specified string in the contained
		 *            menu (item)s. Contains {@link SearchResult}s.
		 */
		private void checkMenu(LinkedList currentPath, JMenuItem menuItem,
				String searchPattern, LinkedList matchingResults) {
			String menuItemText = menuItem.getText();
			if (menuItemText.toLowerCase().indexOf(searchPattern) >= 0) {
				matchingResults.addLast(new SearchResult(jmb, currentPath,
						menuItem));
			}
			if (menuItem instanceof JMenu) {
				JMenu menu = (JMenu) menuItem;
				currentPath.addLast(menu);
				for (int i = 0; i < menu.getMenuComponentCount(); i++) {
					Component menuComponent = menu.getMenuComponent(i);
					if (menuComponent instanceof JMenuItem) {
						JMenuItem childItem = (JMenuItem) menuComponent;
						this.checkMenu(currentPath, childItem, searchPattern,
								matchingResults);
					}
				}
				currentPath.removeLast();
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.Component#setVisible(boolean)
		 */
		public void setVisible(boolean aFlag) {
			super.setVisible(aFlag);
			if (aFlag)
				this.searchStringField.requestFocus();
		}
	}

	/**
	 * Listener on the <code>search result</code> button. The action itself -
	 * show the associated menu path to the menu item that contains the string
	 * that has been specified during the search.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private static class SearchResultListener implements ActionListener {
		/**
		 * The associated search result.
		 */
		private SearchResult searchResult;

		/**
		 * Simple constructor.
		 * 
		 * @param searchResult
		 *            The associated search result.
		 */
		public SearchResultListener(SearchResult searchResult) {
			super();
			this.searchResult = searchResult;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
		 */
		public void actionPerformed(ActionEvent e) {
			// start opening the menus
			MenuElement[] menuElements = this.searchResult.menuElements;
			MenuSelectionManager.defaultManager().setSelectedPath(menuElements);
		}
	}

	/**
	 * Single result of menu search.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private static class SearchResult {
		/**
		 * Path to the menu (item). The first element is always {@link JMenuBar},
		 * and after each {@link JMenu} there is it's
		 * {@link JMenu#getPopupMenu()}.
		 */
		private MenuElement[] menuElements;

		/**
		 * Simple constructor.
		 * 
		 * @param menuBar
		 *            The main menu bar.
		 * @param menuPath
		 *            The menus leading to the matching entry. Contains
		 *            {@link JMenu}s.
		 * @param menuLeaf
		 *            The menu (item) that matches the search pattern string.
		 */
		public SearchResult(JMenuBar menuBar, LinkedList menuPath,
				JMenuItem menuLeaf) {
			int count = 1;
			if (menuPath != null)
				count += 2 * menuPath.size();
			if (menuLeaf != null)
				count++;
			this.menuElements = new MenuElement[count];
			count = 0;

			// the first element is the menu bar itself
			this.menuElements[count++] = menuBar;
			if (menuPath != null) {
				for (Iterator it = menuPath.iterator(); it.hasNext();) {
					JMenu menu = (JMenu) it.next();
					this.menuElements[count++] = menu;
					// important - don't forget the popup menu of the menu
					this.menuElements[count++] = menu.getPopupMenu();
				}
			}
			if (menuLeaf != null)
				this.menuElements[count] = menuLeaf;
		}

		/**
		 * Returns the path to the menu (item).
		 * 
		 * @return Path to the menu (item). The first element is always
		 *         {@link JMenuBar}, and after each {@link JMenu} there is it's
		 *         {@link JMenu#getPopupMenu()}.
		 */
		public MenuElement[] getMenuElements() {
			return this.menuElements;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		public String toString() {
			StringBuffer sb = new StringBuffer();
			if (this.menuElements != null) {
				String sep = "";
				for (int i = 0; i < this.menuElements.length; i++) {
					MenuElement menuElem = this.menuElements[i];
					if (menuElem instanceof JMenuItem) {
						sb.append(sep);
						sep = " -> ";
						sb.append(((JMenuItem) menuElem).getText());
					}
				}
			}
			return sb.toString();
		}

		/**
		 * Checks that all entries leading to the associated menu (item) are
		 * enabled.
		 * 
		 * @return <code>true</code> if all entries leading to the associated
		 *         menu (item) are enabled, <code>false</code> otherwise.
		 */
		public boolean isEnabled() {
			// all parts must be enabled
			for (int i = 0; i < this.menuElements.length; i++) {
				MenuElement menuElem = this.menuElements[i];
				if (menuElem instanceof JMenuItem) {
					JMenuItem menuItem = (JMenuItem) menuElem;
					if (!menuItem.isEnabled())
						return false;
				}
			}
			return true;
		}
	}

	/**
	 * Returns the number of menu items under the specified menu item.
	 * 
	 * @param menuItem
	 *            The root menu item.
	 * @return The number of menu items under the specified menu item.
	 */
	private static int getMenuItemCount(JMenuItem menuItem) {
		int result = 1;

		if (menuItem instanceof JMenu) {
			JMenu menu = (JMenu) menuItem;
			for (int i = 0; i < menu.getMenuComponentCount(); i++) {
				Component child = menu.getMenuComponent(i);
				if (child instanceof JMenuItem)
					result += MenuSearchWidget
							.getMenuItemCount((JMenuItem) child);
			}
		}

		return result;
	}

	/**
	 * Returns the number of menu items under the specified menu bar.
	 * 
	 * @param menuBar
	 *            The root menu bar.
	 * @return The number of menu items under the specified menu bar.
	 */
	public static int getMenuItemCount(JMenuBar menuBar) {
		int result = 0;

		for (int i = 0; i < menuBar.getMenuCount(); i++) {
			JMenu menu = menuBar.getMenu(i);
			if (menu != null) {
				result += MenuSearchWidget.getMenuItemCount(menu);
			}
		}

		return result;
	}

	/**
	 * Hides search panels recursively on the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @param toRepaint
	 *            Indication whether the relevant menu bars should be repainted.
	 */
	private static void hideSearchPanels(Component comp, final boolean toRepaint) {
		if (comp instanceof JFrame) {
			JFrame jf = (JFrame) comp;
			if (jf.getRootPane() != null) {
				JMenuBar menuBar = jf.getJMenuBar();
				if (menuBar != null) {
					for (int j = 0; j < menuBar.getComponentCount(); j++) {
						if (menuBar.getComponent(j) instanceof SearchPanel) {
							SearchPanel sPanel = (SearchPanel) menuBar
									.getComponent(j);
							menuBar.remove(sPanel);
							if (toRepaint)
								menuBar.repaint();
							break;
						}
					}
				}
			}
		}

		if (comp instanceof JInternalFrame) {
			JInternalFrame jif = (JInternalFrame) comp;
			if (jif.getRootPane() != null) {
				JMenuBar menuBar = jif.getJMenuBar();
				if (menuBar != null) {
					for (int j = 0; j < menuBar.getComponentCount(); j++) {
						if (menuBar.getComponent(j) instanceof SearchPanel) {
							SearchPanel sPanel = (SearchPanel) menuBar
									.getComponent(j);
							menuBar.remove(sPanel);
							if (toRepaint)
								menuBar.repaint();
							break;
						}
					}
				}
			}
		}

		if (comp instanceof Container) {
			Container cont = (Container) comp;
			for (int i = 0; i < cont.getComponentCount(); i++) {
				Component child = cont.getComponent(i);
				if (child instanceof JDesktopIcon)
					child = ((JDesktopIcon) child).getInternalFrame();
				hideSearchPanels(child, toRepaint);
			}
		}
	}

	/**
	 * Hides search panels on all menu bars (both JFrames and JInternalFrames).
	 * 
	 * @param toRepaint
	 *            Indication whether the relevant menu bars should be repainted.
	 */
	public static void hideSearchPanels(final boolean toRepaint) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				Frame[] frames = Frame.getFrames();
				for (int i = 0; i < frames.length; i++) {
					hideSearchPanels(frames[i], toRepaint);
				}
			};
		});
	}

	/**
	 * Shows search panels on all descendant internal frames of the specified
	 * component.
	 * 
	 * @param comp
	 *            A component.
	 */
	protected static void showSearchPanels(Component comp) {
		if (comp instanceof JDesktopPane) {
			JDesktopPane desktop = (JDesktopPane) comp;
			JInternalFrame[] iFrames = desktop.getAllFrames();
			for (int i = 0; i < iFrames.length; i++) {
				JInternalFrame jif = iFrames[i];
				if (jif.getRootPane() != null) {
					JMenuBar menuBar = jif.getJMenuBar();
					if (menuBar != null)
						SwingUtilities.updateComponentTreeUI(menuBar);
				}
			}
			return;
		}
		if (comp instanceof Container) {
			Container cont = (Container) comp;
			for (int i = 0; i < cont.getComponentCount(); i++) {
				MenuSearchWidget.showSearchPanels(cont.getComponent(i));
			}
		}
	}

	/**
	 * Shows search panels on all menu bars (both JFrames and JInternalFrames).
	 */
	public static void showSearchPanels() {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				Frame[] frames = Frame.getFrames();
				for (int i = 0; i < frames.length; i++) {
					Frame frame = frames[i];

					if (frame instanceof JFrame) {
						JFrame jf = (JFrame) frame;
						if (jf.getRootPane() != null) {
							JMenuBar menuBar = jf.getJMenuBar();
							if (menuBar != null)
								SwingUtilities.updateComponentTreeUI(menuBar);
						}
					}
					// fix for defect 134 - menubars on internal frames
					MenuSearchWidget.showSearchPanels(frame);
				}
			};
		});
	}

	public void setComponent(JComponent jcomp) {
		super.setComponent(jcomp);
		this.jmb = (JMenuBar) jcomp;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvnet.lafwidget.LafWidgetAdapter#installUI()
	 */
	public void installUI() {
		// boolean toAddListener = false;

		final LafWidgetSupport lafSupport = LafWidgetRepository.getRepository()
				.getLafSupport();
		this.searchPanel = new SearchPanel(this.jmb);
		this.jmb.add(searchPanel, this.jmb.getComponentCount());
		this.searchPanel.setVisible(lafSupport.toInstallMenuSearch(this.jmb));
		// NewMenuSearchWidget.panels.put(this.jmb, searchPanel);
		// toAddListener = true;
		// }

		// if (toAddListener) {
		// need to add a container listener that will move a newly added
		// JMenu one entry before the last (so that our search panel
		// will always be the last).
		this.jmb.addContainerListener(new ContainerAdapter() {
			public void componentAdded(ContainerEvent e) {
				if (!(e.getChild() instanceof JMenu))
					return;
				if (!MenuSearchWidget.inEvent) {
					MenuSearchWidget.inEvent = true;
					Component removed = null;
					for (int i = 0; i < MenuSearchWidget.this.jmb
							.getComponentCount(); i++) {
						if (MenuSearchWidget.this.jmb.getComponent(i) instanceof SearchPanel) {
							removed = MenuSearchWidget.this.jmb.getComponent(i);
							break;
						}
					}
					if (removed != null) {
						MenuSearchWidget.this.jmb.remove(removed);
						MenuSearchWidget.this.jmb.add(removed,
								MenuSearchWidget.this.jmb.getComponentCount());
						// Show search panel only if the LAF-specific
						// support requests this
						if (lafSupport.toInstallMenuSearch(jmb))
							removed.setVisible(true);
						else
							removed.setVisible(false);
					}
					MenuSearchWidget.inEvent = false;
				}
			}
		});
		// }

		// SearchPanel sp = (SearchPanel)
		// NewMenuSearchWidget.panels.get(this.jmb);
		// if (sp != null) {
		searchPanel.applyComponentOrientation(this.jmb
				.getComponentOrientation());
		// }
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvnet.lafwidget.LafWidgetAdapter#uninstallUI()
	 */
	public void uninstallUI() {
		this.jmb.remove(this.searchPanel);
		super.uninstallUI();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvnet.lafwidget.LafWidgetAdapter#installListeners()
	 */
	public void installListeners() {
		super.installListeners();

		this.propertyListener = new PropertyChangeListener() {
			public void propertyChange(final PropertyChangeEvent evt) {
				if ("componentOrientation".equals(evt.getPropertyName())) {
					// final SearchPanel sp = (SearchPanel)
					// NewMenuSearchWidget.panels
					// .get(NewMenuSearchWidget.this.jmb);
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							if (searchPanel != null) {
								searchPanel
										.applyComponentOrientation((ComponentOrientation) evt
												.getNewValue());
							}
							MenuSearchWidget.this.reset();
						}
					});
				}
			}
		};
		this.jmb.addPropertyChangeListener(this.propertyListener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvnet.lafwidget.LafWidgetAdapter#uninstallListeners()
	 */
	public void uninstallListeners() {
		this.jmb.removePropertyChangeListener(this.propertyListener);
		this.propertyListener = null;
	}

	public void reset() {
		LafWidgetSupport support = LafWidgetRepository.getRepository()
				.getLafSupport();
		// SearchPanel searchPanel = (SearchPanel) NewMenuSearchWidget.panels
		// .get(this.jmb);
		if (searchPanel == null)
			return;
		for (Iterator it = searchPanel.resultButtons.entrySet().iterator(); it
				.hasNext();) {
			Map.Entry entry = (Map.Entry) it.next();
			int index = ((Integer) entry.getKey()).intValue();
			JButton button = (JButton) entry.getValue();

			Icon markerIcon = (support == null) ? LafWidgetUtilities
					.getHexaMarker(index) : support.getNumberIcon(index);
			button.setIcon(markerIcon);
		}
		int iconDim = support.getLookupIconSize();
		Icon searchIcon = (support == null) ? LafWidgetUtilities.getSearchIcon(
				iconDim, searchPanel.getComponentOrientation().isLeftToRight())
				: support.getSearchIcon(iconDim, searchPanel
						.getComponentOrientation());
		searchPanel.searchButton.setIcon(searchIcon);
		searchPanel.searchButton.setToolTipText(LafWidgetRepository
				.getLabelBundle().getString("Tooltip.menuSearchButton"));
		searchPanel.searchStringField.setToolTipText(LafWidgetRepository
				.getLabelBundle().getString("Tooltip.menuSearchField"));
	}

	/**
	 * Layout for the search panel. Note that {@link FlowLayout} is almost
	 * perfect for us, but we need the following:
	 * <ul>
	 * <li>Minimum size to be 16*16 (for the search icon)
	 * <li>When there isn't enough place for result buttons, they should
	 * continue (even if they are unseen) and not flow to the next line.
	 * </ul>
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class SearchResultsLayout implements LayoutManager {
		/**
		 * The associated search panel.
		 */
		private SearchPanel searchPanel;

		/**
		 * Simple constructor.
		 * 
		 * @param searchPanel
		 *            The associated search panel.
		 */
		public SearchResultsLayout(SearchPanel searchPanel) {
			this.searchPanel = searchPanel;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
		 *      java.awt.Component)
		 */
		public void addLayoutComponent(String name, Component c) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
		 */
		public void removeLayoutComponent(Component c) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
		 */
		public Dimension preferredLayoutSize(Container c) {
			if (this.searchPanel.searchButton.isSelected())
				return c.getSize();
			int buttonSize = LafWidgetRepository.getRepository()
					.getLafSupport().getLookupButtonSize();
			return new Dimension(buttonSize, buttonSize);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
		 */
		public Dimension minimumLayoutSize(Container c) {
			// enough for the search icon
			int buttonSize = LafWidgetRepository.getRepository()
					.getLafSupport().getLookupButtonSize();
			return new Dimension(buttonSize, buttonSize);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		public void layoutContainer(Container c) {
			int height = c.getHeight();
			int width = c.getWidth();

			if (!this.searchPanel.searchButton.isVisible())
				return;

			boolean leftToRight = jmb.getComponentOrientation().isLeftToRight();

			if (leftToRight) {
				// start from the toggle button
				int x = 2;
				int sbWidth = this.searchPanel.searchButton.getPreferredSize().width;
				int sbHeight = this.searchPanel.searchButton.getPreferredSize().height;
				this.searchPanel.searchButton.setBounds(x,
						(height - sbHeight) / 2, sbWidth, sbHeight);

				x += (sbWidth + 4);

				if (this.searchPanel.isVisible()) {
					// now - text field
					int tbWidth = this.searchPanel.searchStringField
							.getPreferredSize().width;
					int tbHeight = this.searchPanel.searchStringField
							.getPreferredSize().height;
					this.searchPanel.searchStringField.setBounds(x,
							(height - tbHeight) / 2, tbWidth, tbHeight);

					x += (tbWidth + 2);

					// result buttons
					int buttonCount = this.searchPanel.resultButtons.size();
					for (int i = 1; i <= buttonCount; i++) {
						JButton button = (JButton) this.searchPanel.resultButtons
								.get(new Integer(i));
						int bw = button.getPreferredSize().width;
						int bh = button.getPreferredSize().height;

						button.setBounds(x, (height - bh) / 2, bw, bh);
						x += (bw + 1);
					}
				}
			} else {
				// start from the toggle button
				int x = width - 2;
				int sbWidth = this.searchPanel.searchButton.getPreferredSize().width;
				int sbHeight = this.searchPanel.searchButton.getPreferredSize().height;
				this.searchPanel.searchButton.setBounds(x - sbWidth,
						(height - sbHeight) / 2, sbWidth, sbHeight);

				x -= (sbWidth + 4);

				if (this.searchPanel.isVisible()) {
					// now - text field
					int tbWidth = this.searchPanel.searchStringField
							.getPreferredSize().width;
					int tbHeight = this.searchPanel.searchStringField
							.getPreferredSize().height;
					this.searchPanel.searchStringField.setBounds(x - tbWidth,
							(height - tbHeight) / 2, tbWidth, tbHeight);

					x -= (tbWidth + 2);

					// result buttons
					int buttonCount = this.searchPanel.resultButtons.size();
					for (int i = 1; i <= buttonCount; i++) {
						JButton button = (JButton) this.searchPanel.resultButtons
								.get(new Integer(i));
						int bw = button.getPreferredSize().width;
						int bh = button.getPreferredSize().height;

						button.setBounds(x - bw, (height - bh) / 2, bw, bh);
						x -= (bw + 1);
					}
				}
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvnet.lafwidget.LafWidget#requiresCustomLafSupport()
	 */
	public boolean requiresCustomLafSupport() {
		return false;
	}
}
