/*******************************************************************************
* 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.view;
import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import edu.cmu.cs.hcii.cogtool.CogToolLID;
import edu.cmu.cs.hcii.cogtool.CogToolPref;
import edu.cmu.cs.hcii.cogtool.model.ImportConverter;
import edu.cmu.cs.hcii.cogtool.util.DoubleEntry;
import edu.cmu.cs.hcii.cogtool.util.Keypad;
import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifier;
import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifier.ILIDTransmuter;
import edu.cmu.cs.hcii.cogtool.util.ListenerIdentifierMap;
import edu.cmu.cs.hcii.cogtool.util.ManagedCombo;
import edu.cmu.cs.hcii.cogtool.util.ManagedText;
import edu.cmu.cs.hcii.cogtool.util.MenuUtil.MenuItemDefinition;
import edu.cmu.cs.hcii.cogtool.util.OSUtils;
import edu.cmu.cs.hcii.cogtool.util.RcvrUIException;
import edu.cmu.cs.hcii.cogtool.util.StatusDisplayable;
import edu.cmu.cs.hcii.cogtool.util.WindowUtil;
/**
* Abstract class for sharing implementation that depends on having
* an SWT Shell instance representing the View's window.
*
* @author mlh
*/
public abstract class View implements StatusDisplayable
{
/**
* Drawing block flag -- set true when you know useless drawing is about
* to be triggered indirectly.
*/
public static final ThreadLocal<Boolean> drawingFlags =
new ThreadLocal<Boolean>()
{
@Override
protected Boolean initialValue()
{
return Boolean.TRUE;
}
};
protected static Image[] images;
/**
* The SWT instance representing the View's window.
*/
protected Shell shell;
/**
* The window's contextual menu manager
*/
protected ContextMenuManager contextMenus = null;
protected ListenerIdentifierMap lIDMap;
protected ILIDTransmuter transmuter;
protected boolean performingAction = false;
protected MenuFactory.IWindowMenuData<?> windowMenuData;
// Generic selection adapter for events
// used by FrameEditor and DemoView and others to have an easy way of
// adding a listener to an SWT widget which has a WidgetSelectEvent
// Great for buttons
protected class SWTWidgetChangeHandler extends SelectionAdapter
{
CogToolLID lid;
public SWTWidgetChangeHandler(CogToolLID setLid)
{
if (setLid == null) {
throw new IllegalArgumentException(
"Cannot create a widgetChangeHandler with a null lid");
}
lid = setLid;
}
@Override
public void widgetSelected(SelectionEvent evt)
{
Control source = (Control) evt.getSource();
if (source.isEnabled()) {
performAction(lid);
}
}
}
/**
* Property sheet text support
*/
public static class PropertiesChangeText extends View.PerformActionText
{
/**
* The LID to post when this event "happens"
*/
protected ListenerIdentifier lid;
protected View view;
public PropertiesChangeText(Composite parent,
int style,
ListenerIdentifier setLid,
View v)
{
super(parent, style);
if (setLid == null) {
throw new IllegalArgumentException(
"Cannot create a WidgetChangeText with a null lid");
}
lid = setLid;
view = v;
}
@Override
public boolean confirm(int focusRule)
{
if (! view.isPerformingAction()) {
return super.confirm(focusRule);
}
// otherwise, do nothing!
return true;
}
@Override
protected boolean doChangeAction()
{
return view.performAction(lid);
}
@Override
protected void onFocus()
{
super.onFocus();
view.getTransmuter().setLIDEnabledState();
}
}
/**
* Subclass this when assigning SWT SelectionListener
* (for widgetDefaultSelected; i.e., when Enter is pressed) and
* FocusListener (for focusLost) to Text instances that should
* perform a Controller action as a result.
*/
public static class PerformActionDouble extends DoubleEntry
{
/**
* Prevent recursive calls (annoyingly enough)
*
* Don't handle events for changes to properties
* when another event is active
*/
protected boolean notActive = true;
public PerformActionDouble(Composite parent, int style)
{
super(parent, style);
}
@Override
public boolean confirm(int focusRule)
{
if (isEnabled()) {
return performChangeAction();
}
return true;
}
public boolean performChangeAction()
{
boolean performed = true;
if (notActive) {
try {
notActive = false;
performed = doChangeAction();
}
finally {
notActive = true;
}
}
return performed;
}
protected boolean doChangeAction()
{
// OVERRIDE this method
return true;
}
}
/**
* Subclass this when assigning SWT SelectionListener
* (for widgetDefaultSelected; i.e., when Enter is pressed) and
* FocusListener (for focusLost) to Text instances that should
* perform a Controller action as a result.
*/
public static class PerformActionText extends ManagedText
{
/**
* Prevent recursive calls (annoyingly enough)
*
* Don't handle events for changes to properties
* when another event is active
*/
protected boolean notActive = true;
public PerformActionText(Composite parent, int style)
{
// TODO: Technically, View is generic enough that we should
// *not* be citing CogToolPrefs here but instead
// requiring the caller to specify the value as a parameter.
super(parent, style, Keypad.FULL_KEYPAD);
}
@Override
public boolean confirm(int focusRule)
{
if (isEnabled()) {
return performChangeAction();
}
return true;
}
public boolean performChangeAction()
{
boolean performed = true;
if (notActive) {
try {
notActive = false;
performed = doChangeAction();
}
finally {
notActive = true;
}
}
return performed;
}
protected boolean doChangeAction()
{
// OVERRIDE this method
return true;
}
}
/**
* Subclass this when assigning SWT SelectionListener
* (for widgetDefaultSelected; i.e., when Enter is pressed) and
* FocusListener (for focusLost) to Combo instances that should
* perform a Controller action as a result.
*/
public static class PerformActionCombo extends ManagedCombo
{
/**
* Prevent recursive calls (annoyingly enough)
*
* Don't handle events for changes to properties
* when another event is active
*/
protected boolean notActive = true;
public PerformActionCombo(Composite parent, int style)
{
super(parent, style);
}
@Override
public boolean confirm(int focusRule)
{
if (isEnabled()) {
return performChangeAction();
}
return true;
}
public boolean performChangeAction()
{
boolean performed = true;
if (notActive) {
try {
notActive = false;
performed = doChangeAction();
}
finally {
notActive = true;
}
}
return performed;
}
protected boolean doChangeAction()
{
// OVERRIDE this method
return true;
}
}
protected Listener selectionListener =
new Listener() {
public void handleEvent(Event evt)
{
//performAction((ListenerIdentifier) evt.widget.getData());
ListenerIdentifier id= (ListenerIdentifier) evt.widget.getData();
if(id instanceof CogToolLID.ConverterFilesLID)
{
MenuItem w = (MenuItem)evt.widget;
String wText = w.getText().substring(w.getText().indexOf("from")+5);
//System.out.println("widget " + wText);
File file = null;
String directory = CogToolPref.CONVERTER_DIRECTORY.getString();
if(directory != null)
{
file = new File(directory);
URL[] urls = null;
try {
/**
* TODO: fix this deprecated method
*/
URL url= file.toURL();
//System.out.println("file "+ file + " url " + url);
urls = new URL[]{url};
}
catch (MalformedURLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
//TODO: comment more here
URLClassLoader classLoader = new URLClassLoader(urls);
String[] children = file.list();
for (String resource : children) {
try {
Class<ImportConverter> translatorClass = (Class<ImportConverter>) classLoader.loadClass(resource.substring(0, resource.indexOf('.')));
try{
Object converter = translatorClass.newInstance();
Class[] parameters = new Class[0];
Method method = translatorClass.getMethod("name", parameters);
String name = (String)method.invoke(converter);
if(name.equals(wText)){
CogToolLID.ConverterFilesLID.NewDesignFromImport.setClassAttribute(translatorClass);
}
}
catch( Exception ex){
ex.printStackTrace();
}
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
performAction(id);
}
};
/**
* Common constant for indicating that context menu is for currently
* selected objects.
*/
public static final boolean SELECTION = false;
/**
* Common constant for indicating that context menu is for transiently
* selected object (menu-click on unselected entity).
*/
public static final boolean CONTEXT = true;
/**
* Initializes the shared implementation for this View instance.
* <p>
* It stores the SWT window instance and constructs the main menu bar
* for the window. The construction of the main menu bar proceeds
* according to the UI conventions of the executing platform.
* <p>
* The <code>ListenerIdentifierMap</code> instance is provided by
* the invoking <code>UIModel</code> and the <code>Shell</code>
* is provided by the subclass.
*
* @param windowShell the SWT instance representing the window
* @param listenerIDMap used to map <code>ListenerIdentifier</code> values
* to application-specific, semantic code snippets
* @param lIDtransformer used to convert <code>ListenerIdentifier</code>
* instances to more concrete values
* @author mlh
* @see MenuFactory.buildMenu
*/
public View(Shell windowShell,
ListenerIdentifierMap listenerIDMap,
ILIDTransmuter lIDtransformer,
MenuFactory.IWindowMenuData<?> menuData)
{
shell = windowShell;
lIDMap = listenerIDMap;
transmuter = lIDtransformer;
windowMenuData = menuData;
// Set an icon for the window control box and Windows taskbar
if (OSUtils.WINDOWS) {
// Load the image data statically and keep it around
if (images == null) {
ImageLoader loader = new ImageLoader();
ImageData[] imageData =
loader.load(ClassLoader.getSystemResourceAsStream
("edu/cmu/cs/hcii/cogtool/resources/CogTool_Icon.png"));
images = new Image[imageData.length];
for (int i = 0; i < imageData.length; i++) {
images[i] = new Image(WindowUtil.GLOBAL_DISPLAY, imageData[i]);
}
}
shell.setImages(images);
}
menuData.setView(this);
buildMenus();
// Add a listener to the close window event to "attempt" to capture changes
// as the window closes.. unfortunately may cause a crash on quit,
// (mlh: haven't seen this!) or close window when the change conflicts.
// and on quit if it doesn't crash, it doesn't ask the user to save the change
shell.addShellListener(new ShellAdapter() {
/**
* Change focus when shell closed to trigger appropriate events.
*/
@Override
public void shellClosed(ShellEvent evt)
{
if (! evt.widget.isDisposed()) {
((Shell) evt.widget).setFocus();
}
super.shellClosed(evt);
}
});
}
public ListenerIdentifierMap getLIDMap()
{
return lIDMap;
}
protected void buildMenus()
{
MenuItemDefinition[][] defs = getContextMenuDefinitions();
if (defs != null) {
if (contextMenus != null) {
contextMenus.dispose();
}
contextMenus = new ContextMenuManager(shell,
lIDMap,
transmuter,
defs);
}
MenuFactory.buildMenu(neededMenus(),
shell,
selectionListener,
lIDMap,
windowMenuData);
}
/**
* Show a dynamic menu for contextual click with the list of menu items.
*/
public void showDynamicMenu(List<MenuItemDefinition> dynamicMenuItems)
{
contextMenus.createDynamicMenu(dynamicMenuItems).setVisible(true);
}
public void rebuildMenus()
{
shell.getMenuBar().dispose();
buildMenus();
}
/**
* Wrapper method for invoking performAction; uses the cached transmuter
* and (!) assumes NOT caused by a context menu. ContextMenuManager
* is currently the only place that invokes performAction as "context".
*
* @param id the general semantic LID to lookup and perform action for
* @return <code>true</code> if it is ok to continue with action processing
* and <code>false</code> if processing should stop.
*/
public boolean performAction(ListenerIdentifier id)
{
return transmuter.performAction(id);
}
/**
* Wrapper method for invoking performAction; uses the cached transmuter
* and (!) assumes NOT caused by a context menu. ContextMenuManager
* is currently the only place that invokes performAction as "context".
*
* @param id the general semantic LID to lookup and perform action for
* @return <code>true</code> if it is ok to continue with action processing
* and <code>false</code> if processing should stop.
*/
public boolean performAction(ListenerIdentifier id,
Object actionParms,
boolean doCleanup)
{
return transmuter.performAction(id, actionParms, doCleanup);
}
/**
* This method provides the top-level pull-downs that this View
* requires in the main menu bar.
* <p>
* Different platforms (specifically, Windows vs. Macintosh)
* have different conventions for presenting the choices for the
* main menu bar when the application has multiple, different windows.
* Windows tends to present only those needed by the specific window
* while Macintosh presents the union of all choices and simply disables
* the choices that are unavailable in the currently active window.
*
* @returns the sequence of top-level pull-downs needed by this View
* @author mlh
*/
protected abstract MenuFactory.MenuType[] neededMenus();
/**
* Fetch the Shell being used to present the view's window.
*
* @return the SWT Shell instance for the view's window
* @author mlh
*/
public Shell getShell()
{
return shell;
}
public ILIDTransmuter getTransmuter()
{
return transmuter;
}
/**
* Set the window's title to the given string.
*
* @param newTitle the new title for the associated window
* @author mlh
*/
public void setWindowTitle(String newTitle)
{
Shell window = getShell();
if (window != null) {
window.setText(newTitle);
}
else {
throw new RcvrUIException("No window available for setting window title.");
}
}
/**
* Set whether the associated window (and view) is visible.
*
* @param visible true iff the associated window/view should be visible
* @author mlh
*/
public void setVisible(boolean visible)
{
Shell window = getShell();
if (window != null) {
if (visible) {
// Suppress drawing until everything is done.
// setDrawingOK(false);
WindowUtil.display(window, false);
// Recompute layout of the shell contents.
// Should not be called too often, but since SetVisible should
// only be called on window creation it should be done here.
window.layout(true, true);
// setDrawingOK(true);
}
else {
window.setVisible(false);
}
}
else {
throw new RcvrUIException("No window available for setting window visibility.");
}
}
/**
* Bring the associated window to the front and make active by
* making the window have the focus and "un-minimize" it.
*
* @author mlh
*/
public void takeFocus()
{
Shell window = getShell();
if (window != null) {
window.setActive();
window.setMinimized(false);
}
else {
throw new RcvrUIException("No window available for taking the focus.");
}
}
/**
* Request that the associated window be closed; it is possible for some
* interaction with the user to occur which might "cancel" the operation.
*
* @return true if and only if the close was successful.
*/
public boolean requestClose()
{
Shell window = getShell();
if (window != null) {
// Ask the shell to close (this will prompt user for save).
window.close();
// If the shell didn't close, the exit command was cancelled.
return window.isDisposed();
}
throw new RcvrUIException("No window available to close.");
}
/**
* Recover any system resources being used to support this window/view.
*
* @author mlh
*/
public void dispose()
{
Shell window = getShell();
if (window != null) {
if (contextMenus != null) {
contextMenus.dispose();
contextMenus = null;
}
if (! window.isDisposed()) {
window.setVisible(false);
window.close();
window.dispose();
}
}
else {
throw new RcvrUIException("No window available for recovering system resources.");
}
}
/**
* Indicate whether or not the associated window has been closed.
*
* @return true if the associated window has been closed; false otherwise
*/
public boolean isDisposed()
{
Shell window = getShell();
if (window != null) {
return window.isDisposed();
}
else {
throw new RcvrUIException("No window available for testing if closed.");
}
}
/**
* Returns the set of context menus used by the view
* @return the context menus
*/
public MenuItemDefinition[][] getContextMenuDefinitions()
{
// By default, we have no context menus
return null;
}
/**
* Accessor for the drawing block flag.
*/
public static boolean isDrawingOK()
{
return true;
// return drawingFlags.get().booleanValue();
}
/**
* Mutator for the drawing block flag -- set true when wasteful drawing is
* about to be triggered indirectly.
*/
public static void setDrawingOK(boolean newVal)
{
drawingFlags.set(newVal ? Boolean.TRUE : Boolean.FALSE);
}
/**
* Support for creating the view's window.
* <p>
* This is called by createShell(Rectangle) in most of our subclasses,
* but needs to be a static method in each of them, hence we can't use
* overriding, and instead have this as a helper to be called explicitly.
*
* @param bounds if null, use a default position, and otherwise use this as
* position and size of the window
* @param width if bounds is null, use this as the default width; otherwise
* the value of <code>width</code> is ignored
* @param height if bounds is null, use this as the default height; otherwise
* the value of <code>height</code> is ignored
* @param layout the Layout used by the window
* @return the new window
*/
protected static Shell createShell(Rectangle bounds,
int width,
int height,
Layout layout)
{
return WindowUtil.createNormalWindow(WindowUtil.GLOBAL_DISPLAY,
"",
((bounds != null)
? bounds
: new Rectangle(0,
0,
width,
height)),
(bounds != null),
layout);
}
/**
* Some reactions should not happen if the application is in the
* middle of performing another action. Use this to detect.
*/
public boolean isPerformingAction()
{
return performingAction;
}
/**
* Indicate whether or not the application is in the midst of performing
* another action.
*/
public void setPerformingAction(boolean performing)
{
performingAction = performing;
}
public void setStatusMessage(String message)
{
// By default, no place to set status message
}
public void setStatusMessage(String message, int duration)
{
// By default, no place to set status message
}
}