package org.korsakow.ide.ui.resourceexplorer; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Collection; import java.util.HashMap; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTable; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.PopupMenuListener; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import org.apache.log4j.Logger; import org.korsakow.ide.lang.LanguageBundle; import org.korsakow.ide.resources.ResourceProperty; import org.korsakow.ide.resources.SnuProperty; import org.korsakow.ide.ui.components.BackgroundImagePanel; import org.korsakow.ide.ui.components.tree.FolderNode; import org.korsakow.ide.ui.components.tree.KNode; import org.korsakow.ide.ui.controller.dnd.ResourceTreeTransferHandler; import org.korsakow.ide.util.Platform; import org.korsakow.ide.util.UIResourceManager; import org.korsakow.ide.util.UIUtil; import com.sun.swingx.treetable.TreeTableModel; public class ResourceBrowser extends JPanel { public static enum Menu { OpenAsSnu, Properties, MediaProperties, Rename, Delete, Duplicate, Copy, Paste, CreateSnu, NewInterface, ExportInterface, NewFolder, ProjectSettings, RevealInPlatformFilesystemBrowser, // AKA "Reveal In Finder" } public static enum Action { AddSnu, AddInterface, AddFolder } public static enum Column { PREVIEW, BACKGROUNDSOUND, CLICKSOUND, ISSNU, } protected ResourceTreeTable resourceTreeTable; protected JScrollPane resourceTreeTableScroll; protected JLabel statusLabel; protected JButton addSnuButton; protected JButton addInterfaceButton; protected JButton addFolderButton; protected JButton editFolderButton; protected JButton deleteFolderButton; protected JPopupMenu contextMenu = new JPopupMenu(); protected HashMap<Object, JMenuItem> contextMenuItems = new HashMap<Object, JMenuItem>(); protected BackgroundImagePanel treePanel; private JPanel buttonPanel; public ResourceBrowser() { initUI(); initListeners(); } public FolderNode getDefaultVideoFolder() { return resourceTreeTable.getRootNode(); } public ResourceTreeTable getResourceTreeTable() { return resourceTreeTable; } public void deleteSelectedResource () { onDeleteFolderAction(); } protected void initUI() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0)); buttonPanel.setBorder( BorderFactory.createEmptyBorder(3, 0, 3, 0) ); buttonPanel.add(addSnuButton = new JButton(UIResourceManager.getIcon(UIResourceManager.ICON_SNU_ADD))); buttonPanel.add(addInterfaceButton = new JButton(UIResourceManager.getIcon(UIResourceManager.ICON_INTERFACE_ADD))); buttonPanel.add(addFolderButton = new JButton(UIResourceManager.getIcon(UIResourceManager.ICON_FOLDER_ADD))); buttonPanel.add(editFolderButton = new JButton(UIResourceManager.getIcon(UIResourceManager.ICON_FOLDER_EDIT))); buttonPanel.add(deleteFolderButton = new JButton(UIResourceManager.getIcon(UIResourceManager.ICON_DELETE))); JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS)); bottomPanel.add(statusLabel = new JLabel(" ")); bottomPanel.add(buttonPanel); statusLabel.setPreferredSize(new Dimension(Short.MAX_VALUE, statusLabel.getHeight())); statusLabel.setBorder(BorderFactory.createLoweredBevelBorder()); addSnuButton.setToolTipText(LanguageBundle.getString("resourcebrowser.addsnubutton.tooltip")); addInterfaceButton.setToolTipText(LanguageBundle.getString("resourcebrowser.addinterfacebutton.tooltip")); addFolderButton.setToolTipText(LanguageBundle.getString("resourcebrowser.addfolderbutton.tooltip")); editFolderButton.setToolTipText(LanguageBundle.getString("resourcebrowser.editfolderbutton.tooltip")); deleteFolderButton.setToolTipText(LanguageBundle.getString("resourcebrowser.deletefolderbutton.tooltip")); addSnuButton.setBorderPainted(false); addInterfaceButton.setBorderPainted(false); addFolderButton.setBorderPainted(false); editFolderButton.setBorderPainted(false); deleteFolderButton.setBorderPainted(false); addSnuButton.setBorder(null); addInterfaceButton.setBorder(null); addFolderButton.setBorder(null); editFolderButton.setBorder(null); deleteFolderButton.setBorder(null); addSnuButton.setEnabled(false); // initially disabled, this is controlled by the controller treePanel = new BackgroundImagePanel(new BorderLayout()); ImageIcon bgIcon = (ImageIcon) UIResourceManager.getLanguageIcon(UIResourceManager.DRAG_DROP_KEY); treePanel.setImage(bgIcon.getImage()); add(treePanel); resourceTreeTableScroll = new JScrollPane(resourceTreeTable = new ResourceTreeTable()); // set the scroll pane, and the viewport as transparent so the background image can show through resourceTreeTableScroll.setOpaque(false); resourceTreeTableScroll.getViewport().setOpaque(false); initResourceTable(); treePanel.add(resourceTreeTableScroll); UIUtil.setDragOnPress(resourceTreeTable); // resourceTreeTableScroll.addMouseListener(resourceTreeTable.createStopEditingOnClickListener()); add(bottomPanel); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourcebrowser.columns.type.label"), ResourceProperty.TYPE); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourcebrowser.columns.issnu.label"), ResourceBrowser.Column.ISSNU); // UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourcebrowser.columns.filename.label"), Media.Property.FILENAME); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourcebrowser.columns.clicksound.label"), ResourceBrowser.Column.CLICKSOUND); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourcebrowser.columns.backgroundsound.label"), ResourceBrowser.Column.BACKGROUNDSOUND); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourcebrowser.columns.preview.label"), ResourceBrowser.Column.PREVIEW); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourceexplorer.columns.starting.label"), SnuProperty.STARTER); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourceexplorer.columns.ending.label"), SnuProperty.ENDER); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourceexplorer.columns.lives.label"), SnuProperty.LIVES); // UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourceexplorer.columns.maxlinks.label"), Snu.Property.MAXLINKS); // UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourceexplorer.columns.mainmedia.label"), Snu.Property.MAINMEDIA); UIUtil.addColumn(resourceTreeTable, LanguageBundle.getString("resourceexplorer.columns.interface.label"), SnuProperty.INTERFACE); resourceTreeTable.getColumn(ResourceProperty.NAME).setPreferredWidth(200); resourceTreeTable.getColumn(SnuProperty.INTERFACE).setPreferredWidth(120); // UIUtil.setColumnFixedSize(resourceTreeTable, Resource.Property.TYPE, 40); // UIUtil.setColumnFixedSize(resourceTreeTable, ResourceBrowser.Column.ISSNU, 70); // UIUtil.setColumnFixedSize(resourceTreeTable, MediaResourceListColumns.Column.CLICKSOUND, 30); // UIUtil.setColumnFixedSize(resourceTreeTable, ResourceBrowser.Column.BACKGROUNDSOUND, 30); // UIUtil.setColumnFixedSize(resourceTreeTable, ResourceBrowser.Column.PREVIEW, 30); // resourceTreeTable.getColumn(Snu.Property.LIVES).setPreferredWidth(20); // UIUtil.setColumnFixedSize(resourceTreeTable, Snu.Property.STARTER, 30); // UIUtil.setColumnFixedSize(resourceTreeTable, Snu.Property.ENDER, 30); //resourceTreeTableScroll.setPreferredSize(new Dimension(600, 400)); UIUtil.runUITaskLater(new Runnable() { public void run() { resourceTreeTable.getSelectionModel().setSelectionInterval(0, 0); } }); // resourceTreeTable.getTreeTableModel().setColumnIdentifier("kk", 0); createContextMenu(); UIUtil.runUITaskLater(new Runnable() { public void run() { TableCellRenderer renderer = resourceTreeTable.getTableHeader().getDefaultRenderer(); for (int i= 0; i < resourceTreeTable.getColumnCount(); ++i) { TableColumn column = resourceTreeTable.getColumnModel().getColumn(i); if (column.getIdentifier() == ResourceProperty.NAME) continue; if (column.getIdentifier() == SnuProperty.INTERFACE) continue; Component renderComp = renderer.getTableCellRendererComponent(resourceTreeTable, column.getHeaderValue(), false, false, 0, i); Dimension size = renderComp.getPreferredSize(); int margin = 10; column.setPreferredWidth(size.width + margin*2); } resourceTreeTable.invalidate(); } }); } protected void initResourceTable () { TreeModelListener tListener = new TreeModelListener(){ Logger logger = Logger.getLogger(ResourceBrowser.class); public void treeNodesInserted(TreeModelEvent e) { checkToHide(e); } public void treeNodesRemoved(TreeModelEvent e) { checkToShow(e); } public void treeNodesChanged(TreeModelEvent e) { // TODO Auto-generated method stub } public void treeStructureChanged(TreeModelEvent e) { checkToHide(e); checkToShow(e); } protected void checkToHide ( TreeModelEvent e ) { if ( treePanel.isBackgroundVisible() ) { ResourceTreeTableModel tModel = (ResourceTreeTableModel) e.getSource(); if ( tModel.getChildCount(tModel.getRoot()) > 1 ) { treePanel.showBackground(false); } } } protected void checkToShow ( TreeModelEvent e ) { if ( !treePanel.isBackgroundVisible() ) { ResourceTreeTableModel tModel = (ResourceTreeTableModel) e.getSource(); if ( tModel.getChildCount(tModel.getRoot()) <= 1 ) { treePanel.showBackground(true); } } } }; resourceTreeTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); resourceTreeTable.setAutoCreateColumnsFromModel(false); resourceTreeTable.setRootCollapsible(false); resourceTreeTable.getTableHeader().setReorderingAllowed(true); resourceTreeTable.getTableHeader().setDefaultRenderer(new ResourceTreeTableHeaderCellRenderer()); resourceTreeTable.setExternalTreeListener(tListener); } protected void initListeners() { ResourceTreeTransferHandler resourceTreeTransferHandler = new ResourceTreeTransferHandler(resourceTreeTable); resourceTreeTable.setDragEnabled(true); // resourceTreeTable.setDropTarget(new KorsakowDropTarget(resourceTreeTable)); resourceTreeTable.setTransferHandler(resourceTreeTransferHandler); // setting the droptarget to "new KorsakowDropTarget(resourceTreeTable)" causes problems under Windows JDK6; anyway it seems unnecessary. resourceTreeTableScroll.setTransferHandler(resourceTreeTransferHandler); resourceTreeTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { KNode selected = resourceTreeTable.getSelectedNode(); boolean isFolder = (selected instanceof FolderNode); boolean isRoot = selected == resourceTreeTable.getRootNode(); editFolderButton.setEnabled(isFolder && !isRoot); deleteFolderButton.setEnabled(!isRoot); } }); editFolderButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { onEditFolderAction(); } }); deleteFolderButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { onDeleteFolderAction(); } }); resourceTreeTableScroll.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent event) { //seems to be a bug with jtreetable that setting the selection on the treetable doesn'ts set the selection on the tree resourceTreeTable.getTree().setSelectionInterval(-1, -1); } }); resourceTreeTable.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent event) { // technically speaking this is flawed, since the popuptrigger might execute // before us (awt event notification order being arbitrary), before we can actually set the selection // however it seems to work so far on mac/win which is good enough for me, sorry. (probably beacase they execute on something other than pressed) int row = resourceTreeTable.rowAtPoint(event.getPoint()); int selectedRows[] = resourceTreeTable.getSelectedRows(); boolean isPointSelected = false; for (int s : selectedRows) { if (s == row) { isPointSelected = true; break; } } if (!isPointSelected) { resourceTreeTable.getTree().setSelectionInterval(row, row); } } }); MouseListener popupListener = new MouseAdapter() { /** * On OSX when CTRL-CLICK to show context menu on the JTreeTable, Pressed,Released and Clicked are ALL popup triggers * which results in contextMenu.show being called a few times, which for some reason causes it to show up as blank. */ private boolean isAlreadyGoingToShow = false; @Override public void mousePressed(MouseEvent e) { maybeShowPopup(e); } @Override public void mouseReleased(MouseEvent e) { maybeShowPopup(e); isAlreadyGoingToShow = false; } @Override public void mouseClicked(MouseEvent e) { maybeShowPopup(e); } public void maybeShowPopup(MouseEvent e) { if (isAlreadyGoingToShow || !UIUtil.isPopupTrigger(e)) { return; } isAlreadyGoingToShow = true; for (JMenuItem item : contextMenuItems.values()) item.setVisible(false); contextMenu.show(e.getComponent(), e.getX(), e.getY()); } }; resourceTreeTable.addMouseListener(popupListener); resourceTreeTableScroll.addMouseListener(popupListener); } protected void onAddFolderAction() { KNode parent = resourceTreeTable.getSelectedNode(); if (parent == null) parent = resourceTreeTable.getRootNode(); parent = findParentFolderNode(parent); if (parent == null) return; String name = UIUtil.createUniqueName(parent, LanguageBundle.getString("general.newfoldername")); FolderNode node = new FolderNode(name); resourceTreeTable.getTreeTableModel().appendNode(node, parent); } protected void onEditFolderAction() { KNode selected = resourceTreeTable.getSelectedNode(); if (selected instanceof FolderNode == false) return; if (selected == resourceTreeTable.getRootNode()) return; resourceTreeTable.editCellAt(selected, 0); } protected void onDeleteFolderAction() { Collection<? extends KNode> selected = resourceTreeTable.getSelectedNodes(); for (ResourceBrowserListener listener : listenerList.getListeners(ResourceBrowserListener.class)) listener.onDeleteNodes(selected); } protected void onTreeCellEditingStopped() { final KNode node = resourceTreeTable.getNodeAt(resourceTreeTable.getEditingRow()); String name = (String)resourceTreeTable.getDefaultEditor(TreeTableModel.class).getCellEditorValue(); if (node.getParent().getChild(name) != null) { JOptionPane.showMessageDialog(resourceTreeTable, LanguageBundle.getString("general.errors.namealreadytaken.message")); UIUtil.runUITaskLater(new Runnable() { public void run() { resourceTreeTable.editCellAt(node, 0); } }); return; } node.setName(name); resourceTreeTable.revalidate(); } protected KNode findParentFolderNode(KNode node) { while (node!=null && node instanceof FolderNode == false) { node = node.getParent(); } return node; } public void addEventListener(ResourceBrowserListener listener) { listenerList.add(ResourceBrowserListener.class, listener); } public void addContextMenuItem(Object key, JMenuItem item) { contextMenuItems.put(key, item); } public JMenuItem getContextMenuItem(Object key) { return contextMenuItems.get(key); } public void addPopupMenuListener(PopupMenuListener listener) { contextMenu.addPopupMenuListener(listener); } public void setContextMenuAction(Object key, ActionListener action) { getContextMenuItem(key).addActionListener(action); } public void setAction(Action key, ActionListener listener) { // it betrays the name "set" that we actually "add" actionlisteners, but thats largely just our lazy implementation // given that its unlikely to happen that we "set" a given action more than once. switch (key) { case AddFolder: addFolderButton.addActionListener(listener); break; case AddSnu: addSnuButton.addActionListener(listener); break; case AddInterface: addInterfaceButton.addActionListener(listener); break; } } public void setActionButtonEnabled(Action key, boolean enabled) { switch (key) { case AddFolder: addFolderButton.setEnabled(enabled); break; case AddSnu: addSnuButton.setEnabled(enabled); break; case AddInterface: addInterfaceButton.setEnabled(enabled); break; } } protected JPopupMenu createContextMenu() { contextMenu.removeAll(); JMenuItem menuItem; contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.createsnu.label"), LanguageBundle.getString("resourcebrowser.menu.createsnu.mnemonic"), Menu.CreateSnu)); contextMenu.add(new JSeparator()); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.openassnu.label"), LanguageBundle.getString("resourcebrowser.menu.openassnu.mnemonic"), Menu.OpenAsSnu)); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.properties.label"), LanguageBundle.getString("resourcebrowser.menu.properties.mnemonic"), Menu.Properties)); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.mediaproperties.label"), LanguageBundle.getString("resourcebrowser.menu.mediaproperties.mnemonic"), Menu.MediaProperties)); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.duplicate.label"), LanguageBundle.getString("resourcebrowser.menu.duplicate.mnemonic"), Menu.Duplicate)); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.copy.label"), LanguageBundle.getString("resourcebrowser.menu.copy.mnemonic"), Menu.Copy)); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.paste.label"), LanguageBundle.getString("resourcebrowser.menu.paste.mnemonic"), Menu.Paste)); contextMenu.add(menuItem = createMenuItem(Platform.isMacOS()?LanguageBundle.getString("resourcebrowser.menu.revealinplatformfilesystembrowser.label.osx"):LanguageBundle.getString("resourcebrowser.menu.revealinplatformfilesystembrowser.label"), LanguageBundle.getString("resourcebrowser.menu.revealinplatformfilesystembrowser.mnemonic"), Menu.RevealInPlatformFilesystemBrowser)); contextMenu.add(new JSeparator()); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.rename.label"), LanguageBundle.getString("resourcebrowser.menu.rename.mnemonic"), Menu.Rename)); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.delete.label"), LanguageBundle.getString("resourcebrowser.menu.delete.mnemonic"), Menu.Delete)); contextMenu.add(new JSeparator()); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.newfolder.label"), LanguageBundle.getString("resourcebrowser.menu.newfolder.mnemonic"), Menu.NewFolder)); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.newinterface.label"), LanguageBundle.getString("resourcebrowser.menu.newinterface.mnemonic"), Menu.NewInterface)); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.exportinterface.label"), LanguageBundle.getString("resourcebrowser.menu.exportinterface.mnemonic"), Menu.ExportInterface)); contextMenu.add(new JSeparator()); contextMenu.add(menuItem = createMenuItem(LanguageBundle.getString("resourcebrowser.menu.projectsettings.label"), LanguageBundle.getString("resourcebrowser.menu.projectsettings.mnemonic"), Menu.ProjectSettings)); return contextMenu; } protected JMenuItem createMenuItem(String label, String mnemonic, Object key) { return createMenuItem(label, (mnemonic!=null&&mnemonic.length()>0)?(int)mnemonic.charAt(0):null, key); } protected JMenuItem createMenuItem(String label, Integer mnemonic, Object key) { JMenuItem menuItem; menuItem = UIUtil.createMenuItem(listenerList, label, mnemonic, key.toString()); addContextMenuItem(key, menuItem); return menuItem; } public void setStatus(String text) { statusLabel.setText(text); } // protected JCheckBoxMenuItem createCheckboxMenuItem(String label, String actionCommand) // { // JCheckBoxMenuItem menuItem; // menuItem = UIUtil.createCheckboxMenuItem(listenerList,label, Menu.Duplicate.name()); // addContextMenuItem(actionCommand, menuItem); // return menuItem; // } }