/* * Copyright (C) 2004 The Concord Consortium, Inc., * 10 Concord Crossing, Concord, MA 01742 * * Web Site: http://www.concord.org * Email: info@concord.org * * This library 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 2.1 of the License, or (at your option) any later version. * * This library 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 copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * END LICENSE */ /* * Last modification information: * $Revision: 1.61 $ * $Date: 2007-10-22 19:37:02 $ * $Author: scytacki $ * * Licence Information * Copyright 2004 The Concord Consortium */ package org.concord.otrunk.view; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.EventQueue; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JViewport; import javax.swing.SwingUtilities; import org.concord.framework.otrunk.OTControllerService; import org.concord.framework.otrunk.OTObject; import org.concord.framework.otrunk.OTObjectService; import org.concord.framework.otrunk.view.OTControllerServiceFactory; import org.concord.framework.otrunk.view.OTFrameManager; import org.concord.framework.otrunk.view.OTJComponentService; import org.concord.framework.otrunk.view.OTJComponentServiceFactory; import org.concord.framework.otrunk.view.OTJComponentView; import org.concord.framework.otrunk.view.OTViewContainer; import org.concord.framework.otrunk.view.OTViewContainerChangeEvent; import org.concord.framework.otrunk.view.OTViewContainerListener; import org.concord.framework.otrunk.view.OTViewContext; import org.concord.framework.otrunk.view.OTViewEntry; import org.concord.framework.otrunk.view.OTViewFactory; import org.concord.otrunk.OTControllerServiceImpl; import org.concord.swing.util.ComponentScreenshot; /** * OTViewContainerPanel * Class name and description * * Date created: Jan 20, 2005 * * @author scott<p> * */ public class OTViewContainerPanel extends JPanel { /** * First version of this class */ private static final long serialVersionUID = 1L; OTObject currentObject = null; OTJComponentView currentView = null; OTViewEntry currentViewEntry = null; OTViewChild currentViewChild = null; private OTJComponentService jComponentService; protected OTFrameManager frameManager; private boolean useScrollPane = true; private boolean autoRequestFocus = true; private boolean updateable = false; private OTViewContainer parentContainer; ArrayList<OTViewContainerListener> containerListeners = new ArrayList<OTViewContainerListener>(); MyViewContainer viewContainer; /** * This is used to ignore the scrollRectToVisible method * both in the viewport and in the ourselves. scrollRectToVisible * is called when the content of a child component is initialized. One place * where it is called is when the caret position is changed during loading. * If the scrolling is not disabled then this causes the view to scroll to * the bottom. If this view is embedded in another view then the scroll * "event" is propgated to the parent and it is scrolled so the bottom of this * embedded view is visible. * * This is a count, so that scrolling stays disabled until all of the * scroll causing operations have finished. This happens because setCurrentObject * is called multiple times not in the awt thread. The first one disables * scrolling and queues up an invokeLater to enable it again. Then the second * call to setCurrentObject happens before the queued up enableScrolling is * run. And then the enableScrolling is run before the gui operations happen * from the second call. So without a counter, the scrolling would be enabled while some of theThis is a bit dangerous * so it would be better have some kind of logging so we can track this better. */ private int unwantedScrollingCount = 0; private String viewMode = null; private boolean topLevelContainer = false; private OTControllerService controllerService; private boolean scrollPanelHasBorder = true; private boolean showTempLoadingLabel = true; private OTObject previousObject; /** * */ public OTViewContainerPanel(OTFrameManager frameManager) { super(new BorderLayout()); // using Box layout helps several things // but it affects lots of code, so we should try to turn it on when we have some // breathing room //setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); viewContainer = new MyViewContainer(); this.frameManager = frameManager; JLabel loadingLabel = new JLabel("Loading..."); add(loadingLabel); } public OTJComponentService getOTJComponentService() { return jComponentService; } /** * This method is for legacy the object really only needs * OTJComponentService. But to make the migration easier this method is still * available. And each time it is called more a new jComponentService is * created. * * @param factory */ public void setOTViewFactory(OTViewFactory factory) { // add our own controller service factory if we have been setup to do so. if(isTopLevelContainer()){ OTControllerServiceFactory controllerServiceFactory = new OTControllerServiceFactory(){ /** * @deprecated the object service should be passed in otherwise applications which * use multiple overlay databases will not function properly * * @see org.concord.framework.otrunk.view.OTControllerServiceFactory#createControllerService() */ public OTControllerService createControllerService() { OTObjectService objectService = ((OTControllerServiceImpl) controllerService).getObjectService(); return createControllerService(objectService); } public OTControllerService createControllerService(OTObjectService objService) { OTControllerService subControllerService = ((OTControllerServiceImpl) controllerService).createSubControllerService(objService); return subControllerService; } }; OTViewContext factoryContext = factory.getViewContext(); factoryContext.addViewService(OTControllerServiceFactory.class, controllerServiceFactory); } // get the OTJComponentService so we can create the OTJComponentView OTViewContext viewContext = factory.getViewContext(); OTJComponentServiceFactory componentServiceFactory = viewContext.getViewService(OTJComponentServiceFactory.class); jComponentService = componentServiceFactory.createOTJComponentService(factory, false); } public boolean isTopLevelContainer() { return topLevelContainer ; } /** * This is the preferred method for giving this object the ability to create new views * Using this method will allow this object to share the jComponentService with * other viewContainerPanels or view creators. This way those views can access * each other though the OTViewHost interface. * * @param jComponentService */ public void setOTJComponentService(OTJComponentService jComponentService) { this.jComponentService = jComponentService; } public void setMessage(String message) { removeAll(); add(new JLabel(message)); } public OTObject getCurrentObject() { return currentObject; } public void setCurrentObject(OTObject otObject) { setCurrentObject(otObject, null); } public void setCurrentObject(OTObject otObject, OTViewEntry viewEntry) { if(currentView != null) { try { // Call on the event thread if we're not already on it if (EventQueue.isDispatchThread()) { currentView.viewClosed(); } else { EventQueue.invokeAndWait(new Runnable() { public void run() { currentView.viewClosed(); } }); } } catch (Throwable t) { // attempting to close the view caused some form of exception // print the exception and keep going. This might cause later // instability. So this type of event should trigger a message // back to developers, so we can track down the problem. System.err.println("Exception while closing view: " + currentView); t.printStackTrace(); } currentView = null; } if(controllerService != null){ controllerService.dispose(); } if (currentObject != null){ previousObject = currentObject; } // FIXME There might already be an event queued to setup this current object // We should compare the last queued event with this request and // have the option to not queue this event. currentObject = otObject; currentViewEntry = viewEntry; currentViewChild = null; if (otObject instanceof OTViewChild){ OTViewChild viewChild = (OTViewChild) otObject; currentObject = viewChild.getObject(); if (viewChild.getViewid() != null){ currentViewEntry = viewChild.getViewid(); } // We save the currentViewChild so its settings can be used for the scroll pane // The scroll settings from the viewChild are not simply set here because // some views reuse their container panels, and set the scroll properties // when the panel is created. Those settings should be preserved // even if a viewChild temporarily overrides them. currentViewChild = viewChild; } removeAll(); // Even if if the object is null we want to show something on the screen // so we should not give up if the object is null. // if(otObject == null){ // return; // } if(isTopLevelContainer() && otObject != null){ controllerService = otObject.getOTObjectService().createControllerService(); } disableScrolling(); // Unfortunately the size of this label matters, when these objects // are embedded in tables inside of the htmleditorkit. I think the // editorkit gets messed up when the width of a component changes. // It seems to be a problem only when it shrinks, not when it grows. // so instead of this: // JLabel loading = new JLabel("Loading..."); // we'll use this which is really short. if (showTempLoadingLabel){ JLabel loading = new JLabel("..."); loading.setBorder(BorderFactory.createLineBorder(Color.black)); add(loading); } revalidate(); // By doing a double invokeLater we make sure this code doesn't get // run until much later in the process. Unless something else is doing // an double invoke latter, this will execute after all of the currently // queued up code which calls invokeLater. // For some reason this allows popups to incrementally draw them selves // without it, the popup blocks until the inside content is rendered // before showing anything Runnable createComponentTask = new CreateComponentTask(currentObject); /** * This is disabled for now but we might have to come back to it it * fixed a loading problems with sidebars that were popup windows. * * Only do a double invoke later if the item is a sidebar item * * String otObjectClassName = otObject.getClass().getInterfaces()[0].getName(); System.out.println(otObjectClassName); if(otObject.getClass().getInterfaces()[0].getName().endsWith("OTUDLSideBarItem")){ System.out.println("delaying sidebar item update"); SwingUtilities.invokeLater(new Runnable(){ public void run() { SwingUtilities.invokeLater(createComponentTask); } }); } else { SwingUtilities.invokeLater(createComponentTask); } */ SwingUtilities.invokeLater(createComponentTask); } public void setScrollPanelHasBorder(boolean scrollPanelHasBorder){ this.scrollPanelHasBorder = scrollPanelHasBorder; } /** * This method trys to make a view of the current object and viewEntry if * there is one. * * @return */ protected JComponent createJComponent() { if(currentObject == null) { return new JLabel("Null object"); } // get the OTJComponentService so we can create the OTJComponentView OTJComponentService jComponentService = getOTJComponentService(); currentView = jComponentService.getObjectView(currentObject, viewContainer, getViewMode(), currentViewEntry); if(currentView == null) { return new JLabel("No view for object: " + currentObject); } if (currentView instanceof AbstractOTJComponentContainerView){ ((AbstractOTJComponentContainerView)currentView).setMode(getViewMode()); } return jComponentService.getComponent(currentObject, currentView); } /* (non-Javadoc) * @see javax.swing.JComponent#scrollRectToVisible(java.awt.Rectangle) */ public void scrollRectToVisible(Rectangle aRect) { // disabling this removes the flicker that occurs during the loading of the page. // if we could if(!isScrollingAllowed()){ return; } super.scrollRectToVisible(aRect); } protected boolean isScrollingAllowed() { return unwantedScrollingCount <=0; } protected void disableScrolling() { // System.out.println("disable Scrolling: " + unwantedScrollingCount + " " // + currentObject.getGlobalId()); unwantedScrollingCount++; } protected void enableScrolling() { unwantedScrollingCount--; //System.out.println("enabling Scrolling: " + // unwantedScrollingCount + " " + // currentObject.getGlobalId().toString()); if(unwantedScrollingCount < 0){ System.err.println("unwantedScrollingCount dropped below 0"); } } public Component getCurrentComponent() { Component currentComp = getComponent(0); if(currentComp instanceof JScrollPane) { currentComp = ((JScrollPane)currentComp).getViewport().getView(); } return currentComp; } public void addViewContainerListener(OTViewContainerListener listener) { containerListeners.add(listener); } public void removeViewContainerListener(OTViewContainerListener listener) { containerListeners.remove(listener); } public void notifyListeners() { OTViewContainerChangeEvent evt; if (currentObject == null){ evt = new OTViewContainerChangeEvent(viewContainer, OTViewContainerChangeEvent.DELETE_CURRENT_OBJECT_EVT); } else if (previousObject == null){ evt = new OTViewContainerChangeEvent(viewContainer, OTViewContainerChangeEvent.NEW_CURRENT_OBJECT_EVT); } else if (previousObject == currentObject){ evt = new OTViewContainerChangeEvent(viewContainer, OTViewContainerChangeEvent.CHANGE_CURRENT_OBJECT); } else { evt = new OTViewContainerChangeEvent(viewContainer, OTViewContainerChangeEvent.REPLACE_CURRENT_OBJECT_EVT, previousObject); } for(int i=0; i<containerListeners.size(); i++) { (containerListeners.get(i)). currentObjectChanged(evt); } } private final class CreateComponentTask implements Runnable { private OTObject componentObject; public CreateComponentTask(OTObject otObject) { this.componentObject = otObject; } public void run() { if(currentObject != componentObject) { // the object has been updated since this task started. // so we don't need to do anything here, because there will // be another task happening later. return; } JComponent myComponent = createJComponent() ; boolean localUseScrollPane = isUseScrollPane(); if(currentViewChild != null){ localUseScrollPane = currentViewChild.getUseScrollPane(); } if(localUseScrollPane) { JScrollPane scrollPane = new JScrollPane(); scrollPane.setOpaque(false); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); scrollPane.setViewport(new JViewport(){ /** * Not intended to be serialized, just added remove compile warning */ private static final long serialVersionUID = 1L; public void scrollRectToVisible(Rectangle contentRect) { // disabling this removes the flicker that occurs during the loading of the page. // if we could if(!isScrollingAllowed()){ return; } super.scrollRectToVisible(contentRect); } }); scrollPane.setViewportView(myComponent); int horizontalScroll = JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED; if(currentViewChild != null && !currentViewChild.getUseHorizontalScrollPane()){ horizontalScroll = JScrollPane.HORIZONTAL_SCROLLBAR_NEVER; } scrollPane.setHorizontalScrollBarPolicy(horizontalScroll); scrollPane.getVerticalScrollBar().setBlockIncrement(100); scrollPane.getVerticalScrollBar().setBlockIncrement(20); scrollPane.getHorizontalScrollBar().setBlockIncrement(100); scrollPane.getHorizontalScrollBar().setBlockIncrement(20); boolean localScrollPanelHasBorder = scrollPanelHasBorder; if(currentViewChild != null){ localScrollPanelHasBorder = currentViewChild.getScrollPanelHasBorder(); } if (!localScrollPanelHasBorder){ scrollPane.setBorder(BorderFactory.createEmptyBorder()); } myComponent = scrollPane; } removeAll(); add(myComponent, BorderLayout.CENTER); revalidate(); notifyListeners(); if(isAutoRequestFocus()){ myComponent.requestFocus(); } // We have to queue this up, because during the setup of this // component other things might be queued, that cause scrolling // to happen. // this way the scrolling should remain disabled until all // of them are complete. SwingUtilities.invokeLater(new Runnable(){ /* (non-Javadoc) * @see java.lang.Runnable#run() */ public void run() { enableScrolling(); } }); } } /** * Internal class so views which get passed a view container do not * have direct access to the viewcontainer panel. This also makes it * easier to see who is using view containers and who is using * the viewcontainerpanel directly * * @author scott * */ class MyViewContainer implements OTViewContainer { public OTObject getCurrentObject() { return OTViewContainerPanel.this.getCurrentObject(); } public void setCurrentObject(OTObject pfObject) { OTViewContainerPanel.this.setCurrentObject(pfObject); } public void setCurrentObject(OTObject otObject, OTViewEntry viewEntry){ OTViewContainerPanel.this.setCurrentObject(otObject, viewEntry); } public boolean isUpdateable() { return OTViewContainerPanel.this.isUpdateable(); } public void setUpdateable(boolean b) { OTViewContainerPanel.this.setUpdateable(b); } public void setParentContainer(OTViewContainer c) { OTViewContainerPanel.this.setParentContainer(c); } public OTViewContainer getParentContainer() { return OTViewContainerPanel.this.getParentContainer(); } public OTViewContainer getUpdateableContainer() { return OTViewContainerPanel.this.getUpdateableContainer(); } public void reloadView(){ OTViewContainerPanel.this.reloadView(); } public OTViewEntry getCurrentViewEntry() { return currentViewEntry; } } /** * Return the viewContainer of this bodyPanel, if you plan on changing * the currentObject of this container you should call getUpdateableContainer. * @return */ public OTViewContainer getViewContainer() { return viewContainer; } public void reloadView() { setCurrentObject(currentObject, currentViewEntry); } public OTViewContainer getUpdateableContainer() { if (this.isUpdateable()) { return this.viewContainer; } else { if(parentContainer == null){ System.err.println("No updatable parent container was found returning the root container"); return this.viewContainer; } return parentContainer.getUpdateableContainer(); } } public OTJComponentView getView() { return currentView; } public boolean isUseScrollPane() { return useScrollPane; } public void setUseScrollPane(boolean useScrollPane) { this.useScrollPane = useScrollPane; } public boolean isAutoRequestFocus() { return autoRequestFocus; } public void setAutoRequestFocus(boolean autoRequestFocus) { this.autoRequestFocus = autoRequestFocus; } public boolean isUpdateable() { return updateable; } public void setUpdateable(boolean b) { this.updateable = b; } /** * @param viewMode */ public void setViewMode(String viewMode) { this.viewMode = viewMode; if (viewMode == null){ this.viewMode = jComponentService.getViewFactory().getDefaultViewMode(); } } public String getViewMode() { return viewMode; } public void saveScreenshotAsByteArrayOutputStream(ByteArrayOutputStream out, String type) throws Throwable { ComponentScreenshot.saveScreenshotAsOutputStream(this, out, type); } /** * * @param type "png" or "gif" are best bets * @return ByteArrayOutputStream representing image * @throws Throwable */ public ByteArrayOutputStream getScreenshotAsByteArrayOutputStream(String type) throws Throwable { return ComponentScreenshot.saveScreenshotAsByteArrayOutputStream(this, type); } public BufferedImage getScreenShot() throws Exception { BufferedImage image = ComponentScreenshot.getScreenshot(this); return image; } public void setParentContainer(OTViewContainer parentContainer) { this.parentContainer = parentContainer; } public OTViewContainer getParentContainer() { return parentContainer; } public void setTopLevelContainer(boolean topLevelContainer) { this.topLevelContainer = topLevelContainer; } public boolean isShowTemporaryLoadingLabel() { return showTempLoadingLabel; } public void setShowTemporaryLoadingLabel(boolean showTempLoadingLabel) { this.showTempLoadingLabel = showTempLoadingLabel; } }