All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.globalmentor.swing.ResourceComponentManager Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 1996-2009 GlobalMentor, Inc. 
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.globalmentor.swing;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Event;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.net.URI;
import java.util.*;
import java.util.prefs.Preferences;

import javax.swing.*;
import javax.swing.event.EventListenerList;

import com.globalmentor.log.Log;
import com.globalmentor.model.DefaultObjectState;
import com.globalmentor.model.Modifiable;
import com.globalmentor.model.Verifiable;
import com.globalmentor.net.Resource;
import com.globalmentor.net.ResourceSelector;

import static com.globalmentor.net.URIs.*;
import static com.globalmentor.swing.Components.*;

import com.globalmentor.util.prefs.Preferencesable;

/**
 * An abstract class that manages resources, their views, and their modified states. This class does not actually change the displayed component in any
 * container, relying on some other class to perform that function in response to a change in resource component state.
 * @param  The type of resource the components of which are being managed.
 * @see ResourceComponentManager#ResourceComponentState
 * @author Garret Wilson
 */
public abstract class ResourceComponentManager implements Preferencesable {

	/** The list of event listeners. */
	protected final EventListenerList eventListenerList = new EventListenerList();

	/** The preferences that should be used for this object, or null if the default preferences for this class should be used. */
	private Preferences preferences = null;

	/**
	 * @return The preferences that should be used for this object, or the default preferences for this class if no preferences are specifically set.
	 * @throws SecurityException Thrown if a security manager is present and it denies RuntimePermission("preferences").
	 */
	public Preferences getPreferences() throws SecurityException {
		return preferences != null ? preferences : Preferences.userNodeForPackage(getClass()); //return the user preferences node for whatever class extends this one 
	}

	/**
	 * Sets the preferences to be used for this panel.
	 * @param preferences The preferences that should be used for this panel, or null if the default preferences for this class should be used
	 */
	public void setPreferences(final Preferences preferences) {
		this.preferences = preferences; //store the preferences
	}

	/** The action for opening a resource. */
	private final Action openAction;

	/**
	 * @return The action for opening a resource.
	 * @see #open
	 */
	public Action getOpenAction() {
		return openAction;
	}

	/** The action for closing a resource. */
	private final Action closeAction;

	/**
	 * @return The action for closing a resource.
	 * @see #close
	 */
	public Action getCloseAction() {
		return closeAction;
	}

	/** The action for saving the resource. */
	private final Action saveAction;

	/**
	 * @return The action for saving the resource.
	 * @see #save
	 */
	public Action getSaveAction() {
		return saveAction;
	}

	/** The action for reverting a resource. */
	private final Action revertAction;

	/**
	 * @return The action for reverting a resource.
	 * @see #revert
	 */
	public Action getRevertAction() {
		return revertAction;
	}

	/** The component to serve as a parent for file dialogs. */
	private final Component parentComponent;

	/** @return The component to serve as a parent for file dialogs. */
	protected Component getParentComponent() {
		return parentComponent;
	}

	/** The implementation for selecting resources. */
	private final ResourceSelector resourceSelector;

	/** @return The implementation for selecting resources. */
	public ResourceSelector getResourceSelector() {
		return resourceSelector;
	}

	/** The maximum number of resources (>0) that can be loaded at one time, effectively determining SDI or MDI functionality. */
	private final int maxResourceCount;

	/** @return The maximum number of resources (>0) that can be loaded at one time, effectively determining SDI or MDI functionality. */
	public int getMaxResourceCount() {
		return maxResourceCount;
	}

	/** The list of all resources and corresponding views. */
	private final List resourceComponentStateList;

	/** @return The list of all resources and corresponding views. */
	protected final Iterable getResourceComponentStates() {
		return resourceComponentStateList;
	}

