/*
* Rapid Beans Framework: DocumentTreeViewSwing.java
*
* Copyright (C) 2009 Martin Bluemel
*
* Creation Date: 02/11/2006
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation;
* either version 3 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 Lesser General Public License for more details.
* You should have received a copies of the GNU Lesser General Public License and the
* GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
package org.rapidbeans.presentation.swing;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.rapidbeans.core.basic.Property;
import org.rapidbeans.core.basic.PropertyCollection;
import org.rapidbeans.core.basic.RapidBean;
import org.rapidbeans.core.common.RapidBeansLocale;
import org.rapidbeans.core.event.PropertyChangeEvent;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.datasource.Document;
import org.rapidbeans.datasource.Filter;
import org.rapidbeans.datasource.event.AddedEvent;
import org.rapidbeans.datasource.event.ChangedEvent;
import org.rapidbeans.datasource.event.RemovedEvent;
import org.rapidbeans.presentation.Application;
import org.rapidbeans.presentation.DocumentTreeView;
import org.rapidbeans.presentation.EditorBean;
import org.rapidbeans.service.ActionDocumentSave;
/**
* Swing implementation for a tree view for a bean document.
*
* @author Martin Bluemel
*/
public final class DocumentTreeViewSwing extends DocumentTreeView {
/**
* the tree view's scroll pane.
*/
private JScrollPane scrollPane = null;
/**
* the Swing tree model.
*/
private DocumentTreeModel treeModel = null;
/**
* the Swing tree view.
*/
private JTree tree = null;
/**
* @return the Swing tree.
*/
public JTree getTree() {
return this.tree;
}
/**
* the editor's popup menu.
*/
private JPopupMenu popupMenu = new JPopupMenu();
/**
* the editor's popup delete menu.
*/
private JMenuItem popupMenuItemNew = new JMenuItem();
/**
* the editor's popup delete menu.
*/
private JMenuItem popupMenuItemEdit = new JMenuItem();
/**
* the editor's popup delete menu.
*/
private JMenuItem popupMenuItemDelete = new JMenuItem();
/**
* @return the JTreeView.
*/
public Object getWidget() {
return this.scrollPane;
}
/**
* change the tree view selection.
*
* @param treePathObject
* the tree path identifying the tree object to select
*/
protected void setSelectedBean(final Object treePathObject) {
suppressSelectionChangeHandling(true);
TreePath treePath = (TreePath) treePathObject;
try {
if (treePath == null) {
this.tree.clearSelection();
} else {
this.tree.setSelectionPath(treePath);
this.tree.expandPath(treePath);
}
} finally {
suppressSelectionChangeHandling(false);
}
}
/**
* Change the show bean links behaviour.
*/
protected void changeShowBeanLinks() {
this.treeModel.setShowBeanLinks(this.getShowBeanLinks());
this.treeModel.fireTreeStructureChanged(this.treeModel.getRoot(), this.tree);
}
/**
* Change the show properties behaviour.
*/
protected void changeShowProperties() {
this.treeModel.setShowProperties(this.getShowProperties());
this.treeModel.fireTreeStructureChanged(this.treeModel.getRoot(), this.tree);
}
/**
* constructor.
*
* @param client
* the client
* @param doc
* the document to show
* @param filter
* the filter
*/
public DocumentTreeViewSwing(final Application client, final Document doc, final Filter filter) {
super(client, doc, filter);
this.treeModel = new DocumentTreeModel(doc, filter);
this.treeModel.setShowBeanLinks(this.getShowBeanLinks());
this.tree = new JTree(this.treeModel);
this.tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
this.scrollPane = new JScrollPane(this.tree);
final RapidBeansLocale loc = client.getCurrentLocale();
this.tree.setCellRenderer(new DocumentTreeCellRenderer(doc, loc));
this.tree.addMouseListener(new MouseListener() {
/**
* event handler for mouse clicks.
*/
@SuppressWarnings({ "synthetic-access", "unqualified-field-access" })
public void mouseClicked(final MouseEvent e) {
switch (e.getButton()) {
case MouseEvent.BUTTON1:
mouseClickedLeft(e);
break;
default:
break;
}
}
public void mouseEntered(final MouseEvent e) {
}
public void mouseExited(final MouseEvent e) {
}
public void mousePressed(final MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
mousePressedRight(e);
}
}
public void mouseReleased(final MouseEvent e) {
}
});
this.tree.addKeyListener(new KeyListener() {
public void keyPressed(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_DELETE:
deleteBeans();
break;
case KeyEvent.VK_S:
if (e.getModifiers() == 2) {
(new ActionDocumentSave()).execute();
}
break;
default:
break;
}
}
public void keyTyped(KeyEvent e) {
}
});
this.popupMenuItemNew.setText(client.getCurrentLocale().getStringGui("commongui.text.new"));
this.popupMenuItemNew.isFocusable();
this.popupMenuItemEdit.setText(client.getCurrentLocale().getStringGui("commongui.text.edit"));
this.popupMenuItemDelete.setText(client.getCurrentLocale().getStringGui("commongui.text.delete"));
this.popupMenuItemNew.addActionListener(new ActionListener() {
@SuppressWarnings({ "synthetic-access", "unqualified-field-access" })
public void actionPerformed(final ActionEvent e) {
popupMenu.setVisible(false);
createBean();
}
});
this.popupMenuItemEdit.addActionListener(new ActionListener() {
@SuppressWarnings({ "synthetic-access", "unqualified-field-access" })
public void actionPerformed(final ActionEvent e) {
popupMenu.setVisible(false);
editBeans();
}
});
this.popupMenuItemDelete.addActionListener(new ActionListener() {
@SuppressWarnings({ "synthetic-access", "unqualified-field-access" })
public void actionPerformed(final ActionEvent e) {
popupMenu.setVisible(false);
deleteBeans();
}
});
this.popupMenu.addPopupMenuListener(new PopupMenuListener() {
public void popupMenuCanceled(final PopupMenuEvent e) {
}
public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) {
}
public void popupMenuWillBecomeVisible(final PopupMenuEvent e) {
updatePopupMenu();
}
});
this.popupMenu.add(this.popupMenuItemNew);
this.popupMenu.add(this.popupMenuItemEdit);
this.popupMenu.add(this.popupMenuItemDelete);
// this.popupMenu.isFocusable();
// the component popup menu eats up the right click mouse event
this.tree.setComponentPopupMenu(this.popupMenu);
}
/**
* Delete a set of selected beans.
*/
// Is protected to enable Unit Tests.
protected void deleteBeans() {
// retrieve the paths selected in the tree
final TreePath[] paths = this.tree.getSelectionPaths();
final Object[] selObjs = new Object[paths.length];
for (int i = 0; i < paths.length; i++) {
selObjs[i] = paths[i].getLastPathComponent();
}
suppressSelectionChangeHandling(true);
try {
super.deleteBeans(selObjs);
} finally {
suppressSelectionChangeHandling(false);
}
}
/**
* initiates creating of a new bean.
*
* @return the bean editor used for creating the new bean
*/
public EditorBean createBean() {
EditorBean editor = null;
if (this.tree != null && this.tree.getSelectionPaths() != null && this.tree.getSelectionPaths().length > 0) {
final TreePath path = this.tree.getSelectionPaths()[0];
final Object selObj = path.getLastPathComponent();
if (selObj instanceof DocumentTreeNodePropColComp) {
DocumentTreeNodePropColComp treenode = (DocumentTreeNodePropColComp) selObj;
editor = createBean(path, treenode.getColProp());
} else if (selObj instanceof RapidBean) {
RapidBean selBean = (RapidBean) selObj;
if (selBean.getParentBean() != null) {
editor = createBean(path, selBean.getParentProperty());
}
}
}
return editor;
}
/**
* notifies all registered listeners that a bean has bee selected.
*
* @return the bean editor of the last bean edited
*/
public EditorBean editBeans() {
return super.editBeans(this.tree.getSelectionPaths(), getSelectedBeans());
}
/**
* determines all selected beans.
*/
public RapidBean[] getSelectedBeans() {
final TreePath[] paths = this.tree.getSelectionPaths();
final RapidBean[] selBeans = new RapidBean[paths.length];
for (int i = 0; i < paths.length; i++) {
selBeans[i] = (RapidBean) paths[i].getLastPathComponent();
}
return selBeans;
}
/**
* the left mouse button has been clicked.
*
* @param e
* the mouse event.
*/
private void mouseClickedLeft(final MouseEvent e) {
// this.popupMenu.setVisible(false);
if (e.getClickCount() == 2) {
final TreePath path = this.tree.getPathForLocation(e.getX(), e.getY());
if (path != null) {
final Object[] keys = new Object[1];
keys[0] = path;
final Object[] selObjs = new Object[1];
selObjs[0] = path.getLastPathComponent();
editBeans(keys, selObjs);
}
}
}
/**
* retrieve the original object for the link object.
*
* @param link
* the link object
*
* @return the original object
*/
protected TreePath getOriginalForLink(final Object link) {
TreePath linkPath = (TreePath) link;
RapidBean linkedBean = ((DocumentTreeNodeBeanLink) linkPath.getLastPathComponent()).getLinkedBean();
TreePath path = new TreePath(this.treeModel.getParentObjects(linkedBean, true));
this.tree.expandPath(path);
this.tree.setSelectionPath(path);
TreePath pathFromTree = this.tree.getSelectionPath();
return pathFromTree;
}
/**
* Retrieve an object representing the bean in the TreeView.
*
* @param bean
* the bean
*
* @return the path
*/
protected Object getTreeKey(final RapidBean bean) {
return new TreePath(this.treeModel.getParentObjects(bean, true));
}
/**
* the right mouse button has been clicked.
*
* @param e
* the mouse event.
*/
private void mousePressedRight(final MouseEvent e) {
// select the node right "clicked" if the pointer's
// location is not already on a selected node
final TreePath[] selPaths = tree.getSelectionPaths();
final TreePath rightClickedPath = tree.getPathForLocation(e.getX(), e.getY());
boolean pointerIsOnSelected = false;
if (selPaths != null && selPaths.length > 0) {
for (int i = 0; i < selPaths.length; i++) {
if (selPaths[i] == rightClickedPath) {
pointerIsOnSelected = true;
break;
}
}
}
if (!pointerIsOnSelected) {
suppressSelectionChangeHandling(true);
try {
tree.setSelectionPath(rightClickedPath);
} finally {
suppressSelectionChangeHandling(false);
}
}
}
/**
* update the texts and the enabeling of the popup menu's entries.
*/
private void updatePopupMenu() {
final TreePath[] selPaths = this.tree.getSelectionPaths();
if (selPaths == null) {
this.popupMenuItemNew.setEnabled(false);
this.popupMenuItemEdit.setEnabled(false);
this.popupMenuItemDelete.setEnabled(false);
return;
}
switch (selPaths.length) {
case 0:
this.popupMenuItemNew.setEnabled(false);
this.popupMenuItemEdit.setEnabled(false);
this.popupMenuItemDelete.setEnabled(false);
break;
case 1:
if (selPaths[0].getLastPathComponent() instanceof RapidBean) {
final RapidBean selBean = (RapidBean) selPaths[0].getLastPathComponent();
if (selBean.getParentBean() == null) {
this.popupMenuItemNew.setEnabled(false);
this.popupMenuItemDelete.setEnabled(false);
} else {
this.popupMenuItemNew.setEnabled(true);
this.popupMenuItemDelete.setEnabled(true);
}
this.popupMenuItemEdit.setEnabled(true);
} else {
this.popupMenuItemNew.setEnabled(true);
this.popupMenuItemEdit.setEnabled(false);
this.popupMenuItemDelete.setEnabled(false);
}
break;
default:
this.popupMenuItemNew.setEnabled(false);
boolean atLeastOneBeanSelected = false;
for (int i = 0; i < selPaths.length; i++) {
if (selPaths[i].getLastPathComponent() instanceof RapidBean) {
atLeastOneBeanSelected = true;
break;
}
}
if (atLeastOneBeanSelected) {
this.popupMenuItemEdit.setEnabled(true);
this.popupMenuItemDelete.setEnabled(true);
} else {
this.popupMenuItemEdit.setEnabled(false);
this.popupMenuItemDelete.setEnabled(false);
}
break;
}
}
/**
* event handler for bean added event.
*
* @param e
* the added event
*/
public void beanAddPre(final AddedEvent e) {
}
/**
* event handler for bean added event.
*
* @param e
* the added event
*/
public void beanAdded(final AddedEvent e) {
}
/**
* event handler for bean changed event.
*
* @param e
* the changed event
*/
public void beanChangePre(final ChangedEvent e) {
}
/**
* event handler for bean changed event.
*
* @param e
* the changed event
*/
public void beanChanged(final ChangedEvent e) {
if (!(this.getDocument() == e.getBean().getContainer())) {
return;
}
if (e.getPropertyEvents().length == 0) {
throw new RapidBeansRuntimeException("unexpectedly got no property changed");
}
for (final PropertyChangeEvent pe : e.getPropertyEvents()) {
if (pe.getProperty() instanceof PropertyCollection) {
switch (pe.getType()) {
case addlink:
case removelink:
case set:
final Property prop = pe.getProperty();
final DocumentTreeNodePropCol colPropNode = treeModel.findColPropNode(prop);
if (colPropNode != null) {
this.treeModel.fireTreeStructureChanged(colPropNode, this.tree);
}
break;
}
}
}
final TreePath beanPath = new TreePath(this.treeModel.getParentObjects(e.getBean(), true));
this.treeModel.fireBeanChanged(beanPath);
}
/**
* handler for the pre remove event.
*
* @param e
* the removed event
*/
public void beanRemovePre(final RemovedEvent e) {
}
/**
* event handler for bean removed event.
*
* @param e
* the removed event
*/
public void beanRemoved(final RemovedEvent e) {
}
}