/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * TargetResultDisplayer.java * Creation date: Dec 17, 2003 * By: Edward Lam */ package org.openquark.gems.client; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JLayeredPane; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; import org.openquark.gems.client.valueentry.ValueEditor; /** * A class to handle displaying result values. * @author Edward Lam */ public class ResultValueDisplayer extends InternalFrameAdapter implements ComponentListener { /** The frame in which the result will be displayed. */ private final JFrame frame; /** List of JInternalFrames open and not moved or resized */ private final List<JInternalFrame> unmodifiedFrameList; /** Set of all open JInternalFrames */ private final Set<JInternalFrame> openFrameSet; /** The index of the first free spot where we can display the next free frame */ private int firstFreeIndex; /** The target bounds the last time we showed a result */ private Rectangle oldTargetBounds; /** The index of the next internal frame to be displayed */ private int resultIndex = 1; /** * Constructor for an ResultValueDisplayer. * @param frame the parent frame in which the result value will be displayed */ public ResultValueDisplayer(JFrame frame) { this.frame = frame; unmodifiedFrameList = new ArrayList<JInternalFrame>(); openFrameSet = new HashSet<JInternalFrame>(); firstFreeIndex = 0; } /** * Close all open result frames. */ public void closeAllResultFrames() { // Note: we can't close the frame while iterating over openFrameSet, // as closing the frame modifies openFrameSet! Set<JInternalFrame> openFrames = new HashSet<JInternalFrame>(openFrameSet); for (final JInternalFrame internalFrame : openFrames) { try { internalFrame.setClosed(true); } catch (java.beans.PropertyVetoException pve) { // this should never happen.. throw new Error("Programming error: can't close result windows"); } } } /** * Create a scroll pane to be used for displaying error messages. */ private JScrollPane getJScrollPane1() { JScrollPane ivjJScrollPane1 = null; try { ivjJScrollPane1 = new JScrollPane(); ivjJScrollPane1.setName("JScrollPane1"); //getJScrollPane1().setViewportView(getTextArea()); } catch (Throwable ivjExc) { handleException(ivjExc); } return ivjJScrollPane1; } /** * Create a text area to be used for displaying error messages. */ private JTextArea getTextArea() { JTextArea ivjTextArea = null; try { ivjTextArea = new JTextArea(); ivjTextArea.setName("TextArea"); ivjTextArea.setLineWrap(true); ivjTextArea.setWrapStyleWord(true); ivjTextArea.setBounds(0, 0, 100, 20); } catch (Throwable ivjExc) { handleException(ivjExc); } return ivjTextArea; } /** * Called whenever the part throws an exception. * @param exception Throwable */ private static void handleException(Throwable exception) { /* Uncomment the following lines to print uncaught exceptions to stdout */ System.out.println("--------- UNCAUGHT EXCEPTION ---------"); exception.printStackTrace(System.out); } /** * Generate and display a Frame containing the result. * Prepares the internal frame for display, and calls the other method to actually display it. * @param resultEditor the editor with the result to display. null if we only want to display an error message * @param messageTitle the title of the message to display * @param errorMessage null if there is no error message. * @return JInternalFrame the JInternalFrame that was displayed */ public JInternalFrame showResultFrame(ValueEditor resultEditor, String messageTitle, String errorMessage) { // Create the internal frame to hold resultVEP. JInternalFrame internalFrame = new JInternalFrame(messageTitle, true, true, false, false); internalFrame.getContentPane().setLayout(new BorderLayout ()); JScrollPane sp = null; JTextArea ta = null; if (errorMessage != null) { StringBuilder errorBuffer = new StringBuilder ("Error: "); errorBuffer.append (errorMessage); sp = getJScrollPane1 (); if (sp != null) { ta = getTextArea (); if (ta != null) { ta.setRows (4); ta.setColumns (40); sp.setViewportView (ta); ta.setText (errorBuffer.toString ()); } internalFrame.getContentPane().add(sp, BorderLayout.CENTER); } } // Make this frame wide enough to at least display its title // in the title bar. if (resultEditor != null) { internalFrame.getContentPane().add(resultEditor, BorderLayout.SOUTH); } resultIndex++; internalFrame.pack(); // Make sure the frame is wide enough to display its title. // If you add additional titlebar icons to the frame make sure to change this. FontMetrics metrics = frame.getFontMetrics(UIManager.getDefaults().getFont("InternalFrame.titleFont")); int minWidth = metrics.stringWidth(messageTitle); minWidth += frame.getIconImage().getWidth(null); minWidth += UIManager.getDefaults().getIcon("InternalFrame.closeIcon").getIconWidth(); minWidth += internalFrame.getInsets().left + internalFrame.getInsets().right; minWidth += 20; // a totally exact size looks weird Dimension size = internalFrame.getPreferredSize(); if (size.width < minWidth) { size.width = minWidth; internalFrame.setPreferredSize(size); internalFrame.setSize(size); } // Fix the height of the frame but make the width resizable up to the // maximum width of the editor this frame contains. Dimension prefSize = internalFrame.getPreferredSize(); internalFrame.setMinimumSize(prefSize); if (resultEditor != null) { int maxWidth = resultEditor.getMaximumSize().width + internalFrame.getInsets().left + internalFrame.getInsets().right; internalFrame.setMaximumSize(new Dimension(maxWidth, prefSize.height)); } else { internalFrame.setMaximumSize(new Dimension(65536, prefSize.height)); } // Give the result internal frame the gemcutter icon. internalFrame.setFrameIcon(new ImageIcon(ResultValueDisplayer.class.getResource("/Resources/gemcutter_16.gif"))); // defer frame display to another method showResultFrame(internalFrame); return internalFrame; } /** * Display the Frame containing the result. * We currently display these in the layered pane. * @param internalFrame JInternalFrame the JInternalFrame to display */ private void showResultFrame(JInternalFrame internalFrame) { // get the internal frame dimensions Dimension internalFrameSize = internalFrame.getSize(); // get the target info Rectangle targetBounds = getTargetBounds(); int targetRightEdge = targetBounds.x + targetBounds.width; int targetBottomEdge = targetBounds.y + targetBounds.height; // check against the old target bounds, update the oldTargetBounds if (oldTargetBounds != null && !oldTargetBounds.equals(targetBounds)) { // the target moved targetMoved(); } oldTargetBounds = targetBounds; // the frame will be shown within the parent frame, thus we need to know its bounds int parentWidth = frame.getLayeredPane().getWidth(); int parentHeight = frame.getLayeredPane().getHeight(); // the right edge of the target should line up with the right edge of the internal frame, unless // that would put it or the left edge outside the gemcutter window. int newX = Math.min( Math.max(targetRightEdge - internalFrameSize.width, 0), parentWidth - internalFrameSize.width ); // the top of the first panel is aligned with the bottom of the target, // others with the bottom of the previous panel. int newY = Math.min( Math.max(targetBottomEdge, 0), parentHeight - internalFrameSize.height ); if (firstFreeIndex != 0) { // calculate to the bottom of the previous frame JInternalFrame previousFrame = unmodifiedFrameList.get(firstFreeIndex - 1); Rectangle previousBounds = previousFrame.getBounds(); newY = previousBounds.y + previousBounds.height; // we have to check to see if it would go off the window int newBottom = newY + internalFrame.getBounds().height; //System.out.println ("internalFrame height = " + internalFrame.getBounds().height); Point windowPoint = SwingUtilities.convertPoint(frame.getLayeredPane(), newX, newBottom, frame); if (windowPoint.y > frame.getHeight()) { // goes off the window // place in the first position under the target (will overlap an already showing window) newY = targetBottomEdge; // pretend that we set the frame at the first index firstFreeIndex = 0; } } // place the frame and display it internalFrame.setLocation(newX, newY); frame.getLayeredPane().add(internalFrame, JLayeredPane.PALETTE_LAYER, 0); internalFrame.setVisible(true); //System.out.println ("final iframe size = " + internalFrame.getSize ()); // add it to the set of open frames openFrameSet.add(internalFrame); // add it to the (unmodified frame) list int frameListSize = unmodifiedFrameList.size(); if (firstFreeIndex < frameListSize) { unmodifiedFrameList.set(firstFreeIndex, internalFrame); } else { unmodifiedFrameList.add(internalFrame); frameListSize++; } // update the firstFreeIndex to point to the next free spot, or if overlapping just the next spot firstFreeIndex++; // listen for when they are moved, resized, or closed internalFrame.addInternalFrameListener(this); internalFrame.addComponentListener(this); } /** * Invoked when the component has been made invisible. * @param e ComponentEvent the related event */ public void componentHidden(ComponentEvent e) { // do nothing } /** * Invoked when the component's position changes. * @param e ComponentEvent the related event */ public void componentMoved(ComponentEvent e) { // System.out.println ("e.getsource.getsize = " + ((JInternalFrame)e.getSource()).getSize()); // end management of this frame removeFromManagedFrameList((JInternalFrame)e.getSource()); } /** * Invoked when the component's size changes. * @param e ComponentEvent the related event */ public void componentResized(ComponentEvent e) { // end management of this frame removeFromManagedFrameList((JInternalFrame)e.getSource()); } /** * Invoked when the component has been made visible. * @param e ComponentEvent the related event */ public void componentShown(ComponentEvent e) { // do nothing } /** * Invoked when an internal frame has been closed. * @param e InternalFrameEvent the related event */ @Override public void internalFrameClosed(InternalFrameEvent e) { JInternalFrame frameClosed = (JInternalFrame)e.getSource(); // end management of this frame removeFromManagedFrameList(frameClosed); // remove from the set of open frames openFrameSet.remove(frameClosed); } /** * Remove the internalFrame from management by this manager * @param frameToRemove JInternalFrame the JInternalFrame to remove. */ private void removeFromManagedFrameList(JInternalFrame frameToRemove) { // iterate through the frameList, finding the one to remove int indexRemoved = -1; int frameListSize = unmodifiedFrameList.size(); for (int i = 0; i < frameListSize; i++) { JInternalFrame listFrame = unmodifiedFrameList.get(i); if (listFrame == frameToRemove) { // Found it. Set to null and exit the loop unmodifiedFrameList.set(i, null); indexRemoved = i; break; } } // note that the frame may already have been removed from the list before we enter this method // (for instance if first resize, then close) if (indexRemoved > -1) { // We removed something. A spot opens up there where we put the next frame. // update firstFreeIndex if we removed at a lower index firstFreeIndex = Math.min(firstFreeIndex, indexRemoved); } // we should trim down the list for null's at the end while (frameListSize > 0 && unmodifiedFrameList.get(frameListSize - 1) == null){ unmodifiedFrameList.remove(frameListSize - 1); frameListSize--; } } /** * Notify this manager that the associated target has moved */ private void targetMoved() { // for now we just reset the state of the manager unmodifiedFrameList.clear(); firstFreeIndex = 0; } /** * Returns the set of open frames. * @return the set of open frames */ protected Set<JInternalFrame> getOpenFrameSet() { return Collections.unmodifiableSet(openFrameSet); } /** * Returns the rectangle of the target near which the result value will be displayed. * The position of this rectangle will be relative to the frame. * @return the rectangle of the target near which the result value will be displayed */ protected Rectangle getTargetBounds() { // By default, just return a rectangle of zero size at (0, 0). return new Rectangle(); } }