	/**
	 * Determines if a resource and component are already present for the given URI.
	 * @param uri The reference URI of the resource to retrieve.
	 * @return The component state of the resource, if that resource is already known, or null if no resource with that URI is present.
	 */
	protected ResourceComponentState getResourceComponentState(final URI uri) {
		for(final ResourceComponentState resourceComponentState : resourceComponentStateList) { //for each known resource component state; this is an expensive iterative search, but a simple map won't help because some resources may not yet have assigned URIs
			if(uri.equals(resourceComponentState.getResource().getURI())) { //if this resource has the requested URI
				return resourceComponentState; //return this resource and its component
			}
		}
		return null; //indicate that we could find no corresponding resource and component
	}

	/**
	 * Determines if a resource and component are present for the given component.
	 * @param component The component of the resource to retrieve.
	 * @return The component state of the resource, if there is a resource with the given component, or null if no resource with that component is
	 *         present.
	 */
	protected ResourceComponentState getResourceComponentState(final Component component) {
		for(final ResourceComponentState resourceComponentState : resourceComponentStateList) { //for each known resource component state; a map could be used to ameliorate this expensive operation, but component-changing functionality may be desired in the future
			if(component.equals(resourceComponentState.getComponent())) { //if this resource has the requested component
				return resourceComponentState; //return this resource and its component
			}
		}
		return null; //indicate that we could find no corresponding resource and component
	}

	/**
	 * Adds a resource and its corresponding view and fires an event.
	 * @param resourceComponentState The resource and component to add.
	 */
	protected void addResourceComponentState(final ResourceComponentState resourceComponentState) {
		resourceComponentStateList.add(resourceComponentState); //add the state to our list
		fireResourceComponentAdded(resourceComponentState); //indicate that the resource component state was added
	}

	/**
	 * Removes a resource and its corresponding view and fires an event.
	 * @param resourceComponentState The resource and component to remove.
	 */
	protected void removeResourceComponentState(final ResourceComponentState resourceComponentState) {
		resourceComponentStateList.remove(resourceComponentState); //remove the state from our list
		fireResourceComponentRemoved(resourceComponentState); //indicate that the resource component state was removed
	}

	/** The state of the resource and its view. */
	private ResourceComponentState resourceComponentState;

	/** @return The state of the resource and its view. */
	protected ResourceComponentState getResourceComponentState() {
		return resourceComponentState;
	}

