/******************************************************************************* * 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.controller; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Monitor; import edu.cmu.cs.hcii.cogtool.util.EmptyIterator; import edu.cmu.cs.hcii.cogtool.util.WindowUtil; /** * Tracks open windows associated with each given "nexus" model object. * A "nexus" object is one that "represents" a collection of other objects. * In CogTool, the <code>Project</code> instances act as nexus objects for * all the other model objects that the project contains. Note that the set * of objects represented by a nexus contains the nexus object itself. * <p> * Implemented as a singleton. * * @author mlh */ public class ControllerNexus<T> { /** * Note: Expected to be a singleton */ public ControllerNexus() { } protected Map<T, Set<Controller>> openControllers = new HashMap<T, Set<Controller>>(); protected Map<T, Map<Object, Rectangle>> controllerLocations = new HashMap<T, Map<Object, Rectangle>>(); protected Map<T, Map<Object, Double>> controllerZooms = new HashMap<T, Map<Object, Double>>(); /** * Register the given controller with the given "nexus" object. * * @param nexus the object that represents a set of other model objects * @param controller a controller for one of the objects represented * by the nexus object * @author mlh */ public void addController(T nexus, Controller controller) { // Find the set of controllers for the given nexus Set<Controller> nexusControllers = this.openControllers.get(nexus); // If no set has yet been assigned for the nexus, create one // and associate it with the nexus. if (nexusControllers == null) { nexusControllers = new HashSet<Controller>(); this.openControllers.put(nexus, nexusControllers); } // Add the given controller to the set associated with the nexus nexusControllers.add(controller); } /** * Return the number of controllers associated with the given nexus. * * @return the number of controllers associated with the given nexus * @author mlh */ public int getControllerCount(T nexus) { // Find the set of controllers for the given nexus Set<Controller> nexusControllers = this.openControllers.get(nexus); // If no set has yet been assigned, the count is zero; // otherwise, return the count associated with the nexus return (nexusControllers != null) ? nexusControllers.size() : 0; } /** * Unregister the given controller from the set associated with the * given "nexus" object. * * @param nexus the object that represents a set of other model objects * @param controller a controller for one of the objects represented * by the nexus object * @return true if and only if the given controller was associated * with the given nexus, and therefore removed * @author mlh */ public boolean removeController(T nexus, Controller controller) { // Find the set of controllers for the given nexus Set<Controller> nexusControllers = this.openControllers.get(nexus); // If no set has yet been assigned, indicate failure to remove if (nexusControllers == null) { return false; } // Attempt to remove from the set and indicate success/failure if (nexusControllers.remove(controller)) { if (nexusControllers.size() == 0) { this.openControllers.remove(nexus); this.controllerLocations.remove(nexus); this.controllerZooms.remove(nexus); } return true; // successful! } return false; // not successful } /** * Generate the controllers associated with the given "nexus" object. * A valid <code>Iterator</code> is always returned. * * @param nexus the object that represents a set of other model objects * @return an iterator to enumerate the controllers associated with * the given nexus object * @author mlh */ public Iterator<Controller> getControllers(T nexus) { // Find the set of controllers for the given nexus Set<Controller> nexusControllers = this.openControllers.get(nexus); // If no set has yet been assigned, return an iterator that // enumerates no objects if (nexusControllers == null) { return new EmptyIterator<Controller>(); } // Otherwise, return one that enumerates the controllers associated // with the nexus. return nexusControllers.iterator(); } /** * Close all the controllers associated with the given nexus. * <p> * It is important to address the issue of modifying the controller * set during iteration (<code>requestClose</code> and * <code>dispose</code>); that is, by not using an <code>Iterator</code>. * <p> * If saving, the user will be asked whether to save the associated * nexus object (in CogTool, the project). One possible response * is to abort/cancel the window closing. * * @param nexus the object that represents a set of other model objects * @param checkToSave whether to check to save the associated nexus * @return true if and only the close(s) were successful and * not aborted/canceled * @author mlh */ public boolean closeControllers(T nexus, boolean checkToSave) { // Find the set of controllers for the given nexus Set<Controller> nexusControllers = this.openControllers.get(nexus); // Unless a set has been assigned, there is nothing to do if (nexusControllers != null) { Object[] controllers = nexusControllers.toArray(); // Use an array to avoid ConcurrentModificationException for (Object controller : controllers) { Controller c = (Controller) controller; // If allowing the user to approve or disapprove of // saving the associated modified nexus object, // check to see if the close succeeded. If not, // the user requested a cancel of the operation // that called this method. if (checkToSave) { if (! c.requestClose()) { return false; } } else { // Not checking for unmodified nexus instances; // simply close and recover resources. c.dispose(); } } } // If we got here, all associated controllers have been // closed and recovered successfully. return true; } /** * Return set of all nexus objects that have * ever had an associated controller. * * @return a set of all nexus objects that have * ever had an associated controller * @author mlh */ public Set<T> getControllerNexuses() { return this.openControllers.keySet(); } /** * Checks that a stored window location is still visible with the current * screen layout. * (Really, just checks that the top-left corner is on screen somewhere.) * * @param loc the location info to check * @return a corrected rectangle, completely visible with the * current screen layout */ protected static Rectangle checkWindowBounds(Rectangle loc) { if (loc == null) { return loc; } // Indicates that upper-left overlaps with the client area of the // monitor. boolean safe = false; // Check if the window is fully contained on the client areas of // some set of monitors Monitor[] monitors = WindowUtil.GLOBAL_DISPLAY.getMonitors(); for (int i = 0; ! safe && (i < monitors.length); i++) { Monitor monitor = monitors[i]; if (monitor != null) { safe = monitor.getClientArea().contains(loc.x, loc.y); } } if (! safe) { Monitor prim = WindowUtil.GLOBAL_DISPLAY.getPrimaryMonitor(); Rectangle r = prim.getClientArea(); loc.x = r.x; loc.y = r.y; if (loc.width > r.width) { loc.width = r.width; } if (loc.height > r.height) { loc.height = r.height; } } return loc; } /** * Retrieves a saved window location for a previously-edited model object. * * @param nexus the object that represents a set of other model objects * @param model the model object being edited in the window * @return the location Rectangle previously saved for this model */ public Rectangle getWindowLocation(T nexus, Object model) { // Find the map of controller locations for the given nexus Map<Object, Rectangle> nexusLocations = this.controllerLocations.get(nexus); // If no map has yet been assigned, return null if (nexusLocations == null) { return null; } return checkWindowBounds(nexusLocations.get(model)); } /** * Saves a window location associated with a particular model object. * * @param nexus the object that represents a set of other model objects * @param model the model object being edited in the window * @param loc the location Rectangle for the window */ public void saveWindowLocation(T nexus, Object model, Rectangle loc) { // Find the map of controller locations for the given nexus Map<Object, Rectangle> nexusLocations = this.controllerLocations.get(nexus); // If no map has yet been assigned for the nexus, create one // and associate it with the nexus. if (nexusLocations == null) { nexusLocations = new HashMap<Object, Rectangle>(); this.controllerLocations.put(nexus, nexusLocations); } nexusLocations.put(model, loc); } /** * Returns a saved window zoom level for a previously-edited model object. * Recall that the map stores the zoom factor in a <code>Double</code> * instance. * * @param nexus the object that represents a set of other model objects * @param model the model object being edited in the window * @return the zoom level previously saved for this model */ public double getWindowZoom(T nexus, Object model) { // Find the map of controller locations for the given nexus Map<Object, Double> nexusZooms = this.controllerZooms.get(nexus); // If no map has yet been assigned, return standard default zoom factor if (nexusZooms == null) { return 1.0d; } // Try to find a stored zoom. Double zoom = nexusZooms.get(model); if (zoom == null) { return 1.0d; } return zoom.doubleValue(); } /** * Saves a window zoom level associated with a particular model object. * * @param nexus the object that represents a set of other model objects * @param model the model object being edited in the window * @param loc the zoom level for the window */ public void saveWindowZoom(T nexus, Object model, double zoom) { // Find the map of controller locations for the given nexus Map<Object, Double> nexusZooms = this.controllerZooms.get(nexus); // If no map has yet been assigned for the nexus, create one // and associate it with the nexus. if (nexusZooms == null) { nexusZooms = new HashMap<Object, Double>(); this.controllerZooms.put(nexus, nexusZooms); } nexusZooms.put(model, new Double(zoom)); } }