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

com.globalmentor.swing.Book 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.*;
import java.awt.event.*; //TODO fix for image mouse clicks
import java.beans.*;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.prefs.Preferences;
import static java.util.Collections.*;
import javax.sound.sampled.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*; //TODO del when image click code is moved elsewhere

import com.globalmentor.io.*;
import com.globalmentor.log.Log;
import com.globalmentor.net.*;
import com.globalmentor.oebps.spec.OEBGuide;
import com.globalmentor.rdf.*;
import com.globalmentor.swing.event.*;
import com.globalmentor.swing.rdf.*;
import com.globalmentor.swing.text.*;
import com.globalmentor.swing.text.Annotation;
import com.globalmentor.swing.text.xml.*;
import com.globalmentor.swing.text.xml.xhtml.XHTMLSwingText;
import com.globalmentor.util.prefs.PreferencesUtilities;

/**
 * A component shows information in book form. Has an XMLTextPane as a child.
 * 

* Bound properties: *

*
*
ANTIALIAS_PROPERTY (Boolean)
*
Indicates the antialias setting has changed.
*
BOOKMARKS_PROPERTY (null)
*
Indicates the bookmarks have changed. Returns null for old and new values.
*
DISPLAY_PAGE_COUNT_PROPERTY (Integer)
*
Indicates the number of pages being displayed has changed.
*
HISTORY_INDEX_PROPERTY (Integer)
*
Indicates the history has changed. Returns the old and new history index.
*
ZOOM_PROPERTY (Float)
*
Indicates the zoom level has changed.
*
* @author Garret Wilson * @see javax.swing.JComponent * @see javax.swing.JPanel * @see com.globalmentor.swing.XMLTextPane */ public class Book extends ToolStatusPanel implements PageListener, AdjustmentListener, CaretListener, ProgressListener, HyperlinkListener, MouseListener //TODO testing MouseListener for image viewing { /** The property representing the antialias setting. */ public static final String ANTIALIAS_PROPERTY = "antialias"; /** The property representing bookmark changes. */ public static final String BOOKMARKS_PROPERTY = "bookmarks"; /** The property representing the display page count. */ public static final String DISPLAY_PAGE_COUNT_PROPERTY = "displayPageCount"; /** The property representing the history index. */ public static final String HISTORY_INDEX_PROPERTY = "historyIndex"; /** The property which stores the user data File object. */ //TODO del public static final String USER_DATA_FILE_PROPERTY="userData"; /** The property representing the zoom level. */ public static final String ZOOM_PROPERTY = "zoom"; /** The preference for storing the search text. */ protected final String SEARCH_TEXT_PREFERENCE = PreferencesUtilities.getPreferenceName(getClass(), "search.text"); /** The highlight painter used for displaying bookmark locations. */ protected static final BookmarkHighlightPainter bookmarkHighlightPainter = new BookmarkHighlightPainter(); /** * Keeps track of whether a mouse press or release was the popup trigger; if so, it disables the normal mouse click functionality from occurring. */ private boolean mousePressReleasePopupTrigger = false; /** The text pane used to display information. */ private final XMLTextPane xmlTextPane; /** @return The text pane used to display information. */ public XMLTextPane getXMLTextPane() { return xmlTextPane; } //TODO maybe later change this to protected access /** The scrollbar for showing the position within the book. */ private final JScrollBar scrollBar; /** * @return The scrollbar for showing the position within the book, or null if there is no scrollbar. */ protected JScrollBar getScrollBar() { return scrollBar; } /** * Sets the scrollbar for showing the position within the book. * @param newScrollBar The scrollbar to use for showing the book position. */ /*TODO del if not needed protected void setScrollBar(final JScrollBar newScrollBar) { if(scrollBar!=newScrollBar) { //if we're really changing scrollbars, if(scrollBar!=null) { // and we already had a scrollbar scrollBar.removeAdjustmentListener(this); //remove ourselves as an adjustment listener } scrollBar=newScrollBar; //change scrollbars newScrollBar.addAdjustmentListener(this); //tell the scrollbar we want to know when it changes } } */ /** The action for navigating to the previous page. */ private final Action previousPageAction; /** @return The action for navigating to the previous page. */ public Action getPreviousPageAction() { return previousPageAction; } /** The action for navigating to the next page. */ private final Action nextPageAction; /** @return The action for navigating to the next page. */ public Action getNextPageAction() { return nextPageAction; } /** The action for moving backwards in navigation history. */ private final Action backAction; /** @return The action for moving backwards in navigation history. */ public Action getBackAction() { return backAction; } /** The action for closing a book. */ private final Action closeAction; /** @return The action for closing a book. */ public Action getCloseAction() { return closeAction; } /** The action for copying text. */ private final Action copyAction; /** @return The action for copying text. */ public Action getCopyAction() { return copyAction; } /** The action for inserting a highlight. */ private final Action insertHighlightAction; /** * @return The action for inserting a highlight, which is updated to be enabled or disabled at the appropriate times based upon the selection. */ public Action getInsertHighlightAction() { return insertHighlightAction; } /** The action for searching. */ private final Action searchAction; /** @return The action for searching. */ public Action getSearchAction() { return searchAction; } /** The action for searching for the next occurrence of text. */ private final Action searchAgainAction; /** @return The action for searching for the next occurrence of text. */ public Action getSearchAgainAction() { return searchAgainAction; } /** The action for displaying the book properties. */ private final Action viewPropertiesAction; /** @return The action for displaying the book properties. */ public Action getViewPropertiesAction() { return viewPropertiesAction; } /** The action for displaying one page at a time. */ private final AbstractToggleAction displayOnePageAction; /** @return The action for displaying one page at a time. */ public AbstractToggleAction getDisplayOnePageAction() { return displayOnePageAction; } /** The action for displaying two pages at a time. */ private final AbstractToggleAction displayTwoPagesAction; /** @return The action for displaying two pages at a time. */ public AbstractToggleAction getDisplayTwoPagesAction() { return displayTwoPagesAction; } /** The action for inserting a bookmark at the current location. */ private final Action insertBookmarkAction; /** @return The action for inserting a bookmark at the current location. */ public Action getInsertBookmarkAction() { return insertBookmarkAction; } /** The array of default zoom-level actions. */ private final ZoomAction[] zoomActions; /** @return The array of default zoom-level actions. */ public ZoomAction[] getZoomActions() { return zoomActions; } /** The action for turning text smoothing on or off. */ private final AntialiasAction antialiasAction; /** @return The action for turning text smoothing on or off. */ public AntialiasAction getAntialiasAction() { return antialiasAction; } /** The current highlight color (defaults to yellow). */ private Color highlightColor = Color.yellow; /** @return The current highlight color. */ public Color getHighlightColor() { return highlightColor; } /** * Sets the current highlight color. * @param color The new highlight color. */ public void setHighlightColor(final Color color) { highlightColor = color; } /** Whether data saved in the user data file, such as bookmarks and annotations, have been modified. */ private boolean userDataModified = false; /** @return Whether data saved in the user data file, such as bookmarks and annotations, have been modified. */ public boolean isUserDataModified() { return userDataModified; } /** * The map of highlight tags for the book, keyed to bookmarks. This map therefore serves as both a definitive list of bookmarks and a map of highlights which * correspond to those bookmarks. This tree map ensures that the key set will always be in natural bookmark order, from lowest to highest. */ //TODO maybe make this a bound property private final Map bookmarkHighlightTagMap; /** @return The list of bookmarks in the book. */ //TODO maybe later just make an Iterator available //TODO del protected List getBookmarkList() {return bookmarkList;} /** Adds an unnamed bookmark at the current location in the document. */ public void addBookmark() { addBookmark((String)null); //add a bookmark with no name } /** * Adds a bookmark with a specified name at the current location in the document. * @param name The name to give the bookmark, or null if the bookmark should not be given a name. */ public void addBookmark(final String name) { Log.trace(); //TODO del int offset = -1; //we'll store the offset here at which the bookmark should be inserted //see if there is a selection final boolean isSelection = getXMLTextPane().getSelectionEnd() > getXMLTextPane().getSelectionStart(); if(isSelection) { //if there is a selection offset = getXMLTextPane().getSelectionStart(); //we'll put the bookmark at the start of the selection } else { //if nothing is selected, put the bookmark at the start of the current page final int pageIndex = getPageIndex(); //get our current page index if(pageIndex != -1) { //if we have a valid page index offset = getXMLTextPane().getPageStartOffset(pageIndex); //get the starting offset of our page } } if(offset >= 0) { //if we found a valid offset try { addBookmark(name, offset); //add a bookmark with this name at this offset } catch(BadLocationException badLocationException) { //we should never have a bad location throw (AssertionError)new AssertionError(badLocationException.getMessage()).initCause(badLocationException); } } } /** * Adds a bookmark with a specified name at the specified location in the document. * @param name The name to give the bookmark, or null if the bookmark should not be given a name. * @param offset The position in the document at which the bookmark should be added. * @throws BadLocationException Thrown if the position represents an invalid location in the document */ public void addBookmark(final String name, final int offset) throws BadLocationException { final Bookmark bookmark = new Bookmark(name, getXMLTextPane().getDocument(), offset); //create a bookmark at the beginning of our first page addBookmark(bookmark); //add the bookmark } /** * Adds a bookmark to the book, attaching it at its specified offset if the bookmark isn't already attached to the document. If the bookmark already is added, * no action will take place. * @param bookmark The bookmark to add. * @throws BadLocationException Thrown if the bookmark represents an invalid location in the document */ public void addBookmark(final Bookmark bookmark) throws BadLocationException { if(bookmarkHighlightTagMap.get(bookmark) == null) { //if this bookmark isn't already added if(!bookmark.isAttached()) //if the bookmark isn't attached to the document bookmark.attach(getXMLTextPane().getDocument()); //attach the bookmark to the document //add a bookmark highlighter to the text pane to show the bookmark at the correct location final Object bookmarkHighlight = getXMLTextPane().getHighlighter().addHighlight(bookmark.getOffset(), bookmark.getOffset() + 1, bookmarkHighlightPainter); Log.trace("bookmark highlight returned is: ", bookmarkHighlight); //TODO del bookmarkHighlightTagMap.put(bookmark, bookmarkHighlight); //add the bookmark highlight to the map, keyed to the bookmark Log.trace("key set: ", bookmarkHighlightTagMap.keySet()); Log.trace("values: ", bookmarkHighlightTagMap.values()); Log.trace("after putting bookmark, found tag: ", bookmarkHighlightTagMap.get(bookmark)); Log.trace("contains bookmark key: ", new Boolean(bookmarkHighlightTagMap.containsKey(bookmark))); //TODO del Log.trace("contains value key: ", new Boolean(bookmarkHighlightTagMap.containsKey(bookmarkHighlight))); Log.trace("contains value: ", new Boolean(bookmarkHighlightTagMap.containsValue(bookmarkHighlight))); userDataModified = true; //show that the user data has been modified firePropertyChange(BOOKMARKS_PROPERTY, null, null); //fire an event showing that the bookmarks have changed } } /** * Removes a bookmark from the book, if the book currently has the bookmark. * @param bookmark The bookmark to remove. */ public void removeBookmark(final Bookmark bookmark) { Log.trace("removing bookmark: ", bookmark); final Object bookmarkHighlightTag = bookmarkHighlightTagMap.get(bookmark); //get the highlight for this bookmark Log.trace("found highlight tag: ", bookmarkHighlightTag); if(bookmarkHighlightTag != null) { //if we have a highlight for this bookmark Log.trace("ready to remove tag from map"); bookmarkHighlightTagMap.remove(bookmark); //remove the bookmark and highlight tag from the map getXMLTextPane().getHighlighter().removeHighlight(bookmarkHighlightTag); //remove the highlight itself userDataModified = true; //show that the user data has been modified Log.trace("ready to fire property change"); firePropertyChange(BOOKMARKS_PROPERTY, null, null); //fire an event showing that the bookmarks have changed } } /** * Finds and returns the last bookmark nearest the given position. * @param offset The position in a document for which a bookmark should be returned. * @return The last bookmark at the given position, or null if there are no bookmarks at the given position. * @see Bookmark#contains */ public Bookmark getBookmark(final int offset) { Bookmark bookmark = null; //show that we haven't yet found a bookmark final Iterator iterator = getBookmarkIterator(); //get an iterator to search the bookmarks while(iterator.hasNext()) { //while there are more bookmarks final Bookmark currentBookmark = (Bookmark)iterator.next(); //get the next bookmark //TODO del when works if(currentBookmark.getOffset()==offset) //if this bookmark starts at the offset if(currentBookmark.contains(offset)) //if this bookmark starts at the offset bookmark = currentBookmark; //store the bookmark and look for another one } return bookmark; //return the last matching bookmark, or null if there was no match } /** Removes all bookmarks from the book. */ public void clearBookmarks() { bookmarkHighlightTagMap.clear(); //clear the bookmarks and their corresponding highlights TextComponents.removeHighlights(getXMLTextPane(), bookmarkHighlightPainter); //remove all bookmark highlights TODO it would probably be better to remove them one at a time with the highlight tag userDataModified = true; //show that the user data has been modified firePropertyChange(BOOKMARKS_PROPERTY, null, null); //fire an event showing that the bookmarks have changed } /** @return A read-only iterator of all available bookmarks in natural order. */ public Iterator getBookmarkIterator() { return unmodifiableSet(bookmarkHighlightTagMap.keySet()).iterator(); //return a read-only iterator to the bookmarks (the keys are already sorted because they are stored in a TreeMap) } /** * Creates an action for navigating to a specific bookmark. * @param bookmark The bookmark for which an action should be created. * @return A new action representing the given bookmark. */ public Action createGoBookmarkAction(final Bookmark bookmark) { return new GoBookmarkAction(bookmark); //create and return a new action to represent the bookmark } /** * Creates an action for navigating to a specific guide. * @param guide The guide for which an action should be created. * @return A new action representing the given guide. */ public Action createGoGuideAction(final OEBGuide guide) { return new GoGuideAction(guide); //create and return a new action to represent the guide } /** * The map of highlight tags for the book, keyed to annotations. This map therefore serves as both a definitive list of annotations and a map of highlights * which correspond to those annotations. This tree map ensures that the key set will always be in natural annotation order, from lowest to highest. */ private final Map annotationHighlightTagMap; /** * Adds an annotation with a specified starting and ending offsets in the document. * @param startOffset The position in the document at which the annotation should be added. * @param endOffset The position in the document which indicates the end of the annotation. * @param color The highlight color of the annotation. * @throws BadLocationException Thrown if the position represents an invalid location in the document */ public void addAnnotation(final int startOffset, final int endOffset, final Color color) throws BadLocationException { final Annotation annotation = new Annotation(getXMLTextPane().getDocument(), startOffset, endOffset, color); //create a bookmark at the beginning of our first page addAnnotation(annotation); //add the annotation } /** * Adds an annotation to the book, attaching it at its specified offsets if the annotation isn't already attached to the document. If the annotation already * is added, no action will take place. * @param annotation The annotation to add. * @throws BadLocationException Thrown if the bookmark represents an invalid location in the document */ public void addAnnotation(final Annotation annotation) throws BadLocationException { if(annotationHighlightTagMap.get(annotation) == null) { //if this annotation isn't already added if(!annotation.isAttached()) //if the annotation isn't attached to the document annotation.attach(getXMLTextPane().getDocument()); //attach the annotation to the document final Highlighter.HighlightPainter annotationHighlightPainter = new DefaultHighlighter.DefaultHighlightPainter(annotation.getColor()); //TODO testing; comment //add an annotation highlighter to the text pane to show the annotation at the correct location final Object annotationHighlight = getXMLTextPane().getHighlighter().addHighlight(annotation.getStartOffset(), annotation.getEndOffset(), annotationHighlightPainter); annotationHighlightTagMap.put(annotation, annotationHighlight); //add the annotation highlight to the map, keyed to the annotation userDataModified = true; //show that the user data has been modified //TODO fix firePropertyChange(BOOKMARKS_PROPERTY_NAME, null, null); //fire an event showing that the bookmarks have changed } } /** * Removes an annotation from the book, if the book currently has the annotation. * @param annotation The annotation to remove. */ public void removeAnnotation(final Annotation annotation) { final Object annotationHighlightTag = annotationHighlightTagMap.get(annotation); //get the highlight for this annotation if(annotationHighlightTag != null) { //if we have a highlight for this annotation annotationHighlightTagMap.remove(annotation); //remove the annotation and highlight tag from the map getXMLTextPane().getHighlighter().removeHighlight(annotationHighlightTag); //remove the highlight itself userDataModified = true; //show that the user data has been modified //TODO fix firePropertyChange(BOOKMARKS_PROPERTY_NAME, null, null); //fire an event showing that the bookmarks have changed } } /** * Finds and returns the last annotation containing given position. * @param offset The position in a document for which an annotation should be returned. * @return The last annotation containing the given position, or null if no annotations contain at the given position. * @see Bookmark#contains */ public Annotation getAnnotation(final int offset) { Annotation annotation = null; //show that we haven't yet found a annotation final Iterator iterator = getAnnotationIterator(); //get an iterator to search the annotations while(iterator.hasNext()) { //while there are more annotations final Annotation currentAnnotation = (Annotation)iterator.next(); //get the next annotation if(currentAnnotation.contains(offset)) //if this annotation contains the given offset annotation = currentAnnotation; //store the annotation and look for another one } return annotation; //return the last matching annotation, or null if there was no match } /** Removes all annotations from the book. */ public void clearAnnotations() { final Set annotationSet = annotationHighlightTagMap.keySet(); //get the set of annotations //convert the annotation set to an array so we will have a local copy as we remove the annotations final Annotation[] annotationArray = (Annotation[])annotationSet.toArray(new Annotation[annotationSet.size()]); for(int i = annotationArray.length - 1; i >= 0; --i) //look at each annotation removeAnnotation(annotationArray[i]); //remove this annotation userDataModified = true; //show that the user data has been modified } /** @return A read-only iterator of all available annotations in natural order. */ public Iterator getAnnotationIterator() { return unmodifiableSet(annotationHighlightTagMap.keySet()).iterator(); //return a read-only iterator to the annotations (the keys are already sorted because they are stored in a TreeMap) } /** * @return The URI of the loaded publication or file, or null if there is no file loaded. */ public URI getURI() { //TODO del; now close() removes the base URI, making this next line work return DocumentUtilities.getBaseURI(getXMLTextPane().getDocument()); //get the base URI of the document return getXMLTextPane().getBaseURI(); //get the base URI property value } /** * @return The RDF data model, if this book's text pane has an XML document with RDF metadata. * @return The RDF data model associated with the book, or null if there is no RDF metadata. // TODO this will eventually probably go somewhere * else, when this turns into an XMLReader component or something; we'll probably store the publication in a property */ public RDFModel getRDF() { final Document document = getXMLTextPane().getDocument(); //get the document associated with the text pane if(document instanceof BasicStyledDocument) //if the document is a basic styled document return ((BasicStyledDocument)document).getRDF(); //get the RDF data model from the XML document TODO why don't we get the property value directly? return null; //show that we could not find an RDF data model } /** * @return The publication description associated with the document, if this book's text pane has a document with a publication description. * @return The publication description associated with the book, or null if there is none. */ public RDFResource getPublication() { final Document document = getXMLTextPane().getDocument(); //get the document associated with the text pane if(document instanceof XMLDocument) //if the document is an XML document return ((XMLDocument)document).getPublication(); //get the OEB publication object from the OEB document TODO why don't we get the property value directly? return null; //show that we could not find a publication } /** * @return The file object representing the user data file associated with the loaded publication or file, or null if there is no user data file. * @see #USER_DATA_FILE_PROPERTY */ /*TODO del if not needed public File getUserDataFile() { final Object userDataFile=getXMLTextPane().getDocument().getProperty(USER_DATA_FILE_PROPERTY); //get the user data file from the document return userDataFile instanceof File ? (File)userDataFile : null; //return the file, if that's really what it is; otherwise, return null } */ /** * @return The file object representing the user data file associated with the loaded publication or file, or null if there is no user data file. * @see #getURI() */ public File getUserDataFile() { final URI uri = getURI(); //get our current URI //if the URI specifies a file, we can have a user data file if(uri != null && URIs.FILE_SCHEME.equals(uri.getScheme())) { final File file = new File(uri); //create a file from the URI //create a userdata filename with ".userdata.xml" appended final File userDataFile = new File(file.getParent(), file.getName() + Files.FILENAME_EXTENSION_SEPARATOR + "bookuserdata" + Files.FILENAME_EXTENSION_SEPARATOR + "xml"); return userDataFile; //return the user data file } else { //if there is no URI return null; //there is no user data file } } /** * @return The number of pages to display at a time. * @see XMLTextPane#getDisplayPageCount * @see XMLPagedView#getDisplayPageCount */ public int getDisplayPageCount() { return getXMLTextPane().getDisplayPageCount(); } /** * Sets the number of pages to display at a time. This is a bound property. * @param newDisplayPageCount The new number of pages to display at a time. * @see XMLTextPane#setDisplayPageCount * @see XMLPagedView#setDisplayPageCount */ public void setDisplayPageCount(final int newDisplayPageCount) { final int oldDisplayPageCount = getXMLTextPane().getDisplayPageCount(); //get the current display page count if(oldDisplayPageCount != newDisplayPageCount) { //if the display page count is really changing getXMLTextPane().setDisplayPageCount(newDisplayPageCount); //tell the text pane the number of pages do be displayed firePropertyChange(DISPLAY_PAGE_COUNT_PROPERTY, oldDisplayPageCount, newDisplayPageCount); //show that the display page count changed //TODO it would probably be better if we listened for the display page count changing, although we would still have to initialize with the correct value } if(newDisplayPageCount == 1) //update the actions in response to the new value getDisplayOnePageAction().setSelected(true); else if(newDisplayPageCount == 2) getDisplayTwoPagesAction().setSelected(true); } /** @return Whether text is antialiased. */ public boolean isAntialias() { return getXMLTextPane().isAntialias(); } /** * Sets whether text is antialiased. * @param newAntialias Whether text should be antialias. */ public void setAntialias(final boolean newAntialias) { final boolean oldAntialias = getXMLTextPane().isAntialias(); //get the current value if(oldAntialias != newAntialias) { //if the value is really changing getXMLTextPane().setAntialias(newAntialias); //change the value firePropertyChange(ANTIALIAS_PROPERTY, oldAntialias, newAntialias); //show that the antialias setting has changed } getAntialiasAction().setSelected(newAntialias); //show whether or not antialias is now turned on } /** @return The value by which text should be zoomed, such as 1.00. */ public float getZoom() { return getXMLTextPane().getZoom(); } /** * Sets the factor by which text should be zoomed. This is a bound property. * @param newZoom The amount by which normal text should be multiplied. */ public void setZoom(final float newZoom) { final float oldZoom = getXMLTextPane().getZoom(); //get the current value if(oldZoom != newZoom) { //if the zoom is really changing getXMLTextPane().setZoom(newZoom); //change the zoom firePropertyChange(ZOOM_PROPERTY, oldZoom, newZoom); //show that the zoom has changed } //make sure the correct zoom action is selected (the zoom action selection can initially be out of synch even if the zoom level hasn't changed) final ZoomAction[] zoomActions = getZoomActions(); //get the zoom actions for(int i = zoomActions.length - 1; i >= 0; --i) { //look at all our zoom actions final ZoomAction zoomAction = zoomActions[i]; //look at this zoom action if(zoomAction.getZoom() == newZoom) { //if this action represents the new value zoomAction.setSelected(true); //show that this action is now selected break; //we found a matching zoom action; there should only be one, so don't look further } } } //history //TODO probably make something to limit the size of the history list //TODO don't forget to clear the history list when a new document is loaded //TODO tie the history to a property /** The list of history positions. */ private java.util.List historyList; /** The index of the next history index to populate. */ private int historyIndex = 0; /** * @return The index representing the next history index to populate; also represents the number of previous history items available. */ public int getHistoryIndex() { return historyIndex; } /** * Updates the history index and fires a property changed event. * @param newHistoryIndex The new history index. */ private void setHistoryIndex(final int newHistoryIndex) { final int oldHistoryIndex = getHistoryIndex(); //get the old history index if(newHistoryIndex != oldHistoryIndex) { //if the history index is really changing historyIndex = newHistoryIndex; //update the history index getBackAction().setEnabled(hasPreviousHistory()); //only enable the back button if there is previous history firePropertyChange(HISTORY_INDEX_PROPERTY, oldHistoryIndex, newHistoryIndex); //fire an event showing that the property changed } } /** @return Whether or not there is past history that can be visited. */ public boolean hasPreviousHistory() { return getHistoryIndex() > 0; } /** @return Whether or not there is next history that can be visited. */ public boolean hasNextHistory() { return getHistoryIndex() < historyList.size(); } /** * Returns the previous history and moves the history index back one. * @return The previous history position, or null if not available. */ protected Position decrementHistory() { if(hasPreviousHistory()) { //if there is previous history final int previousHistoryIndex = getHistoryIndex() - 1; //get the previous history index final Position position = (Position)historyList.get(previousHistoryIndex); //get the previous history position setHistoryIndex(previousHistoryIndex); //decrement the history index, firing a property changed event return position; //return the position } else //if there is no previous history return null; //show that there is no previous history } /** * Returns the next history and moves the history index forward one. * @return The next history position, or null if not available. */ protected Position incrementHistory() { if(hasNextHistory()) { //if there is next history final int historyIndex = getHistoryIndex(); //get the current history index final Position position = (Position)historyList.get(historyIndex); //get the item at the current history index setHistoryIndex(historyIndex + 1); //increment the history index, firing a property changed event return position; //return the position } else //if there is no next history return null; //show that there is no next history } /** * Adds a history position to the list. If there were future history positions, they are removed. */ protected void addHistory(final Position position) { //make sure all history positions after and including the current history index are removed for(int i = historyList.size(); i > historyIndex; historyList.remove(--i)) ; historyList.add(position); //add this position to our history list setHistoryIndex(getHistoryIndex() + 1); //increment our history index } /** Stores the current position in the history list. */ protected void storePositionHistory() { Log.trace(); //TODO del final int pageIndex = getPageIndex(); //get our current page index if(pageIndex != -1) { //if we have a valid page index //TODO del Log.trace("pageIndex: "+pageIndex); final int offset = getXMLTextPane().getPageStartOffset(pageIndex); //get the starting offset of our page //TODO del Log.trace("offset: "+offset); //TODO del Log.trace(""+getXMLTextPane().getDocument().getStartPosition()); //TODO testing try { //TODO del Log.trace("Before creating position from offset: "+offset+" document length: "+getXMLTextPane().getDocument().getLength()); final Position position = getXMLTextPane().getDocument().createPosition(offset); //create a position from our offset //TODO del Log.trace("After creating position from offset: "+offset); addHistory(position); //add this position to our history list } catch(BadLocationException e) { //we should never have a bad location Log.error(e); //report the error } } } /** The implementation to use for retrieving an input stream to a URI. */ private URIInputStreamable uriInputStreamable; /** @return The implementation to use for retrieving an input stream to a URI. */ public URIInputStreamable getURIInputStreamable() { return uriInputStreamable; } /** * Sets the implementation to use for retrieving an input stream to a URI. * @param inputStreamable The implementation to use for accessing a URI for input. */ public void setURIInputStreamable(final URIInputStreamable inputStreamable) { uriInputStreamable = inputStreamable; } /** The implementation to use for retrieving an output stream to a URI. */ private URIOutputStreamable uriOutputStreamable; /** @return The implementation to use for retrieving an output stream to a URI. */ public URIOutputStreamable getURIOutputStreamable() { return uriOutputStreamable; } /** * Sets the implementation to use for retrieving an output stream to a URI. * @param outputStreamable The implementation to use for accessing a URI for output. */ public void setURIOutputStreamable(final URIOutputStreamable outputStreamable) { uriOutputStreamable = outputStreamable; } /** Default constructor which displays two pages. */ public Book() { this(2); //default to showing two pages } /** * Constructs a new book with the specified number of pages displayed. * @param displayPageCount The number of pages to display. */ public Book(final int displayPageCount) { super(new XMLTextPane(), true, true, false); //construct the parent class, but don't initialize the book uriInputStreamable = DefaultURIAccessible.getDefaultURIAccessible(); //start with a default method of getting input streams uriOutputStreamable = DefaultURIAccessible.getDefaultURIAccessible(); //start with a default method of getting output streams xmlTextPane = (XMLTextPane)getContentComponent(); //store the text pane for use in the future (it will be used by setDisplayPageCount()) previousPageAction = new PreviousPageAction(); nextPageAction = new NextPageAction(); backAction = new BackAction(); closeAction = new CloseAction(); copyAction = new CopyAction(); insertHighlightAction = new InsertHighlightAction(); searchAction = new SearchAction(); searchAgainAction = new SearchAgainAction(); viewPropertiesAction = new ViewPropertiesAction(); displayOnePageAction = new DisplayPageCountAction(1); displayTwoPagesAction = new DisplayPageCountAction(2); insertBookmarkAction = new InsertBookmarkAction(); zoomActions = new ZoomAction[] { new ZoomAction(0.25f), new ZoomAction(0.50f), new ZoomAction(0.60f), new ZoomAction(0.70f), new ZoomAction(0.80f), new ZoomAction(0.90f), new ZoomAction(1.00f), new ZoomAction(1.10f), new ZoomAction(1.20f), new ZoomAction(1.30f), new ZoomAction(1.40f), new ZoomAction(1.50f), new ZoomAction(1.75f), new ZoomAction(2.00f) }; antialiasAction = new AntialiasAction(); bookmarkHighlightTagMap = new TreeMap(); annotationHighlightTagMap = new TreeMap(); historyList = new ArrayList(); scrollBar = new JScrollBar(JScrollBar.HORIZONTAL); setDisplayPageCount(displayPageCount); //set the number of pages to display setDefaultFocusComponent(xmlTextPane); //set the default focus component initialize(); //initialize the book } /** * Initializes actions in the action manager. * @param actionManager The implementation that manages actions. */ protected void initializeActions(final ActionManager actionManager) { super.initializeActions(actionManager); //do the default initialization /*TODO transfer to BookApplicationPanel final Action fileMenuAction=ActionManager.getFileMenuAction(); actionManager.addMenuAction(fileMenuAction); //file actionManager.addMenuAction(fileMenuAction, sdiManager.getResourceComponentManager().getOpenAction()); //file|open */ //set up the tool actions actionManager.addToolAction(getBackAction()); //back actionManager.addToolAction(new ActionManager.SeparatorAction()); //- actionManager.addToolAction(getPreviousPageAction()); //previous actionManager.addToolAction(getNextPageAction()); //next actionManager.addToolAction(new ActionManager.SeparatorAction()); //- actionManager.addToolAction(getSearchAction()); //search actionManager.addToolAction(getSearchAgainAction()); //search again } /** Initializes the user interface. */ protected void initializeUI() { // TODO fix setDefaultFocusComponent(burrowTreePanel); //TODO put this in the constructor, maybe super.initializeUI(); //do the default initialization backAction.setEnabled(false); //default to no history viewPropertiesAction.setEnabled(false); //default to having no properties to view closeAction.setEnabled(false); //default to nothing to close copyAction.setEnabled(false); //disable all our local actions based on selection state insertHighlightAction.setEnabled(false); //disable all our local actions based on selection state searchAction.setEnabled(false); //default to not allowing searching searchAgainAction.setEnabled(false); //default to not allowing searching getXMLTextPane().setAsynchronousLoad(true); //turn on asynchronous loading TODO fix this better; tidy up throughout the code getXMLTextPane().setPaged(true); //show that the text pane should page its information setAntialias(true); //default to antialiasing, updating the action setZoom(Documents.DEFAULT_ZOOM); //set the default zoom level, selecting the appropriate action add(getXMLTextPane(), BorderLayout.CENTER); //add the text pane to the center of our control xmlTextPane.setEditable(false); //don't let the OEB text pane be edited in this implementation xmlTextPane.addProgressListener(this); //listen for progress events xmlTextPane.addHyperlinkListener(this); //listen for hyperlink events //catch all document changes in the text pane, since the document is actually changed in a separate thread xmlTextPane.addPropertyChangeListener(TextComponents.DOCUMENT_PROPERTY, new PropertyChangeListener() { //if the document property changes, call onDocumentChange() public void propertyChange(final PropertyChangeEvent event) { onDocumentChange(); } }); xmlTextPane.addPageListener(this); //add ourselves as a page listener, so that we can update the forward and backwards actions TODO we probably want to get the events directly from the book /*TODO del when works getXMLTextPane().addHyperlinkListener( //add a listener for hyperlink events new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent) {//TODO check about the loadingPage flag if(hyperlinkEvent.getEventType()==HyperlinkEvent.EventType.ACTIVATED) { //if the cursor is entering the activateLink(hyperlinkEvent); //activate the link } } }); */ xmlTextPane.addCaretListener(this); //listen for caret events so that we can enable or disable certain actions xmlTextPane.addMouseListener(this); //TODO testing //TODO fix statusBar.add(statusProgressBar, new GridBagConstraints(1, 0, 1, 1, 0.5, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); //TODO testing //TODO fix statusSlider.setPaintLabels(true); //turn on label painting //TODO fix statusSlider.setPaintTicks(true); //turn on tick painting //TODO fix statusSlider.setPaintTrack(true); //turn on track painting //TODO fix statusSlider.setMajorTickSpacing(2); //TODO testing //TODO fix statusSlider.setMajorTickSpacing(1); getStatusBar().setStatusVisible(true); //always show the status label getStatusBar().setProgressVisible(true); //always show the progress bar getStatusBar().validate(); //make sure the status bar is correctly sized getStatusBar().setPreferredSize(getStatusBar().getPreferredSize()); //fix the status bar at its current preferred size so that it won't change the book dimensions when it's updated scrollBar.addAdjustmentListener(this); //tell the scrollbar we want to know when it changes add(scrollBar, BorderLayout.SOUTH); //add the scroll bar to the panel } /** * Invoked when progress has been made. * @param progressEvent The event object representing the progress made. */ public void madeProgress(final ProgressEvent progressEvent) { final StatusBar statusBar = getStatusBar(); //get our status bar //TODO del Log.trace("made progress: ", progressEvent.getTask()); //G**del if(progressEvent.isFinished()) { //if the progress is finished (whatever the progress is) //TODO del Log.trace("is finished: ", progressEvent.getTask()); //G**del if(progressEvent.getTask().equals(XMLTextPane.CONSTRUCT_TASK)) { //if the document is finished being constructed, we'll treat this as a "finished loading" notification TODO probably add some specific document setting event later, or something; actually, that's already there with the document property changing //TODO fix refreshGoGuidesMenu(); //TODO testing; comment; TODO fix for multithreaded loading } else if(progressEvent.getTask().equals(XMLTextPane.PAGINATE_TASK)) { //if the document is finished being paginated TODO probably add some specific document setting event later, or something; actually, that's already there with the document property changing firePropertyChange(BOOKMARKS_PROPERTY, null, null); //fire an event showing that the bookmarks have changed, because pagination changes the pages they point to } //TODO del when works setStatus(""); //clear the status //TODO del Log.trace("setting status progress to zero"); //TODO del statusBar.setProgress("", 0); //set the value of the progress bar to zero } else { //if the progress is still ongoing final int value = Math.round(progressEvent.getValue()); //get the current value of the progress if(value >= 0) { //if they've given us a valid value final int maximum = Math.round(progressEvent.getMaximum()); //get the maximum value of the progress statusBar.setProgressRange(0, maximum); //set the range statusBar.setProgress(progressEvent.getStatus(), value); //show the progress status and progress value on the status bar } else { //if there is no progress value statusBar.setProgress(progressEvent.getStatus()); //show the progress status on the status bar } /*TODO maybe fix for threaded pagination if(e.getTask().equals(XMLPagedView.PAGINATE_TASK)) { //if we've paginated another page TODO testing; maybe only do this if we have threaded pagination final int pageIndex=book.getPageIndex(); //get our current page index final int pageCount=book.getPageCount(); //get our current page count final int displayPageCount=book.getDisplayPageCount(); //find out how many pages at a time are being displayed previousPageAction.setEnabled(pageIndex>0); //we can only go back if the new page index is greater than zero nextPageAction.setEnabled(pageIndex+displayPageCount 1) { //if there are more than one page being shown toolTipTextStringBuffer.append('-'); //a separator toolTipTextStringBuffer.append(pageIndex + displayPageCount <= pageCount ? pageIndex + displayPageCount : pageCount); //add the last page number in the range to the string } scrollBar.setToolTipText(toolTipTextStringBuffer.toString()); //add the tooltip text showing the page number //TODO *fix statusSlider.setToolTipText(toolTipTextStringBuffer.toString()); //add the tooltip text showing the page number } previousPageAction.setEnabled(pageIndex > 0); //we can only go back if the new page index is greater than zero nextPageAction.setEnabled(pageIndex + displayPageCount < pageCount); //we can only go forwards if the turning the page would not be over the total number of pages } /** * Called when the book should be adjusted based upon the scrollbar change. * @param adjustmentEvent The event containing the adjustment information. */ public void adjustmentValueChanged(final AdjustmentEvent adjustmentEvent) { if(getPageCount() > 0) { //if the book has pages final int displayPageCount = getDisplayPageCount(); //find out how many pages at a time are being displayed final int newValue = adjustmentEvent.getValue(); //get the new slider value Log.trace("new adjustment value", newValue); //besides the first page, the other pages will be offset by the number of empty pages in the first set final int newPageIndex = newValue == 0 ? newValue : getXMLTextPane().getLogicalPageIndex(newValue); Log.trace("new page index", newPageIndex); setPageIndex(newPageIndex); //change to the specified page in the book } } /** * Called when the caret position is updated. * @param caretEvent The event holding information about the caret movement. */ public void caretUpdate(final CaretEvent caretEvent) { final boolean isSelection = caretEvent.getMark() != caretEvent.getDot(); //see if there is a selection copyAction.setEnabled(isSelection); //only allow copying if something is selected insertHighlightAction.setEnabled(isSelection); //only allow highlighting if something is selected } /** * Called when a mouse click occurs. If the click is a popup trigger, the popup-related activities are performed. Otherwise, specific actions take place on * special objects, such as displaying images. * @param mouseEvent The mouse event. * @see #popupTriggered * @see MouseListener#mouseClicked */ public void mouseClicked(MouseEvent mouseEvent) { //TODO del Log.trace("Mouse clicked..."); if(mouseEvent.isPopupTrigger()) { //if this is a trigger to popup a context menu popupTriggered(mouseEvent); //show that a popup was triggered } else { //if this is a normal click if(mousePressReleasePopupTrigger) { //if a previous press or release triggered a popup mousePressReleasePopupTrigger = false; //don't perform any action, but reset the flag for the next click } else { //if no popup was triggered in any previous press or release final JEditorPane editorPane = (JEditorPane)mouseEvent.getSource(); //get the source of the event TODO should we really assume the source is the editor pane? probably not if(!editorPane.isEditable()) { //if the editor pane is read-only final Point point = new Point(mouseEvent.getX(), mouseEvent.getY()); //create a point from the mouse click coordinates final int pos = editorPane.viewToModel(point); //get the position of the mouse click //get the view closest to the viewer, making sure we have a forward bias or we'll get the wrong view final View view = SwingText.getLeafView(editorPane.getUI().getRootView(editorPane), pos, Position.Bias.Forward); final AttributeSet attributeSet = view.getAttributes(); //get the view's attributes if(XHTMLSwingText.isImage(attributeSet)) { //if this is an image Log.trace("Is image element."); //TODO del final String src=(String)attributeSet.getAttribute("src"); //TODO fix; use a constant final String href = XHTMLSwingText.getImageHRef(attributeSet); //get a reference to the image file represented by the element //TODO del Debug.notify("image from: "+src); //TODO fix if(href != null) { //if we found a reference to the image //take into account that the href is relative to this file's base URI try { final String baseRelativeHRef = XMLStyles.getBaseRelativeHRef(attributeSet, href); Log.trace("Image href: ", href); viewImage(baseRelativeHRef); //show the image } catch(URISyntaxException uriSyntaxException) { //we should never get this error Log.error(uriSyntaxException); //report the error } } } //TODO this doesn't work with object fallback images; we really need to get the *view* represented and get the *attribute* set from the view to check for an image //TODO del Debug.notify("Mouse clicked on position: "+pos); //TODO fix /*TODO fix if(pos>=0) //if we found a valid position in the document activateLink(pos, editorPane, e.getX(), e.getY()); //try activate a link at that position */ /*TODO del when we've successfully switched from using elements to using views final Document document=editorPane.getDocument(); //get the document in the editor pane if(document instanceof XMLDocument) { //if this is an XML document //TODO del Log.trace("OEBBook mouse clicked in an OEBDocument"); XMLDocument xmlDocument=(XMLDocument)document; //cast the document to an XML document Element element=xmlDocument.getCharacterElement(pos); //get the element this position represents element=element!=null ? element.getParentElement() : null; //if the element has a parent, get that parent; this is because, right now, the images have dummy text beneath them TODO fix eventually Log.trace("Checking mouse click element."); //TODO del final AttributeSet attributeSet=element.getAttributes(); //get the attributes of this element //TODO del final String elementName=XMLStyleConstants.getXMLElementName(attributeSet); //get the name of this element //TODO del Log.trace("mouse clicked on element: "+elementName); //TODO del //TODO del when works final String elementName=(String)attributeSet.getAttribute(StyleConstants.NameAttribute); //get the name of this element if(OEBSwingTextUtilities.isImageElement(element)) { //if this is an image element Log.trace("Is image element."); //TODO del final String src=(String)attributeSet.getAttribute("src"); //TODO fix; use a constant final String href=OEBSwingTextUtilities.getImageElementHRef(element); //get a reference to the image file represented by the element //TODO del Debug.notify("image from: "+src); //TODO fix if(href!=null) { //if we found a reference to the image Log.trace("Image href: ", href); viewImage(href); //show the image } } } */ } } } } /** * Invoked when a mouse button has been pressed on a component. Checks to see if the event is a popup trigger. * @param mouseEvent The mouse event. * @see #popupTriggered */ public void mousePressed(MouseEvent mouseEvent) { //TODO del Log.trace("mousePressed is trigger: ", new Boolean(mouseEvent.isPopupTrigger())); //TODO del if(mouseEvent.isPopupTrigger()) { //if this is a trigger to popup a context menu mousePressReleasePopupTrigger = true; //show that a mouse press or release triggered a popup popupTriggered(mouseEvent); //show that a popup was triggered } } /** * Invoked when a mouse button has been released on a component. Checks to see if the event is a popup trigger. * @param mouseEvent The mouse event. * @see #popupTriggered */ public void mouseReleased(MouseEvent mouseEvent) { //TODO del Log.trace("mouseReleased is trigger: ", new Boolean(mouseEvent.isPopupTrigger())); //TODO del if(mouseEvent.isPopupTrigger()) { //if this is a trigger to popup a context menu mousePressReleasePopupTrigger = true; //show that a mouse press or release triggered a popup popupTriggered(mouseEvent); //show that a popup was triggered } } /** * Invoked when the mouse enters a component. */ public void mouseEntered(MouseEvent mouseEvent) { } /** * Invoked when the mouse exits a component. */ public void mouseExited(MouseEvent mouseEvent) { } /** * Called when the popup trigger occurred (e.g. the right mouse button on the Windows look-and-feel). * @param mouseEvent The mouse event. */ public void popupTriggered(final MouseEvent mouseEvent) { final JPopupMenu popupMenu = new JPopupMenu(); //create a new popup menu final Object mouseEventSource = mouseEvent.getSource(); //get the source of the mouse event if(mouseEventSource instanceof JEditorPane) { //if the mouse was clicked on the editor pane final JEditorPane editorPane = (JEditorPane)mouseEventSource; //get the editor pane final Point point = new Point(mouseEvent.getX(), mouseEvent.getY()); //create a point from the mouse click coordinates final int pos = editorPane.viewToModel(point); //get the position of the mouse click final Document document = editorPane.getDocument(); //get the document in the editor pane if(document instanceof DefaultStyledDocument) { //if this is a default styled document DefaultStyledDocument defaultStyledDocument = (DefaultStyledDocument)document; //cast the document to a default styled document //"View Image" //see if we can get an image element at this position final Element imageElement = XHTMLSwingText.getImageElement(defaultStyledDocument, pos); if(imageElement != null) { //if we found an image element final String href = XHTMLSwingText.getImageHRef(imageElement.getAttributes()); //get a reference to the image file represented by the element if(href != null) { //if we found a reference to the image try { //TODO add something here to extract the name of the image, especially after we can get a URL relative to the file base //take into account that the href is relative to this file's base URL final String baseRelativeHRef = XMLStyles.getBaseRelativeHRef(imageElement.getAttributes(), href); JMenuItem viewImageMenuItem = popupMenu.add(new ViewImageAction(baseRelativeHRef)); //add an action to view the image popupMenu.addSeparator(); //add a separator } catch(URISyntaxException uriSyntaxException) { //we should never get this error Log.error(uriSyntaxException); //report the error } } } } //"Delete Bookmark" final Bookmark bookmark = getBookmark(pos); //get the bookmark at this position, if there is one if(bookmark != null) { //if there is a bookmark at this position JMenuItem deleteBookmarkMenuItem = popupMenu.add(new DeleteBookmarkAction(bookmark)); //add an action to delete the bookmark //TODO fix or del deleteBookmarkMenuItem.setMnemonic('i'); //TODO i18n popupMenu.addSeparator(); //add a separator } //"Delete Annotation" final Annotation annotation = getAnnotation(pos); //get the annotation at this position, if there is one if(annotation != null) { //if there is an annotation at this position JMenuItem deleteAnnotationMenuItem = popupMenu.add(new DeleteAnnotationAction(annotation)); //add an action to delete the annotation //TODO fix or del deleteBookmarkMenuItem.setMnemonic('i'); //TODO i18n popupMenu.addSeparator(); //add a separator } //"Define XXXX" try { final String defineText; //this will be the text to define //see if there is a selection final boolean isSelection = getXMLTextPane().getSelectionEnd() > getXMLTextPane().getSelectionStart(); //see if there is a selection if(isSelection) { //if there is a selection final int textOffset = getXMLTextPane().getSelectionStart(); //we'll start with the selection start final int textLength = Math.min(getXMLTextPane().getSelectionEnd() - textOffset, 128); //we'll end with the selection end, making sure the selection isn't too large TODO use a constant here defineText = document.getText(textOffset, textLength).trim(); //get the text they've selected and trim it, in case they accidentally got whitespace along with it } else { //if there isn't a selection, try to see what word is being clicked on //TODO check to see if text is highlighted; if so, use the highlighted portion final int textOffset = Math.max(pos - 64, 0); //find out the start of the text to retrieve; make sure we don't go past the beginning of the document TODO use a constant here final int textLength = Math.min(document.getLength() - textOffset, 128); //find out how much text to retrieve; make sure we don't go past the end of the document TODO use a constant here final int relativeOffset = Math.min(pos - textOffset, textLength - 1); //find out where the position will be in the text we retrieve, makeing sure it isn't past the end of the text /*TODO del Log.trace("position: ", pos); Log.trace("Text offset: ", textOffset); Log.trace("Text length: ", textLength); Log.trace("Relative offset: ", relativeOffset); */ //TODO this ignores different elements, so

