Java tutorial
/* * RDV * Real-time Data Viewer * http://rdv.googlecode.com/ * * Copyright (c) 2005-2007 University at Buffalo * Copyright (c) 2005-2007 NEES Cyberinfrastructure Center * Copyright (c) 2008 Palta Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * $URL$ * $Revision$ * $Date$ * $Author$ */ package org.rdv.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.datatransfer.Transferable; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragSource; import java.awt.dnd.InvalidDnDOperationException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; 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.Collections; import java.util.Iterator; import java.util.List; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.TransferHandler; import javax.swing.border.EmptyBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rdv.DataPanelManager; import org.rdv.DataViewer; import org.rdv.Extension; import org.rdv.RDV; import org.rdv.action.ActionFactory; import org.rdv.data.Channel; import org.rdv.data.LocalChannelManager; import org.rdv.rbnb.MetadataListener; import org.rdv.rbnb.Player; import org.rdv.rbnb.RBNBController; import org.rdv.rbnb.RBNBUtilities; import org.rdv.rbnb.StateListener; import org.rdv.util.ReadableStringComparator; import com.jgoodies.uif_lite.panel.SimpleInternalFrame; import com.rbnb.sapi.ChannelTree; import com.rbnb.sapi.ChannelTree.Node; import com.rbnb.sapi.ChannelTree.NodeTypeEnum; /** * A panel that contains the channels in a tree. * * @author Jason P. Hanley */ public class ChannelListPanel extends JPanel implements MetadataListener, StateListener { /** serialization version identifier */ private static final long serialVersionUID = -5091214984427475802L; static Log log = LogFactory.getLog(ChannelListPanel.class.getName()); private DataPanelManager dataPanelManager; private RBNBController rbnb; private List<ChannelSelectionListener> channelSelectionListeners; private JTextField filterTextField; private JButton clearFilterButton; private JTree tree; private ChannelTreeModel treeModel; private JButton metadataUpdateButton; public ChannelListPanel(DataPanelManager dataPanelManager, RBNBController rbnb) { super(); this.dataPanelManager = dataPanelManager; this.rbnb = rbnb; channelSelectionListeners = new ArrayList<ChannelSelectionListener>(); initPanel(); } /** * Create the main UI panel. */ private void initPanel() { setLayout(new BorderLayout()); setMinimumSize(new Dimension(130, 27)); JComponent filterComponent = createFilterPanel(); JComponent treePanel = createTreePanel(); JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); mainPanel.add(filterComponent, BorderLayout.NORTH); mainPanel.add(treePanel, BorderLayout.CENTER); JToolBar channelToolBar = createToolBar(); SimpleInternalFrame treeFrame = new SimpleInternalFrame(DataViewer.getIcon("icons/channels.gif"), "Channels", channelToolBar, mainPanel); add(treeFrame, BorderLayout.CENTER); } /** * Create the UI panel that contains the controls to filter the channel list. * * @return the UI component dealing with filtering */ private JComponent createFilterPanel() { JPanel filterPanel = new JPanel(); filterPanel.setLayout(new BorderLayout(5, 5)); filterPanel.setBackground(Color.white); filterPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); JLabel filterIconLabel = new JLabel(DataViewer.getIcon("icons/filter.gif")); filterPanel.add(filterIconLabel, BorderLayout.WEST); filterTextField = new JTextField(); filterTextField.setToolTipText("Enter text here to filter the channel list"); filterTextField.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent e) { treeModel.setFilter(filterTextField.getText()); } public void insertUpdate(DocumentEvent e) { changedUpdate(e); } public void removeUpdate(DocumentEvent e) { changedUpdate(e); } }); filterPanel.add(filterTextField, BorderLayout.CENTER); Action focusFilterAction = new AbstractAction() { /** serialization version identifier */ private static final long serialVersionUID = -2443410059209958411L; public void actionPerformed(ActionEvent e) { filterTextField.requestFocusInWindow(); filterTextField.selectAll(); } }; int modifier = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); filterTextField.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) .put(KeyStroke.getKeyStroke(KeyEvent.VK_F, modifier), "focusFilter"); filterTextField.getActionMap().put("focusFilter", focusFilterAction); Action cancelFilterAction = new AbstractAction(null, DataViewer.getIcon("icons/cancel.gif")) { /** serialization version identifier */ private static final long serialVersionUID = 8913797349366699615L; public void actionPerformed(ActionEvent e) { treeModel.setFilter(null); } }; cancelFilterAction.putValue(Action.SHORT_DESCRIPTION, "Cancel filter"); filterTextField.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancelFilter"); filterTextField.getActionMap().put("cancelFilter", cancelFilterAction); clearFilterButton = new JButton(cancelFilterAction); clearFilterButton.setBorderPainted(false); clearFilterButton.setVisible(false); filterPanel.add(clearFilterButton, BorderLayout.EAST); return filterPanel; } /** * Create the channel tree panel. * * @return the component containing the channel tree */ private JComponent createTreePanel() { treeModel = new ChannelTreeModel(); treeModel.addPropertyChangeListener(new FilterPropertyChangeListener()); tree = new JTree(treeModel); tree.setRootVisible(false); tree.setShowsRootHandles(true); tree.setExpandsSelectedPaths(true); tree.setCellRenderer(new ChannelTreeCellRenderer()); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); tree.addTreeSelectionListener(new ChannelTreeSelectionListener()); tree.addMouseListener(new ChannelTreeMouseListener()); tree.setBorder(new EmptyBorder(0, 5, 5, 5)); JScrollPane treeView = new JScrollPane(tree); treeView.setBorder(null); treeView.setViewportBorder(null); tree.setDragEnabled(true); tree.setTransferHandler(new ChannelTransferHandler()); DragSource dragSource = DragSource.getDefaultDragSource(); dragSource.createDefaultDragGestureRecognizer(tree, DnDConstants.ACTION_LINK, new ChannelDragGestureListener()); return treeView; } /** * Create the tool bar. * * @return the tool bar for the channel list panel */ private JToolBar createToolBar() { JToolBar channelToolBar = new JToolBar(); channelToolBar.setFloatable(false); JButton button = new ToolBarButton("icons/expandall.gif", "Expand channel list"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { expandTree(); } }); channelToolBar.add(button); button = new ToolBarButton("icons/collapseall.gif", "Collapse channel list"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { collapseTree(); } }); channelToolBar.add(button); metadataUpdateButton = new ToolBarButton("icons/refresh.gif", "Update channel list"); metadataUpdateButton.setEnabled(false); metadataUpdateButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { rbnb.updateMetadata(); } }); channelToolBar.add(metadataUpdateButton); return channelToolBar; } public void channelTreeUpdated(final ChannelTree newChannelTree) { ChannelTree ctree = newChannelTree; TreePath[] paths = tree.getSelectionPaths(); boolean structureChanged = treeModel.setChannelTree(ctree); if (structureChanged) { tree.clearSelection(); if (paths != null) { for (int i = 0; i < paths.length; i++) { Object o = paths[i].getLastPathComponent(); if (o instanceof ChannelTree.Node) { ChannelTree.Node node = (ChannelTree.Node) o; selectNode(node.getFullName()); } else { selectRootNode(); } } } } } private class FilterPropertyChangeListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent pce) { if (!pce.getPropertyName().equals("filter")) { return; } String filterText = (String) pce.getNewValue(); if (filterText.compareToIgnoreCase(filterTextField.getText()) != 0) { filterTextField.setText(filterText); } clearFilterButton.setVisible(filterText.length() > 0); if (filterText.length() > 0) { expandTree(); } } } public List<String> getSelectedChannels() { ArrayList<String> channels = new ArrayList<String>(); TreePath[] paths = tree.getSelectionPaths(); if (paths != null) { for (int i = 0; i < paths.length; i++) { if (paths[i].getLastPathComponent() != treeModel.getRoot()) { Node node = (ChannelTree.Node) paths[i].getLastPathComponent(); NodeTypeEnum type = node.getType(); if (type == ChannelTree.SERVER || type == ChannelTree.SOURCE || type == ChannelTree.PLUGIN || type == ChannelTree.PLUGIN) { for (String channel : RBNBUtilities.getChildChannels(node, treeModel.isHiddenChannelsVisible())) { if (!channels.contains(channel)) { channels.add(channel); } } } else if (type == ChannelTree.CHANNEL) { if (!channels.contains(node.getFullName())) { channels.add(node.getFullName()); } } } } } Collections.sort(channels, new ReadableStringComparator()); return channels; } public void addChannelSelectionListener(ChannelSelectionListener e) { channelSelectionListeners.add(e); } public void removeChannelSelectionListener(ChannelSelectionListener e) { channelSelectionListeners.remove(e); } private void fireChannelSelected(Object o) { boolean isRoot = (o == treeModel.getRoot()); int children = treeModel.getChildCount(o); String channelName = null; if (!isRoot) { ChannelTree.Node node = (ChannelTree.Node) o; channelName = node.getFullName(); } ChannelSelectionEvent cse = new ChannelSelectionEvent(channelName, children, isRoot); Iterator<ChannelSelectionListener> i = channelSelectionListeners.iterator(); while (i.hasNext()) { ChannelSelectionListener csl = (ChannelSelectionListener) i.next(); csl.channelSelected(cse); } } private void fireNoChannelsSelected() { Iterator<ChannelSelectionListener> i = channelSelectionListeners.iterator(); while (i.hasNext()) { ChannelSelectionListener csl = (ChannelSelectionListener) i.next(); csl.channelSelectionCleared(); } } public boolean isShowingHiddenChannles() { return treeModel.isHiddenChannelsVisible(); } public void showHiddenChannels(boolean showHiddenChannels) { treeModel.setHiddenChannelsVisible(showHiddenChannels); } private void expandTree() { for (int i = 0; i < tree.getRowCount(); i++) { tree.expandRow(i); } } private void collapseTree() { for (int i = tree.getRowCount() - 1; i >= 0; i--) { tree.collapseRow(i); } } private void selectRootNode() { tree.addSelectionPath(new TreePath(treeModel.getRoot())); } private void selectNode(String nodeName) { ChannelTree.Node node = treeModel.getChannelTree().findNode(nodeName); if (node != null) { int depth = node.getDepth(); Object[] path = new Object[depth + 2]; path[0] = treeModel.getRoot(); for (int i = path.length - 1; i > 0; i--) { path[i] = node; node = node.getParent(); } tree.addSelectionPath(new TreePath(path)); } } public class ChannelTreeSelectionListener implements TreeSelectionListener { public void valueChanged(TreeSelectionEvent e) { TreePath selectedPath = e.getNewLeadSelectionPath(); if (selectedPath == null) { fireNoChannelsSelected(); } else { Object o = selectedPath.getLastPathComponent(); fireChannelSelected(o); } } } public Transferable createChannelsTransferable() { List<String> channels = getSelectedChannels(); if (channels.size() == 0) { return null; } return new ChannelListTransferable(channels); } private class ChannelTransferHandler extends TransferHandler { /** serialization version identifier */ private static final long serialVersionUID = 5965378439143524577L; public int getSourceActions(JComponent c) { return DnDConstants.ACTION_LINK; } protected Transferable createTransferable(JComponent c) { return createChannelsTransferable(); } } private class ChannelDragGestureListener implements DragGestureListener { public void dragGestureRecognized(DragGestureEvent dge) { Transferable transferable = createChannelsTransferable(); if (transferable == null) { return; } try { dge.startDrag(DragSource.DefaultLinkDrop, transferable); } catch (InvalidDnDOperationException e) { } } } private class ChannelTreeMouseListener extends MouseAdapter { public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { handleDoubleClick(e); } } public void mousePressed(MouseEvent e) { if (e.isPopupTrigger()) { handlePopup(e); } } public void mouseReleased(MouseEvent e) { if (e.isPopupTrigger()) { handlePopup(e); } } } private void handleDoubleClick(MouseEvent e) { TreePath treePath = tree.getSelectionPath(); if (treePath == null) { return; } Object o = treePath.getLastPathComponent(); if (o != treeModel.getRoot()) { ChannelTree.Node node = (ChannelTree.Node) o; if (node.getType() == ChannelTree.CHANNEL) { String channelName = node.getFullName(); viewChannel(channelName); } } } private void handlePopup(MouseEvent e) { TreePath treePath = tree.getPathForLocation(e.getX(), e.getY()); if (treePath == null) { return; } // select only the node under the mouse if it is not already selected if (!tree.isPathSelected(treePath)) { tree.setSelectionPath(treePath); } JPopupMenu popup = null; Object o = treePath.getLastPathComponent(); if (o == treeModel.getRoot()) { popup = getRootPopup(); } else { popup = getChannelPopup(); } if (popup != null && popup.getComponentCount() > 0) { popup.show(tree, e.getX(), e.getY()); } } private JPopupMenu getRootPopup() { JPopupMenu popup = new JPopupMenu(); JMenuItem menuItem = new JMenuItem("Import data...", DataViewer.getIcon("icons/import.gif")); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { ActionFactory.getInstance().getDataImportAction().importData(); } }); popup.add(menuItem); menuItem = new JMenuItem("Export data...", DataViewer.getIcon("icons/export.gif")); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { ActionFactory.getInstance().getDataExportAction().exportData(RBNBUtilities .getAllChannels(treeModel.getChannelTree(), treeModel.isHiddenChannelsVisible())); } }); popup.add(menuItem); return popup; } private JPopupMenu getChannelPopup() { JPopupMenu popup = new JPopupMenu(); JMenuItem menuItem; final List<String> channels = getSelectedChannels(); if (channels.isEmpty()) { return null; } String plural = (channels.size() == 1) ? "" : "s"; List<Extension> extensions = dataPanelManager.getExtensions(channels); if (extensions.size() > 0) { for (final Extension extension : extensions) { menuItem = new JMenuItem("View channel" + plural + " with " + extension.getName()); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { viewChannels(channels, extension); } }); popup.add(menuItem); } popup.addSeparator(); } if (dataPanelManager.isAnyChannelSubscribed(channels)) { menuItem = new JMenuItem("Unsubscribe from channel" + plural); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { dataPanelManager.unsubscribeChannels(channels); } }); popup.add(menuItem); popup.addSeparator(); } // menu item to remove local channels if (areLocal(channels)) { menuItem = new JMenuItem("Remove channel" + plural); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { LocalChannelManager lcm = LocalChannelManager.getInstance(); lcm.removeChannels(channels); } }); popup.add(menuItem); popup.addSeparator(); } String mime = getMime(channels); if (mime != null) { if (mime.equals("application/octet-stream")) { menuItem = new JMenuItem("Export data channel" + plural + "...", DataViewer.getIcon("icons/export.gif")); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { ActionFactory.getInstance().getDataExportAction().exportData(channels); } }); popup.add(menuItem); } else if (mime.equals("image/jpeg")) { menuItem = new JMenuItem("Export video channel" + plural + "...", DataViewer.getIcon("icons/export.gif")); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { JFrame frame = RDV.getInstance(RDV.class).getMainFrame(); new ExportVideoDialog(frame, rbnb, channels); } }); popup.add(menuItem); } else { popup.remove(popup.getComponentCount() - 1); } } return popup; } private void viewChannel(final String channelName) { new Thread() { public void run() { dataPanelManager.viewChannel(channelName); } }.start(); } private void viewChannels(final List<String> channels, final Extension extension) { new Thread() { public void run() { dataPanelManager.viewChannels(channels, extension); } }.start(); } /** * Gets the common mime type amoung all the channels. If the channels are all * the same mime type this will return their mime type. If they are of * different type this will return null. * * @param channels the list of channels * @return the common mime type of the channels, or null if there is * none */ private String getMime(List<String> channels) { if (channels == null || channels.isEmpty()) { return null; } String mime = rbnb.getChannel(channels.get(0)).getMetadata("mime"); for (int i = 1; i < channels.size(); i++) { String channel = channels.get(i); if (!mime.equals(rbnb.getChannel(channel).getMetadata("mime"))) { return null; } } return mime; } /** * Checks if the list of <code>channels</code> are local channels. * * @param channels the list of channels to check * @return if all the channels are local, false otherwise */ private boolean areLocal(List<String> channels) { if (channels == null || channels.isEmpty()) { return false; } List<Channel> metadata = rbnb.getChannels(channels); for (Channel channel : metadata) { if (channel.getMetadata("local") == null) { return false; } } return true; } public void postState(int newState, int oldState) { if (newState == Player.STATE_DISCONNECTED || newState == Player.STATE_EXITING) { metadataUpdateButton.setEnabled(false); filterTextField.setEnabled(false); clearFilterButton.setEnabled(false); tree.setEnabled(false); } else { metadataUpdateButton.setEnabled(true); filterTextField.setEnabled(true); clearFilterButton.setEnabled(true); tree.setEnabled(true); } } }