	/**
	 * Sets the state of the resource and its view.
	 * 

* The component will be added appropriately to the parent component. *

* This is a bound property. * @param newResourceComponentState The new state of the resource and its view. * @see #getParentComponent() */ public void setResourceComponentState(final ResourceComponentState newResourceComponentState) { final ResourceComponentState oldResourceComponentState = resourceComponentState; //get the old value if(oldResourceComponentState != newResourceComponentState) { //if the value is really changing if(oldResourceComponentState != null && oldResourceComponentState.getComponent() instanceof Modifiable) { //if the old component was modifiable oldResourceComponentState.getComponent().removePropertyChangeListener(Modifiable.MODIFIED_PROPERTY, getUpdateStatusModifiedPropertyChangeListener()); //remove our modifiable listener } resourceComponentState = newResourceComponentState; //update the value if(newResourceComponentState != null && newResourceComponentState.getComponent() instanceof Modifiable) { //if the new component is modifiable newResourceComponentState.getComponent().addPropertyChangeListener(Modifiable.MODIFIED_PROPERTY, getUpdateStatusModifiedPropertyChangeListener()); //listen for component modifications } updateStatus(); //update the status of the actions //show that the selected resource has changed fireResourceComponentSelected(oldResourceComponentState, newResourceComponentState); } } /** The listener that updates the status when the component is modified. */ private final PropertyChangeListener updateStatusModifiedPropertyChangeListener; /** @return The listener that updates the status when the component is modified. */ protected PropertyChangeListener getUpdateStatusModifiedPropertyChangeListener() { return updateStatusModifiedPropertyChangeListener; } /** The number of resources that have been added; used for generating a unique ID. */ private long resourceCount = 0; /** @return A unique number representing the number of resources added. */ protected synchronized long getNextResourceCount() { return ++resourceCount; //increase the resource count and return it } /** * Parent component and resource selector constructor with unlimited resources (as many as an integer can hold) managed. * @param parentComponent The component to serve as a parent for error messages. * @param resourceSelector The implementation to use for selecting resources. */ public ResourceComponentManager(final Component parentComponent, final ResourceSelector resourceSelector) { this(parentComponent, resourceSelector, Integer.MAX_VALUE); //use the maximum number of resources } /** * Parent component and resource selector constructor * @param parentComponent The component to serve as a parent for error messages. * @param resourceSelector The implementation to use for selecting resources. * @param maxResourceCount The maximum number of resources (>0) that can be loaded at one time, effectively determining SDI or MDI functionality. */ public ResourceComponentManager(final Component parentComponent, final ResourceSelector resourceSelector, final int maxResourceCount) { this.parentComponent = parentComponent; //save the parent component this.resourceSelector = resourceSelector; //save the resource selector this.maxResourceCount = maxResourceCount; //save the maximum resource count resourceComponentStateList = new ArrayList(); //create the list of resource component states; we can't use a map, because many of the states may not yet have URIs assigned updateStatusModifiedPropertyChangeListener = new PropertyChangeListener() { //create a property change listener to update our status when modification occurs public void propertyChange(final PropertyChangeEvent propertyChangeEvent) { updateStatus(); } }; openAction = new OpenAction(); //create the open action closeAction = new CloseAction(); //create the close action closeAction.setEnabled(false); //the close action is disable by default, as there's nothing to close saveAction = new SaveAction(); //create the save action saveAction.setEnabled(false); //the save action is disable by default, as there's nothing to save revertAction = new RevertAction(); //create the revert action revertAction.setEnabled(false); //the revert action is disable by default, as there's nothing to revert try { resourceSelector.setPreferences(getPreferences()); //tell the resource selector to use the same preferences we use } catch(final SecurityException securityException) { //if we can't get preferences Log.warn(securityException); //don't do anything drastic } } /** Updates the states of the actions, including enabled/disabled status, proxied actions, etc. */ public void updateStatus() { final ResourceComponentState resourceComponentState = getResourceComponentState(); //get the current state of the resource component getCloseAction().setEnabled(resourceComponentState != null); //only enable the close action when there is a component open if(resourceComponentState != null) { //if we have a resource and its component final Component component = resourceComponentState.getComponent(); //get the resource component assert component != null : "No component associated with resource."; if(component instanceof Modifiable) { //if the component is modifiable final boolean isModified = ((Modifiable)component).isModified(); //see if the component has been modified getSaveAction().setEnabled(isModified); //only enable the save action when the component is modified getRevertAction().setEnabled(isModified); //only enable the revert action when the component is modified } else { //if the component is not modifiable getSaveAction().setEnabled(true); //always allow save getRevertAction().setEnabled(true); //always allow revert } } else { //if there is no resource and component getSaveAction().setEnabled(false); //there's nothing to save getRevertAction().setEnabled(false); //there's nothing to revert } } /** * Determines if all open resources and associated components can close. * @return true if all open resources and associated components can close. * @see #canClose(ResourceComponentState) */ public boolean canCloseAll() { //for a better user experience, check the current resource component state first final ResourceComponentState currentResourceComponentState = getResourceComponentState(); //get the current resource component state if(currentResourceComponentState != null && !canClose(currentResourceComponentState)) { //if we have a resource component state, see if we can close it return false; //show that the current resource cannot be closed } for(final ResourceComponentState resourceComponentState : getResourceComponentStates()) { //for each resource component state //if this isn't the current resource (which we already checked), see if it can be closed if(resourceComponentState != currentResourceComponentState && !canClose(resourceComponentState)) { return false; //show that this resource cannot be closed } } return true; //report that all open resources can be closed } /** * Determines if the current resource, if any, and its component can close. * @return true if the resource, if any, and its component can close. * @see #canClose(ResourceComponentState) */ /*TODO fix public boolean canClose() { final ResourceComponentState resourceComponentState=getResourceComponentState(); //get the current resource component state //if we have a resource component state, see if we can close it return resourceComponentState!=null? canClose(resourceComponentState) : true; } */ /** * Determines if a resource and its component can close. This verion asks the resource component if it can close, if that component implements * CanClosable. * @param resourceComponentState The state information of the resource that should be checked for closing. * @return true if the resource and its component can close. * @see CanClosable#canClose() */ protected boolean canClose(final ResourceComponentState resourceComponentState) { final Component component = resourceComponentState.getComponent(); //get the resource component //if the component can be checked for closing, and it doesn't wish to close if(component instanceof CanClosable && !((CanClosable)component).canClose()) { return false; //the component doesn't want to close for some reason } if(component instanceof Modifiable && ((Modifiable)component).isModified()) { //if the component says it can close, but it has been modified setResourceComponentState(resourceComponentState); //switch to this resource final R resource = resourceComponentState.getResource(); //get the resource final String resourceURIString = resource.getURI() != null ? resource.getURI().toString() : ""; //get a string representing the resource URI, if there is one //see if they want to save the changes switch(BasicOptionPane.showConfirmDialog(component, "Save modified resource " + resourceURIString + "?", "Resource Modified", BasicOptionPane.YES_NO_CANCEL_OPTION)) { //TODO i18n case BasicOptionPane.YES_OPTION: //if they want to save the changes return save(resourceComponentState); //save the selected resource and report whether the save was successful case BasicOptionPane.NO_OPTION: //if they do not want to save the changes return true; //allow the resource to close default: //if they want to cancel (they pressed the cancel button *or* they just hit Esc) return false; //don't allow the resource to close } } return true; //default to allowing the component to be closed } /** * Ensures that less that maxResourceCount resources are loaded by closing resources as necessary. * @return true if the number of resources were successfully brought below the maximum. * @see #getMaxResourceCount() */ protected boolean ensureNotMaxCount() { while(resourceComponentStateList.size() >= getMaxResourceCount()) { //while there are too many resources loaded final ResourceComponentState resourceComponentState = resourceComponentStateList.get(0); //get the first resource if(canClose(resourceComponentState)) { //if we can close this resource close(resourceComponentState); //close the resource } else { //if we can't close the resource return false; //show that we can't ensure that enough resources are closed } } return true; //show that there are less than the maximum number of resources } /** * Unloads all open resources, if any. If no resource is open, no action occurs. * @see #getResourceComponentState() */ /*TODO fix public void closeAll() { final ResourceComponentState resourceComponentState=getResourceComponentState(); //get the current resource component state if(resourceComponentState!=null) { //if a resource is open close(resourceComponentState); //close this resource component state } } */ /** * Unloads the open resource, if any, after checking to see if the resource can close. If no resource is open, no action occurs. * @return true if there was a resource to close and the operation was not canceled. * @see #getResourceComponentState() */ public boolean close() { final ResourceComponentState resourceComponentState = getResourceComponentState(); //get the current resource component state if(resourceComponentState != null) { //if a resource is open if(canClose(resourceComponentState)) { //if we can close the open resource close(resourceComponentState); //close this resource component state } } return false; //show that there was no resource to close, or closing was canceled /*TODO decide which semantics we like final ResourceComponentState resourceComponentState=getResourceComponentState(); //get the current resource component state if(resourceComponentState!=null) { //if a resource is open close(resourceComponentState); //close this resource component state } */ } /** * Closes the given resource, selecting a new resource if the closed resource was the selected resource. * @param oldResourceComponentState The state information of the resource that should be closed. */ protected void close(final ResourceComponentState oldResourceComponentState) { final int oldIndex = resourceComponentStateList.indexOf(oldResourceComponentState); //see where the resource component state is in our list assert oldIndex >= 0 : "Unrecognized resource component state."; final int newIndex = oldIndex < resourceComponentStateList.size() - 1 ? oldIndex : oldIndex - 1; //if the last resource was removed, move back one, which may return a negative index if there are no more resource component states final ResourceComponentState selectedResourceComponentState = getResourceComponentState(); //see which resource is selected removeResourceComponentState(oldResourceComponentState); //remove this resource component state if(oldResourceComponentState == selectedResourceComponentState) { //if the closed resource was previously selected, switch to a different resource final ResourceComponentState newResourceComponentState = newIndex >= 0 ? resourceComponentStateList.get(newIndex) : null; //get the new resource component state, if there is one left setResourceComponentState(newResourceComponentState); //switch to the new resource } } /** * Opens a resource. * @return true if the resource was successfully opened, or falseif the operation was canceled. * @see #open(Resource) */ public boolean open() { return open((R)null); //open without yet knowing which resource to open } /** * Opens a resource from the location specified. * @param referenceURI The URI of the resource to open. * @return true if the resource was successfully opened, or falseif the operation was canceled. */ public boolean open(final URI referenceURI) { try { final R resource = getResourceSelector().getResource(referenceURI); //get a description of the resource final Cursor originalCursor = Components.setPredefinedCursor(getParentComponent(), Cursor.WAIT_CURSOR); //change the cursor try { return open(resource); //open the resource } finally { getParentComponent().setCursor(originalCursor); //always change the component's cursor back to normal } } catch(final IOException ioException) { //if there is an error opening the resource AbstractSwingApplication.displayApplicationError(getParentComponent(), ioException); //display the error to the user } return false; //show that we couldn't open anything, for some reason } /** * Opens the specified resource. * @param resource The resource to open, or null if a resource should be chosen. * @return true if the resource was successfully opened, or falseif the operation was canceled. * @see ResourceSelector#selectInputResource(Resource) * @see #setResourceComponentState(ResourceComponentState) */ public boolean open(R resource) { //TODO test; this is being made public to retrofit the old MentoractTeacherPanel; tread carefully, lest we corrupt the API needlessly try { if(resource == null) { //if no resource was indicated final ResourceComponentState resourceComponentState = getResourceComponentState(); //get the current resource component state //ask for a resource for input resource = getResourceSelector().selectInputResource(resourceComponentState != null ? resourceComponentState.getResource() : null); } if(resource != null) { //if we now have a valid resource assert resource.getURI() != null : "Selected resource has no URI."; final ResourceComponentState existingResourceComponentState = getResourceComponentState(resource.getURI()); //see if there is already a resource with that URI if(existingResourceComponentState != null) { //if we already know about that resource setResourceComponentState(existingResourceComponentState); //switch to the existing resource component state } else { //if this is a different resource if(ensureNotMaxCount()) { //make sure there aren't too many resources open already; if we can ensure that final ResourceComponentState newResourceComponentState = read(resource); //try to open the resource if(newResourceComponentState != null) { //if we succeed in opening the resource setResourceComponentState(newResourceComponentState); //change to the new state return true; //show that we successfully opened the resource } } } } } catch(final SecurityException securityException) { //if there is an error selecting the resource AbstractSwingApplication.displayApplicationError(getParentComponent(), securityException); //display the error to the user } catch(final IOException ioException) { //if there is an error opening the resource AbstractSwingApplication.displayApplicationError(getParentComponent(), ioException); //display the error to the user } return false; //show that we couldn't open anything, for some reason } /** * Reads the specified resource. * @param resource The resource to open. * @return An object representing the opened resource and its state, or null if the process was canceled. * @throws IOException Thrown if there was an error reading the resource. */ protected ResourceComponentState read(final R resource) throws IOException { final Cursor oldCursor = setCursor(getParentComponent(), Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); //switch to the wait cursor on the parent component try { //get an input stream to the resource final InputStream inputStream = getResourceSelector().getInputStream(resource.getURI()); try { final Component component = read(resource, inputStream); //read the component from the input stream final ResourceComponentState resourceComponentState = new ResourceComponentState(resource, component, getNextResourceCount()); //create a new state for the resource addResourceComponentState(resourceComponentState); //add the resource component state return resourceComponentState; //return the component state } finally { inputStream.close(); //always close the input stream } } finally { setCursor(getParentComponent(), oldCursor); //always switch back to the original cursor } } /** * Reads a resource from an input stream. * @param resource The resource to open. * @param inputStream The input stream from which to read the data. * @throws IOException Thrown if there is an error reading the data. */ protected abstract Component read(final R resource, final InputStream inputStream) throws IOException; /** * Saves the current resource. *

* If the current resource component is verifiable, the component is first verified. *

*

* By default if no location is available, the saveAs method is called. If a location is available, save(Resource) is called. *

*

* For normal operation, this method should not be modified and save(Resource) should be overridden. *

* @return true if there was a resource to save and the operation was not canceled. * @see #save(Resource) * @see #saveAs * @see #getResourceComponentState() */ public boolean save() { final ResourceComponentState resourceComponentState = getResourceComponentState(); //get the current resource component state return resourceComponentState != null ? save(resourceComponentState) : false; //save the resource, returning false if there is no resource to save } /** * Saves the current resource. *

* If the current resource component is verifiable, the component is first verified. *

*

* By default if no location is available, the saveAs method is called. If a location is available, save(Resource) is called. *

*

* For normal operation, this method should not be modified and save(Resource) should be overridden. *

* @param resourceComponentState The state information of the resource to be saved. * @return true if the operation was not canceled. * @see #save(Resource) * @see #saveAs * @see #getResourceComponentState() */ protected boolean save(final ResourceComponentState resourceComponentState) { //if the component is verifiable, make sure it verifies before we save the contents if(!(resourceComponentState.getComponent() instanceof Verifiable) || ((Verifiable)resourceComponentState.getComponent()).verify()) { assert resourceComponentState.getResource() != null : "Resource component state does not represent a valid resource."; if(resourceComponentState.getResource().getURI() != null) { //if we have a URI try { write(resourceComponentState.getResource(), resourceComponentState.getComponent()); //save using the resource we already have return true; } catch(IOException ioException) { //if there is an error saving the resource AbstractSwingApplication.displayApplicationError(getParentComponent(), ioException); //display the error to the user } } else { //if we don't have a URI return saveAs(resourceComponentState); //save with a user-specified URI } } return false; //show that we couldn't save the contents because the component didn't verify or there was some other error } /** * Saves the current resource after first asking the user for a URI. * @return true if there was a resource to save and the operation was not canceled. */ public boolean saveAs() { final ResourceComponentState resourceComponentState = getResourceComponentState(); //get the current resource component state if(resourceComponentState != null) { //if a resource is open return saveAs(resourceComponentState); //save the resource using a user-specified URI } else //if we have no resource return false; //show that we couldn't save the resource } /** * Saves the open resource after first asking for a URI. * @param resourceComponentState The state information of the resource to save. * @return true if the operation was not canceled. * @see #setResourceComponentState(ResourceComponentState) */ protected boolean saveAs(final ResourceComponentState resourceComponentState) { try { final R resource = getResourceSelector().selectOutputResource(resourceComponentState.getResource()); //get the resource to use for saving if(resource != null) { //if a valid resource was returned write(resource, resourceComponentState.getComponent()); //save the resource resourceComponentState.setResource(resource); //change the resource of the component state /*TODO del if not needed if(!ObjectUtilities.equals(resourceComponentState.getResource().getReferenceURI(), uri)) { //if the URI isn't the same //TODO fix or delete comment: wasn't updated (e.g. the overridden saveFile() didn't call the version in this class) resourceComponentState.getResource().setReferenceURI(uri); //update the resource description's URI } */ //TODO fix or del setFile(file); //update the file, just in case they override saveFile() and don't call this version return true; //show that the resource was successfully saved } } catch(final SecurityException securityException) { //if there is an error selecting the resource AbstractSwingApplication.displayApplicationError(getParentComponent(), securityException); //display the error to the user } catch(IOException ioException) { //if there is an error saving the resource AbstractSwingApplication.displayApplicationError(getParentComponent(), ioException); //display the error to the user } return false; //show that we couldn't save the resource } /** * Writes a resource. * @param resource The resource to save. * @param component The component that contains the data to save. * @throws IOException Thrown if there was an error writing the resource. */ protected void write(final R resource, final Component component) throws IOException { final Cursor oldCursor = setCursor(getParentComponent(), Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); //switch to the wait cursor on the parent component try { final OutputStream outputStream = getResourceSelector().getOutputStream(resource.getURI()); //get an output stream to this resource's URI try { write(resource, component, outputStream); //write the component to the output stream } finally { outputStream.close(); //always close the output stream } } finally { setCursor(getParentComponent(), oldCursor); //always switch back to the original cursor } } /** * Saves a resource to an output stream. * @param resource The resource to save. * @param component The component that contains the data to save. * @param outputStream The input stream to which to write the data. * @throws IOException Thrown if there is an error writing the data. */ protected abstract void write(final R resource, final Component component, final OutputStream outputStream) throws IOException; /** * Reverts the open resource, if any. If no resource is open, no action occurs. * @see #getResourceComponentState() */ public void revert() { final ResourceComponentState resourceComponentState = getResourceComponentState(); //get the current resource component state if(resourceComponentState != null) { //if a resource is open //TODO del if not needed if(canClose(resourceComponentState)) //if we can close the open resource { revert(resourceComponentState); //revert this resource component state } } } /** * Reverts the given resource. This version does nothing. * @param resourceComponentState The state information of the resource that should be checked for closing. */ protected void revert(final ResourceComponentState resourceComponentState) { } /** * Adds a ResourceComponentListener to the list. * @param resourceComponentListener The ResourceComponentListener to be added. */ public void addResourceComponentListener(final ResourceComponentListener resourceComponentListener) { eventListenerList.add(ResourceComponentListener.class, resourceComponentListener); } /** * Removes a ResourceComponentListener from the list. * @param resourceComponentListener the listener to be removed */ public void removeActionListener(final ResourceComponentListener resourceComponentListener) { eventListenerList.remove(ResourceComponentListener.class, resourceComponentListener); } /** * Notifies all listeners that a resource component has been added. * @param resourceComponentState The resource and component added. */ protected void fireResourceComponentAdded(final ResourceComponentState resourceComponentState) { for(final Object listener : eventListenerList.getListeners(ResourceComponentListener.class)) { //for each resource component listener ((ResourceComponentListener)listener).onResourceComponentAdded(resourceComponentState); //notify this listener that this component state was added } } /** * Notifies all listeners that a resource component has been removed. * @param resourceComponentState The resource and component removed. */ protected void fireResourceComponentRemoved(final ResourceComponentState resourceComponentState) { for(final Object listener : eventListenerList.getListeners(ResourceComponentListener.class)) { //for each resource component listener ((ResourceComponentListener)listener).onResourceComponentRemoved(resourceComponentState); //notify this listener that this component state was removed } } /** * Notifies all listeners that a resource component has been selected. * @param oldResourceComponentState The previously selected resource and component. * @param newResourceComponentState The newly selected resource and component. */ protected void fireResourceComponentSelected(final ResourceComponentState oldResourceComponentState, final ResourceComponentState newResourceComponentState) { for(final Object listener : eventListenerList.getListeners(ResourceComponentListener.class)) { //for each resource component listener ((ResourceComponentListener)listener).onResourceComponentSelected(oldResourceComponentState, newResourceComponentState); //notify this listener that this component state was selected } } /** Action for creating a new file. */ public static class NewAction extends AbstractAction { /** Default constructor. */ public NewAction() { super("New..."); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "Create a new file "); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Create a new file."); //set the long description TODO i18n putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_N)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.NEW_ICON_FILENAME)); //load the correct icon putValue(ActionManager.MENU_ORDER_PROPERTY, new Integer(ActionManager.FILE_NEW_MENU_ACTION_ORDER)); //set the order } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { } } /** Action for opening a resource. */ class OpenAction extends AbstractAction { /** Default constructor. */ public OpenAction() { super("Open..."); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Open a resource"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Display a dialog to select a file, and then load the selected resource."); //set the long description TODO Int putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.OPEN_ICON_FILENAME)); //load the correct icon putValue(ActionManager.MENU_ORDER_PROPERTY, new Integer(ActionManager.FILE_OPEN_MENU_ACTION_ORDER)); //set the order } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { open(); //open a resource } } /** Action for closing a resource. */ protected class CloseAction extends AbstractAction { /** Default constructor. */ public CloseAction() { super("Close"); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Close the open resource"); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Close the currently open resource."); //set the long description TODO i18n putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_C)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.CLOSE_ICON_FILENAME)); //load the correct icon putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_F4, Event.CTRL_MASK)); //add the accelerator putValue(ActionManager.MENU_ORDER_PROPERTY, new Integer(ActionManager.FILE_CLOSE_MENU_ACTION_ORDER)); //set the order } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { close(); //close the resource } } /** Action for saving a resource. */ protected class SaveAction extends AbstractAction { /** Default constructor. */ public SaveAction() { super("Save"); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Save the open resource"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Save the currently open resource."); //set the long description TODO Int putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_V)); //set the mnemonic key; for some reason, 's' causes the action to be activated when Alt+F4 is pressed TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.SAVE_ICON_FILENAME)); //load the correct icon putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.CTRL_MASK)); //add the accelerator TODO i18n putValue(ActionManager.MENU_ORDER_PROPERTY, new Integer(ActionManager.FILE_SAVE_MENU_ACTION_ORDER)); //set the order } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { save(); //save the resource } } /** Action for reverting a book. */ protected class RevertAction extends AbstractAction { /** Default constructor. */ public RevertAction() { super("Revert"); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "Revert the open resource"); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Revert the currently open resource."); //set the long description TODO i18n putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_R)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.REDO_ICON_FILENAME)); //load the correct icon putValue(ActionManager.MENU_ORDER_PROPERTY, new Integer(ActionManager.FILE_REVERT_MENU_ACTION_ORDER)); //set the order } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { revert(); //revert the resource } } /** * A representation of a resource and its associated view. A resource component state always represents a valid resource and a valid component, although the * resource may be anonymous with no reference URI. * @param The type of resource the state of which is being stored. * @author Garret Wilson */ public class ResourceComponentState extends DefaultObjectState { /** The unique number representing the order created. */ private final long number; /** @return The unique number representing the order created. */ public long getNumber() { return number; } /** * @return The non-null resource being described by delegating to getObject(). * @see DefaultObjectState#getObject() */ public R getResource() { return getObject(); } /** * Sets the resource being described. *

* This method delegates to the parent class, and is declared here solely so that the component manager can change the resource represented. * @param resource The new resource to describe. * @throws NullPointerException Thrown if the resource is null. */ protected void setResource(final R resource) { super.setObject(resource); //set the resource object } /** The component that acts as a view to the resource. */ private Component component; /** @return The component that acts as a view to the resource. */ public Component getComponent() { return component; } /** * Constructs a resource state with a resource and a component. * @param resource The description of the resource. * @param component The component that represents a view of the resource. * @param number A unique number representing the order created. * @throws NullPointerException Thrown if the resource is null. */ public ResourceComponentState(final R resource, final Component component, final long number) { super(resource); //construct the parent class this.component = component; //save the resource component this.number = number; //save the number } /** @return A label representing this resource component. */ public String getLabel() { final URI uri = getResource().getURI(); //get the resource reference URI return uri != null ? getName(uri) : "Resource " + getNumber(); //return the URI filename if there is a URI; otherwise, generate a unique label TODO i18n } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy