package org.chesmapper.view.gui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.swing.DefaultListModel; import javax.swing.JButton; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.chesmapper.map.main.ScreenSetup; import org.chesmapper.view.cluster.Cluster; import org.chesmapper.view.cluster.ClusterController; import org.chesmapper.view.cluster.Clustering; import org.chesmapper.view.cluster.ClusteringImpl; import org.chesmapper.view.cluster.Compound; import org.chesmapper.view.cluster.Clustering.SelectionListener; import org.chesmapper.view.gui.swing.ComponentFactory; import org.chesmapper.view.gui.swing.TransparentViewPanel; import org.chesmapper.view.gui.swing.ComponentFactory.ClickableLabel; import org.mg.javalib.gui.DoubleNameListCellRenderer; import org.mg.javalib.gui.MouseOverList; import org.mg.javalib.util.ArrayUtil; import org.mg.javalib.util.ImageLoader; public class CompoundListPanel extends JPanel { JScrollPane listScrollPane; MouseOverList list; DefaultListModel<Compound> listModel; DoubleNameListCellRenderer listRenderer; boolean selfBlock = false; Clustering clustering; ClusterController clusterControler; ViewControler viewControler; GUIControler guiControler; ClickableLabel clearSelectedButton; JButton filterButton; public CompoundListPanel(Clustering clustering, ClusterController clusterControler, ViewControler controler, GUIControler guiControler) { this.clustering = clustering; this.clusterControler = clusterControler; this.viewControler = controler; this.guiControler = guiControler; buildLayout(); updateList(); installListeners(controler); } private void installListeners(final ViewControler controler) { clustering.addListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ClusteringImpl.CLUSTER_MODIFIED) || evt.getPropertyName().equals(ClusteringImpl.CLUSTER_CLEAR)) { updateList(); } } }); clustering.addSelectionListener(new SelectionListener() { @Override public void compoundWatchedChanged(Compound[] c) { if (selfBlock) return; selfBlock = true; if (c.length == 0) list.clearSelection(); else list.setSelectedValue(c[0], true); selfBlock = false; } @Override public void compoundActiveChanged(Compound[] c) { if (!selfBlock) { selfBlock = true; updateActiveCompoundSelection(); selfBlock = false; } updateClearButton(); updateFilterButton(); } @Override public void clusterWatchedChanged(Cluster c) { updateList(); } @Override public void clusterActiveChanged(Cluster c) { updateList(); updateClearButton(); updateFilterButton(); } }); list.addMouseListener(new MouseAdapter() { public void mouseClicked(final MouseEvent e) { if (selfBlock) return; guiControler.block("click compound"); selfBlock = true; viewControler.clearMouseMoveWatchUpdates(false); Thread th = new Thread(new Runnable() { public void run() { try { int idx = list.getLastSelectedIndex(); Compound m = (Compound) listModel.elementAt(idx); if (m == null) throw new IllegalStateException(); if (e.isControlDown()) clusterControler.toggleCompoundActive(m); else if (e.isShiftDown() && clustering.isCompoundActive()) { int startIdx = listModel.indexOf(clustering.getActiveCompound()); int min, max; if (startIdx < idx) { min = startIdx + 1; max = idx; } else { min = idx; max = startIdx - 1; } List<Compound> comps = new ArrayList<Compound>(); for (int i = min; i <= max; i++) comps.add((Compound) listModel.elementAt(i)); for (Compound c : clustering.getActiveCompounds()) if (!comps.contains(c)) comps.add(c); clusterControler.setCompoundActive(ArrayUtil.toArray(comps), true); } else { if (clustering.isCompoundActive(m)) clusterControler.clearCompoundActive(true); else clusterControler.setCompoundActive(m, true); } } finally { selfBlock = false; SwingUtilities.invokeLater(new Runnable() { public void run() { guiControler.unblock("click compound"); } }); } } }); th.start(); } }); list.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (selfBlock) return; selfBlock = true; int index = list.getSelectedIndex(); if (index == -1) { //compoundWatched.clearSelection(); } else { final Compound watched = (Compound) listModel.elementAt(index); controler.doMouseMoveWatchUpdates(new Runnable() { public void run() { clusterControler.clearClusterWatched(); clusterControler.setCompoundWatched(watched); } }); } selfBlock = false; } }); controler.addViewListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(ViewControler.PROPERTY_COMPOUND_DESCRIPTOR_CHANGED) || evt.getPropertyName().equals(ViewControler.PROPERTY_HIGHLIGHT_CHANGED) || evt.getPropertyName().equals(ViewControler.PROPERTY_FEATURE_SORTING_CHANGED) || evt.getPropertyName().equals(ViewControler.PROPERTY_COMPOUND_FILTER_CHANGED)) updateList(); else if (evt.getPropertyName().equals(ViewControler.PROPERTY_SINGLE_COMPOUND_SELECTION_ENABLED)) { updateList(); updateClearButton(); updateFilterButton(); } else if (evt.getPropertyName().equals(ViewControler.PROPERTY_FONT_SIZE_CHANGED)) updateListSize(); } }); guiControler.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals(GUIControler.PROPERTY_VIEWER_SIZE_CHANGED)) updateListSize(); } }); filterButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { clusterControler.applyCompoundFilter(ArrayUtil.toList(clustering.getActiveCompounds()), true); } }); clearSelectedButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent arg0) { Thread noAwt = new Thread(new Runnable() { public void run() { if (clustering.isClusterActive() && clustering.getNumClusters() > 1 && clustering.getActiveCluster().getCompounds().size() == 1) clusterControler.clearClusterActive(true, true); else if (clustering.isCompoundActive()) clusterControler.clearCompoundActive(true); else if (clustering.isClusterActive() && clustering.getNumClusters() > 1) clusterControler.clearClusterActive(true, true); else if (viewControler.isSingleCompoundSelection()) { viewControler.setSingleCompoundSelection(false); SwingUtilities.invokeLater(new Runnable() { public void run() { updateList(); updateClearButton(); } }); } } }); noAwt.start(); } }); } private void updateFilterButton() { filterButton.setVisible(listScrollPane.isVisible() && clustering.getActiveCompounds().length > 1 && clustering.getActiveCompounds().length < clustering.getNumCompounds(false) - 1); } private void updateClearButton() { clearSelectedButton .setVisible(listScrollPane.isVisible() && ((clustering.isClusterActive() && clustering.getNumClusters() > 1) || clustering.isCompoundActive() || viewControler.isSingleCompoundSelection())); } @SuppressWarnings("unchecked") private void buildLayout() { listModel = new DefaultListModel<Compound>(); list = new MouseOverList(listModel); list.setClearOnExit(false); listRenderer = new DoubleNameListCellRenderer(listModel) { @Override public void updateUI() { super.updateUI(); if (getFontLabel1() != null) { setFontLabel1(getFontLabel1().deriveFont((float) ScreenSetup.INSTANCE.getFontSize())); setFontLabel2(getFontLabel2().deriveFont((float) ScreenSetup.INSTANCE.getFontSize())); list.setFixedCellHeight(getRowHeight()); } } @SuppressWarnings("rawtypes") public Component getListCellRendererComponent(JList list, Object value, int i, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, i, isSelected, cellHasFocus); Compound c = (Compound) value; setOpaque(isSelected || clustering.isCompoundActive(c)); setForeground(ComponentFactory.FOREGROUND); if (clustering.isCompoundActive(c)) { setBackground(ComponentFactory.LIST_ACTIVE_BACKGROUND); setForeground(ComponentFactory.LIST_SELECTION_FOREGROUND); } else if (isSelected) { setBackground(ComponentFactory.LIST_WATCH_BACKGROUND); setForeground(ComponentFactory.LIST_SELECTION_FOREGROUND); } else { setForegroundLabel2(c.getHighlightColorText()); } return this; } }; listRenderer.setFontLabel2(listRenderer.getFontLabel2().deriveFont(Font.ITALIC)); list.setCellRenderer(listRenderer); list.setOpaque(false); list.setFocusable(false); JPanel p = new JPanel(new BorderLayout(6, 6)); p.setOpaque(false); listScrollPane = ComponentFactory.createViewScrollpane(list); guiControler.registerSizeComponent(ComponentSize.COMPOUND_LIST, listScrollPane); JPanel listWrapped = new TransparentViewPanel(new BorderLayout()); listWrapped.add(listScrollPane); p.add(listWrapped); clearSelectedButton = ComponentFactory.createCrossViewButton(); filterButton = ComponentFactory.createViewButton(ImageLoader.getImage(ImageLoader.Image.filter14_black), ImageLoader.getImage(ImageLoader.Image.filter14)); filterButton.setName("filter-button");//for tests JPanel buttonWrapped = new TransparentViewPanel(new BorderLayout(6, 6)); buttonWrapped.add(clearSelectedButton); buttonWrapped.add(filterButton, BorderLayout.SOUTH); JPanel removeButtonPanel = new JPanel(new BorderLayout()); removeButtonPanel.setOpaque(false); removeButtonPanel.add(buttonWrapped, BorderLayout.NORTH); p.add(removeButtonPanel, BorderLayout.EAST); clearSelectedButton.setVisible(false); filterButton.setVisible(false); setLayout(new BorderLayout()); add(p); setOpaque(false); } private void updateActiveCompoundSelection() { if (clustering.isCompoundActive()) list.setSelectedValue(clustering.getActiveCompound(), true); else list.clearSelection(); } private void updateList() { if (!SwingUtilities.isEventDispatchThread()) throw new IllegalStateException("GUI updates only in event dispatch thread plz"); selfBlock = true; setVisible(true); setIgnoreRepaint(true); if (clustering.getNumClusters() == 0) { listScrollPane.setVisible(false); clearSelectedButton.setVisible(false); filterButton.setVisible(false); } else if (!clustering.isClusterActive() && !viewControler.isSingleCompoundSelection()) { listScrollPane.setVisible(false); } else { listModel.removeAllElements(); listScrollPane.setPreferredSize(null); Compound m[] = new Compound[viewControler.isSingleCompoundSelection() ? clustering.getCompounds(false) .size() : clustering.getActiveCluster().getCompounds().size()]; int i = 0; for (Compound mod : (viewControler.isSingleCompoundSelection() ? clustering.getCompounds(false) : clustering.getActiveCluster().getCompounds())) { m[i++] = mod; if (mod.getDisplayName() == null) throw new IllegalStateException("display name for compound is nil, check order of listeners"); } for (Compound comp : (viewControler.isSingleCompoundSelection() ? clustering.getCompounds(false) : clustering.getActiveCluster().getCompounds())) comp.setFeatureSortingEnabled(true);//viewControler.isFeatureSortingEnabled()); for (Compound compound : m) if (compound == null) throw new IllegalStateException(); if (m.length > 1) Arrays.sort(m); for (Compound compound : m) listModel.addElement(compound); updateActiveCompoundSelection(); updateListSize(); listScrollPane.setVisible(true); } setIgnoreRepaint(false); revalidate(); repaint(); selfBlock = false; } private void updateListSize() { int rowCount = (guiControler.getComponentMaxHeight(1) / listRenderer.getRowHeight()) / 3; double ratioVisible = rowCount / (double) listModel.getSize(); if (ratioVisible <= 0.5) { // if less then 50% of elements is visible increase by up to 50% double ratioIncrease = 0.5 - ratioVisible; rowCount += (int) (ratioIncrease * rowCount); } list.setVisibleRowCount(rowCount); if (viewControler.getHighlightedProperty() != null) { // features values are shown on the right, restrict long compound names to show feature values without scroll pane listRenderer .setMaxl1Width(guiControler.getComponentMaxWidth((ComponentSize.COMPOUND_LIST.getValue() - 5) * 0.01)); } else { // no features values are shown on the right -> long names can be written out, scroll-pane will show up listRenderer.setMaxl1Width(Integer.MAX_VALUE); } listScrollPane.setPreferredSize(null); int width = guiControler.getComponentMaxWidth(ComponentSize.COMPOUND_LIST.getValue() * 0.01); if (!guiControler.isAccentuateComponents()) width = Math.min(width, listScrollPane.getPreferredSize().width); listScrollPane.setPreferredSize(new Dimension(width, listScrollPane.getPreferredSize().height)); listScrollPane.revalidate(); listScrollPane.repaint(); } }