Title

text

give "Titletext"; fix so that only the element text is returned final String text = document.getText(textOffset, textLength); //get the text surrounding the position final BreakIterator wordBreakIterator = BreakIterator.getWordInstance(); //get a break iterator to find a word based on the appropriate locale TODO use the specific locale here wordBreakIterator.setText(text); //set the text of the break iterator final int wordEnd = wordBreakIterator.following(relativeOffset); //get the end of the word final int wordBegin = wordBreakIterator.previous(); //get the beginning of the word //TODO do the isLetter thing to make sure this is a word defineText = text.substring(wordBegin, wordEnd).trim(); //get the word to define, trimming it in case the selection was whitespace } if(defineText.length() > 0 && defineText.length() < 64) { //if there is something to define at this location, and it's not too long TODO use a constant here //TODO check the current locale and record that in the action as well JMenuItem defineWordMenuItem = popupMenu.add(new DefineAction(defineText)); //add an action to define the word //separator popupMenu.addSeparator(); //add a separator } } catch(BadLocationException e) { //we should never get a bad location, since we test the offsets and lengths Log.error(e); } //"Insert Bookmark..." JMenuItem insertBookmarkMenuItem = popupMenu.add(new InsertBookmarkAction(pos)); //add an action to insert a bookmark at this position //"Insert Highlight..." JMenuItem insertHighlightMenuItem = popupMenu.add(insertHighlightAction); //add the pre-created action to highlight //separator popupMenu.addSeparator(); //add a separator //"Copy" JMenuItem copyMenuItem = popupMenu.add(copyAction); //add the pre-created action to copy text } if(mouseEventSource instanceof Component) { //if the mouse event source was a component popupMenu.show((Component)mouseEventSource, mouseEvent.getX(), mouseEvent.getY()); //show the popup menu, using the source component as the invoker } } /** * Sets the given XML data. * @param xmlDocument The XML document that contains the data. * @param baseURI The base URI, corresponding to the XML document. * @param mediaType The media type of the XML document. */ public void setXML(final org.w3c.dom.Document xmlDocument, final URI baseURI, final ContentType mediaType) { //TODO del if not needed setXML(new org.w3c.dom.Document[]{xmlDocument}, new URI[]{baseURI}, new ContentType[]{mediaType}, mediaType); //set the XML using arrays, specifying the media type close(); //close whatever book is open getXMLTextPane().setContentType(mediaType.toString()); //set the content type of the text pane getXMLTextPane().setXML(xmlDocument, baseURI, mediaType); //tell the XML text pane to set the XML } /** * Sets the given XML data. * @param xmlDocumentArray The array of XML documents that contain the data. * @param baseURIArray The array of base URIs, corresponding to the XML documents. * @param mediaTypeArray The array of media types of the documents. * @param mediaType The media type of the book itself. */ /*TODO del if not needed public void setXML(final org.w3c.dom.Document[] xmlDocumentArray, final URI[] baseURIArray, final ContentType[] mediaTypeArray, final ContentType mediaType) { close(); //close whatever book is open getXMLTextPane().setContentType(mediaType.toString()); //set the content type of the text pane getXMLTextPane().setXML(xmlDocumentArray, baseURIArray, mediaTypeArray); //tell the XML text pane to set the XML } */ /** * Reads the book content from a URI. * @param uri The location of the book. * @throws IOExeption Thrown if an I/O error occurs. * @see OEBTextPane#read */ public void open(final URI uri) throws IOException { close(); //close whatever book is open /*TODO fix final XMLEditorKit.XMLViewFactory xmlViewFactory=(XMLEditorKit.XMLViewFactory)oebEditorKit.getViewFactory(); //get the view factory from the editor kit TODO make sure this is an XMLViewFactory //register a QTI view factory with the QTI namespace, with the normal XML view factory as the fallback //TODO if fix, register with the XMLTextPane, not the view factory xmlViewFactory.registerViewFactory(QTIConstants.QTI_1_1_NAMESPACE_URI, new QTIViewFactory()); */ getXMLTextPane().setPage(uri, getURIInputStreamable()); //tell the text pane to read from the URI } /** Closes the book, if one is open. */ public void close() { historyList.clear(); //clear the history list TODO probably put a clearHistory() method instead setHistoryIndex(0); //show that we have no history clearBookmarks(); //clear the bookmark list getXMLTextPane().setBaseURI(null); //show that nothing is open (do this so that the new blank document will not have its base URI set automatically) getXMLTextPane().setDocument(getXMLTextPane().getEditorKit().createDefaultDocument()); //create a default document and assign it to the text pane userDataModified = false; //show that the user data has not been modified } /** * Closes and opens the book content from the same location. If no file is open, no action is taken. * @throws IOExeption Thrown if an I/O error occurs. * @see #getURI * @see #open * @see #close */ public void reload() throws IOException { final URI uri = getURI(); //get the current URI if(uri != null) { //if there is content loaded from some location close(); //close the current book open(uri); //open the book from the same location } } /** * Reads the book content from a reader. * @param in The stream to read from. * @param desc An object describing the stream. * @throws IOExeption Thrown if an I/O error occurs. * @see OEBTextPane#read */ /*TODO del public void read(final Reader in, final Object desc) throws IOException { getXMLTextPane().read(in, desc); //let the OEB text pane read the content } */ /** * Returns a new object reflecting the book's user data. This user data should be considered read-only, as its information may reference information in the * book. */ public UserData getUserData() { return new UserData(this); //create a new user data object to represent the data in this book } /** * Updates the book user data from a UserData object. * @param userData The object containing the user data. */ public void setUserData(final UserData userData) { //set the bookmarks clearBookmarks(); //clear all bookmarks final Bookmark[] bookmarks = userData.getBookmarks(); //get the bookmarks in the user data for(int i = bookmarks.length - 1; i >= 0; --i) { //look at each bookmark try { addBookmark(bookmarks[i]); //add this bookmark, which will automatically attach the bookmark to the document } catch(BadLocationException e) { //if this bookmark represents a bad location Log.warn(e); //ignore the error } } //set the annotations clearAnnotations(); //clear all bookmarks final Annotation[] annotations = userData.getAnnotations(); //get the annotations in the user data for(int i = annotations.length - 1; i >= 0; --i) { //look at each annotation try { addAnnotation(annotations[i]); //add this annotation, which will automatically attach the annotation to the document } catch(BadLocationException e) { //if this annotation represents a bad location Log.warn(e); //ignore the error } } userDataModified = false; //show that the user data not has been modified } /** * Called when the document has changed, so that we can update our record of the user data file and load it if needed. This method is needed because the * document is set asynchronously in the AWT thread, not immediately in open(). * @see #open */ protected void onDocumentChange() { final URI uri = getURI(); //get our current URI Log.trace("document change, URI: ", uri); //update the actions closeAction.setEnabled(uri != null); //only enable the close button if there is a book open searchAction.setEnabled(uri != null); //only enable searching if there is a file open searchAgainAction.setEnabled(uri != null); //only enable searching if there is a file open //TODO transfer this to BookApplicationPanel reloadAction.setEnabled(book.getURI()!=null); //only enable the reload button if there is a file open final RDFModel rdf = getRDF(); //get the loaded metadata getViewPropertiesAction().setEnabled(rdf != null); //only enable the properties button if there is RDF TODO do we want to show the RDF or the publication description? } /** * Paints the book-specific items such as bookmarks. * @param graphics The object used for painting. */ public void paint(Graphics graphics) { super.paint(graphics); //do the default painting } /* ***Page methods*** */ /** @return The number of pages available. */ public int getPageCount() { return getXMLTextPane().getPageCount(); //return the page count of the OEB text pane } /** @return The index of the currently displayed page. */ public int getPageIndex() { return getXMLTextPane().getPageIndex(); //return the page index of the OEB text pane } /** * Sets the index of the currently displayed page. * @param pageIndex The index of the new page to be displayed. */ public void setPageIndex(final int pageIndex) { getXMLTextPane().setPageIndex(pageIndex); //sets the page index of the OEB text pane } //TODO fix this with the correct modelToView() stuff; comment public int getPageIndex(final int pos) { return getXMLTextPane().getPageIndex(pos); //TODO comment; decide how we really want to allow access here } /** * @return true if the specified page is one of the pages being displayed. */ public boolean isPageShowing(final int pageIndex) { return getXMLTextPane().isPageShowing(pageIndex); //ask the paged view whether this page is showing } /** * Advances to the next page(s), if one is available, correctly taking into account the number of pages displayed. */ public void goNextPage() { //TODO del System.out.println("OEBBook.goNextPage()"); //TODO del getXMLTextPane().goNextPage(); //tell the text pane to go to the next page } /** * Changes to the previous page(s), if one is available, correctly taking into account the number of pages displayed. */ public void goPreviousPage() { getXMLTextPane().goPreviousPage(); //tell the text pane to go to the previous page } /** * Changes to the last position in the history, if previous history is available. */ public void goBack() { final Position backPosition = decrementHistory(); //get the previous location in history if(backPosition != null) //if we have a previous position getXMLTextPane().go(backPosition.getOffset()); //go to that offset without storing history information } /** * Changes to the next position in the history, if next history is available. */ public void goForward() { final Position forwardPosition = incrementHistory(); //get the next location in history if(forwardPosition != null) //if we have a next position getXMLTextPane().go(forwardPosition.getOffset()); //go to that offset without storing history information } /** * Navigates to specified URL. If the URL is already loaded, it is displayed. If the URL is outside the publication, the location is loaded into the default * browser. The previous location is stored in the history list. * @param url The destination URL. */ /*TODO fix or del public void go(final URL url) { //TODO fix all this -- this is just a quick kludge to see if it will work Log.trace("Inside OEBBook.goURL()"); //TODO del storePositionHistory(); //store our position in the history list Log.trace("ready to call XMLTextPane.go(URL)"); getXMLTextPane().go(url); //tell the text pane to go to the URL } */ /** * Navigates to specified URI. If the URI is already loaded, it is displayed. If the URI is outside the publication, the location is loaded into the default * browser. The previous location is stored in the history list. * @param uri The destination URI. */ public void go(final URI uri) { Log.trace("Inside OEBBook.goURI()"); //TODO del storePositionHistory(); //store our position in the history list Log.trace("ready to call XMLTextPane.go(URI)"); getXMLTextPane().go(uri); //tell the text pane to go to the URI } /** * Navigates to the specified position. The previous position is stored in the history list. * @param offset The new position to navigate to. */ public void go(final int offset) { storePositionHistory(); //store our position in the history list getXMLTextPane().go(offset); //tell the text pane to go to the specified position } /** * Attempts to navigate to the target of the given OEB guide. * @param guide The OEB guide which contains the target href location. */ protected void go(final OEBGuide guide) { final Document document = getXMLTextPane().getDocument(); //get a reference to the document if(document instanceof XMLDocument) { //if this is an XML document try { final URI guideURI = ((XMLDocument)document).getResourceURI(guide.getHRef()); //try to construct a URI from the guide's href go(guideURI); //go to the URL specified by the guide } catch(final IllegalArgumentException illegalArgumentException) { AbstractSwingApplication.displayApplicationError(this, illegalArgumentException); } } } /** * Activates a particular hyperlink from a given hyperlink event. * @param hyperlinkEvent The event which contains information about the hyperlink to be activated. */ //TODO add checking for out-of-spine content here public void activateLink(final HyperlinkEvent hyperlinkEvent) { Log.trace("Inside OEBBook.activateLink()."); final URI hyperlinkURI; //we'll get the hyperlink event's URI if(hyperlinkEvent instanceof XMLLinkEvent) { //if this is an XML link event hyperlinkURI = ((XMLLinkEvent)hyperlinkEvent).getURI(); //get the XML link event's URI } else { try { //TODO check that conversion from URL toURI happens correctly---as URL is ambiguous about encoding, we should probably note any syntax errors that occur when converting to URLs hyperlinkURI = new URI(hyperlinkEvent.getURL().toString()); //get the hyperlink event's URI } catch(URISyntaxException e) { //if we can't get a URI from the URL return; //don't process this event } } try { final XMLDocument xmlDocument = (XMLDocument)getXMLTextPane().getDocument(); //get the loaded document TODO should we assume this is an XML document? final ContentType mediaType = xmlDocument.getResourceMediaType(hyperlinkURI.toString()); //get the media type of the resource specified by this hyperlink event Log.trace("Media type: ", mediaType); //TODO del if(mediaType != null) { //if we think we know the media type of the file involved //TODO create convenience utility methods similar to MediaTypeUtilities.isAudio() for all of these checks final String topLevelType = mediaType.getPrimaryType(); //get the top-level media type if(Audio.isAudio(mediaType)) { //if this is an audio media type Log.trace("found an audio file."); //TODO del; fix mouseEvent.consume(); //consume the event so that the mouse click won't be interpreted elsewhere final Clip clip = (Clip)xmlDocument.getResource(hyperlinkURI.toString()); //get and open a clip to the audio Log.trace("ready to start clip."); clip.start(); //start the clip playing TODO do we need to close it later? return; //don't do any more processing } else if(topLevelType.equals(ContentType.IMAGE_PRIMARY_TYPE)) { //if this is an image media type TODO does this work correctly relative to the document base URI? viewImage(hyperlinkURI.toString()); //view the image at the given location return; //don't do any more processing } } //TODO add an option in goURI() to go later, in the AWT thread //TODO check to see if we actually went to the hyperlink; if not, try to use the browser SwingUtilities.invokeLater(new Runnable() { //invoke the hyperlink traversal until a later time in the event thread, so the mouse click won't be re-interpreted when we arrive at the hyperlink destination public void run() { go(hyperlinkURI); } //if the hyperlink was not for a special-case URI, just go to the URI }); } catch(Exception exception) { //if anything goes wrong with any of this TODO is this too broad? Log.traceStack(exception); //TODO fix Log.error("Error activating hyperlink " + hyperlinkURI + ": " + exception); //TODO fix; this is an important error which should be reported back to the user in a consistent way } } /** The text to search for, saved between searches. */ protected String searchText = ""; /** * Ask the user for a search string and searches for text starting on the current page. */ public void search() { search(getXMLTextPane().getPageStartOffset(getPageIndex())); //we'll start searching at the beginning of whichever page is showing } /** * Ask the user for a search string and searches for text at the given offset. * @param searchOffset The offset at which searching should begin, or XMLTextPane.NEXT_SEARCH_OFFSET if searching should take place where the * last search left off. * @see XMLTextPane#NEXT_SEARCH_OFFSET */ public void search(final int searchOffset) { String defaultSearchText = searchText; //get the current search text if(defaultSearchText == null || defaultSearchText.length() == 0) { //if there is no default search text, try to find default search text from the preferences try { final Preferences preferences = getPreferences(); //get the preferences defaultSearchText = preferences.get(SEARCH_TEXT_PREFERENCE, ""); //get the stored search text } catch(SecurityException securityException) { //if we can't access preferences Log.warn(securityException); //warn of the security problem } } final String newSearchText = (String)JOptionPane.showInputDialog(this, "Enter search word or phrase:", "Search", JOptionPane.QUESTION_MESSAGE, null, null, defaultSearchText); //TODO i18n if(newSearchText != null && newSearchText.length() > 0) { //if they want to search searchText = newSearchText; //save the search text for other searches try { final Preferences preferences = getPreferences(); //get the preferences preferences.put(SEARCH_TEXT_PREFERENCE, searchText); //store the search text for next time } catch(SecurityException securityException) { //if we can't access preferences Log.warn(securityException); //warn of the security problem } search(searchText, searchOffset); //search for the text } } /** * Searches for the current search text at the last search offset. If there is no search text, the user is asked for search text. * @see #search() * @see #search(String, int) */ public void searchAgain() { if(searchText != null && searchText.length() > 0) { //if there is search text search(searchText, XMLTextPane.NEXT_SEARCH_OFFSET); //search for the text at the next search position } else { //if there is no search text search(); //start searching from scratch } } /** * Searches for text at the given offset. * @param searchText The text for which to search. * @param searchOffset The offset at which searching should begin, or XMLTextPane.NEXT_SEARCH_OFFSET if searching should take place where the * last search left off. * @see XMLTextPane#NEXT_SEARCH_OFFSET */ public void search(final String searchText, final int searchOffset) { final int matchOffset; //TODO testing; comment; don't access deeply getStatusBar().setStatus("Searching..."); //TODO testing; i18n TODO doesn't work, because this is in a separate AWT thread try { matchOffset = getXMLTextPane().search(searchText, searchOffset); //ask the XML text pane to search for text } finally { getStatusBar().setStatus(""); //TODO testing; comment } if(matchOffset < 0) { //if the text was not found AbstractSwingApplication.displayApplicationError(this, "Search Results", "The requested text was not found."); //show that the text was not found } } /** * Shows an image in a separate image viewing window. * @param href The absolute or relative reference to the image file. TODO fix later when images are relative to documents in other directories */ protected void viewImage(final String href) { final Document document = getXMLTextPane().getDocument(); //get the document in the editor pane if(document instanceof XMLDocument) { //if this is an XML document //TODO del Log.trace("OEBBook mouse clicked in an OEBDocument"); XMLDocument xmlDocument = (XMLDocument)document; //cast the document to an XML document try { final Image image = (Image)xmlDocument.getResource(href); //get the image resource TODO check to make sure what is returned is really an image viewImage(image, href); //view the image } catch(URISyntaxException ex) { //TODO fix Log.error(ex); } catch(IOException ex) { //TODO fix Log.error(ex); } } } /** * Shows an image in a separate image viewing window. * @param image The image object to display. * @param title The title to show when viewing the image. */ public void viewImage(final Image image, final String title) { //TODO del; this doesn't seem to make loading faster ImageUtilities.loadImage(image); //make sure the image is loaded TODO do we need this? this doesn't seem to be causing the blank image problem final ImagePanel imagePanel = new ImagePanel(image); //create an image panel in which to show the image JOptionPane.showMessageDialog(this, imagePanel, title, JOptionPane.INFORMATION_MESSAGE); //show the image panel /*TODO del when works final ImageIcon imageIcon=new ImageIcon(image); //TODO testing //have an option pane create and show a new dialog using our about panel new JOptionPane(imageIcon, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION).createDialog(this, title).show(); */ } /** A highlighter that displays a bookmark icon. */ protected static class BookmarkHighlightPainter extends LayeredHighlighter.LayerPainter { /** The shared bookmark icon for indicating a bookmark. */ public static final Icon BOOKMARK_ICON = IconResources.getIcon(IconResources.BOOKMARK_ICON_FILENAME); /** This method is never called in a layered highlighter. */ public void paint(final Graphics graphics, final int startOffset, final int endOffset, final Shape bounds, final JTextComponent textComponent) { } /** * Paints the bookmark. * @param graphics The object for painting the bookmark. * @param startOffset The starting offset of the part of the highlight that crosses the view. * @param endOffset The ending offset of the part of the highlight that crosses the view. * @param viewBounds The bounds of the view. * @param textComponent The component painting the highlights. * @param view The view being rendered. */ public Shape paintLayer(final Graphics graphics, final int startOffset, final int endOffset, final Shape viewBounds, final JTextComponent textComponent, final View view) { //TODO fix; this is abstract; maybe descend from DefaultLayerPainter or whatever it is super.paintLayer(graphics, startOffset, endOffset, viewBounds, textComponent, view); //draw the default highlighting first //TODO save the return value from super.paintLayer() and merge it with what we paint from the icon try { final Shape highlightShape = view.modelToView(startOffset, Position.Bias.Forward, endOffset, Position.Bias.Backward, viewBounds); //get the display coordinates of this model offset //get a rectangle that contains the shape final Rectangle highlightRectangle = highlightShape instanceof Rectangle ? (Rectangle)highlightShape : highlightShape.getBounds(); //TODO del or fix; this is not immediately updated because it puts it out of the highlight bounds highlightRectangle.x-=BOOKMARK_ICON.getIconWidth(); //we'll center the icon horizontally at the starting offset highlightRectangle.width = BOOKMARK_ICON.getIconWidth(); //update the width of our rectangle to reflect what is painted highlightRectangle.height = BOOKMARK_ICON.getIconWidth(); //update the height of our rectangle to reflect what is painted highlightRectangle.x -= highlightRectangle.width / 2; //center the highlight horizontally //paint the bookmark at the correct location BOOKMARK_ICON.paintIcon(textComponent, graphics, highlightRectangle.x, highlightRectangle.y); return highlightRectangle; //return the area that was actually highlighted by the painting of the icon } catch(BadLocationException e) { //if there was a bad location return null; //show that we did not paint the layer } } } /** * Contains information regarding the user's use information concerning a particular file loaded in the Book. Such user information includes * bookmarks and notes. */ public static class UserData { /** Default constructor. */ public UserData() { } /** * Creates user data from the current contents of a book. * @param book The book for which user data should be constructed. */ public UserData(final Book book) { //TODO create defensive copies of the data //create an array to hold the bookmarks, fill the array with the bookmarks, and store the array setBookmarks((Bookmark[])book.bookmarkHighlightTagMap.keySet().toArray(new Bookmark[book.bookmarkHighlightTagMap.size()])); //create an array to hold the annotations, fill the array with the annotations, and store the array setAnnotations((Annotation[])book.annotationHighlightTagMap.keySet().toArray(new Annotation[book.annotationHighlightTagMap.size()])); } /** The array of bookmarks in the book. */ private Bookmark[] bookmarks = new Bookmark[] {}; /** @return The array of bookmarks in the book. */ public Bookmark[] getBookmarks() { return bookmarks; } /** * Sets the array of bookmarks. * @param newBookmarks The array of bookmarks in the book. */ public void setBookmarks(final Bookmark[] newBookmarks) { bookmarks = newBookmarks; } /** The array of annotations in the book. */ private Annotation[] annotations = new Annotation[] {}; /** @return The array of annotations in the book. */ public Annotation[] getAnnotations() { return annotations; } /** * Sets the array of annotations. * @param newAnnotations The array of annotations in the book. */ public void setAnnotations(final Annotation[] newAnnotations) { annotations = newAnnotations; } } /** Action for returning to a previously visited location. */ class BackAction extends AbstractAction { /** Default constructor. */ public BackAction() { super("Back"); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Back"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Go back to the previous location in the book."); //set the long description TODO Int putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_B)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.BACK_VERSION_ICON_FILENAME)); //load the correct icon putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, Event.ALT_MASK)); //add the accelerator } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { goBack(); //go back in history } } /** Action for closing a book. */ protected class CloseAction extends AbstractAction { /** Default constructor. */ public CloseAction() { super("Close"); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Close the open eBook"); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Close the currently open eBook."); //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.BOOK_CLOSED_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 book } } /** Action for copying text. */ protected class CopyAction extends AbstractAction { /** Default constructor. */ public CopyAction() { super("Copy"); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "Copy selected text."); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Copy the selected text to the clipboard."); //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.COPY_ICON_FILENAME)); //load the correct icon putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_C, Event.CTRL_MASK)); //add the accelerator } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { getXMLTextPane().copy(); //tell the text pane to copy } } /** Action for defining a word. */ class DefineAction extends AbstractAction { protected String text; /** * Constructor that indicates the word to be defined. * @param word The word to be defined */ public DefineAction(final String word) { super("Define \"" + word + "\""); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "View the definition of \"" + word + "\"."); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "View the definition of \"" + word + "\"."); //set the long description TODO i18n putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_D)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.BOOK_QUESTION_ICON_FILENAME)); //load the correct icon text = word; //store the reference to the word } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { /*TODO bring back after BrowserLauncher is linked externally { //create a string for looking up the text; the BrowserLauncher.openURL() method should automatically URLEncode the text final String definitionURLString="http://www.dictionary.com/cgi-bin/dict.pl?term="+text; BrowserLauncher.openURL(definitionURLString); //browse to dictionary.com to lookup the word TODO try internal dictionaries first } catch(IOException ioException) { //if there is an IO exception browsing to the URL Log.error(ioException); //we don't expect to see this exception } */ } } /** Action for deleting an annotation. */ class DeleteAnnotationAction extends AbstractAction { protected Annotation deleteAnnotation; /** * Constructor. * @param annotation The annotation to be deleted. */ public DeleteAnnotationAction(final Annotation annotation) { super("Delete Annotation"); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "Delete annoation."); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Delete the selected annotation."); //set the long description TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.NOTEPAD_DELETE_ICON_FILENAME)); //load the correct icon deleteAnnotation = annotation; //store the annotation to be deleted } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { //TODO ask for verification removeAnnotation(deleteAnnotation); //remove the annotation } } /** Action for deleting a bookmark. */ class DeleteBookmarkAction extends AbstractAction { protected Bookmark deleteBookmark; /** * Constructor. * @param bookmark The bookmark to be deleted. */ public DeleteBookmarkAction(final Bookmark bookmark) { super("Delete Bookmark"); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "Delete bookmark."); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Delete the selected bookmark."); //set the long description TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.BOOKMARK_DELETE_ICON_FILENAME)); //load the correct icon deleteBookmark = bookmark; //store the bookmark to be deleted } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { //TODO ask for verification Log.trace("Ready to remove bookmark at position: ", deleteBookmark.getOffset()); //TODO del removeBookmark(deleteBookmark); //remove the bookmark } } /** Action for inserting a bookmark at the current or given location. */ protected class InsertBookmarkAction extends AbstractAction { /** The position of the bookmark, or -1 if the current position should be used. */ protected final int bookmarkPos; /** Default constructor that always uses the current position when performed. */ public InsertBookmarkAction() { this(-1); //show that we should always use the current position } /** * Constructor that specifies the position of a bookmark. * @param pos The position in the document at which the bookmark should be added. */ public InsertBookmarkAction(final int pos) { super("Insert Bookmark..."); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "Insert a bookmark."); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Insert a bookmark at the current location in the book."); //set the long description TODO i18n putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_B)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.BOOKMARK_ICON_FILENAME)); //load the correct icon bookmarkPos = pos; //store the position of the bookmark } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { final String defaultBookmarkName = "bookmark name"; //we need a default name to work around a JDK 1.3 bug which does not display an input field with no default text TODO i18n //ask the user for a bookmark name final String specifiedName = (String)JOptionPane.showInputDialog(Book.this, "Enter a bookmark name, or leave blank for an unnamed bookmark:", "Insert Bookmark", JOptionPane.QUESTION_MESSAGE, null, null, defaultBookmarkName); //TODO i18n if(specifiedName != null) { //if they didn't cancel final String trimmedSpecifiedName = specifiedName.trim(); //trim the bookmark name //if the user didn't specify a name, or they took the default name, don't use a bookmark name final String bookmarkName = trimmedSpecifiedName.length() > 0 && !trimmedSpecifiedName.equals(defaultBookmarkName) ? trimmedSpecifiedName : null; if(bookmarkPos >= 0) { //if we know where to add the bookmark try { addBookmark(bookmarkName, bookmarkPos); //add a bookmark with this name at this offset } catch(BadLocationException badLocationException) { //we should never have a bad location throw new AssertionError(badLocationException); } } else { //if we don't have a particular location addBookmark(bookmarkName); //insert a bookmark at the current location in the book } } } } /** * Action for inserting a highlight at the current location. Annotations are used to represent highlights. */ class InsertHighlightAction extends AbstractAction { //TODO del protected int highlightStartPos; //TODO del protected int highlightEndPos; //TODO del protected Color highlightColor; /** * Constructor that specifies the starting and ending position of a highlight. * @param startPos The starting position in the document at which the highlight should be added. * @param endPos The ending position in the document at which the highlight should be added. param color The color of the highlight. */ /*TODO del public InsertHighlightAction(final int startPos, final int endPos, final Color color) { */ /** * Default constructor for highlighting the current selection using the current highlight color. */ public InsertHighlightAction() { super("Insert Highlighted Annotation"); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "Insert a highlighted annotation."); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Insert a highlighted annotation at the current selection in the book."); //set the long description TODO i18n putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_H)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.PAINT_ICON_FILENAME)); //load the correct icon /*TODO del highlightStartPos=startPos; //store the starting position of the highlight highlightEndPos=endPos; //store the ending position of the highlight highlightColor=color; //store the color of the highlight */ } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { /*TODO fix final String defaultBookmarkName="bookmark name"; //we need a default name to work around a JDK 1.3 bug which does not display an input field with no default text TODO i18n //ask the user for a bookmark name final String specifiedName=(String)JOptionPane.showInputDialog(Book.this, "Enter a bookmark name, or leave blank for an unnamed bookmark:", "Insert Bookmark", JOptionPane.QUESTION_MESSAGE, null, null, defaultBookmarkName); //TODO i18n if(specifiedName!=null) { //if they didn't cancel final String trimmedSpecifiedName=specifiedName.trim(); //trim the bookmark name //if the user didn't specify a name, or they took the default name, don't use a bookmark name final String bookmarkName=trimmedSpecifiedName.length()>0 && !trimmedSpecifiedName.equals(defaultBookmarkName) ? trimmedSpecifiedName : null; */ try { //add an annotation with the current selection offsets and current highlight color addAnnotation(getXMLTextPane().getSelectionStart(), getXMLTextPane().getSelectionEnd(), getHighlightColor()); } catch(BadLocationException badLocationException) { //we should never have a bad location Log.error(badLocationException); //report the error } //TODO del } } } /** Action for going to the previous page. */ class PreviousPageAction extends AbstractAction { /** Default constructor. */ public PreviousPageAction() { super("Previous Page"); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Previous Page"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Turn to the previous page in the book."); //set the long description TODO Int //TODO fix putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_P)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.HAND_POINT_LEFT_ICON_FILENAME)); //load the correct icon } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { goPreviousPage(); //tell the book to go to the previous page } } /** Action for going to the next page. */ class NextPageAction extends AbstractAction { /** Default constructor. */ public NextPageAction() { super("Next Page"); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Next Page"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Turn to the next page in the book."); //set the long description TODO Int //TODO fix putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_N)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.HAND_POINT_RIGHT_ICON_FILENAME)); //load the correct icon } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { goNextPage(); //tell the book to go to the next page } } /** Action for showing book properties. */ class ViewPropertiesAction extends AbstractAction { /** Default constructor. */ public ViewPropertiesAction() { super("Properties..."); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "View properties."); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "View the document metadata properties."); //set the long description TODO i18n putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_P)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.PROPERTY_ICON_FILENAME)); //load the correct icon } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { final RDFModel rdf = getRDF(); //get the loaded metadata if(rdf != null) { //if we have RDF final RDFResource publication = getPublication(); //get the loaded publication //create a new panel in which to show the RDF final RDFPanel> rdfPanel = new RDFPanel>( new ResourceModel(publication, getXMLTextPane().getBaseURI(), getXMLTextPane().getURIInputStreamable())); //show the properties in an information dialog BasicOptionPane.showMessageDialog(Book.this, rdfPanel, "Properties", BasicOptionPane.INFORMATION_MESSAGE); //TODO i18n } } } /** Action for showing an image. */ class ViewImageAction extends AbstractAction { protected String imageHRef; /** * Constructor. * @param href The absolute or relative reference to the image file. TODO fix later when images are relative to documents in other directories */ public ViewImageAction(final String href) { super("View Image"); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "View the image."); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "View the image in a separate window."); //set the long description TODO i18n putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_I)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.IMAGE_ICON_FILENAME)); //load the correct icon imageHRef = href; //store the reference to the image } /** * Called when the action should be performed. * @param e The event causing the action. */ public void actionPerformed(ActionEvent e) { viewImage(imageHRef); //view the image } } /** Action for searching for text. */ protected class SearchAction extends AbstractAction { /** Default constructor. */ public SearchAction() { super("Find..."); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Find text"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Search for text within the book."); //set the long description TODO Int putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_F)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.SEARCH_ICON_FILENAME)); //load the correct icon putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK)); //add the accelerator } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { search(); //start the search } } /** Action for searching again for text. */ protected class SearchAgainAction extends AbstractAction { /** Default constructor. */ public SearchAgainAction() { super("Find Again"); //create the base class TODO Int putValue(SHORT_DESCRIPTION, "Find text again"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Search for text that occurs after the last search."); //set the long description TODO Int putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_A)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.SEARCH_AGAIN_ICON_FILENAME)); //load the correct icon putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0)); //add the accelerator } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { searchAgain(); //continue the search } } /** The group indicating exclusive display page count actions. */ private final ActionGroup displayPageCountGroup = new ActionGroup() {}; /** Action for displaying a particular number of pages at a time. */ protected class DisplayPageCountAction extends AbstractToggleAction //TODO create a factory method to make sure a singleton is returned for each page count { /** The number of pages to display. */ protected final int displayPageCount; /** * Constructs an action which changes the number of pages displayed. * @param displayPageCount How many pages should be displayed when this action is performed. */ public DisplayPageCountAction(final int displayPageCount) { super(displayPageCountGroup); //construct the parent class this.displayPageCount = displayPageCount; //save the display page count switch(displayPageCount) { //see how many pages we should display case 1: //if we should show a single page putValue(NAME, "1 Page"); //set the correct name TODO i18n putValue(SHORT_DESCRIPTION, "Single Page"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Display only one page at a time."); //set the long description TODO Int putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_1)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.COLUMNS1_ICON_FILENAME)); //load the correct icon break; case 2: //if we should show two pages default: //for any other number of pages putValue(NAME, "2 Pages"); //set the correct name TODO i18n putValue(SHORT_DESCRIPTION, "Facing Pages"); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Display two pages at a time."); //set the long description TODO Int putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_2)); //set the mnemonic key TODO i18n putValue(SMALL_ICON, IconResources.getIcon(IconResources.COLUMNS2_ICON_FILENAME)); //load the correct icon break; } } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { setDisplayPageCount(displayPageCount); //show the correct number of pages at a time } } /** The group indicating exclusive zoom actions. */ private final ActionGroup zoomGroup = new ActionGroup() {}; /** Action for changing the zoom level. */ protected class ZoomAction extends AbstractToggleAction { /** The amount by which to zoom. */ protected final float zoom; /** @return The amount by which to zoom. */ public float getZoom() { return zoom; } /** * Constructs an action which changes the zoom. * @param zoom The amount by which normal size will be multiplied. */ public ZoomAction(final float zoom) { super(zoomGroup); //construct the parent class this.zoom = zoom; //save the zoom factor final String percentString = NumberFormat.getPercentInstance().format(zoom); //create a percentage string from the zoom factor TODO i18n use selected locale putValue(NAME, percentString); //set the correct name putValue(SHORT_DESCRIPTION, "Zoom " + percentString); //set the short description TODO i18n putValue(LONG_DESCRIPTION, "Display everything " + percentString + " of its original size."); //set the long description TODO i18n } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { setZoom(zoom); //change the book's zoom factor } } /** Action for turning on text antialiasing. */ protected class AntialiasAction extends AbstractToggleAction { /** Default constructor. */ public AntialiasAction() { super("Smooth Text"); //create the base class TODO i18n putValue(SHORT_DESCRIPTION, "Turn on font smoothing."); //set the short description TODO Int putValue(LONG_DESCRIPTION, "Turn on antialias-based font smoothing."); //set the long description TODO Int putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_S)); //set the mnemonic key TODO i18n //TODO fix putValue(SMALL_ICON, new ImageIcon(ReaderFrame.class.getResource("info.gif"))); //load the correct icon TODO use a constant here } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { setAntialias(!isAntialias()); //turn antialiasing on or off -- the opposite of what it is now } } /** Action for going to an OEB guide. */ protected class GoGuideAction extends AbstractAction { /** The action guide. */ protected final OEBGuide oebGuide; /** * Constructs an action which represents a guide target. * @param guide The OEB guide which contains the target information. */ public GoGuideAction(final OEBGuide guide) { oebGuide = guide; //save the guide putValue(NAME, guide.getTitle()); //set the correct name putValue(SHORT_DESCRIPTION, guide.getTitle()); //set the short description putValue(LONG_DESCRIPTION, guide.getHRef()); //set the long description to equal the actual reference //TODO fix putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_P)); //set the mnemonic key TODO i18n } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { go(oebGuide); //go to the guide's target reference } } /** Action for going to a bookmark. */ protected class GoBookmarkAction extends AbstractAction { /** The action bookmark. */ protected final Bookmark bookmark; /** * Constructs an action which represents a bookmark. * @param bookmark The bookmark which contains the target information. */ public GoBookmarkAction(final Bookmark bookmark) { Log.trace("ReaderFrame.GoBookmarkAction constructor, offset:", bookmark.getOffset()); this.bookmark = bookmark; //save the bookmark //TODO del when works setBookmark(bookmark); //save the bookmark final int pageIndex = getPageIndex(bookmark.getOffset()); //get the page index of the bookmark final Document document = getXMLTextPane().getDocument(); //get a reference to the document Log.trace("document length", document.getLength()); final int bookmarkedTextLength = Math.min(document.getLength() - bookmark.getOffset(), 16); //find out how much text to show; make sure we don't go past the document TODO use a constant here final String bookmarkNameString = bookmark.getName() != null ? bookmark.getName() + ": " : ""; //if there is a bookmark name, include it try { final String bookmarkedText = document.getText(bookmark.getOffset(), bookmarkedTextLength); //get the bookmarked text TODO use a constant final String bookmarkString = bookmarkNameString + "Page " + (pageIndex + 1) + " (" + bookmarkedText + "...)"; //G**i18n; use a getPageNumber(pageIndex) method; comment putValue(NAME, bookmarkString); //set the correct name TODO fix putValue(SHORT_DESCRIPTION, bookmarkString); //set the short description TODO fix putValue(LONG_DESCRIPTION, bookmarkString); //set the long description to equal the actual reference TODO fix } catch(BadLocationException badLocationException) { //we should never get a bad location, since we test the offsets and lengths throw new AssertionError(badLocationException); } } /** * Called when the action should be performed. * @param actionEvent The event causing the action. */ public void actionPerformed(final ActionEvent actionEvent) { go(bookmark.getOffset()); //go to the bookmark's offset } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy