/* * FurnitureCatalogListPanel.java 10 janv 2010 * * Sweet Home 3D, Copyright (c) 2010 Emmanuel PUYBARET / eTeks <info@eteks.com> * * This program 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 2 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.eteks.sweethome3d.swing; import java.awt.Component; import java.awt.Container; import java.awt.Cursor; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.dnd.DnDConstants; import java.awt.event.ActionEvent; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.AbstractListModel; import javax.swing.DefaultComboBoxModel; import javax.swing.DefaultListCellRenderer; import javax.swing.Icon; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JToolTip; import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.LayoutFocusTraversalPolicy; import javax.swing.ListCellRenderer; import javax.swing.ListModel; import javax.swing.SwingUtilities; import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListDataEvent; import javax.swing.event.ListDataListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.MouseInputAdapter; import javax.swing.text.AttributeSet; import javax.swing.text.Element; import javax.swing.text.html.HTML; import javax.swing.text.html.HTMLDocument; import com.eteks.sweethome3d.model.CatalogPieceOfFurniture; import com.eteks.sweethome3d.model.CollectionEvent; import com.eteks.sweethome3d.model.CollectionListener; import com.eteks.sweethome3d.model.Content; import com.eteks.sweethome3d.model.FurnitureCatalog; import com.eteks.sweethome3d.model.FurnitureCategory; import com.eteks.sweethome3d.model.SelectionEvent; import com.eteks.sweethome3d.model.SelectionListener; import com.eteks.sweethome3d.model.UserPreferences; import com.eteks.sweethome3d.tools.OperatingSystem; import com.eteks.sweethome3d.viewcontroller.FurnitureCatalogController; import com.eteks.sweethome3d.viewcontroller.View; /** * A furniture catalog view that displays furniture in a list, with a combo and search text field. * @author Emmanuel Puybaret */ class FurnitureCatalogListPanel extends JPanel implements View { private ListSelectionListener listSelectionListener; private JLabel categoryFilterLabel; private JComboBox categoryFilterComboBox; private JLabel searchLabel; private JTextField searchTextField; private JList catalogFurnitureList; /** * Creates a panel that displays <code>catalog</code> furniture in a list with a filter combo box * and a search field. */ public FurnitureCatalogListPanel(FurnitureCatalog catalog, UserPreferences preferences, FurnitureCatalogController controller) { super(new GridBagLayout()); createComponents(catalog, preferences, controller); setMnemonics(preferences); layoutComponents(); } /** * Creates the components displayed by this panel. */ private void createComponents(FurnitureCatalog catalog, final UserPreferences preferences, final FurnitureCatalogController controller) { final CatalogListModel catalogListModel = new CatalogListModel(catalog); this.catalogFurnitureList = new JList(catalogListModel) { private FurnitureToolTip toolTip = new FurnitureToolTip(false, preferences); @Override public JToolTip createToolTip() { this.toolTip.setComponent(this); return this.toolTip; } @Override public String getToolTipText(MouseEvent ev) { // Return a tooltip for furniture pieces described in the list. int index = locationToIndex(ev.getPoint()); if (index != -1) { this.toolTip.setPieceOfFurniture((CatalogPieceOfFurniture)getModel().getElementAt(index)); return this.toolTip.getToolTipText(); } else { return null; } } }; this.catalogFurnitureList.setLayoutOrientation(JList.HORIZONTAL_WRAP); this.catalogFurnitureList.setCellRenderer(new CatalogCellRenderer()); // Remove Select all action this.catalogFurnitureList.getActionMap().getParent().remove("selectAll"); addDragListener(this.catalogFurnitureList); addMouseListeners(this.catalogFurnitureList, controller); catalogListModel.addListDataListener(new ListDataListener() { public void contentsChanged(ListDataEvent ev) { spreadFurnitureIconsAlongListWidth(); } public void intervalAdded(ListDataEvent ev) { spreadFurnitureIconsAlongListWidth(); } public void intervalRemoved(ListDataEvent ev) { spreadFurnitureIconsAlongListWidth(); } }); this.catalogFurnitureList.addAncestorListener(new AncestorListener() { public void ancestorAdded(AncestorEvent ev) { spreadFurnitureIconsAlongListWidth(); } public void ancestorMoved(AncestorEvent ev) { spreadFurnitureIconsAlongListWidth(); } public void ancestorRemoved(AncestorEvent ev) { } }); addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent ev) { spreadFurnitureIconsAlongListWidth(); } }); updateListSelectedFurniture(catalog, controller); addSelectionListeners(catalog, controller); this.categoryFilterLabel = new JLabel(preferences.getLocalizedString( FurnitureCatalogListPanel.class, "categoryFilterLabel.text")); List<FurnitureCategory> categories = new ArrayList<FurnitureCategory>(); categories.add(null); categories.addAll(catalog.getCategories()); this.categoryFilterComboBox = new JComboBox(new DefaultComboBoxModel(categories.toArray())) { @Override public Dimension getMinimumSize() { return new Dimension(60, super.getMinimumSize().height); } }; this.categoryFilterComboBox.setMaximumRowCount(20); this.categoryFilterComboBox.setRenderer(new DefaultListCellRenderer() { public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (value == null) { return super.getListCellRendererComponent(list, preferences.getLocalizedString(FurnitureCatalogListPanel.class, "categoryFilterComboBox.noCategory"), index, isSelected, cellHasFocus); } else { return super.getListCellRendererComponent(list, ((FurnitureCategory)value).getName(), index, isSelected, cellHasFocus); } } }); this.categoryFilterComboBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent ev) { catalogListModel.setFilterCategory((FurnitureCategory)categoryFilterComboBox.getSelectedItem()); catalogFurnitureList.clearSelection(); } }); this.searchLabel = new JLabel(preferences.getLocalizedString( FurnitureCatalogListPanel.class, "searchLabel.text")); this.searchTextField = new JTextField(5); this.searchTextField.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent ev) { Object selectedValue = catalogFurnitureList.getSelectedValue(); catalogListModel.setFilterText(searchTextField.getText()); catalogFurnitureList.clearSelection(); catalogFurnitureList.setSelectedValue(selectedValue, true); if (catalogListModel.getSize() == 1) { catalogFurnitureList.setSelectedIndex(0); } } public void insertUpdate(DocumentEvent ev) { changedUpdate(ev); } public void removeUpdate(DocumentEvent ev) { changedUpdate(ev); } }); this.searchTextField.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "deleteContent"); this.searchTextField.getActionMap().put("deleteContent", new AbstractAction() { public void actionPerformed(ActionEvent ev) { searchTextField.setText(""); } }); if (OperatingSystem.isMacOSXLeopardOrSuperior()) { this.searchTextField.putClientProperty("JTextField.variant", "search"); } PreferencesChangeListener preferencesChangeListener = new PreferencesChangeListener(this); preferences.addPropertyChangeListener(UserPreferences.Property.LANGUAGE, preferencesChangeListener); catalog.addFurnitureListener(preferencesChangeListener); } /** * Language and catalog listener bound to this component with a weak reference to avoid * strong link between preferences and this component. */ private static class PreferencesChangeListener implements PropertyChangeListener, CollectionListener<CatalogPieceOfFurniture> { private final WeakReference<FurnitureCatalogListPanel> furnitureCatalogPanel; public PreferencesChangeListener(FurnitureCatalogListPanel furnitureCatalogPanel) { this.furnitureCatalogPanel = new WeakReference<FurnitureCatalogListPanel>(furnitureCatalogPanel); } public void propertyChange(PropertyChangeEvent ev) { // If panel was garbage collected, remove this listener from preferences FurnitureCatalogListPanel furnitureCatalogPanel = this.furnitureCatalogPanel.get(); UserPreferences preferences = (UserPreferences)ev.getSource(); if (furnitureCatalogPanel == null) { preferences.removePropertyChangeListener(UserPreferences.Property.LANGUAGE, this); } else { furnitureCatalogPanel.categoryFilterLabel.setText(preferences.getLocalizedString( FurnitureCatalogListPanel.class, "categoryFilterLabel.text")); furnitureCatalogPanel.searchLabel.setText(preferences.getLocalizedString( FurnitureCatalogListPanel.class, "searchLabel.text")); furnitureCatalogPanel.setMnemonics(preferences); // Update categories List<FurnitureCategory> categories = new ArrayList<FurnitureCategory>(); categories.add(null); categories.addAll(preferences.getFurnitureCatalog().getCategories()); furnitureCatalogPanel.categoryFilterComboBox.setModel(new DefaultComboBoxModel(categories.toArray())); } } public void collectionChanged(CollectionEvent<CatalogPieceOfFurniture> ev) { // If panel was garbage collected, remove this listener from catalog FurnitureCatalogListPanel furnitureCatalogPanel = this.furnitureCatalogPanel.get(); FurnitureCatalog catalog = (FurnitureCatalog)ev.getSource(); if (furnitureCatalogPanel == null) { catalog.removeFurnitureListener(this); } else { DefaultComboBoxModel model = (DefaultComboBoxModel)furnitureCatalogPanel.categoryFilterComboBox.getModel(); FurnitureCategory category = ev.getItem().getCategory(); List<FurnitureCategory> categories = catalog.getCategories(); if (!categories.contains(category)) { model.removeElement(category); furnitureCatalogPanel.categoryFilterComboBox.setSelectedIndex(0); } else if (model.getIndexOf(category) == -1) { model.insertElementAt(category, categories.indexOf(category) + 1); } } } } /** * Adds a mouse motion listener that will initiate a drag operation * when the user drags a piece of furniture from the furniture list. */ private void addDragListener(final JList catalogFurnitureList) { catalogFurnitureList.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged(MouseEvent ev) { if (SwingUtilities.isLeftMouseButton(ev) && catalogFurnitureList.getSelectedValue() != null && catalogFurnitureList.getTransferHandler() != null) { catalogFurnitureList.getTransferHandler().exportAsDrag(catalogFurnitureList, ev, DnDConstants.ACTION_COPY); } } }); } /** * Adds mouse listeners to the furniture list to modify selected furniture * and manage links in piece information. */ private void addMouseListeners(final JList catalogFurnitureList, final FurnitureCatalogController controller) { final Cursor handCursor = new Cursor(Cursor.HAND_CURSOR); MouseInputAdapter mouseListener = new MouseInputAdapter () { @Override public void mouseClicked(MouseEvent ev) { if (SwingUtilities.isLeftMouseButton(ev)) { if (ev.getClickCount() == 2) { int clickedPieceIndex = catalogFurnitureList.locationToIndex(ev.getPoint()); if (clickedPieceIndex != -1) { controller.modifySelectedFurniture(); } } else if (catalogFurnitureList.getCellRenderer() instanceof CatalogCellRenderer) { URL url = ((CatalogCellRenderer)catalogFurnitureList.getCellRenderer()).getURLAt(ev.getPoint(), (JList)ev.getSource()); if (url != null) { SwingTools.showDocumentInBrowser(url); } } } } @Override public void mouseMoved(MouseEvent ev) { if (catalogFurnitureList.getCellRenderer() instanceof CatalogCellRenderer) { URL url = ((CatalogCellRenderer)catalogFurnitureList.getCellRenderer()).getURLAt(ev.getPoint(), (JList)ev.getSource()); if (url != null) { EventQueue.invokeLater(new Runnable() { public void run() { setCursor(handCursor); } }); } } setCursor(Cursor.getDefaultCursor()); } }; catalogFurnitureList.addMouseListener(mouseListener); catalogFurnitureList.addMouseMotionListener(mouseListener); } /** * Sets components mnemonics and label / component associations. */ private void setMnemonics(UserPreferences preferences) { if (!OperatingSystem.isMacOSX()) { this.categoryFilterLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( FurnitureCatalogListPanel.class, "categoryFilterLabel.mnemonic")).getKeyCode()); this.categoryFilterLabel.setLabelFor(this.categoryFilterComboBox); this.searchLabel.setDisplayedMnemonic(KeyStroke.getKeyStroke(preferences.getLocalizedString( FurnitureCatalogListPanel.class, "searchLabel.mnemonic")).getKeyCode()); this.searchLabel.setLabelFor(this.searchTextField); } } /** * Layouts the components displayed by this panel. */ private void layoutComponents() { int labelAlignment = OperatingSystem.isMacOSX() ? GridBagConstraints.LINE_END : GridBagConstraints.LINE_START; // First row Insets labelInsets = new Insets(0, 2, 5, 3); Insets componentInsets = new Insets(0, 2, 3, 0); if (!OperatingSystem.isMacOSX()) { labelInsets.top = 2; componentInsets.top = 2; componentInsets.right = 2; } add(this.categoryFilterLabel, new GridBagConstraints( 0, 0, 1, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, labelInsets, 0, 0)); add(this.categoryFilterComboBox, new GridBagConstraints( 1, 0, 1, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, componentInsets, 0, 0)); // Second row if (OperatingSystem.isMacOSXLeopardOrSuperior()) { add(this.searchTextField, new GridBagConstraints( 0, 1, 2, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 3, 0), 0, 0)); } else { add(this.searchLabel, new GridBagConstraints( 0, 1, 1, 1, 0, 0, labelAlignment, GridBagConstraints.NONE, labelInsets, 0, 0)); add(this.searchTextField, new GridBagConstraints( 1, 1, 1, 1, 0, 0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, componentInsets, 0, 0)); } // Last row JScrollPane listScrollPane = new JScrollPane(this.catalogFurnitureList); listScrollPane.setPreferredSize(new Dimension(250, 250)); listScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); add(listScrollPane, new GridBagConstraints( 0, 2, 2, 1, 1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); SwingTools.installFocusBorder(this.catalogFurnitureList); setFocusTraversalPolicyProvider(true); setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { @Override public Component getDefaultComponent(Container aContainer) { EventQueue.invokeLater(new Runnable() { public void run() { // Return furniture list only at the first request setFocusTraversalPolicyProvider(false); } }); return catalogFurnitureList; } }); } /** * Computes furniture list visible row count to ensure its horizontal scrollbar * won't be seen. */ private void spreadFurnitureIconsAlongListWidth() { int size = this.catalogFurnitureList.getModel().getSize(); int extentWidth = ((JViewport)this.catalogFurnitureList.getParent()).getExtentSize().width; // Compute a fixed cell width that will spread ListCellRenderer cellRenderer = this.catalogFurnitureList.getCellRenderer(); Dimension rendererPreferredSize = ((JComponent)cellRenderer).getPreferredSize(); int minCellWidth = rendererPreferredSize.width; int visibleItemsPerRow = Math.max(1, extentWidth / minCellWidth); this.catalogFurnitureList.setVisibleRowCount(size % visibleItemsPerRow == 0 ? size / visibleItemsPerRow : size / visibleItemsPerRow + 1); this.catalogFurnitureList.setFixedCellWidth(minCellWidth + (extentWidth % minCellWidth) / visibleItemsPerRow); // Set also cell height otherwise first calls to repaint done by icon manager won't repaint it // because the list have a null size at the beginning if (cellRenderer instanceof CatalogCellRenderer) { this.catalogFurnitureList.setFixedCellHeight(((CatalogCellRenderer)cellRenderer).getPreferredHeight(this.catalogFurnitureList)); } else { this.catalogFurnitureList.setFixedCellHeight(rendererPreferredSize.height); } } /** * Adds the listeners that manage selection synchronization in this tree. */ private void addSelectionListeners(final FurnitureCatalog catalog, final FurnitureCatalogController controller) { final SelectionListener modelSelectionListener = new SelectionListener() { public void selectionChanged(SelectionEvent selectionEvent) { updateListSelectedFurniture(catalog, controller); } }; this.listSelectionListener = new ListSelectionListener() { public void valueChanged(ListSelectionEvent ev) { // Updates selected furniture in catalog from selected nodes in tree. controller.removeSelectionListener(modelSelectionListener); controller.setSelectedFurniture(getSelectedFurniture()); controller.addSelectionListener(modelSelectionListener); } }; controller.addSelectionListener(modelSelectionListener); this.catalogFurnitureList.getSelectionModel().addListSelectionListener(this.listSelectionListener); } /** * Updates selected items in list from <code>controller</code> selected furniture. */ private void updateListSelectedFurniture(FurnitureCatalog catalog, FurnitureCatalogController controller) { if (this.listSelectionListener != null) { this.catalogFurnitureList.getSelectionModel().removeListSelectionListener(this.listSelectionListener); } this.catalogFurnitureList.clearSelection(); List<CatalogPieceOfFurniture> selectedFurniture = controller.getSelectedFurniture(); if (selectedFurniture.size() > 0) { ListModel model = this.catalogFurnitureList.getModel(); List<Integer> selectedIndices = new ArrayList<Integer>(); for (CatalogPieceOfFurniture piece : selectedFurniture) { for (int i = 0, n = model.getSize(); i < n; i++) { if (piece == model.getElementAt(i)) { selectedIndices.add(i); break; } } } if (selectedIndices.size() > 0) { int [] indices = new int [selectedIndices.size()]; for (int i = 0; i < indices.length; i++) { indices [i] = selectedIndices.get(i); } this.catalogFurnitureList.setSelectedIndices(indices); this.catalogFurnitureList.ensureIndexIsVisible(indices [0]); } } if (this.listSelectionListener != null) { this.catalogFurnitureList.getSelectionModel().addListSelectionListener(this.listSelectionListener); } } /** * Returns the selected furniture in list. */ private List<CatalogPieceOfFurniture> getSelectedFurniture() { Object [] selectedValues = this.catalogFurnitureList.getSelectedValues(); CatalogPieceOfFurniture [] selectedFurniture = new CatalogPieceOfFurniture [selectedValues.length]; System.arraycopy(selectedValues, 0, selectedFurniture, 0, selectedValues.length); return Arrays.asList(selectedFurniture); } /** * Sets the transfer handler of the list displayed by this panel. */ @Override public void setTransferHandler(TransferHandler handler) { this.catalogFurnitureList.setTransferHandler(handler); } /** * Returns the transfer handler of the list displayed by this panel. */ @Override public TransferHandler getTransferHandler() { return this.catalogFurnitureList.getTransferHandler(); } /** * Sets the popup menu of the list displayed by this panel. */ @Override public void setComponentPopupMenu(JPopupMenu popup) { this.catalogFurnitureList.setComponentPopupMenu(popup); } /** * Returns the popup menu of the list displayed by this panel. */ @Override public JPopupMenu getComponentPopupMenu() { return this.catalogFurnitureList.getComponentPopupMenu(); } /** * Cell renderer for the furniture list. */ private static class CatalogCellRenderer extends JComponent implements ListCellRenderer { private static final int DEFAULT_ICON_HEIGHT = 48; private Font defaultFont; private Font modifiablePieceFont; private DefaultListCellRenderer nameLabel; private JEditorPane informationPane; public CatalogCellRenderer() { setLayout(null); this.nameLabel = new DefaultListCellRenderer() { @Override public Dimension getPreferredSize() { return new Dimension(DEFAULT_ICON_HEIGHT * 3 / 2 + 5, super.getPreferredSize().height); } }; this.nameLabel.setHorizontalTextPosition(JLabel.CENTER); this.nameLabel.setVerticalTextPosition(JLabel.BOTTOM); this.nameLabel.setHorizontalAlignment(JLabel.CENTER); this.nameLabel.setText("-"); this.nameLabel.setIcon(IconManager.getInstance().getWaitIcon(DEFAULT_ICON_HEIGHT)); this.defaultFont = UIManager.getFont("ToolTip.font"); this.modifiablePieceFont = new Font(this.defaultFont.getFontName(), Font.ITALIC, this.defaultFont.getSize()); this.nameLabel.setFont(this.defaultFont); this.informationPane = new JEditorPane("text/html", "-"); this.informationPane.setOpaque(false); this.informationPane.setEditable(false); String bodyRule = "body { font-family: " + this.defaultFont.getFamily() + "; " + "font-size: " + this.defaultFont.getSize() + "pt; " + "text-align: center; }"; ((HTMLDocument)this.informationPane.getDocument()).getStyleSheet().addRule(bodyRule); add(this.nameLabel); add(this.informationPane); } public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { CatalogPieceOfFurniture piece = (CatalogPieceOfFurniture)value; // Configure name label with its icon, background and focus colors this.nameLabel.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); this.nameLabel.setText(" " + piece.getName() + " "); this.nameLabel.setIcon(getLabelIcon(list, piece.getIcon())); this.nameLabel.setFont(piece.isModifiable() ? this.modifiablePieceFont : this.defaultFont); String information = piece.getInformation(); if (information != null) { this.informationPane.setText(information); this.informationPane.setVisible(true); } else { this.informationPane.setVisible(false); } return this; } public int getPreferredHeight(JList list) { ListModel model = list.getModel(); this.informationPane.setVisible(false); for (int i = 0, size = model.getSize(); i < size; i++) { if (((CatalogPieceOfFurniture)model.getElementAt(i)).getInformation() != null) { this.informationPane.setVisible(true); break; } } return getPreferredSize().height; } @Override public void doLayout() { Dimension namePreferredSize = this.nameLabel.getPreferredSize(); this.nameLabel.setSize(getWidth(), namePreferredSize.height); if (this.informationPane.isVisible()) { this.informationPane.setBounds(0, namePreferredSize.height, getWidth(), getHeight() - namePreferredSize.height); } } @Override public Dimension getPreferredSize() { Dimension preferredSize = this.nameLabel.getPreferredSize(); if (this.informationPane.isVisible()) { preferredSize.height += this.informationPane.getPreferredSize().height + 2; } return preferredSize; } /** * The following methods are overridden for performance reasons. */ @Override public void revalidate() { } @Override public void repaint(long tm, int x, int y, int width, int height) { } @Override public void repaint(Rectangle r) { } @Override public void repaint() { } private Icon getLabelIcon(JList list, Content content) { return IconManager.getInstance().getIcon(content, DEFAULT_ICON_HEIGHT, list); } @Override protected void paintChildren(Graphics g) { // Force text anti aliasing on texts ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); super.paintChildren(g); } public URL getURLAt(Point point, JList list) { int pieceIndex = list.locationToIndex(point); if (pieceIndex != -1) { CatalogPieceOfFurniture piece = (CatalogPieceOfFurniture)list.getModel().getElementAt(pieceIndex); String information = piece.getInformation(); if (information != null) { getListCellRendererComponent(list, piece, pieceIndex, false, false); Rectangle cellBounds = list.getCellBounds(pieceIndex, pieceIndex); point.x -= cellBounds.x; point.y -= cellBounds.y + this.informationPane.getY(); if (point.x > 0 && point.y > 0) { // Search in information pane if point is over a HTML link int position = this.informationPane.viewToModel(point); if (position > 1) { HTMLDocument hdoc = (HTMLDocument)this.informationPane.getDocument(); Element element = hdoc.getCharacterElement(position); AttributeSet a = element.getAttributes(); AttributeSet anchor = (AttributeSet)a.getAttribute(HTML.Tag.A); if (anchor != null) { String href = (String)anchor.getAttribute(HTML.Attribute.HREF); if (href != null) { try { return new URL(href); } catch (MalformedURLException ex) { // Ignore malformed URL } } } } } } } return null; } } /** * List model adaptor to CatalogPieceOfFurniture instances of catalog. */ private static class CatalogListModel extends AbstractListModel { private FurnitureCatalog catalog; private List<CatalogPieceOfFurniture> furniture; private FurnitureCategory filterCategory; private Pattern filterNamePattern; public CatalogListModel(FurnitureCatalog catalog) { this.catalog = catalog; this.furniture = new ArrayList<CatalogPieceOfFurniture>(); this.filterNamePattern = Pattern.compile(".*"); catalog.addFurnitureListener(new CatalogFurnitureListener(this)); updateFurnitureList(); } public void setFilterCategory(FurnitureCategory filterCategory) { this.filterCategory = filterCategory; updateFurnitureList(); } public void setFilterText(String filterText) { this.filterNamePattern = Pattern.compile(".*" + filterText + ".*", Pattern.CASE_INSENSITIVE); updateFurnitureList(); } public Object getElementAt(int index) { return this.furniture.get(index); } public int getSize() { return this.furniture.size(); } private void updateFurnitureList() { this.furniture.clear(); for (FurnitureCategory category : this.catalog.getCategories()) { for (CatalogPieceOfFurniture piece : category.getFurniture()) { if (this.filterCategory == null || piece.getCategory().equals(this.filterCategory)) { if (this.filterNamePattern.matcher(piece.getName()).matches() || this.filterNamePattern.matcher(piece.getCategory().getName()).matches() || (piece.getCreator() != null && this.filterNamePattern.matcher(piece.getCreator()).matches()) || (piece.getDescription() != null && this.filterNamePattern.matcher(piece.getDescription()).matches())) { this.furniture.add(piece); } else { for (String tag : piece.getTags()) { if (this.filterNamePattern.matcher(tag).matches()) { this.furniture.add(piece); break; } } } } } } Collections.sort(this.furniture); fireContentsChanged(this, 0, this.furniture.size() - 1); } /** * Catalog furniture listener bound to this tree model with a weak reference to avoid * strong link between catalog and this tree. */ private static class CatalogFurnitureListener implements CollectionListener<CatalogPieceOfFurniture> { private WeakReference<CatalogListModel> catalogListModel; public CatalogFurnitureListener(CatalogListModel catalogListModel) { this.catalogListModel = new WeakReference<CatalogListModel>(catalogListModel); } public void collectionChanged(CollectionEvent<CatalogPieceOfFurniture> ev) { // If catalog tree model was garbage collected, remove this listener from catalog CatalogListModel catalogTreeModel = this.catalogListModel.get(); FurnitureCatalog catalog = (FurnitureCatalog)ev.getSource(); if (catalogTreeModel == null) { catalog.removeFurnitureListener(this); } else { catalogTreeModel.updateFurnitureList(); } } } } }