/******************************************************************************* * CogTool Copyright Notice and Distribution Terms * CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * CogTool 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. * * CogTool 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 CogTool; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * CogTool makes use of several third-party components, with the * following notices: * * Eclipse SWT version 3.448 * Eclipse GEF Draw2D version 3.2.1 * * Unless otherwise indicated, all Content made available by the Eclipse * Foundation is provided to you under the terms and conditions of the Eclipse * Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this * Content and is also available at http://www.eclipse.org/legal/epl-v10.html. * * CLISP version 2.38 * * Copyright (c) Sam Steingold, Bruno Haible 2001-2006 * This software is distributed under the terms of the FSF Gnu Public License. * See COPYRIGHT file in clisp installation folder for more information. * * ACT-R 6.0 * * Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere & * John R Anderson. * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * Apache Jakarta Commons-Lang 2.1 * * This product contains software developed by the Apache Software Foundation * (http://www.apache.org/) * * jopt-simple version 1.0 * * Copyright (c) 2004-2013 Paul R. Holser, Jr. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Mozilla XULRunner 1.9.0.5 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/. * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The J2SE(TM) Java Runtime Environment version 5.0 * * Copyright 2009 Sun Microsystems, Inc., 4150 * Network Circle, Santa Clara, California 95054, U.S.A. All * rights reserved. U.S. * See the LICENSE file in the jre folder for more information. ******************************************************************************/ package edu.cmu.cs.hcii.cogtool.uimodel; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.ImageFigure; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.TreeSearch; import org.eclipse.draw2d.XYLayout; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Image; import edu.cmu.cs.hcii.cogtool.CogToolWorkThread; import edu.cmu.cs.hcii.cogtool.model.CheckBox; import edu.cmu.cs.hcii.cogtool.model.Design; import edu.cmu.cs.hcii.cogtool.model.DoubleRectangle; import edu.cmu.cs.hcii.cogtool.model.DoubleSize; import edu.cmu.cs.hcii.cogtool.model.Frame; import edu.cmu.cs.hcii.cogtool.model.GridButton; import edu.cmu.cs.hcii.cogtool.model.GridButtonGroup; import edu.cmu.cs.hcii.cogtool.model.ContextMenu; import edu.cmu.cs.hcii.cogtool.model.ListItem; import edu.cmu.cs.hcii.cogtool.model.MenuHeader; import edu.cmu.cs.hcii.cogtool.model.MenuItem; import edu.cmu.cs.hcii.cogtool.model.AMenuWidget; import edu.cmu.cs.hcii.cogtool.model.PullDownHeader; import edu.cmu.cs.hcii.cogtool.model.PullDownItem; import edu.cmu.cs.hcii.cogtool.model.IWidget; import edu.cmu.cs.hcii.cogtool.model.SimpleWidgetGroup; import edu.cmu.cs.hcii.cogtool.model.RadioButton; import edu.cmu.cs.hcii.cogtool.model.Widget; import edu.cmu.cs.hcii.cogtool.model.WidgetAttributes; import edu.cmu.cs.hcii.cogtool.util.Alerter; import edu.cmu.cs.hcii.cogtool.util.GraphicsUtil; import edu.cmu.cs.hcii.cogtool.util.AlertHandler; import edu.cmu.cs.hcii.cogtool.util.IAttributed; import edu.cmu.cs.hcii.cogtool.util.NullSafe; import edu.cmu.cs.hcii.cogtool.util.PrecisionUtilities; import edu.cmu.cs.hcii.cogtool.util.ThreadManager; import edu.cmu.cs.hcii.cogtool.util.WindowUtil; import edu.cmu.cs.hcii.cogtool.view.CogToolScalableFigure; import edu.cmu.cs.hcii.cogtool.view.ScalableFrameFigure; import edu.cmu.cs.hcii.cogtool.view.View; /** * the UIModel for controlling all visual aspects of a Frame. * * This class is used by both FrameEditor and DesignEditorFrame. * * @author alexeiser * */ public class FrameUIModel extends Alerter implements WidgetFigureSupport { /** * Handler which updates graphical widgets * in response to changes in their models. */ protected class WidgetChangeHandler implements AlertHandler { /** * The graphical widget which changed */ protected GraphicalWidget<?> gw; /** * Simple constructor. * @param widgetFigure */ public WidgetChangeHandler(GraphicalWidget<?> widgetFigure) { gw = widgetFigure; } /** * Handle the event, * Update the widget based on the event specified. * Image change, shape change etc. */ public void handleAlert(EventObject alert) { Widget.WidgetChange evt = (Widget.WidgetChange) alert; switch (evt.getChangeType()) { case Widget.WidgetChange.GROUP: raiseAlert(new FrameUIModel.WidgetGroupChange(FrameUIModel.this, (IWidget) evt.getSource(), evt.isAdd)); return; case Widget.WidgetChange.IMAGE: gw.updateImage(); raiseAlert(new FrameUIModel.WidgetShapeImageChange(FrameUIModel.this, (IWidget) evt.getSource())); break; // Update the shape. case Widget.WidgetChange.SHAPE: gw.updateShape(); raiseAlert(new FrameUIModel.WidgetShapeImageChange(FrameUIModel.this, (IWidget) evt.getSource())); break; // Update the visual text of the widget case Widget.WidgetChange.TITLE: case Widget.WidgetChange.AUXILIARY: gw.updateTitle(); raiseAlert(new FrameUIModel.WidgetTitleChange(FrameUIModel.this, (IWidget) evt.getSource())); break; // Set the type of the widget. case Widget.WidgetChange.TYPE: gw.updateType(); break; } // Force changes to be reflected immediately drawWidgets(); } } /** * Handler which updates graphical widgets * in response to changes in their models. */ protected class AttributeChangeHandler implements AlertHandler { /** * Handle the event, * Update the widget based on the event specified. * Image change, shape change etc. */ public void handleAlert(EventObject alert) { // Force changes to be reflected immediately drawWidgets(); } } /** * Class which filters figures by the type. * Pass in an int specified by * ONLY_GRAPHICAL_WIDGETS * ALL_FIGURES * * This restricts the search to either only IGraphicalWidgets or * all figures. IE: anything else which may be drawn. * * @author alexeiser * */ protected static class FigureFilterSearch implements TreeSearch { // NOT THREAD-SAFE!! /** * The filter to use for this search */ protected int filter; public FigureFilterSearch(int figFilter) { filter = figFilter; } public void setFilter(int newFigureFilter) { filter = newFigureFilter; } /** * used to test if the figure passes the filter. */ public boolean accept(IFigure figure) { switch (filter) { case FrameUIModel.ONLY_GRAPHICAL_WIDGETS: { return (figure instanceof GraphicalWidget<?>); } case FrameUIModel.ALL_FIGURES: default: { return true; } } } /** * Prune this figure from the search space. */ public boolean prune(IFigure figure) { return false; } } public static class SourcesFilterSearch implements TreeSearch { // NOT THREAD-SAFE!! protected List<GraphicalSource<?>> sources = new ArrayList<GraphicalSource<?>>(); public void resetSearch() { sources.clear(); } public List<GraphicalSource<?>> getSources() { return sources; } public boolean accept(IFigure figure) { if (figure instanceof GraphicalSource<?>) { sources.add((GraphicalSource<?>) figure); } return false; } public boolean prune(IFigure figure) { return false; } } /** * Indicates to interested listeners that some widget has had its * shape altered. * @author weianw */ public static class WidgetShapeImageChange extends EventObject { public IWidget widget; public WidgetShapeImageChange(FrameUIModel frameUIModel, IWidget w) { super(frameUIModel); widget = w; } } /** * Indicates to interested listeners that some widget has had its * title altered. */ public static class WidgetTitleChange extends EventObject { public IWidget widget; public WidgetTitleChange(FrameUIModel frameUIModel, IWidget w) { super(frameUIModel); widget = w; } } /** * Indicates to interested listeners that some widget has had its * group altered. * @author weianw */ public static class WidgetGroupChange extends EventObject { public IWidget widget; public boolean isAdd; public WidgetGroupChange(FrameUIModel frameUIModel, IWidget w, boolean add) { super(frameUIModel); widget = w; isAdd = add; } } /** * Event object to indicate that a widget has been removed. * Holds onto the widget figure removed and the frameUIModel. */ public static class WidgetRecovery extends EventObject { public GraphicalWidget<?> widgetFigure = null; public WidgetRecovery(FrameUIModel ui) { super(ui); } public GraphicalWidget<?> getWidgetFigure() { return widgetFigure; } public void setWidgetFigure(GraphicalWidget<?> widgetFig) { widgetFigure = widgetFig; } } public static class WidgetFigureIterator implements Iterator<GraphicalWidget<?>> { protected Iterator<GraphicalWidget<?>> widgetFigures; public WidgetFigureIterator(FrameUIModel frameUI) { widgetFigures = frameUI.getFigureListIterator(); } public boolean hasNext() { if (widgetFigures != null) { if (widgetFigures.hasNext()) { return true; } widgetFigures = null; } return false; } public GraphicalWidget<?> next() { if (hasNext()) { return widgetFigures.next(); } throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException(); } } /** * An ONLY for the figure filtering search. * By default it returns all figures. */ protected static final FigureFilterSearch figureFilter = new FigureFilterSearch(FrameUIModel.ALL_FIGURES); /** * An ONLY for finding all transition sources at a given x, y. */ protected static final SourcesFilterSearch sourcesFilter = new SourcesFilterSearch(); /** * Contains the list of Figures corresponding to the Widget they represent. * The Graphical figures contained with it all are IGraphicalWidgets * These widgets also point to their Widget Models. * Maps IWidget to GraphicalWidget */ protected Map<IWidget, GraphicalWidget<?>> figureList = new HashMap<IWidget, GraphicalWidget<?>>(); /** * Should tool tips be shown in this view of the FrameUIModel. */ protected boolean showToolTips; /** * What cursor should be used on rollover of a widget */ protected int widgetRolloverCursor; /** * The base Frame Model. */ protected Frame frame; /** * A link to the background image used for this frame. */ protected ImageFigure backgroundImage; /** * Contains all elements which are displayed. * This includes the interaction layer which must always be on top * As well as the figures stored in the figurelist. */ protected ScalableFrameFigure contents; /** * A specialized eventObject used to tell people that a widget was removed. * used currently by the structure view. */ protected FrameUIModel.WidgetRecovery widgetRecoveryEvent = new FrameUIModel.WidgetRecovery(this); // public static long LoadingTime = 0; /** * Flag dictating if this uiModel can do lazy loading. * Lazy loading dictates if threading and other techniques can be used * to improve UI performance. */ protected boolean lazyLoading = false; /** * Flag to indicate that the FrameUIModel was disposed; this allows * any threads lazy loading images to recover system resources * if the FrameUIModel was disposed before the thread completed. */ protected boolean isDisposed = false; /** * Alpha to use when contained graphical widgets are not selected. */ protected int widgetNormalAlpha; /** * Alpha to use when contained graphical widgets are selected. */ protected int widgetSelectedAlpha; protected DefaultSEUIModel attrOverride = null; protected AlertHandler widgetAttrChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { IWidget widget = (IWidget) alert.getSource(); Object isSep = widget.getAttribute(WidgetAttributes.IS_SEPARATOR_ATTR); GraphicalWidget<?> gw = getWidgetFigure(widget); String title; if (NullSafe.equals(WidgetAttributes.IS_SEPARATOR, isSep)) { title = GraphicalWidgetRenderer.SEPARATOR_STRING; } else { title = widget.getTitle(); } gw.getRenderer().setText(title); gw.getRenderer().updateData(); //TODO better solution? } }; /** * Return only hits on Graphical Widgets. IE: IGraphicalWidget */ public static final int ONLY_GRAPHICAL_WIDGETS = 1; /** * Return contact with all figures in a figure search */ public static final int ALL_FIGURES = 0; /** * Constructor for Script Editor use. */ public FrameUIModel(Frame model, DefaultSEUIModel override) { this(model, true, WindowUtil.LINK_CURSOR, 1.0, true, override); } /** * Constructor that allows the parent to be a Figure. * This allows the frameUIModel to be associated with * the design editor. * * @param model the frame being displayed * @param supportToolTips whether to show tool tips when mouse hovers * over a widget * @param srcRolloverCursor the cursor to use when rolling over a widget * @param scale Sets the initial scale to use * @param selectedAlpha alpha to use when the widget is selected */ public FrameUIModel(Frame model, boolean supportToolTips, int srcRolloverCursor, double scale, boolean lazyLoad, DefaultSEUIModel override) { this(model, supportToolTips, srcRolloverCursor, scale, lazyLoad, GraphicsUtil.WIDGET_NORMAL_ALPHA, GraphicsUtil.WIDGET_SELECTED_ALPHA, override); } /** * Constructor that allows the parent to be a Figure. * This allows the frameUIModel to be associated with * the design editor. * * @param model the frame being displayed * @param supportToolTips whether to show tool tips when mouse hovers * over a widget * @param srcRolloverCursor the cursor to use when rolling over a widget * @param scale Sets the initial scale to use * @param lazyLoad Sets if the ui model can use lazy loading and threading * @param normalAlpha alpha to use when the widget is not selected * @param selectedAlpha alpha to use when the widget is selected */ public FrameUIModel(Frame model, boolean supportToolTips, int srcRolloverCursor, double scale, boolean lazyLoad, int normalAlpha, int selectedAlpha, DefaultSEUIModel override) { super(); lazyLoading = lazyLoad; widgetNormalAlpha = normalAlpha; widgetSelectedAlpha = selectedAlpha; // long start = System.currentTimeMillis(); // System.out.print("\nFrameUIModel<init>:" + start + " { \n"); // if the passed in model is null, throw an invalid parameter. if (model == null) { throw new IllegalArgumentException ("Cannot create a FrameUIModel with a null Frame model"); } // Store model for reference frame = model; showToolTips = supportToolTips; widgetRolloverCursor = srcRolloverCursor; attrOverride = override; // Show the widgets & react to changes setUpFrameContents(scale); addDesignChangeListeners(); addFrameChangeListeners(); // long end = System.currentTimeMillis(); // System.out.println((end-start) + " } " + end); } /** * Return the Frame associated with this uimodel. */ public Frame getFrame() { return frame; } /** * Set up the contents of the frame. * This creates the scalable figure and sets the initial scaling. * * The layout manager is also created with an XYLayout to allow absolute * positioning of elements. * * All structures needed to support drawing and selecting objects are * created here. * * @param double Scale: used to set the initial size of the contents. */ protected void setUpFrameContents(double scale) { // Set up the content pane // Create the underlying contents pane. // And set the contents pane's default widget color. contents = new ScalableFrameFigure(frame.getWidgetColor()); contents.setScale(scale); contents.setLayoutManager(new XYLayout()); // Create a new graphical widget for each widget in Frame. Iterator<IWidget> modelWidgets = frame.getWidgets().iterator(); while (modelWidgets.hasNext()) { IWidget w = modelWidgets.next(); createGraphicalWidget(w); } // Initialize the background image backgroundImage = new ImageFigure(); final byte[] bgImg = frame.getBackgroundImage(); if (bgImg != null) { if (lazyLoading) { // Use a thread to load the image, then set it later ThreadManager.IWorkThread imageLoadThread = new CogToolWorkThread() { protected Image img = null; public void doWork() { // Performed by the scheduled thread img = new Image(null, new ByteArrayInputStream(bgImg)); } @Override public void doneCallback() { // Performed by the main UI thread if (img != null) { if (isDisposed) { img.dispose(); } else { backgroundImage.setImage(img); } } // If an exception was thrown here, log it to stderr // TODO: We might want to add a real logging package if (exBucket.containsExceptions()) { // TODO: It is unclear what to do here. Maybe // we should just replace failed images with red // Xs rather than popping up a dialog box System.err.println(exBucket); // RcvrExceptionHandler.recoverWorkThread(this, // interaction); } } }; ThreadManager.startNewThread(imageLoadThread, 2); } else { try { Image img = null; // = AUIModel.imageCache.get(frame); if (img == null) { img = new Image(null, new ByteArrayInputStream(bgImg)); // AUIModel.imageCache.put(frame, img); } backgroundImage.setImage(img); } catch (SWTException ex) { throw new GraphicsUtil.ImageException("Setting frame background image failed", ex); } } backgroundImage.setBounds(PrecisionUtilities.getDraw2DRectangle( frame.getBackgroundBounds())); } // Always align the picture to top left corner backgroundImage.setAlignment(PositionConstants.NORTH | PositionConstants.WEST); // Resize to preferred size. DoubleSize s = getPreferredSize(); // Set the size of the contents area to fit all elements. contents.setSize(PrecisionUtilities.round(s.width), PrecisionUtilities.round(s.height)); // Draw all widgets. drawWidgets(); } /** * Get the contents. * Most often, callers should get the contents through an * interactive/standard editor then through this call. */ public CogToolScalableFigure getContents() { return contents; } /** * Add listeners for when things change on the design. */ protected void addDesignChangeListeners() { AlertHandler designChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { Design.WidgetAppearanceChange chg = (Design.WidgetAppearanceChange) alert; if (chg != null) { Iterator<GraphicalWidget<?>> gws = figureList.values().iterator(); // Update graphical widgets while (gws.hasNext()) { GraphicalWidget<?> gw = gws.next(); gw.updateType(); } } } }; // Add the new handler for design changes to the list of things to // alert to changes on the design frame.getDesign().addHandler(this, Design.WidgetAppearanceChange.class, designChangeHandler); } /** * Add listeners for when things change on the frame. */ protected void addFrameChangeListeners() { AlertHandler frameChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { Frame.WidgetChange chg = (Frame.WidgetChange) alert; IWidget chgWidget = chg.getChangeElement(); if (chg != null) { // Determine the action to take based on the // action dictated by the change. switch (chg.action) { // Add the graphical representation of the widget case Frame.WidgetChange.ELEMENT_ADD: createGraphicalWidget(chgWidget); raiseAlert(new FrameUIModel.WidgetShapeImageChange(FrameUIModel.this, chgWidget)); break; // Remove the graphical representation of the widget case Frame.WidgetChange.ELEMENT_DELETE: removeWidget(chgWidget); raiseAlert(new FrameUIModel.WidgetShapeImageChange(FrameUIModel.this, chgWidget)); break; // Update all existing widgets to the new color case Frame.WidgetChange.WIDGET_COLORS_CHANGED: Iterator<GraphicalWidget<?>> gws = figureList.values().iterator(); // Update graphical widgets while (gws.hasNext()) { GraphicalWidget<?> gw = gws.next(); gw.setColor(frame.getWidgetColor()); // gw.setFastMode(false); } // Update potential temporary widget contents.setWidgetColor(frame.getWidgetColor()); break; } } // Draw the viewable widgets. // Adds any new items from the lists.. or moves them drawWidgets(); } }; // Add the new handler for frame changes to the list of things to // alert to changes on the frame frame.addHandler(this, Frame.WidgetChange.class, frameChangeHandler); // A separate handler is required to take care of changes of the // background on the frame. AlertHandler frameBackgroundChangeHandler = new AlertHandler() { public void handleAlert(EventObject alert) { Frame.BackgroundImageChange chg = (Frame.BackgroundImageChange) alert; if (chg != null) { // Dispose the old background image if there was one if (backgroundImage.getImage() != null) { backgroundImage.getImage().dispose(); } byte[] bgImg = frame.getBackgroundImage(); // If the image is null, don't create it. if (bgImg != null) { // Convert the byte's into a proper image // Don't rely on the cache, since this event means // the cache is probably out of date try { Image img = new Image(null, new ByteArrayInputStream(bgImg)); // AUIModel.imageCache.put(frame, img); // set the new image to the background backgroundImage.setImage(img); // get the size of the image org.eclipse.swt.graphics.Rectangle bounds = img.getBounds(); // resize background image with the new bounds backgroundImage.setSize(bounds.width, bounds.height); } catch (SWTException ex) { throw new GraphicsUtil.ImageException("Setting frame background image failed", ex); } } else { // Clear the background image. backgroundImage.setImage(null); backgroundImage.setSize(0, 0); } } drawWidgets(); // Need to raise Alert after draw widgets since // the alert call seems to reset the "bounds" raiseAlert(new FrameUIModel.WidgetShapeImageChange(FrameUIModel.this, null)); } }; // Add this handler to the list for changes in the background image frame.addHandler(this, Frame.BackgroundImageChange.class, frameBackgroundChangeHandler); } // addFrameChangeListeners /** * Loop through each element in the widgets & figures list and then * draw them. * * By default the contents Figure would already have these added... * Check to see if they are already there.. or remove all and insert all. */ public void drawWidgets() { // Sort the items to be drawn into an order based on the level. // Highest numbers go first, i.e. render widgets from back to front. Collection<GraphicalWidget<?>> figures = figureList.values(); GraphicalWidget<?>[] figureArray = new GraphicalWidget[figures.size()]; figureArray = figures.toArray(figureArray); Arrays.sort(figureArray, GraphicalWidgetBase.GraphicalWidgetLevelComparator.ONLY); // Add the background if (backgroundImage != null) { // Add the background to the lowest layer 0 contents.add(backgroundImage, new Rectangle(backgroundImage.getBounds()), 0); } // Add each graphical widget from the array to the scalable figure for (GraphicalWidget<?> figureItem : figureArray) { // Add the figure to the contents // Specify the bounds of the figure in the XY layout of the content // Theoretically this could be removed... // but doing so causes lots of rendering bugs DoubleRectangle shapeBounds = figureItem.getModel().getEltBounds(); contents.add(figureItem, PrecisionUtilities.getDraw2DRectangle(shapeBounds), -1); } } // drawWidgets /** * Remove this widget from the figure list & * destroy any change handlers associated with it. * * @param w the model of the widget to remove */ protected void removeWidget(IWidget w) { // Locate the graphical widget in the figurelist and remove it. GraphicalWidget<?> gw = figureList.remove(w); widgetRecoveryEvent.setWidgetFigure(gw); raiseAlert(widgetRecoveryEvent); // Remove the figure from the visual system if (gw != null) { contents.remove(gw); gw.dispose(); } else { throw new IllegalArgumentException ("Cannot remove a widget which is not in the Figure List"); } } /** * Creates a graphical widget along with its associated change handler * @param w the model for the graphical widget */ protected void createGraphicalWidget(IWidget w) { GraphicalWidget<?> gw = null; // Create the graphical representation if (w instanceof ContextMenu) { gw = new GraphicalContextMenu((ContextMenu) w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, this, attrOverride); } else if (w instanceof AMenuWidget) { if (w instanceof MenuHeader) { gw = new GraphicalMenuHeader((MenuHeader) w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, this, attrOverride); } else if (w instanceof MenuItem) { gw = new GraphicalMenuItem((MenuItem) w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, this, attrOverride); gw.setVisible(false); } else { throw new IllegalStateException("Unknown menu widget type."); } } else if (w instanceof PullDownHeader) { gw = new GraphicalPullDownHeader((PullDownHeader) w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, this, attrOverride); } else if (w instanceof PullDownItem) { gw = new GraphicalPullDownItem((PullDownItem) w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, this, attrOverride); gw.setVisible(false); } else if (w instanceof ListItem) { gw = new GraphicalListItem((ListItem) w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, this, attrOverride); } else if (w instanceof RadioButton) { gw = new GraphicalRadioButton((RadioButton) w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, this, attrOverride); } else if (w instanceof CheckBox) { gw = new GraphicalCheckBox((CheckBox) w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, this, attrOverride); } else { gw = new GraphicalWidgetBase<IWidget>(w, frame.getWidgetColor(), showToolTips, widgetRolloverCursor, this, attrOverride); } // Add the new widget to the list. figureList.put(w, gw); // Add change handlers to the widget gw.addChangeHandler(new WidgetChangeHandler(gw), Widget.WidgetChange.class); gw.addChangeHandler(widgetAttrChangeHandler, IAttributed.AttributeChange.class); gw.addChangeHandler(widgetAttrChangeHandler, IAttributed.AuthorityChange.class); } /** * Add a change handler to every graphical widget. */ public void addWidgetChangeHandlerToAll(AlertHandler handler) { Iterator<GraphicalWidget<?>> widgetFigures = figureList.values().iterator(); while (widgetFigures.hasNext()) { GraphicalWidget<?> gw = widgetFigures.next(); gw.addChangeHandler(handler, Widget.WidgetChange.class); gw.addChangeHandler(handler, IAttributed.AttributeChange.class); gw.addChangeHandler(handler, IAttributed.AuthorityChange.class); } } /** * Set the origin of the graphical widget to the new coordinates. * Generally this is a temporary move; since it does not affect the model * it is here for performance reasons. */ public void setGraphicalWidgetOrigin(double x, double y, GraphicalWidget<?> gw) { // Set the new location of the temporary widget gw.setLocation(new Point(PrecisionUtilities.round(x), PrecisionUtilities.round(y))); } /** * Set the origin of the graphical widget to be displaced by the differences. * Generally this is a temporary move; since it does not affect the model * it is here for performance reasons. */ public void setGraphicalWidgetMove(double diffX, double diffY, GraphicalWidget<?> gw) { Rectangle r = gw.getBounds(); setGraphicalWidgetOrigin(r.x + diffX, r.y + diffY, gw); } /** * Set the bounds of the graphical widget to the new coordinates. * Generally this is a temporary move; since it does not affect the model * it is here for performance reasons. */ public void setGraphicalWidgetBounds(double tempOriginX, double tempOriginY, double tempWidth, double tempHeight, GraphicalWidget<?> gw) { Rectangle newBds = PrecisionUtilities.getDraw2DRectangle(tempOriginX, tempOriginY, tempWidth, tempHeight); gw.setBounds(newBds); // For some idiot reason, the setBounds call doesn't refresh the size // during dynamics, so do the setSize to force it (sigh) gw.setSize((newBds.width > 0) ? newBds.width : 1, (newBds.height > 0) ? newBds.height : 1); } /** * Get an iterator for the list of GraphicalWidgets. */ public Iterator<GraphicalWidget<?>> getFigureListIterator() { return figureList.values().iterator(); } /** * Get a specific graphical widget based on its "widget" */ public GraphicalWidget<?> getWidgetFigure(IWidget widget) { if (widget != null) { return figureList.get(widget); } return null; } /** * Get the zoom from the contents. */ public double getZoom() { return contents.getScale(); } /** * Modify the zoom. * Only changes the scale if the scale changes. Does not * specifically call redraws, relies on this.contents to know it is dirty. */ public void setZoom(double scale) { if (scale != getZoom()) { contents.setScale(scale); } } /** * Compute the preferred size of widgets based on the contents scale. */ public DoubleSize getPreferredSize() { return getPreferredSize(contents.getScale()); } /** * Compute the preferred size of the widgets based on a given scale. */ public DoubleSize getPreferredSize(double scaleFactor) { // Compute the area needed for all widgets Rectangle r = computeWidgetArea(); // Add the background image area r.union(backgroundImage.getBounds()); // Apply scaling to result. return new DoubleSize(r.width * scaleFactor, r.height * scaleFactor) ; } /** * Compute what the needed widget area would be if using a 1:1 scale * If no widgets, then returns a (0,0,0,0) area. * The origin of the supplied rectangle may not be 0,0 */ public Rectangle computeWidgetArea() { // Use Figure list since figures may be sized differently // due to "dragging" Rectangle r = null; Iterator<GraphicalWidget<?>> iter = figureList.values().iterator(); // Go through all widgets and union their bounds. while (iter.hasNext()) { GraphicalWidget<?> gw = iter.next(); if (r == null) { r = new Rectangle(gw.getBounds()); } else { r.union(gw.getBounds()); } } // If no widgets, return a 0,0,0,0 rectangle return (r == null) ? new Rectangle(0, 0, 0, 0) : r; } public GraphicalWidget<?> getPrevInGroup(GraphicalWidget<?> fromWidgetFig) { IWidget modelWidget = fromWidgetFig.getModel(); SimpleWidgetGroup group = modelWidget.getParentGroup(); if (group != null) { int widgetIndex = group.indexOf(modelWidget); if (widgetIndex > 0) { return getWidgetFigure(group.get(widgetIndex - 1)); } } return null; } public GraphicalWidget<?> getNextInGroup(GraphicalWidget<?> fromWidgetFig) { IWidget modelWidget = fromWidgetFig.getModel(); SimpleWidgetGroup group = modelWidget.getParentGroup(); if (group != null) { int widgetIndex = group.indexOf(modelWidget); if (widgetIndex < group.size() - 1) { return getWidgetFigure(group.get(widgetIndex + 1)); } } return null; } public GraphicalWidget<?> getLastInGroup(GraphicalWidget<?> fromWidgetFig) { IWidget modelWidget = fromWidgetFig.getModel(); SimpleWidgetGroup group = modelWidget.getParentGroup(); if (group != null) { int widgetIndex = group.size() - 1; return getWidgetFigure(group.get(widgetIndex)); } return null; } /** * Required function to return the view. * @return */ protected View getView() { return null; } /** * Disposes of the frameUIModel * Disposes of the image background if any * Disposes of each Graphical widget * Disposes of all handlers used in the frame and the design. * */ public void dispose() { // Clear the background image // This should be handled by the AUIModel.imageCache Image img = backgroundImage.getImage(); if (img != null) { // In case the ImageFigure needs to recover any internal handles backgroundImage.setImage(null); img.dispose(); } // Clear the graphical widgets Iterator<GraphicalWidget<?>> gws = figureList.values().iterator(); while (gws.hasNext()) { GraphicalWidget<?> gw = gws.next(); gw.dispose(); } // clears the contents contents.dispose(); // remove any handlers used. frame.removeAllHandlers(this); frame.getDesign().removeAllHandlers(this); isDisposed = true; } /** * Get a IWidget at a specific point. * Uses the filter ONLY GRAPHICAL WIDGETS and returns the model * * May return null, if no widget at x,y * * NOTE: this search is done in the CONTENTS: The contents is itself scaled * so the X,Y must be in the zoom coordinates of the content's parent. * IE: for FrameEditor its 1:1 (or the mouse position) */ public IWidget getWidgetAtPoint(int x, int y) { // Get the graphical widget at XY GraphicalWidget<?> gw = (GraphicalWidget<?>) getFigureAtXY(x, y, FrameUIModel.ONLY_GRAPHICAL_WIDGETS); // Return the model if available if (gw != null) { return gw.getModel(); } return null; } /** * Find a figure at XY, with a specific filter. */ public IFigure getFigureAtXY(int x, int y, int filter) { figureFilter.setFilter(filter); return contents.findFigureAt(x, y, figureFilter); } public List<GraphicalSource<?>> getSourcesAtXY(int x, int y) { sourcesFilter.resetSearch(); contents.findFigureAt(x, y, sourcesFilter); return sourcesFilter.getSources(); } /** * Return the graphics alpha value to use for the color overlay * when displaying an IGraphicalSource. * * @param selected whether the IGraphicalSource is selected or not * @return the graphics alpha value to use for the color overlay * when displaying an IGraphicalSource based on its given * selection state */ public int determineAlpha(boolean selected) { return selected ? widgetSelectedAlpha : widgetNormalAlpha; } /** * Hides all child widgets in the frame. */ public void hideAllChildren() { Iterator<GraphicalWidget<?>> widgetFigures = new FrameUIModel.WidgetFigureIterator(this); while (widgetFigures.hasNext()) { GraphicalWidget<?> next = widgetFigures.next(); if (next instanceof GraphicalChildWidget<?, ?>) { ((GraphicalChildWidget<?, ?>) next).setVisible(false); } } } @SuppressWarnings("unchecked") public GraphicalWidget<GridButton> getBottomGridFigure(GraphicalGridButton gridFig) { GridButton gb = gridFig.getModel(); GridButtonGroup group = (GridButtonGroup) gb.getParentGroup(); Iterator<IWidget> gbs = group.iterator(); DoubleRectangle bds = gb.getEltBounds(); double startX = bds.x; double startY = bds.y; IWidget result = null; double resultY = 0.0; while (gbs.hasNext()) { IWidget cur = gbs.next(); if (cur == gb) { continue; } bds = cur.getEltBounds(); double curX = bds.x; double curY = bds.y; if (PrecisionUtilities.withinEpsilon(startX, curX, GridButtonGroup.PIXEL_EPSILON) && (curY > startY)) { if ((result == null) || (curY < resultY)) { result = cur; resultY = curY; } } } return (GraphicalWidget<GridButton>) getWidgetFigure(result); } @SuppressWarnings("unchecked") public GraphicalWidget<GridButton> getLeftGridFigure(GraphicalGridButton gridFig) { GridButton gb = gridFig.getModel(); GridButtonGroup group = (GridButtonGroup) gb.getParentGroup(); Iterator<IWidget> gbs = group.iterator(); DoubleRectangle bds = gb.getEltBounds(); double startX = bds.x; double startY = bds.y; IWidget result = null; double resultX = 0.0; while (gbs.hasNext()) { IWidget cur = gbs.next(); if (cur == gb) { continue; } bds = cur.getEltBounds(); double curX = bds.x; double curY = bds.y; if (PrecisionUtilities.withinEpsilon(startY, curY, GridButtonGroup.PIXEL_EPSILON) && (curX < startX)) { if ((result == null) || (curX > resultX)) { result = cur; resultX = curX; } } } return (GraphicalWidget<GridButton>) getWidgetFigure(result); } @SuppressWarnings("unchecked") public GraphicalWidget<GridButton> getRightGridFigure(GraphicalGridButton gridFig) { GridButton gb = gridFig.getModel(); GridButtonGroup group = (GridButtonGroup) gb.getParentGroup(); Iterator<IWidget> gbs = group.iterator(); DoubleRectangle bds = gb.getEltBounds(); double startX = bds.x; double startY = bds.y; IWidget result = null; double resultX = 0.0; while (gbs.hasNext()) { IWidget cur = gbs.next(); if (cur == gb) { continue; } bds = cur.getEltBounds(); double curX = bds.x; double curY = bds.y; if (PrecisionUtilities.withinEpsilon(startY, curY, GridButtonGroup.PIXEL_EPSILON) && (curX > startX)) { if ((result == null) || (curX < resultX)) { result = cur; resultX = curX; } } } return (GraphicalWidget<GridButton>) getWidgetFigure(result); } @SuppressWarnings("unchecked") public GraphicalWidget<GridButton> getTopGridFigure(GraphicalGridButton gridFig) { GridButton gb = gridFig.getModel(); GridButtonGroup group = (GridButtonGroup) gb.getParentGroup(); Iterator<IWidget> gbs = group.iterator(); DoubleRectangle bds = gb.getEltBounds(); double startX = bds.x; double startY = bds.y; IWidget result = null; double resultY = 0.0; while (gbs.hasNext()) { IWidget cur = gbs.next(); if (cur == gb) { continue; } bds = cur.getEltBounds(); double curX = bds.x; double curY = bds.y; if (PrecisionUtilities.withinEpsilon(startX, curX, GridButtonGroup.PIXEL_EPSILON) && (curY < startY)) { if ((result == null) || (curY > resultY)) { result = cur; resultY = curY; } } } return (GraphicalWidget<GridButton>) getWidgetFigure(result); } }