/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.fib.view.widget; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.MouseListener; import java.util.Collection; import java.util.List; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeSelectionModel; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.openflexo.antar.binding.AbstractBinding; import org.openflexo.fib.controller.FIBBrowserDynamicModel; import org.openflexo.fib.controller.FIBController; import org.openflexo.fib.controller.FIBSelectable; import org.openflexo.fib.model.FIBBrowser; import org.openflexo.fib.view.FIBWidgetView; import org.openflexo.fib.view.widget.browser.FIBBrowserCellEditor; import org.openflexo.fib.view.widget.browser.FIBBrowserCellRenderer; import org.openflexo.fib.view.widget.browser.FIBBrowserModel; import org.openflexo.fib.view.widget.browser.FIBBrowserModel.BrowserCell; import org.openflexo.fib.view.widget.browser.FIBBrowserWidgetFooter; import org.openflexo.toolbox.ToolBox; /** * Widget allowing to display a browser (a tree of various objects) * * @author sguerin */ public class FIBBrowserWidget extends FIBWidgetView<FIBBrowser, JTree, Object> implements FIBSelectable, TreeSelectionListener { private static final Logger logger = Logger.getLogger(FIBBrowserWidget.class.getPackage().getName()); // private static final int DEFAULT_WIDTH = 200; private JTree _tree; private final JPanel _dynamicComponent; private final FIBBrowser _fibBrowser; private FIBBrowserModel _browserModel; private FIBBrowserWidgetFooter _footer; // private ListSelectionModel _listSelectionModel; private JScrollPane scrollPane; private Object selectedObject; private final Vector<Object> selection; public FIBBrowserWidget(FIBBrowser fibBrowser, FIBController controller) { super(fibBrowser, controller); _fibBrowser = fibBrowser; _dynamicComponent = new JPanel(); _dynamicComponent.setOpaque(false); _dynamicComponent.setLayout(new BorderLayout()); selection = new Vector<Object>(); _footer = new FIBBrowserWidgetFooter(this); buildBrowser(); } @Override public synchronized void delete() { _footer.delete(); super.delete(); } public FIBBrowser getBrowser() { return _fibBrowser; } public FIBBrowserModel getBrowserModel() { if (_browserModel == null) { _browserModel = new FIBBrowserModel(_fibBrowser, this, getController()); } return _browserModel; } public JTree getJTree() { return _tree; } public FIBBrowserWidgetFooter getFooter() { return _footer; } public Object getRootValue() { return getWidget().getRoot().getBindingValue(getController()); } // private static final Vector EMPTY_VECTOR = new Vector(); @Override public synchronized boolean updateWidgetFromModel() { // List valuesBeforeUpdating = getBrowserModel().getValues(); Object wasSelected = getSelectedObject(); // boolean debug = false; // if (getWidget().getName() != null && getWidget().getName().equals("PatternRoleTable")) debug=true; // if (debug) System.out.println("valuesBeforeUpdating: "+valuesBeforeUpdating); // if (debug) System.out.println("wasSelected: "+wasSelected); if (_tree.isEditing()) { if (logger.isLoggable(Level.FINE)) { logger.fine(getComponent().getName() + " - Tree is currently editing"); } _tree.getCellEditor().cancelCellEditing(); } else { if (logger.isLoggable(Level.FINE)) { logger.fine(getComponent().getName() + " - Tree is NOT currently edited "); } } if (logger.isLoggable(Level.FINE)) { logger.fine(getComponent().getName() + " updateWidgetFromModel() with " + getValue() + " dataObject=" + getDataObject()); } boolean returned = getBrowserModel().updateRootObject(getRootValue()); // getBrowserModel().setModel(getDataObject()); // We restore value if and only if we represent same browser /*if (getBrowserModel().getValues() == valuesBeforeUpdating && wasSelected != null) { setSelectedObject(wasSelected); } else {*/ // logger.info("Bon, je remets a jour la selection du browser, value="+getComponent().getSelected().getBindingValue(getController())+" was: "+getSelectedObject()); // System.out.println("getComponent().getSelected()="+getComponent().getSelected()); // System.out.println("getComponent().getSelected().isValid()="+getComponent().getSelected().isValid()); // System.out.println("value="+getComponent().getSelected().getBindingValue(getController())); if (getComponent().getSelected().isValid() && getComponent().getSelected().getBindingValue(getController()) != null) { Object newSelectedObject = getComponent().getSelected().getBindingValue(getController()); if (returned = notEquals(newSelectedObject, getSelectedObject())) { setSelectedObject(newSelectedObject); } } // } return returned; } public TreeSelectionModel getTreeSelectionModel() { return _tree.getSelectionModel(); } public void setSelectedObject(Object object) { setSelectedObject(object, false); } public void setSelectedObject(Object object, boolean force) { // logger.info("Select " + object); if (getRootValue() == null) { return; } if (object == getSelectedObject() && !force) { logger.fine("Ignore set selected object"); return; } // logger.info("---------------------> FIBBrowserWidget, setSelectedObject from "+getSelectedObject()+" to "+object); if (object != null) { Collection<BrowserCell> cells = getBrowserModel().getBrowserCell(object); // logger.info("Select " + cells); getTreeSelectionModel().clearSelection(); if (cells != null) { TreePath scrollTo = null; for (BrowserCell cell : cells) { TreePath treePath = cell.getTreePath(); if (scrollTo == null) { scrollTo = treePath; } getTreeSelectionModel().addSelectionPath(treePath); } if (scrollTo != null) { _tree.scrollPathToVisible(scrollTo); } } } else { clearSelection(); } } public void clearSelection() { getTreeSelectionModel().clearSelection(); } @Override public List<AbstractBinding> getDependencyBindings() { List<AbstractBinding> returned = super.getDependencyBindings(); appendToDependingObjects(getWidget().getSelected(), returned); appendToDependingObjects(getWidget().getRoot(), returned); return returned; } @Override public synchronized boolean updateModelFromWidget() { return false; } @Override public JPanel getJComponent() { return _dynamicComponent; } @Override public JTree getDynamicJComponent() { return _tree; } @Override public FIBBrowserDynamicModel createDynamicModel() { return new FIBBrowserDynamicModel(null); } @Override public FIBBrowserDynamicModel getDynamicModel() { return (FIBBrowserDynamicModel) super.getDynamicModel(); } @Override public void updateLanguage() { super.updateLanguage(); updateBrowser(); System.out.println("Ne pas oublier de localiser les actions ici"); /*for (FIBTableAction a : getWidget().getActions()) { if (getWidget().getLocalize()) getLocalized(a.getName()); }*/ } public void updateBrowser() { deleteBrowser(); if (_browserModel != null) { _browserModel.delete(); _browserModel = null; } buildBrowser(); updateDataObject(getDataObject()); } private void deleteBrowser() { if (_tree != null) { _tree.removeFocusListener(this); } for (MouseListener l : _tree.getMouseListeners()) { _tree.removeMouseListener(l); } } private void buildBrowser() { _tree = new JTree(getBrowserModel()) { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (ToolBox.isMacOSLaf()) { if (getSelectionRows() != null && getSelectionRows().length > 0) { for (int selected : getSelectionRows()) { Rectangle rowBounds = getRowBounds(selected); if (getVisibleRect().contains(rowBounds)) { Object value = getPathForRow(selected).getLastPathComponent(); Component treeCellRendererComponent = getCellRenderer().getTreeCellRendererComponent(this, value, true, isExpanded(selected), getModel().isLeaf(value), selected, getLeadSelectionRow() == selected); Color bgColor = treeCellRendererComponent.getBackground(); if (treeCellRendererComponent instanceof DefaultTreeCellRenderer) { bgColor = ((DefaultTreeCellRenderer) treeCellRendererComponent).getBackgroundSelectionColor(); } if (bgColor != null) { g.setColor(bgColor); g.fillRect(0, rowBounds.y, getWidth(), rowBounds.height); } Graphics g2 = g.create(rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height); treeCellRendererComponent.setBounds(rowBounds); treeCellRendererComponent.paint(g2); g2.dispose(); } } } } } }; if (ToolBox.isMacOS()) { _tree.setSelectionModel(new DefaultTreeSelectionModel() { @Override public int[] getSelectionRows() { int[] selectionRows = super.getSelectionRows(); // MacOS X does not support that we return null here during DnD operations. if (selectionRows == null) { return new int[0]; } return selectionRows; } }); } _tree.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); _tree.addFocusListener(this); _tree.setEditable(true); _tree.setScrollsOnExpand(true); FIBBrowserCellRenderer renderer = new FIBBrowserCellRenderer(this); _tree.setCellRenderer(renderer); _tree.setCellEditor(new FIBBrowserCellEditor(_tree, renderer)); ToolTipManager.sharedInstance().registerComponent(_tree); _tree.setAutoscrolls(true); /** Beginning of model dependent settings */ _tree.setRootVisible(getBrowser().getRootVisible()); _tree.setShowsRootHandles(getBrowser().getShowRootsHandle()); if (_fibBrowser.getRowHeight() != null) { _tree.setRowHeight(_fibBrowser.getRowHeight()); } else { _tree.setRowHeight(0); } if (_fibBrowser.getVisibleRowCount() != null) { _tree.setVisibleRowCount(_fibBrowser.getVisibleRowCount()); } getTreeSelectionModel().setSelectionMode(getBrowser().getSelectionMode().getMode()); getTreeSelectionModel().addTreeSelectionListener(this); scrollPane = new JScrollPane(_tree); _dynamicComponent.removeAll(); _dynamicComponent.add(scrollPane, BorderLayout.CENTER); if (_fibBrowser.getShowFooter()) { _dynamicComponent.add(getFooter(), BorderLayout.SOUTH); } _dynamicComponent.revalidate(); _dynamicComponent.repaint(); getBrowserModel().addTreeModelListener(new TreeModelListener() { @Override public void treeStructureChanged(TreeModelEvent e) { ensureRootExpanded(); } @Override public void treeNodesRemoved(TreeModelEvent e) { } @Override public void treeNodesInserted(TreeModelEvent e) { ensureRootExpanded(); } private void ensureRootExpanded() { if (!getBrowser().getRootVisible() && (BrowserCell) getBrowserModel().getRoot() != null && ((BrowserCell) getBrowserModel().getRoot()).getChildCount() == 1) { // Only one cell and roots are hidden, expand this first cell SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // See issue OPENFLEXO-516. Sometimes, the condition may have become false. if (!getBrowser().getRootVisible() && (BrowserCell) getBrowserModel().getRoot() != null && ((BrowserCell) getBrowserModel().getRoot()).getChildCount() == 1) { getJTree().expandPath( new TreePath(new Object[] { (BrowserCell) getBrowserModel().getRoot(), ((BrowserCell) getBrowserModel().getRoot()).getChildAt(0) })); } } }); } } @Override public void treeNodesChanged(TreeModelEvent e) { // TODO Auto-generated method stub } }); } @Override public boolean synchronizedWithSelection() { return getWidget().getBoundToSelectionManager(); } public boolean isLastFocusedSelectable() { return getController() != null && getController().getLastFocusedSelectable() == this; } @Override public boolean mayRepresent(Object o) { return _browserModel.containsObject(o); } @Override public void objectAddedToSelection(Object o) { addToSelectionNoNotification(o); } @Override public void objectRemovedFromSelection(Object o) { removeFromSelectionNoNotification(o); } @Override public void selectionResetted() { resetSelectionNoNotification(); } @Override public void updateDataObject(final Object dataObject) { if (!SwingUtilities.isEventDispatchThread()) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Update data object invoked outside the EDT!!! please investigate and make sure this is no longer the case. \n\tThis is a very SERIOUS problem! Do not let this pass."); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateDataObject(dataObject); } }); return; } super.updateDataObject(dataObject); getBrowserModel().fireTreeRestructured(); } @Override public Object getSelectedObject() { return selectedObject; } @Override public Vector<Object> getSelection() { return selection; } private boolean ignoreNotifications = false; public synchronized void addToSelectionNoNotification(Object o) { ignoreNotifications = true; addToSelection(o); ignoreNotifications = false; } public synchronized void removeFromSelectionNoNotification(Object o) { ignoreNotifications = true; removeFromSelection(o); ignoreNotifications = false; } public synchronized void resetSelectionNoNotification() { ignoreNotifications = true; resetSelection(); ignoreNotifications = false; } @Override public void addToSelection(Object o) { for (TreePath path : getBrowserModel().getPaths(o)) { getTreeSelectionModel().addSelectionPath(path); getJTree().scrollPathToVisible(path); } } @Override public void removeFromSelection(Object o) { for (TreePath path : getBrowserModel().getPaths(o)) { getTreeSelectionModel().removeSelectionPath(path); } } @Override public void resetSelection() { getTreeSelectionModel().clearSelection(); } @Override public synchronized void valueChanged(TreeSelectionEvent e) { Vector<Object> oldSelection = new Vector<Object>(); oldSelection.addAll(selection); /*System.out.println("Selection: "+e); System.out.println("Paths="+e.getPaths()); for (TreePath tp : e.getPaths()) { System.out.println("> "+tp.getLastPathComponent()+" added="+e.isAddedPath(tp)); } System.out.println("New LEAD="+(e.getNewLeadSelectionPath()!=null?e.getNewLeadSelectionPath().getLastPathComponent():"null")); System.out.println("Old LEAD="+(e.getOldLeadSelectionPath()!=null?e.getOldLeadSelectionPath().getLastPathComponent():"null")); */ if (e.getNewLeadSelectionPath() == null || (BrowserCell) e.getNewLeadSelectionPath().getLastPathComponent() == null) { selectedObject = null; } else { selectedObject = ((BrowserCell) e.getNewLeadSelectionPath().getLastPathComponent()).getRepresentedObject(); } for (TreePath tp : e.getPaths()) { if (e.isAddedPath(tp)) { selection.add(((BrowserCell) tp.getLastPathComponent()).getRepresentedObject()); } else { selection.remove(((BrowserCell) tp.getLastPathComponent()).getRepresentedObject()); } } // logger.info("BrowserModel, selected object is now "+selectedObject); if (selectedObject == null) { getDynamicModel().selected = null; } else if (getBrowser().getIteratorClass() == null || getBrowser().getIteratorClass().isAssignableFrom(selectedObject.getClass())) { getDynamicModel().selected = selectedObject; } else { // If selected element is not of expected class, set selected to be null // (we want to be sure that selected is an instance of IteratorClass) getDynamicModel().selected = null; } getDynamicModel().selection = selection; notifyDynamicModelChanged(); if (getComponent().getSelected().isValid()) { logger.fine("Sets SELECTED binding with " + selectedObject); getComponent().getSelected().setBindingValue(selectedObject, getController()); } updateFont(); if (!ignoreNotifications) { getController().updateSelection(this, oldSelection, selection); } _footer.setFocusedObject(selectedObject); } }